xref: /reactos/sdk/lib/crt/startup/pseudo-reloc.c (revision b707be90)
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 WARRENTIES, EXPRESS OR IMPLIED ARE HEREBY
14    DISCLAMED. This includes but is not limited to warrenties 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[MAX_PATH];
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, sizeof (module));
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   __mingw_fprintf (stderr, "Mingw-w64 runtime failure:\n");
150   __mingw_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   PBYTE sec_start;
173   PIMAGE_SECTION_HEADER hash;
174 } sSecInfo;
175 
176 static sSecInfo *the_secs = NULL;
177 static int maxSections = 0;
178 
179 static void
mark_section_writable(LPVOID addr)180 mark_section_writable (LPVOID addr)
181 {
182   MEMORY_BASIC_INFORMATION b;
183   PIMAGE_SECTION_HEADER h;
184   int i;
185 
186   for (i = 0; i < maxSections; i++)
187     {
188       if (the_secs[i].sec_start <= ((LPBYTE) addr)
189           && ((LPBYTE) addr) < (the_secs[i].sec_start + the_secs[i].hash->Misc.VirtualSize))
190         return;
191     }
192   h = __mingw_GetSectionForAddress (addr);
193   if (!h)
194     {
195       __report_error ("Address %p has no image-section", addr);
196       return;
197     }
198   the_secs[i].hash = h;
199   the_secs[i].old_protect = 0;
200   the_secs[i].sec_start = _GetPEImageBase () + h->VirtualAddress;
201 
202   if (!VirtualQuery (the_secs[i].sec_start, &b, sizeof(b)))
203     {
204       __report_error ("  VirtualQuery failed for %d bytes at address %p",
205 		      (int) h->Misc.VirtualSize, the_secs[i].sec_start);
206       return;
207     }
208 
209   if (b.Protect != PAGE_EXECUTE_READWRITE && b.Protect != PAGE_READWRITE)
210     {
211       if (!VirtualProtect (b.BaseAddress, b.RegionSize,
212 			   PAGE_EXECUTE_READWRITE,
213 			   &the_secs[i].old_protect))
214 	__report_error ("  VirtualProtect failed with code 0x%x",
215 	  (int) GetLastError ());
216     }
217   ++maxSections;
218   return;
219 }
220 
221 static void
restore_modified_sections(void)222 restore_modified_sections (void)
223 {
224   int i;
225   MEMORY_BASIC_INFORMATION b;
226   DWORD oldprot;
227 
228   for (i = 0; i < maxSections; i++)
229     {
230       if (the_secs[i].old_protect == 0)
231         continue;
232       if (!VirtualQuery (the_secs[i].sec_start, &b, sizeof(b)))
233 	{
234 	  __report_error ("  VirtualQuery failed for %d bytes at address %p",
235 			  (int) the_secs[i].hash->Misc.VirtualSize,
236 			  the_secs[i].sec_start);
237 	  return;
238 	}
239       VirtualProtect (b.BaseAddress, b.RegionSize, the_secs[i].old_protect,
240 		      &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  */
259 static void
__write_memory(void * addr,const void * src,size_t len)260 __write_memory (void *addr, const void *src, size_t len)
261 {
262   MEMORY_BASIC_INFORMATION b;
263   DWORD oldprot;
264   int call_unprotect = 0;
265 
266   if (!len)
267     return;
268 
269 #ifdef __MINGW64_VERSION_MAJOR
270   mark_section_writable ((LPVOID) addr);
271 #endif
272 
273   if (!VirtualQuery (addr, &b, sizeof(b)))
274     {
275       __report_error ("  VirtualQuery failed for %d bytes at address %p",
276 		      (int) sizeof(b), addr);
277     }
278 
279   /* Temporarily allow write access to read-only protected memory.  */
280   if (b.Protect != PAGE_EXECUTE_READWRITE && b.Protect != PAGE_READWRITE)
281     {
282       call_unprotect = 1;
283       VirtualProtect (b.BaseAddress, b.RegionSize, PAGE_EXECUTE_READWRITE,
284 		      &oldprot);
285     }
286 
287   /* write the data. */
288   memcpy (addr, src, len);
289   /* Restore original protection. */
290   if (call_unprotect && b.Protect != PAGE_EXECUTE_READWRITE && b.Protect != PAGE_READWRITE)
291     VirtualProtect (b.BaseAddress, b.RegionSize, oldprot, &oldprot);
292 }
293 
294 #define RP_VERSION_V1 0
295 #define RP_VERSION_V2 1
296 
297 static void
do_pseudo_reloc(void * start,void * end,void * base)298 do_pseudo_reloc (void * start, void * end, void * base)
299 {
300   ptrdiff_t addr_imp, reldata;
301   ptrdiff_t reloc_target = (ptrdiff_t) ((char *)end - (char*)start);
302   runtime_pseudo_reloc_v2 *v2_hdr = (runtime_pseudo_reloc_v2 *) start;
303   runtime_pseudo_reloc_item_v2 *r;
304 
305   /* A valid relocation list will contain at least one entry, and
306    * one v1 data structure (the smallest one) requires two DWORDs.
307    * So, if the relocation list is smaller than 8 bytes, bail.
308    */
309   if (reloc_target < 8)
310     return;
311 
312   /* Check if this is the old pseudo relocation version.  */
313   /* There are two kinds of v1 relocation lists:
314    *   1) With a (v2-style) version header. In this case, the
315    *      first entry in the list is a 3-DWORD structure, with
316    *      value:
317    *         { 0, 0, RP_VERSION_V1 }
318    *      In this case, we skip to the next entry in the list,
319    *      knowing that all elements after the head item can
320    *      be cast to runtime_pseudo_reloc_item_v1.
321    *   2) Without a (v2-style) version header. In this case, the
322    *      first element in the list IS an actual v1 relocation
323    *      record, which is two DWORDs.  Because there will never
324    *      be a case where a v1 relocation record has both
325    *      addend == 0 and target == 0, this case will not be
326    *      confused with the prior one.
327    * All current binutils, when generating a v1 relocation list,
328    * use the second (e.g. original) form -- that is, without the
329    * v2-style version header.
330    */
331   if (reloc_target >= 12
332       && v2_hdr->magic1 == 0 && v2_hdr->magic2 == 0
333       && v2_hdr->version == RP_VERSION_V1)
334     {
335       /* We have a list header item indicating that the rest
336        * of the list contains v1 entries.  Move the pointer to
337        * the first true v1 relocation record.  By definition,
338        * that v1 element will not have both addend == 0 and
339        * target == 0 (and thus, when interpreted as a
340        * runtime_pseudo_reloc_v2, it will not have both
341        * magic1 == 0 and magic2 == 0).
342        */
343       v2_hdr++;
344     }
345 
346   if (v2_hdr->magic1 != 0 || v2_hdr->magic2 != 0)
347     {
348       /*************************
349        * Handle v1 relocations *
350        *************************/
351       runtime_pseudo_reloc_item_v1 * o;
352       for (o = (runtime_pseudo_reloc_item_v1 *) v2_hdr;
353 	   o < (runtime_pseudo_reloc_item_v1 *)end;
354            o++)
355 	{
356 	  DWORD newval;
357 	  reloc_target = (ptrdiff_t) base + o->target;
358 	  newval = (*((DWORD*) reloc_target)) + o->addend;
359 	  __write_memory ((void *) reloc_target, &newval, sizeof(DWORD));
360 	}
361       return;
362     }
363 
364   /* If we got this far, then we have relocations of version 2 or newer */
365 
366   /* Check if this is a known version.  */
367   if (v2_hdr->version != RP_VERSION_V2)
368     {
369       __report_error ("  Unknown pseudo relocation protocol version %d.\n",
370 		      (int) v2_hdr->version);
371       return;
372     }
373 
374   /*************************
375    * Handle v2 relocations *
376    *************************/
377 
378   /* Walk over header. */
379   r = (runtime_pseudo_reloc_item_v2 *) &v2_hdr[1];
380 
381   for (; r < (runtime_pseudo_reloc_item_v2 *) end; r++)
382     {
383       /* location where new address will be written */
384       reloc_target = (ptrdiff_t) base + r->target;
385 
386       /* get sym pointer. It points either to the iat entry
387        * of the referenced element, or to the stub function.
388        */
389       addr_imp = (ptrdiff_t) base + r->sym;
390       addr_imp = *((ptrdiff_t *) addr_imp);
391 
392       /* read existing relocation value from image, casting to the
393        * bitsize indicated by the 8 LSBs of flags. If the value is
394        * negative, manually sign-extend to ptrdiff_t width. Raise an
395        * error if the bitsize indicated by the 8 LSBs of flags is not
396        * supported.
397        */
398       switch ((r->flags & 0xff))
399         {
400           case 8:
401 	    reldata = (ptrdiff_t) (*((unsigned char *)reloc_target));
402 	    if ((reldata & 0x80) != 0)
403 	      reldata |= ~((ptrdiff_t) 0xff);
404 	    break;
405 	  case 16:
406 	    reldata = (ptrdiff_t) (*((unsigned short *)reloc_target));
407 	    if ((reldata & 0x8000) != 0)
408 	      reldata |= ~((ptrdiff_t) 0xffff);
409 	    break;
410 	  case 32:
411 	    reldata = (ptrdiff_t) (*((unsigned int *)reloc_target));
412 #ifdef _WIN64
413 	    if ((reldata & 0x80000000) != 0)
414 	      reldata |= ~((ptrdiff_t) 0xffffffff);
415 #endif
416 	    break;
417 #ifdef _WIN64
418 	  case 64:
419 	    reldata = (ptrdiff_t) (*((unsigned long long *)reloc_target));
420 	    break;
421 #endif
422 	  default:
423 	    reldata=0;
424 	    __report_error ("  Unknown pseudo relocation bit size %d.\n",
425 		    (int) (r->flags & 0xff));
426 	    break;
427         }
428 
429       /* Adjust the relocation value */
430       reldata -= ((ptrdiff_t) base + r->sym);
431       reldata += addr_imp;
432 
433       /* Write the new relocation value back to *reloc_target */
434       switch ((r->flags & 0xff))
435 	{
436          case 8:
437            __write_memory ((void *) reloc_target, &reldata, 1);
438 	   break;
439 	 case 16:
440            __write_memory ((void *) reloc_target, &reldata, 2);
441 	   break;
442 	 case 32:
443            __write_memory ((void *) reloc_target, &reldata, 4);
444 	   break;
445 #ifdef _WIN64
446 	 case 64:
447            __write_memory ((void *) reloc_target, &reldata, 8);
448 	   break;
449 #endif
450 	}
451      }
452 }
453 
454 void
_pei386_runtime_relocator(void)455 _pei386_runtime_relocator (void)
456 {
457   static NO_COPY int was_init = 0;
458 #ifdef __MINGW64_VERSION_MAJOR
459   int mSecs;
460 #endif /* __MINGW64_VERSION_MAJOR */
461 
462   if (was_init)
463     return;
464   ++was_init;
465 #ifdef __MINGW64_VERSION_MAJOR
466   mSecs = __mingw_GetSectionCount ();
467   the_secs = (sSecInfo *) _alloca (sizeof (sSecInfo) * (size_t) mSecs);
468   maxSections = 0;
469 #endif /* __MINGW64_VERSION_MAJOR */
470 
471   do_pseudo_reloc (&__RUNTIME_PSEUDO_RELOC_LIST__,
472 		   &__RUNTIME_PSEUDO_RELOC_LIST_END__,
473 #ifdef __GNUC__
474 		   &__MINGW_LSYMBOL(_image_base__)
475 #else
476 		   &__ImageBase
477 #endif
478 		   );
479 #ifdef __MINGW64_VERSION_MAJOR
480   restore_modified_sections ();
481 #endif /* __MINGW64_VERSION_MAJOR */
482 }
483