OSDN Git Service

Support pseudo-reloc version 2
authorcwilson <cwilson>
Wed, 7 Oct 2009 15:47:38 +0000 (15:47 +0000)
committercwilson <cwilson>
Wed, 7 Oct 2009 15:47:38 +0000 (15:47 +0000)
winsup/cygwin/ChangeLog
winsup/cygwin/lib/pseudo-reloc.c
winsup/cygwin/ntdll.h
winsup/cygwin/pinfo.cc
winsup/cygwin/sigproc.cc

index c5b27cf..7cdb0c8 100644 (file)
@@ -1,3 +1,32 @@
+2009-10-07  Charles Wilson  <cygwin@cwilson.fastmail.fm>
+
+       Additional pseudo-reloc-v2 support
+       * ntdll.h: Add custom NTSTATUS value for pseudo-reloc
+       errors STATUS_ILLEGAL_DLL_PSEUDO_RELOCATION.
+       * pinfo.cc (status_exit): Map custom pseudo-reloc
+       error value STATUS_ILLEGAL_DLL_PSEUDO_RELOCATION to 127.
+       * sigproc.cc (child_info::proc_retry): Return exit code when
+       STATUS_ILLEGAL_DLL_PSEUDO_RELOCATION.
+
+       Cygwin modifications to pseudo-reloc.c
+       * lib/pseudo-reloc.c: Added comments throughout and various
+       whitespace fixes. Exploit cygwin_internal(CW_EXIT_PROCESS,...)
+       for fatal error handling that is consistent with cygwin process
+       life-cycle. Ensure state variable (in _pei386_runtime_relocator)
+       is unique to each address space, across fork().
+       (__print_reloc_error): New function for reporting errors in a
+       manner supported by cygwin at this early stage of the process
+       life-cycle.
+       (_pei386_runtime_relocator): Ensure relocations performed
+       only once for each address space, but are repeated after fork()
+       in the new address space.
+       only once for each address space (e.g. across fork()).
+       (__write_memory) [MINGW]: Ensure that b is always initialized
+       by call to VirtualQuery, even if -DNDEBUG.
+
+       * lib/pseudo-reloc.c: Import new implementation to support
+       v2 pseudo-relocs implemented by Kai Tietz from mingw.
+
 2009-10-07  Corinna Vinschen  <corinna@vinschen.de>
 
        * syscalls.cc (seteuid32): Call set_cygwin_privileges on primary token
index 5760a69..8e5b0bb 100644 (file)
@@ -1,6 +1,9 @@
 /* pseudo-reloc.c
 
-   Written by Egor Duda <deo@logos-m.ru>
+   Contributed by Egor Duda  <deo@logos-m.ru>
+   Modified by addition of runtime_pseudo_reloc version 2
+   by Kai Tietz  <kai.tietz@onevision.com>
+       
    THIS SOFTWARE IS NOT COPYRIGHTED
 
    This source code is offered for use in the public domain. You may
 */
 
 #include <windows.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#if defined(__CYGWIN__)
+#include <wchar.h>
+#include <ntdef.h>
+#include <stdarg.h>
+#include <sys/cygwin.h>
+/* copied from winsup.h */
+# define NO_COPY __attribute__((nocommon)) __attribute__((section(".data_cygwin_nocopy")))
+/* custom status code: */
+#define STATUS_ILLEGAL_DLL_PSEUDO_RELOCATION ((NTSTATUS) 0xe0000269)
+#else
+# define NO_COPY
+#endif
 
 extern char __RUNTIME_PSEUDO_RELOC_LIST__;
 extern char __RUNTIME_PSEUDO_RELOC_LIST_END__;
 extern char _image_base__;
 
-typedef struct
-  {
-    DWORD addend;
-    DWORD target;
-  }
-runtime_pseudo_reloc;
+/* v1 relocation is basically:
+ *   *(base + .target) += .addend
+ * where (base + .target) is always assumed to point
+ * to a DWORD (4 bytes).
+ */
+typedef struct {
+  DWORD addend;
+  DWORD target;
+} runtime_pseudo_reloc_item_v1;
 
