Attached is a patch which avoids a fork failure due to remap error in the
specific circumstances described in my email [1], by adding an additional pass
to load_after_fork() which forces the DLL to be relocated by VirtualAlloc()ing
a block of memory at the load address as well.

Hopefully it can be seen by inspection that this code doesn't change the
behaviour of the first two passes, and so will only be changing the behaviour
in what was an fatal error case before.

[1] http://cygwin.com/ml/cygwin/2011-03/msg00373.html
2011-03-12  Jon TURNEY  <jon.tur...@dronecode.org.uk>

        * dll_init.cc (reserve_at, release_at): New functions.
        (load_after_fork): Make a 3rd pass at trying to load the DLL in
        the right place.


Index: cygwin/dll_init.cc
===================================================================
RCS file: /cvs/src/src/winsup/cygwin/dll_init.cc,v
retrieving revision 1.77
diff -u -p -r1.77 dll_init.cc
--- cygwin/dll_init.cc  15 Feb 2011 15:56:00 -0000      1.77
+++ cygwin/dll_init.cc  13 Mar 2011 14:50:11 -0000
@@ -254,15 +254,47 @@ release_upto (const PWCHAR name, DWORD h
       }
 }
 
+/* Mark one page at "here" as reserved.  This may force
+   Windows NT to load a DLL elsewhere. */
+static DWORD
+reserve_at (const PWCHAR name, DWORD here)
+{
+  DWORD size;
+  MEMORY_BASIC_INFORMATION mb;
+
+  if (!VirtualQuery ((void *) here, &mb, sizeof (mb)))
+    size = 64 * 1024;
+
+  if (mb.State != MEM_FREE)
+    return 0;
+
+  size = mb.RegionSize;
+  if (!VirtualAlloc ((void *) here, size, MEM_RESERVE, PAGE_NOACCESS))
+    api_fatal ("couldn't allocate memory %p(%d) for '%W' alignment, %E\n",
+               here, size, name);
+  return here;
+}
+
+/* Release the memory previously allocated by "reserve_at" above. */
+static void
+release_at (const PWCHAR name, DWORD here)
+{
+  if (!VirtualFree ((void *) here, 0, MEM_RELEASE))
+    api_fatal ("couldn't release memory %p for '%W' alignment, %E\n",
+               here, name);
+}
+
 /* Reload DLLs after a fork.  Iterates over the list of dynamically loaded
    DLLs and attempts to load them in the same place as they were loaded in the
    parent. */
 void
 dll_list::load_after_fork (HANDLE parent)
 {
+  DWORD preferred_block = 0;
+
   for (dll *d = &dlls.start; (d = d->next) != NULL; )
     if (d->type == DLL_LOAD)
-      for (int i = 0; i < 2; i++)
+      for (int i = 0; i < 3; i++)
        {
          /* See if DLL will load in proper place.  If so, free it and reload
             it the right way.
@@ -281,15 +313,26 @@ dll_list::load_after_fork (HANDLE parent
              if (h == d->handle)
                h = LoadLibraryW (d->name);
            }
-         /* If we reached here on the second iteration of the for loop
+
+         /* If we reached here on subsequent iterations of the for loop
             then there is a lot of memory to release. */
          if (i > 0)
            release_upto (d->name, (DWORD) d->handle);
+
+          /* If we reached here on the last iteration of the for loop
+             then there's a bit of memory to release */
+          if (i > 1)
+            {
+              if (preferred_block)
+                release_at(d->name, preferred_block);
+              preferred_block = NULL;
+            }
+
          if (h == d->handle)
            break;              /* Success */
 
-         if (i > 0)
-           /* We tried once to relocate the dll and it failed. */
+         if (i > 1)
+           /* We tried to relocate the dll and it failed. */
            api_fatal ("unable to remap %W to same address as parent: %p != %p",
                       d->name, d->handle, h);
 
@@ -299,6 +342,15 @@ dll_list::load_after_fork (HANDLE parent
             second DLL always loads into a different location. So, block all
             of the memory up to the new load address and try again. */
          reserve_upto (d->name, (DWORD) d->handle);
+
+          /* Dll *still* loaded in the wrong place.  This can happen if it
+             couldn't load at the preferred base in the parent, but can in
+             the child, due to ordering differences.  Block memory at it's
+             preferred address and try again. */
+          if (i > 0)
+            {
+              preferred_block = reserve_at(d->name, (DWORD)h);
+            }
        }
   in_forkee = false;
 }

Reply via email to