1/* pseudo-reloc.c
2
3 Contributed by Egor Duda <deo@logos-m.ru>
4 Modified by addition of runtime_pseudo_reloc version 2
5 by Kai Tietz <kai.tietz@onevision.com>
6
7 THIS SOFTWARE IS NOT COPYRIGHTED
8
9 This source code is offered for use in the public domain. You may
10 use, modify or distribute it freely.
11
12 This code is distributed in the hope that it will be useful but
13 WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY
14 DISCLAMED. This includes but is not limited to warranties of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16*/
17
18#include <windows.h>
19#include <stdio.h>
20#include <stdlib.h>
21#include <stdarg.h>
22#include <memory.h>
23#include <internal.h>
24#include <stdint.h>
25
26#if defined(__CYGWIN__)
27#include <wchar.h>
28#include <ntdef.h>
29#include <sys/cygwin.h>
30/* copied from winsup.h */
31# define NO_COPY __attribute__((nocommon)) __attribute__((section(".data_cygwin_nocopy")))
32/* custom status code: */
33#define STATUS_ILLEGAL_DLL_PSEUDO_RELOCATION ((NTSTATUS) 0xe0000269)
34#define SHORT_MSG_BUF_SZ 128
35#else
36# define NO_COPY
37#endif
38
39#ifdef __GNUC__
40#define ATTRIBUTE_NORETURN __attribute__ ((noreturn))
41#else
42#define ATTRIBUTE_NORETURN
43#endif
44
45#ifndef __MINGW_LSYMBOL
46#define __MINGW_LSYMBOL(sym) sym
47#endif
48
49extern char __RUNTIME_PSEUDO_RELOC_LIST__;
50extern char __RUNTIME_PSEUDO_RELOC_LIST_END__;
51extern IMAGE_DOS_HEADER __ImageBase;
52
53void _pei386_runtime_relocator (void);
54
55/* v1 relocation is basically:
56 * *(base + .target) += .addend
57 * where (base + .target) is always assumed to point
58 * to a DWORD (4 bytes).
59 */
60typedef struct {
61 DWORD addend;
62 DWORD target;
63} runtime_pseudo_reloc_item_v1;
64
65/* v2 relocation is more complex. In effect, it is
66 * *(base + .target) += *(base + .sym) - (base + .sym)
67 * with care taken in both reading, sign extension, and writing
68 * because .flags may indicate that (base + .target) may point
69 * to a BYTE, WORD, DWORD, or QWORD (w64).
70 */
71typedef struct {
72 DWORD sym;
73 DWORD target;
74 DWORD flags;
75} runtime_pseudo_reloc_item_v2;
76
77typedef struct {
78 DWORD magic1;
79 DWORD magic2;
80 DWORD version;
81} runtime_pseudo_reloc_v2;
82
83static void ATTRIBUTE_NORETURN
84__report_error (const char *msg, ...)
85{
86#ifdef __CYGWIN__
87 /* This function is used to print short error messages
88 * to stderr, which may occur during DLL initialization
89 * while fixing up 'pseudo' relocations. This early, we
90 * may not be able to use cygwin stdio functions, so we
91 * use the win32 WriteFile api. This should work with both
92 * normal win32 console IO handles, redirected ones, and
93 * cygwin ptys.
94 */
95 char buf[SHORT_MSG_BUF_SZ];
96 wchar_t module[PATH_MAX];
97 char * posix_module = NULL;
98 static const char UNKNOWN_MODULE[] = "<unknown module>: ";
99 static const size_t UNKNOWN_MODULE_LEN = sizeof (UNKNOWN_MODULE) - 1;
100 static const char CYGWIN_FAILURE_MSG[] = "Cygwin runtime failure: ";
101 static const size_t CYGWIN_FAILURE_MSG_LEN = sizeof (CYGWIN_FAILURE_MSG) - 1;
102 DWORD len;
103 DWORD done;
104 va_list args;
105 HANDLE errh = GetStdHandle (STD_ERROR_HANDLE);
106 ssize_t modulelen = GetModuleFileNameW (NULL, module, PATH_MAX);
107
108 if (errh == INVALID_HANDLE_VALUE)
109 cygwin_internal (CW_EXIT_PROCESS,
110 STATUS_ILLEGAL_DLL_PSEUDO_RELOCATION,
111 1);
112
113 if (modulelen > 0)
114 posix_module = cygwin_create_path (CCP_WIN_W_TO_POSIX, module);
115
116 va_start (args, msg);
117 len = (DWORD) vsnprintf (buf, SHORT_MSG_BUF_SZ, msg, args);
118 va_end (args);
119 buf[SHORT_MSG_BUF_SZ-1] = '\0'; /* paranoia */
120
121 if (posix_module)
122 {
123 WriteFile (errh, (PCVOID)CYGWIN_FAILURE_MSG,
124 CYGWIN_FAILURE_MSG_LEN, &done, NULL);
125 WriteFile (errh, (PCVOID)posix_module,
126 strlen(posix_module), &done, NULL);
127 WriteFile (errh, (PCVOID)": ", 2, &done, NULL);
128 WriteFile (errh, (PCVOID)buf, len, &done, NULL);
129 free (posix_module);
130 }
131 else
132 {
133 WriteFile (errh, (PCVOID)CYGWIN_FAILURE_MSG,
134 CYGWIN_FAILURE_MSG_LEN, &done, NULL);
135 WriteFile (errh, (PCVOID)UNKNOWN_MODULE,
136 UNKNOWN_MODULE_LEN, &done, NULL);
137 WriteFile (errh, (PCVOID)buf, len, &done, NULL);
138 }
139 WriteFile (errh, (PCVOID)"\n", 1, &done, NULL);
140
141 cygwin_internal (CW_EXIT_PROCESS,
142 STATUS_ILLEGAL_DLL_PSEUDO_RELOCATION,
143 1);
144 __builtin_unreachable ();
145#else
146 va_list argp;
147 va_start (argp, msg);
148# ifdef __MINGW64_VERSION_MAJOR
149 fprintf (stderr, "Mingw-w64 runtime failure:\n");
150 vfprintf (stderr, msg, argp);
151# else
152 fprintf (stderr, "Mingw runtime failure:\n");
153 vfprintf (stderr, msg, argp);
154#endif
155 va_end (argp);
156 abort ();
157#endif
158}
159
160/* For mingw-w64 we have additional helpers to get image information
161 on runtime. This allows us to cache for pseudo-relocation pass
162 the temporary access of code/read-only sections.
163 This step speeds up pseudo-relocation pass. */
164#ifdef __MINGW64_VERSION_MAJOR
165extern int __mingw_GetSectionCount (void);
166extern PIMAGE_SECTION_HEADER __mingw_GetSectionForAddress (LPVOID p);
167extern PBYTE _GetPEImageBase (void);
168
169typedef struct sSecInfo {
170 /* Keeps altered section flags, or zero if nothing was changed. */
171 DWORD old_protect;
172 PVOID base_address;
173 SIZE_T region_size;
174 PBYTE sec_start;
175 PIMAGE_SECTION_HEADER hash;
176} sSecInfo;
177
178static sSecInfo *the_secs = NULL;
179static int maxSections = 0;
180
181static void
182mark_section_writable (LPVOID addr)
183{
184 MEMORY_BASIC_INFORMATION b;
185 PIMAGE_SECTION_HEADER h;
186 int i;
187
188 for (i = 0; i < maxSections; i++)
189 {
190 if (the_secs[i].sec_start <= ((LPBYTE) addr)
191 && ((LPBYTE) addr) < (the_secs[i].sec_start + the_secs[i].hash->Misc.VirtualSize))
192 return;
193 }
194 h = __mingw_GetSectionForAddress (addr);
195 if (!h)
196 {
197 __report_error ("Address %p has no image-section", addr);
198 }
199 the_secs[i].hash = h;
200 the_secs[i].old_protect = 0;
201 the_secs[i].sec_start = _GetPEImageBase () + h->VirtualAddress;
202
203 if (!VirtualQuery (the_secs[i].sec_start, &b, sizeof(b)))
204 {
205 __report_error (" VirtualQuery failed for %d bytes at address %p",
206 (int) h->Misc.VirtualSize, the_secs[i].sec_start);
207 }
208
209 if (b.Protect != PAGE_EXECUTE_READWRITE && b.Protect != PAGE_READWRITE
210 && b.Protect != PAGE_EXECUTE_WRITECOPY && b.Protect != PAGE_WRITECOPY)
211 {
212 ULONG new_protect;
213 if (b.Protect == PAGE_READONLY)
214 new_protect = PAGE_READWRITE;
215 else
216 new_protect = PAGE_EXECUTE_READWRITE;
217 the_secs[i].base_address = b.BaseAddress;
218 the_secs[i].region_size = b.RegionSize;
219 if (!VirtualProtect (b.BaseAddress, b.RegionSize,
220 new_protect,
221 &the_secs[i].old_protect))
222 __report_error (" VirtualProtect failed with code 0x%x",
223 (int) GetLastError ());
224 }
225 ++maxSections;
226 return;
227}
228
229static void
230restore_modified_sections (void)
231{
232 int i;
233 DWORD oldprot;
234
235 for (i = 0; i < maxSections; i++)
236 {
237 if (the_secs[i].old_protect == 0)
238 continue;
239 VirtualProtect (the_secs[i].base_address, the_secs[i].region_size,
240 the_secs[i].old_protect, &oldprot);
241 }
242}
243
244#endif /* __MINGW64_VERSION_MAJOR */
245
246/* This function temporarily marks the page containing addr
247 * writable, before copying len bytes from *src to *addr, and
248 * then restores the original protection settings to the page.
249 *
250 * Using this function eliminates the requirement with older
251 * pseudo-reloc implementations, that sections containing
252 * pseudo-relocs (such as .text and .rdata) be permanently
253 * marked writable. This older behavior sabotaged any memory
254 * savings achieved by shared libraries on win32 -- and was
255 * slower, too. However, on cygwin as of binutils 2.20 the
256 * .text section is still marked writable, and the .rdata section
257 * is folded into the (writable) .data when --enable-auto-import.
258 */
259static void
260__write_memory (void *addr, const void *src, size_t len)
261{
262 if (!len)
263 return;
264
265#ifdef __MINGW64_VERSION_MAJOR
266 /* Mark the section writable once, and unset it in
267 * restore_modified_sections */
268 mark_section_writable ((LPVOID) addr);
269#else
270 MEMORY_BASIC_INFORMATION b;
271 DWORD oldprot = 0;
272 int call_unprotect = 0;
273
274 if (!VirtualQuery (addr, &b, sizeof(b)))
275 {
276 __report_error (" VirtualQuery failed for %d bytes at address %p",
277 (int) sizeof(b), addr);
278 }
279
280 /* Temporarily allow write access to read-only protected memory. */
281 if (b.Protect != PAGE_EXECUTE_READWRITE && b.Protect != PAGE_READWRITE
282 && b.Protect != PAGE_WRITECOPY && b.Protect != PAGE_EXECUTE_WRITECOPY)
283 {
284 call_unprotect = 1;
285 VirtualProtect (b.BaseAddress, b.RegionSize, PAGE_EXECUTE_READWRITE,
286 &oldprot);
287 }
288#endif
289
290 /* write the data. */
291 memcpy (addr, src, len);
292
293#ifndef __MINGW64_VERSION_MAJOR
294 /* Restore original protection. */
295 if (call_unprotect
296 && b.Protect != PAGE_EXECUTE_READWRITE && b.Protect != PAGE_READWRITE
297 && b.Protect != PAGE_WRITECOPY && b.Protect != PAGE_EXECUTE_WRITECOPY)
298 VirtualProtect (b.BaseAddress, b.RegionSize, oldprot, &oldprot);
299#endif
300}
301
302#define RP_VERSION_V1 0
303#define RP_VERSION_V2 1
304
305static void
306do_pseudo_reloc (void * start, void * end, void * base)
307{
308 ptrdiff_t addr_imp, reldata;
309 ptrdiff_t reloc_target = (ptrdiff_t) ((char *)end - (char*)start);
310 runtime_pseudo_reloc_v2 *v2_hdr = (runtime_pseudo_reloc_v2 *) start;
311 runtime_pseudo_reloc_item_v2 *r;
312 unsigned int bits;
313
314 /* A valid relocation list will contain at least one entry, and
315 * one v1 data structure (the smallest one) requires two DWORDs.
316 * So, if the relocation list is smaller than 8 bytes, bail.
317 */
318 if (reloc_target < 8)
319 return;
320
321 /* Check if this is the old pseudo relocation version. */
322 /* There are two kinds of v1 relocation lists:
323 * 1) With a (v2-style) version header. In this case, the
324 * first entry in the list is a 3-DWORD structure, with
325 * value:
326 * { 0, 0, RP_VERSION_V1 }
327 * In this case, we skip to the next entry in the list,
328 * knowing that all elements after the head item can
329 * be cast to runtime_pseudo_reloc_item_v1.
330 * 2) Without a (v2-style) version header. In this case, the
331 * first element in the list IS an actual v1 relocation
332 * record, which is two DWORDs. Because there will never
333 * be a case where a v1 relocation record has both
334 * addend == 0 and target == 0, this case will not be
335 * confused with the prior one.
336 * All current binutils, when generating a v1 relocation list,
337 * use the second (e.g. original) form -- that is, without the
338 * v2-style version header.
339 */
340 if (reloc_target >= 12
341 && v2_hdr->magic1 == 0 && v2_hdr->magic2 == 0
342 && v2_hdr->version == RP_VERSION_V1)
343 {
344 /* We have a list header item indicating that the rest
345 * of the list contains v1 entries. Move the pointer to
346 * the first true v1 relocation record. By definition,
347 * that v1 element will not have both addend == 0 and
348 * target == 0 (and thus, when interpreted as a
349 * runtime_pseudo_reloc_v2, it will not have both
350 * magic1 == 0 and magic2 == 0).
351 */
352 v2_hdr++;
353 }
354
355 if (v2_hdr->magic1 != 0 || v2_hdr->magic2 != 0)
356 {
357 /*************************
358 * Handle v1 relocations *
359 *************************/
360 runtime_pseudo_reloc_item_v1 * o;
361 for (o = (runtime_pseudo_reloc_item_v1 *) v2_hdr;
362 o < (runtime_pseudo_reloc_item_v1 *)end;
363 o++)
364 {
365 DWORD newval;
366 reloc_target = (ptrdiff_t) base + o->target;
367 newval = (*((DWORD*) reloc_target)) + o->addend;
368 __write_memory ((void *) reloc_target, &newval, sizeof(DWORD));
369 }
370 return;
371 }
372
373 /* If we got this far, then we have relocations of version 2 or newer */
374
375 /* Check if this is a known version. */
376 if (v2_hdr->version != RP_VERSION_V2)
377 {
378 __report_error (" Unknown pseudo relocation protocol version %d.\n",
379 (int) v2_hdr->version);
380 }
381
382 /*************************
383 * Handle v2 relocations *
384 *************************/
385
386 /* Walk over header. */
387 r = (runtime_pseudo_reloc_item_v2 *) &v2_hdr[1];
388
389 for (; r < (runtime_pseudo_reloc_item_v2 *) end; r++)
390 {
391 /* location where new address will be written */
392 reloc_target = (ptrdiff_t) base + r->target;
393
394 /* get sym pointer. It points either to the iat entry
395 * of the referenced element, or to the stub function.
396 */
397 addr_imp = (ptrdiff_t) base + r->sym;
398 addr_imp = *((ptrdiff_t *) addr_imp);
399
400 /* read existing relocation value from image, casting to the
401 * bitsize indicated by the 8 LSBs of flags. If the value is
402 * negative, manually sign-extend to ptrdiff_t width. Raise an
403 * error if the bitsize indicated by the 8 LSBs of flags is not
404 * supported.
405 */
406 switch ((r->flags & 0xff))
407 {
408 case 8:
409 reldata = (ptrdiff_t) (*((unsigned char *)reloc_target));
410 if ((reldata & 0x80) != 0)
411 reldata |= ~((ptrdiff_t) 0xff);
412 break;
413 case 16:
414 reldata = (ptrdiff_t) (*((unsigned short *)reloc_target));
415 if ((reldata & 0x8000) != 0)
416 reldata |= ~((ptrdiff_t) 0xffff);
417 break;
418 case 32:
419 reldata = (ptrdiff_t) (*((unsigned int *)reloc_target));
420#ifdef _WIN64
421 if ((reldata & 0x80000000) != 0)
422 reldata |= ~((ptrdiff_t) 0xffffffff);
423#endif
424 break;
425#ifdef _WIN64
426 case 64:
427 reldata = (ptrdiff_t) (*((unsigned long long *)reloc_target));
428 break;
429#endif
430 default:
431 reldata=0;
432 __report_error (" Unknown pseudo relocation bit size %d.\n",
433 (int) (r->flags & 0xff));
434 break;
435 }
436
437 /* Adjust the relocation value */
438 reldata -= ((ptrdiff_t) base + r->sym);
439 reldata += addr_imp;
440
441 bits = r->flags & 0xff;
442 if (bits < sizeof(ptrdiff_t)*8)
443 {
444 /* Check for overflows. We don't know if the target address is
445 * interpreted as a relative offset or as a truncated absolute
446 * address - to avoid false positives, allow offsets within the
447 * whole range of signed and unsigned N bits numbers, but error
448 * out for anything outside of that. Thus for relative offsets,
449 * this won't catch offsets that are only barely too large. */
450 ptrdiff_t max_unsigned = (1LL << bits) - 1;
451 ptrdiff_t min_signed = UINTPTR_MAX << (bits - 1);
452 if (reldata > max_unsigned || reldata < min_signed)
453 __report_error ("%d bit pseudo relocation at %p out of range, "
454 "targeting %p, yielding the value %p.\n",
455 bits, reloc_target, addr_imp, reldata);
456 }
457
458 /* Write the new relocation value back to *reloc_target */
459 switch ((r->flags & 0xff))
460 {
461 case 8:
462 __write_memory ((void *) reloc_target, &reldata, 1);
463 break;
464 case 16:
465 __write_memory ((void *) reloc_target, &reldata, 2);
466 break;
467 case 32:
468 __write_memory ((void *) reloc_target, &reldata, 4);
469 break;
470#ifdef _WIN64
471 case 64:
472 __write_memory ((void *) reloc_target, &reldata, 8);
473 break;
474#endif
475 }
476 }
477}
478
479__attribute__((used)) /* required due to GNU LD bug: https://sourceware.org/bugzilla/show_bug.cgi?id=30343 */
480void
481_pei386_runtime_relocator (void)
482{
483 static NO_COPY int was_init = 0;
484#ifdef __MINGW64_VERSION_MAJOR
485 int mSecs;
486#endif /* __MINGW64_VERSION_MAJOR */
487
488 if (was_init)
489 return;
490 ++was_init;
491#ifdef __MINGW64_VERSION_MAJOR
492 mSecs = __mingw_GetSectionCount ();
493 the_secs = (sSecInfo *) alloca (sizeof (sSecInfo) * (size_t) mSecs);
494 maxSections = 0;
495#endif /* __MINGW64_VERSION_MAJOR */
496
497 do_pseudo_reloc (&__RUNTIME_PSEUDO_RELOC_LIST__,
498 &__RUNTIME_PSEUDO_RELOC_LIST_END__,
499 &__ImageBase
500 );
501#ifdef __MINGW64_VERSION_MAJOR
502 restore_modified_sections ();
503#endif /* __MINGW64_VERSION_MAJOR */
504}