-void
-do_pseudo_reloc (void* start, void* end, void* base)
+/* v2 relocation is more complex. In effect, it is
+ *    *(base + .target) += *(base + .sym) - (base + .sym)
+ * with care taken in both reading, sign extension, and writing
+ * because .flags may indicate that (base + .target) may point
+ * to a BYTE, WORD, DWORD, or QWORD (w64).
+ */
+typedef struct {
+  DWORD sym;
+  DWORD target;
+  DWORD flags;
+} runtime_pseudo_reloc_item_v2;
+
+typedef struct {
+  DWORD magic1;
+  DWORD magic2;
+  DWORD version;
+} runtime_pseudo_reloc_v2;
+
+#if defined(__CYGWIN__)
+#define SHORT_MSG_BUF_SZ 128
+/* This function is used to print short error messages
+ * to stderr, which may occur during DLL initialization
+ * while fixing up 'pseudo' relocations. This early, we
+ * may not be able to use cygwin stdio functions, so we
+ * use the win32 WriteFile api. This should work with both
+ * normal win32 console IO handles, redirected ones, and
+ * cygwin ptys.
+ */
+static BOOL
+__print_reloc_error (const char *fmt, ...)
 {
-  DWORD reloc_target;
-  runtime_pseudo_reloc* r;
-  for (r = (runtime_pseudo_reloc*) start; r < (runtime_pseudo_reloc*) end; r++)
+  char buf[SHORT_MSG_BUF_SZ];
+  wchar_t module[MAX_PATH];
+  char * posix_module = NULL;
+  BOOL rVal = FALSE;
+  static const char * UNKNOWN_MODULE = "<unknown module>: ";
+  DWORD len;
+  DWORD done;
+  va_list args;
+  HANDLE errh = GetStdHandle (STD_ERROR_HANDLE);
+  ssize_t modulelen = GetModuleFileNameW (NULL, module, sizeof (module));
+
+  if (errh == INVALID_HANDLE_VALUE)
+    return FALSE;
+
+  if (modulelen > 0)
+    posix_module = cygwin_create_path (CCP_WIN_W_TO_POSIX, module);
+
+  va_start (args, fmt);
+  len = (DWORD) vsnprintf (buf, SHORT_MSG_BUF_SZ, fmt, args);
+  va_end (args);
+  buf[SHORT_MSG_BUF_SZ-1] = '\0'; /* paranoia */
+
+  if (posix_module)
     {
-      reloc_target = (DWORD) base + r->target;
-      *((DWORD*) reloc_target) += r->addend;
+      rVal = WriteFile (errh, (PCVOID)posix_module,
+                        strlen(posix_module), &done, NULL) &&
+             WriteFile (errh, (PCVOID)": ", 2, &done, NULL) &&
+             WriteFile (errh, (PCVOID)buf, len, &done, NULL);
+      free (posix_module);
     }
+  else
+    {
+      rVal = WriteFile (errh, (PCVOID)UNKNOWN_MODULE,
+                        sizeof(UNKNOWN_MODULE), &done, NULL) &&
+             WriteFile (errh, (PCVOID)buf, len, &done, NULL);
+    }
+  return rVal;
 }
