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