diff options
author | Christopher Faylor <me@cgf.cx> | 2011-04-06 17:58:00 +0400 |
---|---|---|
committer | Christopher Faylor <me@cgf.cx> | 2011-04-06 17:58:00 +0400 |
commit | 8d777a13fcf40c524130d0adee23d35a880b7e6f (patch) | |
tree | 06fab2db4dc50a88f78ae7adb507a29fde305671 /winsup | |
parent | 641965646a35331ba876eefc559d64783389b68a (diff) |
* dll_init.cc (reserve_at, release_at): New functions.
(load_after_fork): If the DLL was loaded higher than the required address,
assume that it loaded at it's base address and also reserve memory there to
force it to be relocated.
Diffstat (limited to 'winsup')
-rw-r--r-- | winsup/cygwin/ChangeLog | 7 | ||||
-rw-r--r-- | winsup/cygwin/dll_init.cc | 72 |
2 files changed, 68 insertions, 11 deletions
diff --git a/winsup/cygwin/ChangeLog b/winsup/cygwin/ChangeLog index 18c74553b..a204f08e4 100644 --- a/winsup/cygwin/ChangeLog +++ b/winsup/cygwin/ChangeLog @@ -1,3 +1,10 @@ +2011-04-06 Jon TURNEY <jon.turney@dronecode.org.uk> + + * dll_init.cc (reserve_at, release_at): New functions. + (load_after_fork): If the DLL was loaded higher than the required + address, assume that it loaded at it's base address and also reserve + memory there to force it to be relocated. + 2011-04-04 Yaakov Selkowitz <yselkowitz@users.sourceforge.net> * include/cygwin/types.h: Move #include <sys/sysmacros.h> to diff --git a/winsup/cygwin/dll_init.cc b/winsup/cygwin/dll_init.cc index 122055722..5ee2f0036 100644 --- a/winsup/cygwin/dll_init.cc +++ b/winsup/cygwin/dll_init.cc @@ -78,7 +78,7 @@ dll::init () /* This should be a no-op. Why didn't we just import this variable? */ if (!p.envptr) p.envptr = &__cygwin_environ; - else + else if (*(p.envptr) != __cygwin_environ) *(p.envptr) = __cygwin_environ; /* Don't run constructors or the "main" if we've forked. */ @@ -257,12 +257,44 @@ release_upto (const PWCHAR name, DWORD here) } } +/* 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++) @@ -284,10 +316,18 @@ 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 then there is a lot of memory to release. */ if (i > 0) - release_upto (d->name, (DWORD) d->handle); + { + release_upto (d->name, (DWORD) d->handle); + + if (preferred_block) + release_at (d->name, preferred_block); + preferred_block = 0; + } + if (h == d->handle) break; /* Success */ @@ -297,11 +337,20 @@ dll_list::load_after_fork (HANDLE parent) d->name, d->handle, h); /* Dll loaded in the wrong place. Dunno why this happens but it - always seems to happen when there are multiple DLLs attempting to - load into the same address space. In the "forked" process, the - second DLL always loads into a different location. So, block all - of the memory up to the new load address and try again. */ + always seems to happen when there are multiple DLLs with the + same base address. In the "forked" process, the relocated DLL + may load at a different address. So, block all of the memory up + to the relocated load address and try again. */ reserve_upto (d->name, (DWORD) d->handle); + + /* Also, if the DLL loaded at a higher address than wanted (probably + it's base address), reserve the memory at that address. This can + happen if it couldn't load at the preferred base in the parent, but + can in the child, due to differences in the load ordering. + Block memory at it's preferred address and try again. */ + if ((DWORD) h > (DWORD) d->handle) + preferred_block = reserve_at (d->name, (DWORD) h); + } in_forkee = false; } @@ -311,7 +360,7 @@ struct dllcrt0_info HMODULE h; per_process *p; int res; - dllcrt0_info (HMODULE h0, per_process *p0): h(h0), p(p0) {} + dllcrt0_info (HMODULE h0, per_process *p0): h (h0), p (p0) {} }; extern "C" int @@ -329,9 +378,9 @@ dll_dllcrt0 (HMODULE h, per_process *p) void dll_dllcrt0_1 (VOID *x) { - HMODULE& h = ((dllcrt0_info *)x)->h; - per_process*& p = ((dllcrt0_info *)x)->p; - int& res = ((dllcrt0_info *)x)->res; + HMODULE& h = ((dllcrt0_info *) x)->h; + per_process*& p = ((dllcrt0_info *) x)->p; + int& res = ((dllcrt0_info *) x)->res; if (p == NULL) p = &__cygwin_user_data; @@ -431,6 +480,7 @@ void __stdcall update_envptrs () { for (dll *d = dlls.istart (DLL_ANY); d; d = dlls.inext ()) - *(d->p.envptr) = __cygwin_environ; + if (*(d->p.envptr) != __cygwin_environ) + *(d->p.envptr) = __cygwin_environ; *main_environ = __cygwin_environ; } |