master
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 /* not reached, but silences noreturn warning */
145 abort ();
146#else
147 va_list argp;
148 va_start (argp, msg);
149# ifdef __MINGW64_VERSION_MAJOR
150 fprintf (stderr, "Mingw-w64 runtime failure:\n");
151 vfprintf (stderr, msg, argp);
152# else
153 fprintf (stderr, "Mingw runtime failure:\n");
154 vfprintf (stderr, msg, argp);
155#endif
156 va_end (argp);
157 abort ();
158#endif
159}
160
161/* For mingw-w64 we have additional helpers to get image information
162 on runtime. This allows us to cache for pseudo-relocation pass
163 the temporary access of code/read-only sections.
164 This step speeds up pseudo-relocation pass. */
165#ifdef __MINGW64_VERSION_MAJOR
166extern int __mingw_GetSectionCount (void);
167extern PIMAGE_SECTION_HEADER __mingw_GetSectionForAddress (LPVOID p);
168extern PBYTE _GetPEImageBase (void);
169
170typedef struct sSecInfo {
171 /* Keeps altered section flags, or zero if nothing was changed. */
172 DWORD old_protect;
173 PVOID base_address;
174 SIZE_T region_size;
175 PBYTE sec_start;
176 PIMAGE_SECTION_HEADER hash;
177} sSecInfo;
178
179static sSecInfo *the_secs = NULL;
180static int maxSections = 0;
181
182static void
183mark_section_writable (LPVOID addr)
184{
185 MEMORY_BASIC_INFORMATION b;
186 PIMAGE_SECTION_HEADER h;
187 int i;
188
189 for (i = 0; i < maxSections; i++)
190 {
191 if (the_secs[i].sec_start <= ((LPBYTE) addr)
192 && ((LPBYTE) addr) < (the_secs[i].sec_start + the_secs[i].hash->Misc.VirtualSize))
193 return;
194 }
195 h = __mingw_GetSectionForAddress (addr);
196 if (!h)
197 {
198 __report_error ("Address %p has no image-section", addr);
199 return;
200 }
201 the_secs[i].hash = h;
202 the_secs[i].old_protect = 0;
203 the_secs[i].sec_start = _GetPEImageBase () + h->VirtualAddress;
204
205 if (!VirtualQuery (the_secs[i].sec_start, &b, sizeof(b)))
206 {
207 __report_error (" VirtualQuery failed for %d bytes at address %p",
208 (int) h->Misc.VirtualSize, the_secs[i].sec_start);
209 return;
210 }
211
212 if (b.Protect != PAGE_EXECUTE_READWRITE && b.Protect != PAGE_READWRITE
213 && b.Protect != PAGE_EXECUTE_WRITECOPY && b.Protect != PAGE_WRITECOPY)
214 {
215 ULONG new_protect;
216 if (b.Protect == PAGE_READONLY)
217 new_protect = PAGE_READWRITE;
218 else
219 new_protect = PAGE_EXECUTE_READWRITE;
220 the_secs[i].base_address = b.BaseAddress;
221 the_secs[i].region_size = b.RegionSize;
222 if (!VirtualProtect (b.BaseAddress, b.RegionSize,
223 new_protect,
224 &the_secs[i].old_protect))
225 __report_error (" VirtualProtect failed with code 0x%x",
226 (int) GetLastError ());
227 }
228 ++maxSections;
229 return;
230}
231
232static void
233restore_modified_sections (void)
234{
235 int i;
236 DWORD oldprot;
237
238 for (i = 0; i < maxSections; i++)
239 {
240 if (the_secs[i].old_protect == 0)
241 continue;
242 VirtualProtect (the_secs[i].base_address, the_secs[i].region_size,
243 the_secs[i].old_protect, &oldprot);
244 }
245}
246
247#endif /* __MINGW64_VERSION_MAJOR */
248
249/* This function temporarily marks the page containing addr
250 * writable, before copying len bytes from *src to *addr, and
251 * then restores the original protection settings to the page.
252 *
253 * Using this function eliminates the requirement with older
254 * pseudo-reloc implementations, that sections containing
255 * pseudo-relocs (such as .text and .rdata) be permanently
256 * marked writable. This older behavior sabotaged any memory
257 * savings achieved by shared libraries on win32 -- and was
258 * slower, too. However, on cygwin as of binutils 2.20 the
259 * .text section is still marked writable, and the .rdata section
260 * is folded into the (writable) .data when --enable-auto-import.
261 */
262static void
263__write_memory (void *addr, const void *src, size_t len)
264{
265 if (!len)
266 return;
267
268#ifdef __MINGW64_VERSION_MAJOR
269 /* Mark the section writable once, and unset it in
270 * restore_modified_sections */
271 mark_section_writable ((LPVOID) addr);
272#else
273 MEMORY_BASIC_INFORMATION b;
274 DWORD oldprot = 0;
275 int call_unprotect = 0;
276
277 if (!VirtualQuery (addr, &b, sizeof(b)))
278 {
279 __report_error (" VirtualQuery failed for %d bytes at address %p",
280 (int) sizeof(b), addr);
281 }
282
283 /* Temporarily allow write access to read-only protected memory. */
284 if (b.Protect != PAGE_EXECUTE_READWRITE && b.Protect != PAGE_READWRITE
285 && b.Protect != PAGE_WRITECOPY && b.Protect != PAGE_EXECUTE_WRITECOPY)
286 {
287 call_unprotect = 1;
288 VirtualProtect (b.BaseAddress, b.RegionSize, PAGE_EXECUTE_READWRITE,
289 &oldprot);
290 }
291#endif
292
293 /* write the data. */
294 memcpy (addr, src, len);
295
296#ifndef __MINGW64_VERSION_MAJOR
297 /* Restore original protection. */
298 if (call_unprotect
299 && b.Protect != PAGE_EXECUTE_READWRITE && b.Protect != PAGE_READWRITE
300 && b.Protect != PAGE_WRITECOPY && b.Protect != PAGE_EXECUTE_WRITECOPY)
301 VirtualProtect (b.BaseAddress, b.RegionSize, oldprot, &oldprot);
302#endif
303}
304
305#define RP_VERSION_V1 0
306#define RP_VERSION_V2 1
307
308static void
309do_pseudo_reloc (void * start, void * end, void * base)
310{
311 ptrdiff_t addr_imp, reldata;
312 ptrdiff_t reloc_target = (ptrdiff_t) ((char *)end - (char*)start);
313 runtime_pseudo_reloc_v2 *v2_hdr = (runtime_pseudo_reloc_v2 *) start;
314 runtime_pseudo_reloc_item_v2 *r;
315 unsigned int bits;
316
317 /* A valid relocation list will contain at least one entry, and
318 * one v1 data structure (the smallest one) requires two DWORDs.
319 * So, if the relocation list is smaller than 8 bytes, bail.
320 */
321 if (reloc_target < 8)
322 return;
323
324 /* Check if this is the old pseudo relocation version. */
325 /* There are two kinds of v1 relocation lists:
326 * 1) With a (v2-style) version header. In this case, the
327 * first entry in the list is a 3-DWORD structure, with
328 * value:
329 * { 0, 0, RP_VERSION_V1 }
330 * In this case, we skip to the next entry in the list,
331 * knowing that all elements after the head item can
332 * be cast to runtime_pseudo_reloc_item_v1.
333 * 2) Without a (v2-style) version header. In this case, the
334 * first element in the list IS an actual v1 relocation
335 * record, which is two DWORDs. Because there will never
336 * be a case where a v1 relocation record has both
337 * addend == 0 and target == 0, this case will not be
338 * confused with the prior one.
339 * All current binutils, when generating a v1 relocation list,
340 * use the second (e.g. original) form -- that is, without the
341 * v2-style version header.
342 */
343 if (reloc_target >= 12
344 && v2_hdr->magic1 == 0 && v2_hdr->magic2 == 0
345 && v2_hdr->version == RP_VERSION_V1)
346 {
347 /* We have a list header item indicating that the rest
348 * of the list contains v1 entries. Move the pointer to
349 * the first true v1 relocation record. By definition,
350 * that v1 element will not have both addend == 0 and
351 * target == 0 (and thus, when interpreted as a
352 * runtime_pseudo_reloc_v2, it will not have both
353 * magic1 == 0 and magic2 == 0).
354 */
355 v2_hdr++;
356 }
357
358 if (v2_hdr->magic1 != 0 || v2_hdr->magic2 != 0)
359 {
360 /*************************
361 * Handle v1 relocations *
362 *************************/
363 runtime_pseudo_reloc_item_v1 * o;
364 for (o = (runtime_pseudo_reloc_item_v1 *) v2_hdr;
365 o < (runtime_pseudo_reloc_item_v1 *)end;
366 o++)
367 {
368 DWORD newval;
369 reloc_target = (ptrdiff_t) base + o->target;
370 newval = (*((DWORD*) reloc_target)) + o->addend;
371 __write_memory ((void *) reloc_target, &newval, sizeof(DWORD));
372 }
373 return;
374 }
375
376 /* If we got this far, then we have relocations of version 2 or newer */
377
378 /* Check if this is a known version. */
379 if (v2_hdr->version != RP_VERSION_V2)
380 {
381 __report_error (" Unknown pseudo relocation protocol version %d.\n",
382 (int) v2_hdr->version);
383 return;
384 }
385
386 /*************************
387 * Handle v2 relocations *
388 *************************/
389
390 /* Walk over header. */
391 r = (runtime_pseudo_reloc_item_v2 *) &v2_hdr[1];
392
393 for (; r < (runtime_pseudo_reloc_item_v2 *) end; r++)
394 {
395 /* location where new address will be written */
396 reloc_target = (ptrdiff_t) base + r->target;
397
398 /* get sym pointer. It points either to the iat entry
399 * of the referenced element, or to the stub function.
400 */
401 addr_imp = (ptrdiff_t) base + r->sym;
402 addr_imp = *((ptrdiff_t *) addr_imp);
403
404 /* read existing relocation value from image, casting to the
405 * bitsize indicated by the 8 LSBs of flags. If the value is
406 * negative, manually sign-extend to ptrdiff_t width. Raise an
407 * error if the bitsize indicated by the 8 LSBs of flags is not
408 * supported.
409 */
410 switch ((r->flags & 0xff))
411 {
412 case 8:
413 reldata = (ptrdiff_t) (*((unsigned char *)reloc_target));
414 if ((reldata & 0x80) != 0)
415 reldata |= ~((ptrdiff_t) 0xff);
416 break;
417 case 16:
418 reldata = (ptrdiff_t) (*((unsigned short *)reloc_target));
419 if ((reldata & 0x8000) != 0)
420 reldata |= ~((ptrdiff_t) 0xffff);
421 break;
422 case 32:
423 reldata = (ptrdiff_t) (*((unsigned int *)reloc_target));
424#ifdef _WIN64
425 if ((reldata & 0x80000000) != 0)
426 reldata |= ~((ptrdiff_t) 0xffffffff);
427#endif
428 break;
429#ifdef _WIN64
430 case 64:
431 reldata = (ptrdiff_t) (*((unsigned long long *)reloc_target));
432 break;
433#endif
434 default:
435 reldata=0;
436 __report_error (" Unknown pseudo relocation bit size %d.\n",
437 (int) (r->flags & 0xff));
438 break;
439 }
440
441 /* Adjust the relocation value */
442 reldata -= ((ptrdiff_t) base + r->sym);
443 reldata += addr_imp;
444
445 bits = r->flags & 0xff;
446 if (bits < sizeof(ptrdiff_t)*8)
447 {
448 /* Check for overflows. We don't know if the target address is
449 * interpreted as a relative offset or as a truncated absolute
450 * address - to avoid false positives, allow offsets within the
451 * whole range of signed and unsigned N bits numbers, but error
452 * out for anything outside of that. Thus for relative offsets,
453 * this won't catch offsets that are only barely too large. */
454 ptrdiff_t max_unsigned = (1LL << bits) - 1;
455 ptrdiff_t min_signed = UINTPTR_MAX << (bits - 1);
456 if (reldata > max_unsigned || reldata < min_signed)
457 __report_error ("%d bit pseudo relocation at %p out of range, "
458 "targeting %p, yielding the value %p.\n",
459 bits, reloc_target, addr_imp, reldata);
460 }
461
462 /* Write the new relocation value back to *reloc_target */
463 switch ((r->flags & 0xff))
464 {
465 case 8:
466 __write_memory ((void *) reloc_target, &reldata, 1);
467 break;
468 case 16:
469 __write_memory ((void *) reloc_target, &reldata, 2);
470 break;
471 case 32:
472 __write_memory ((void *) reloc_target, &reldata, 4);
473 break;
474#ifdef _WIN64
475 case 64:
476 __write_memory ((void *) reloc_target, &reldata, 8);
477 break;
478#endif
479 }
480 }
481}
482
483__attribute__((used)) /* required due to bug in gcc / ld */
484void
485_pei386_runtime_relocator (void)
486{
487 static NO_COPY int was_init = 0;
488#ifdef __MINGW64_VERSION_MAJOR
489 int mSecs;
490#endif /* __MINGW64_VERSION_MAJOR */
491
492 if (was_init)
493 return;
494 ++was_init;
495#ifdef __MINGW64_VERSION_MAJOR
496 mSecs = __mingw_GetSectionCount ();
497 the_secs = (sSecInfo *) alloca (sizeof (sSecInfo) * (size_t) mSecs);
498 maxSections = 0;
499#endif /* __MINGW64_VERSION_MAJOR */
500
501 do_pseudo_reloc (&__RUNTIME_PSEUDO_RELOC_LIST__,
502 &__RUNTIME_PSEUDO_RELOC_LIST_END__,
503 &__ImageBase
504 );
505#ifdef __MINGW64_VERSION_MAJOR
506 restore_modified_sections ();
507#endif /* __MINGW64_VERSION_MAJOR */
508}