+#endif /* __CYGWIN__ */
+
+/* This function temporarily marks the page containing addr
+ * writable, before copying len bytes from *src to *addr, and
+ * then restores the original protection settings to the page.
+ *
+ * Using this function eliminates the requirement with older
+ * pseudo-reloc implementations, that sections containing
+ * pseudo-relocs (such as .text and .rdata) be permanently
+ * marked writable. This older behavior sabotaged any memory
+ * savings achieved by shared libraries on win32 -- and was
+ * slower, too.  However, on cygwin as of binutils 2.20 the
+ * .text section is still marked writable, and the .rdata section
+ * is folded into the (writable) .data when --enable-auto-import.
+ */
+static void
+__write_memory (void *addr,const void *src,size_t len)
+{
+  MEMORY_BASIC_INFORMATION b;
+  DWORD oldprot;
+  SIZE_T memsz;
+
+  if (!len)
+    return;
+
+  memsz = VirtualQuery (addr, &b, sizeof(b));
+
+#if defined(__CYGWIN__)
+  /* CYGWIN: If error, print error message and die. */
+  if (memsz == 0)
+    {
+      __print_reloc_error (
+        "error while loading shared libraries: bad address specified 0x%08x.\n",
+        addr);
+      cygwin_internal (CW_EXIT_PROCESS,
+                       STATUS_ILLEGAL_DLL_PSEUDO_RELOCATION,
+                       1);
+    }
+#else
+  /* MINGW: If error, die. assert() may print error message when !NDEBUG */
+  assert (memsz);
+#endif
+
+  /* Temporarily allow write access to read-only protected memory.  */
+  if (b.Protect != PAGE_EXECUTE_READWRITE && b.Protect != PAGE_READWRITE)
+    VirtualProtect (b.BaseAddress, b.RegionSize, PAGE_EXECUTE_READWRITE,
+                 &oldprot);
+  /* write the data. */
+  memcpy (addr, src, len);
+  /* Restore original protection. */
+  if (b.Protect != PAGE_EXECUTE_READWRITE && b.Protect != PAGE_READWRITE)
+    VirtualProtect (b.BaseAddress, b.RegionSize, oldprot, &oldprot);
+}
+
+#define RP_VERSION_V1 0
+#define RP_VERSION_V2 1
+
+static void
+do_pseudo_reloc (void * start, void * end, void * base)
+{
+  ptrdiff_t addr_imp, reldata;
+  ptrdiff_t reloc_target = (ptrdiff_t) ((char *)end - (char*)start);
+  runtime_pseudo_reloc_v2 *v2_hdr = (runtime_pseudo_reloc_v2 *) start;
+  runtime_pseudo_reloc_item_v2 *r;
+
+  /* A valid relocation list will contain at least one entry, and
+   * one v1 data structure (the smallest one) requires two DWORDs.
+   * So, if the relocation list is smaller than 8 bytes, bail.
+   */
+  if (reloc_target < 8)
+    return;
+
+  /* Check if this is the old pseudo relocation version.  */
+  /* There are two kinds of v1 relocation lists:
+   *   1) With a (v2-style) version header. In this case, the
+   *      first entry in the list is a 3-DWORD structure, with
+   *      value:
+   *         { 0, 0, RP_VERSION_V1 }
+   *      In this case, we skip to the next entry in the list,
+   *      knowing that all elements after the head item can
+   *      be cast to runtime_pseudo_reloc_item_v1.
+   *   2) Without a (v2-style) version header. In this case, the
+   *      first element in the list IS an actual v1 relocation
+   *      record, which is two DWORDs.  Because there will never
+   *      be a case where a v1 relocation record has both
+   *      addend == 0 and target == 0, this case will not be
+   *      confused with the prior one.
+   * All current binutils, when generating a v1 relocation list,
+   * use the second (e.g. original) form -- that is, without the
+   * v2-style version header.
+   */
+  if (reloc_target >= 12
+      && v2_hdr->magic1 == 0 && v2_hdr->magic2 == 0
+      && v2_hdr->version == RP_VERSION_V1)
+    {
+      /* We have a list header item indicating that the rest
+       * of the list contains v1 entries.  Move the pointer to
+       * the first true v1 relocation record.  By definition,
+       * that v1 element will not have both addend == 0 and
+       * target == 0 (and thus, when interpreted as a
+       * runtime_pseudo_reloc_v2, it will not have both
+       * magic1 == 0 and magic2 == 0).
+       */
+      v2_hdr++;
+    }
+
+  if (v2_hdr->magic1 != 0 || v2_hdr->magic2 != 0)
+    {
+      /*************************
+       * Handle v1 relocations *
+       *************************/
+      runtime_pseudo_reloc_item_v1 * o;
+      for (o = (runtime_pseudo_reloc_item_v1 *) v2_hdr;
+          o < (runtime_pseudo_reloc_item_v1 *)end;
+           o++)
+       {
+         DWORD newval;
+         reloc_target = (ptrdiff_t) base + o->target;
+         newval = (*((DWORD*) reloc_target)) + o->addend;
+         __write_memory ((void *) reloc_target, &newval, sizeof(DWORD));
+       }
+      return;
+    }
+
+  /* If we got this far, then we have relocations of version 2 or newer */
+
+  /* Check if this is a known version.  */
+  if (v2_hdr->version != RP_VERSION_V2)
+    {
+#if defined(__CYGWIN__)
+      /* CYGWIN: Print error message and die, even when !DEBUGGING */
+      __print_reloc_error (
+        "error while loading shared libraries: invalid pseudo_reloc version %d.\n",
+        (int) v2_hdr->version);
+      cygwin_internal (CW_EXIT_PROCESS,
+                       STATUS_ILLEGAL_DLL_PSEUDO_RELOCATION,
+                       1);
+#else
+# if defined(DEBUG)
+      /* MINGW: Don't die; just return to caller. If DEBUG, print error message. */
+      fprintf (stderr, "internal mingw runtime error:"
+              "psuedo_reloc version %d is unknown to this runtime.\n",
+              (int) v2_hdr->version);
+# endif
+#endif
+      return;
+    }
+
+  /*************************
+   * Handle v2 relocations *
+   *************************/
+
+  /* Walk over header. */
+  r = (runtime_pseudo_reloc_item_v2 *) &v2_hdr[1];
+
+  for (; r < (runtime_pseudo_reloc_item_v2 *) end; r++)
+    {
+      /* location where new address will be written */
+      reloc_target = (ptrdiff_t) base + r->target;
+
+      /* get sym pointer. It points either to the iat entry
+       * of the referenced element, or to the stub function.
+       */
+      addr_imp = (ptrdiff_t) base + r->sym;
+      addr_imp = *((ptrdiff_t *) addr_imp);
+
+      /* read existing relocation value from image, casting to the
+       * bitsize indicated by the 8 LSBs of flags. If the value is
+       * negative, manually sign-extend to ptrdiff_t width. Raise an
+       * error if the bitsize indicated by the 8 LSBs of flags is not
+       * supported.
+       */
+      switch ((r->flags & 0xff))
+        {
+          case 8:
+           reldata = (ptrdiff_t) (*((unsigned char *)reloc_target));
+           if ((reldata & 0x80) != 0)
+             reldata |= ~((ptrdiff_t) 0xff);
+           break;
+         case 16:
+           reldata = (ptrdiff_t) (*((unsigned short *)reloc_target));
+           if ((reldata & 0x8000) != 0)
+             reldata |= ~((ptrdiff_t) 0xffff);
+           break;
+         case 32:
+           reldata = (ptrdiff_t) (*((unsigned int *)reloc_target));
+#ifdef _WIN64
+           if ((reldata & 0x80000000) != 0)
+             reldata |= ~((ptrdiff_t) 0xffffffff);
+#endif
+           break;
+#ifdef _WIN64
+         case 64:
+           reldata = (ptrdiff_t) (*((unsigned long long *)reloc_target));
+           break;
+#endif
+         default:
+           reldata=0;
+#if defined(__CYGWIN__)
+            /* Print error message and die, even when !DEBUGGING */
+            __print_reloc_error (
+              "error while loading shared libraries: unknown pseudo_reloc bit size %d.\n",
+              (int) (r->flags & 0xff));
+            cygwin_internal (CW_EXIT_PROCESS,
+                             STATUS_ILLEGAL_DLL_PSEUDO_RELOCATION,
+                             1);
+#else
+# ifdef DEBUG
+            /* MINGW: If error, don't die; just print message if DEBUG */
+           fprintf(stderr, "internal mingw runtime error: "
+                   "unknown pseudo_reloc bit size %d\n",
+                   (int) (r->flags & 0xff));
+# endif
+#endif
+           break;
+        }
+
+      /* Adjust the relocation value */
+      reldata -= ((ptrdiff_t) base + r->sym);
+      reldata += addr_imp;
+
+      /* Write the new relocation value back to *reloc_target */
+      switch ((r->flags & 0xff))
+        {
+         case 8:
+           __write_memory ((void *) reloc_target, &reldata, 1);
+          break;
+        case 16:
+           __write_memory ((void *) reloc_target, &reldata, 2);
+          break;
+        case 32:
+           __write_memory ((void *) reloc_target, &reldata, 4);
+          break;
+#ifdef _WIN64
+        case 64:
+           __write_memory ((void *) reloc_target, &reldata, 8);
+          break;
+#endif
+        }
+     }
+ }
 
 void
 _pei386_runtime_relocator ()
 {
+  static NO_COPY int was_init = 0;
+  if (was_init)
+    return;
+  ++was_init;
   do_pseudo_reloc (&__RUNTIME_PSEUDO_RELOC_LIST__,
                   &__RUNTIME_PSEUDO_RELOC_LIST_END__,
                   &_image_base__);
index eaca557..93f786b 100644 (file)
@@ -46,6 +46,8 @@
 #define STATUS_ENTRYPOINT_NOT_FOUND   ((NTSTATUS) 0xc0000139)
 #define STATUS_BAD_DLL_ENTRYPOINT     ((NTSTATUS) 0xc0000251)
 #define STATUS_ILLEGAL_DLL_RELOCATION ((NTSTATUS) 0xc0000269)
+/* custom status code: */
+#define STATUS_ILLEGAL_DLL_PSEUDO_RELOCATION ((NTSTATUS) 0xe0000269)
 
 #define PDI_MODULES 0x01
 #define PDI_HEAPS 0x04
index ba17f23..0243ac8 100644 (file)
@@ -128,6 +128,10 @@ status_exit (DWORD x)
        x = 127;
       }
       break;
+    case STATUS_ILLEGAL_DLL_PSEUDO_RELOCATION: /* custom error value */
+      /* We've already printed the error message in pseudo-reloc.c */
+      x = 127;
+      break;
     default:
       x = 127;
     }
index bbbe983..7c61a51 100644 (file)
@@ -923,6 +923,8 @@ child_info::proc_retry (HANDLE h)
       break;
     case STATUS_DLL_NOT_FOUND:
       return exit_code;
+    case STATUS_ILLEGAL_DLL_PSEUDO_RELOCATION: /* pseudo-reloc.c specific */
+      return exit_code;
     case STATUS_CONTROL_C_EXIT:
       if (saw_ctrl_c ())
        return EXITCODE_OK;