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}