487 lines
13 KiB
C++
487 lines
13 KiB
C++
/* dll_init.cc
|
|
|
|
Copyright 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
|
|
2007, 2008, 2009, 2010, 2011 Red Hat, Inc.
|
|
|
|
This software is a copyrighted work licensed under the terms of the
|
|
Cygwin license. Please consult the file "CYGWIN_LICENSE" for
|
|
details. */
|
|
|
|
#include "winsup.h"
|
|
#include "cygerrno.h"
|
|
#include "perprocess.h"
|
|
#include "dll_init.h"
|
|
#include "environ.h"
|
|
#include "security.h"
|
|
#include "path.h"
|
|
#include "fhandler.h"
|
|
#include "dtable.h"
|
|
#include "cygheap.h"
|
|
#include "pinfo.h"
|
|
#include "cygtls.h"
|
|
#include "exception.h"
|
|
#include <wchar.h>
|
|
#include <sys/reent.h>
|
|
#include <assert.h>
|
|
|
|
extern void __stdcall check_sanity_and_sync (per_process *);
|
|
|
|
dll_list dlls;
|
|
|
|
static bool dll_global_dtors_recorded;
|
|
|
|
/* Run destructors for all DLLs on exit. */
|
|
void
|
|
dll_global_dtors ()
|
|
{
|
|
int recorded = dll_global_dtors_recorded;
|
|
dll_global_dtors_recorded = false;
|
|
if (recorded && dlls.start.next)
|
|
for (dll *d = dlls.end; d != &dlls.start; d = d->prev)
|
|
d->run_dtors ();
|
|
}
|
|
|
|
/* Run all constructors associated with a dll */
|
|
void
|
|
per_module::run_ctors ()
|
|
{
|
|
void (**pfunc)() = ctors;
|
|
|
|
/* Run ctors backwards, so skip the first entry and find how many
|
|
there are, then run them. */
|
|
|
|
if (pfunc)
|
|
{
|
|
int i;
|
|
for (i = 1; pfunc[i]; i++);
|
|
|
|
for (int j = i - 1; j > 0; j--)
|
|
(pfunc[j]) ();
|
|
}
|
|
}
|
|
|
|
/* Run all destructors associated with a dll */
|
|
void
|
|
per_module::run_dtors ()
|
|
{
|
|
void (**pfunc)() = dtors;
|
|
while (*++pfunc)
|
|
(*pfunc) ();
|
|
}
|
|
|
|
/* Initialize an individual DLL */
|
|
int
|
|
dll::init ()
|
|
{
|
|
int ret = 1;
|
|
|
|
/* This should be a no-op. Why didn't we just import this variable? */
|
|
if (!p.envptr)
|
|
p.envptr = &__cygwin_environ;
|
|
else if (*(p.envptr) != __cygwin_environ)
|
|
*(p.envptr) = __cygwin_environ;
|
|
|
|
/* Don't run constructors or the "main" if we've forked. */
|
|
if (!in_forkee)
|
|
{
|
|
/* global contructors */
|
|
p.run_ctors ();
|
|
|
|
/* entry point of dll (use main of per_process with null args...) */
|
|
if (p.main)
|
|
ret = p.main (0, 0, 0);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Look for a dll based on name */
|
|
dll *
|
|
dll_list::operator[] (const PWCHAR name)
|
|
{
|
|
dll *d = &start;
|
|
while ((d = d->next) != NULL)
|
|
if (!wcscasecmp (name, d->name))
|
|
return d;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#define RETRIES 1000
|
|
|
|
/* Allocate space for a dll struct. */
|
|
dll *
|
|
dll_list::alloc (HINSTANCE h, per_process *p, dll_type type)
|
|
{
|
|
WCHAR name[NT_MAX_PATH];
|
|
DWORD namelen = GetModuleFileNameW (h, name, sizeof (name));
|
|
|
|
/* Already loaded? */
|
|
dll *d = dlls[name];
|
|
if (d)
|
|
{
|
|
if (!in_forkee)
|
|
d->count++; /* Yes. Bump the usage count. */
|
|
}
|
|
else
|
|
{
|
|
/* FIXME: Change this to new at some point. */
|
|
d = (dll *) cmalloc (HEAP_2_DLL, sizeof (*d) + (namelen * sizeof (*name)));
|
|
|
|
/* Now we've allocated a block of information. Fill it in with the supplied
|
|
info about this DLL. */
|
|
d->count = 1;
|
|
wcscpy (d->name, name);
|
|
d->handle = h;
|
|
d->has_dtors = true;
|
|
d->p = p;
|
|
d->type = type;
|
|
if (end == NULL)
|
|
end = &start; /* Point to "end" of dll chain. */
|
|
end->next = d; /* Standard linked list stuff. */
|
|
d->next = NULL;
|
|
d->prev = end;
|
|
end = d;
|
|
tot++;
|
|
if (type == DLL_LOAD)
|
|
loaded_dlls++;
|
|
}
|
|
assert (p->envptr != NULL);
|
|
return d;
|
|
}
|
|
|
|
dll *
|
|
dll_list::find (void *retaddr)
|
|
{
|
|
MEMORY_BASIC_INFORMATION m;
|
|
if (!VirtualQuery (retaddr, &m, sizeof m))
|
|
return NULL;
|
|
HMODULE h = (HMODULE) m.AllocationBase;
|
|
|
|
dll *d = &start;
|
|
while ((d = d->next))
|
|
if (d->handle == h)
|
|
break;
|
|
return d;
|
|
}
|
|
|
|
/* Detach a DLL from the chain. */
|
|
void
|
|
dll_list::detach (void *retaddr)
|
|
{
|
|
dll *d;
|
|
if (!myself || !(d = find (retaddr)))
|
|
return;
|
|
if (d->count <= 0)
|
|
system_printf ("WARNING: trying to detach an already detached dll ...");
|
|
if (--d->count == 0)
|
|
{
|
|
/* Ensure our exception handler is enabled for destructors */
|
|
exception protect;
|
|
/* Call finalize function if we are not already exiting */
|
|
if (!exit_state)
|
|
__cxa_finalize (d);
|
|
d->run_dtors ();
|
|
d->prev->next = d->next;
|
|
if (d->next)
|
|
d->next->prev = d->prev;
|
|
if (d->type == DLL_LOAD)
|
|
loaded_dlls--;
|
|
if (end == d)
|
|
end = d->prev;
|
|
cfree (d);
|
|
}
|
|
}
|
|
|
|
/* Initialization for all linked DLLs, called by dll_crt0_1. */
|
|
void
|
|
dll_list::init ()
|
|
{
|
|
/* Walk the dll chain, initializing each dll */
|
|
dll *d = &start;
|
|
dll_global_dtors_recorded = d->next != NULL;
|
|
while ((d = d->next))
|
|
d->init ();
|
|
}
|
|
|
|
#define A64K (64 * 1024)
|
|
|
|
/* Mark every memory address up to "here" as reserved. This may force
|
|
Windows NT to load a DLL in the next available, lowest slot. */
|
|
static void
|
|
reserve_upto (const PWCHAR name, DWORD here)
|
|
{
|
|
DWORD size;
|
|
MEMORY_BASIC_INFORMATION mb;
|
|
for (DWORD start = 0x10000; start < here; start += size)
|
|
if (!VirtualQuery ((void *) start, &mb, sizeof (mb)))
|
|
size = A64K;
|
|
else
|
|
{
|
|
size = A64K * ((mb.RegionSize + A64K - 1) / A64K);
|
|
start = A64K * (((DWORD) mb.BaseAddress + A64K - 1) / A64K);
|
|
|
|
if (start + size > here)
|
|
size = here - start;
|
|
if (mb.State == MEM_FREE &&
|
|
!VirtualAlloc ((void *) start, size, MEM_RESERVE, PAGE_NOACCESS))
|
|
api_fatal ("couldn't allocate memory %p(%d) for '%W' alignment, %E\n",
|
|
start, size, name);
|
|
}
|
|
}
|
|
|
|
/* Release all of the memory previously allocated by "upto" above.
|
|
Note that this may also free otherwise reserved memory. If that becomes
|
|
a problem, we'll have to keep track of the memory that we reserve above. */
|
|
static void
|
|
release_upto (const PWCHAR name, DWORD here)
|
|
{
|
|
DWORD size;
|
|
MEMORY_BASIC_INFORMATION mb;
|
|
for (DWORD start = 0x10000; start < here; start += size)
|
|
if (!VirtualQuery ((void *) start, &mb, sizeof (mb)))
|
|
size = 64 * 1024;
|
|
else
|
|
{
|
|
size = mb.RegionSize;
|
|
if (!(mb.State == MEM_RESERVE && mb.AllocationProtect == PAGE_NOACCESS
|
|
&& (((void *) start < cygheap->user_heap.base
|
|
|| (void *) start > cygheap->user_heap.top)
|
|
&& ((void *) start < (void *) cygheap
|
|
|| (void *) start
|
|
> (void *) ((char *) cygheap + CYGHEAPSIZE)))))
|
|
continue;
|
|
if (!VirtualFree ((void *) start, 0, MEM_RELEASE))
|
|
api_fatal ("couldn't release memory %p(%d) for '%W' alignment, %E\n",
|
|
start, size, name);
|
|
}
|
|
}
|
|
|
|
/* 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++)
|
|
{
|
|
/* See if DLL will load in proper place. If so, free it and reload
|
|
it the right way.
|
|
It stinks that we can't invert the order of the initial LoadLibrary
|
|
and FreeLibrary since Microsoft documentation seems to imply that
|
|
should do what we want. However, once a library is loaded as
|
|
above, the second LoadLibrary will not execute its startup code
|
|
unless it is first unloaded. */
|
|
HMODULE h = LoadLibraryExW (d->name, NULL, DONT_RESOLVE_DLL_REFERENCES);
|
|
|
|
if (!h)
|
|
system_printf ("can't reload %W, %E", d->name);
|
|
else
|
|
{
|
|
FreeLibrary (h);
|
|
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);
|
|
|
|
if (preferred_block)
|
|
release_at (d->name, preferred_block);
|
|
preferred_block = 0;
|
|
}
|
|
|
|
if (h == d->handle)
|
|
break; /* Success */
|
|
|
|
if (i > 0)
|
|
/* We tried once 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);
|
|
|
|
/* Dll loaded in the wrong place. Dunno why this happens but it
|
|
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;
|
|
}
|
|
|
|
struct dllcrt0_info
|
|
{
|
|
HMODULE h;
|
|
per_process *p;
|
|
int res;
|
|
dllcrt0_info (HMODULE h0, per_process *p0): h (h0), p (p0) {}
|
|
};
|
|
|
|
extern "C" int
|
|
dll_dllcrt0 (HMODULE h, per_process *p)
|
|
{
|
|
dllcrt0_info x (h, p);
|
|
|
|
if (_my_tls.isinitialized ())
|
|
dll_dllcrt0_1 (&x);
|
|
else
|
|
_my_tls.call ((DWORD (*) (void *, void *)) dll_dllcrt0_1, &x);
|
|
return x.res;
|
|
}
|
|
|
|
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;
|
|
|
|
if (p == NULL)
|
|
p = &__cygwin_user_data;
|
|
else
|
|
{
|
|
*(p->impure_ptr_ptr) = __cygwin_user_data.impure_ptr;
|
|
_pei386_runtime_relocator (p);
|
|
}
|
|
|
|
bool linked = !in_forkee && !cygwin_finished_initializing;
|
|
|
|
/* Broken DLLs built against Cygwin versions 1.7.0-49 up to 1.7.0-57
|
|
override the cxx_malloc pointer in their DLL initialization code,
|
|
when loaded either statically or dynamically. Because this leaves
|
|
a stale pointer into demapped memory space if the DLL is unloaded
|
|
by a call to dlclose, we prevent this happening for dynamically
|
|
loaded DLLS in dlopen by saving and restoring cxx_malloc around
|
|
the call to LoadLibrary, which invokes the DLL's startup sequence.
|
|
Modern DLLs won't even attempt to override the pointer when loaded
|
|
statically, but will write their overrides directly into the
|
|
struct it points to. With all modern DLLs, this will remain the
|
|
default_cygwin_cxx_malloc struct in cxx.cc, but if any broken DLLs
|
|
are in the mix they will have overridden the pointer and subsequent
|
|
overrides will go into their embedded cxx_malloc structs. This is
|
|
almost certainly not a problem as they can never be unloaded, but
|
|
if we ever did want to do anything about it, we could check here to
|
|
see if the pointer had been altered in the early parts of the DLL's
|
|
startup, and if so copy back the new overrides and reset it here.
|
|
However, that's just a note for the record; at the moment, we can't
|
|
see any need to worry about this happening. */
|
|
|
|
/* Partially initialize Cygwin guts for non-cygwin apps. */
|
|
if (dynamically_loaded && user_data->magic_biscuit == 0)
|
|
dll_crt0 (p);
|
|
else
|
|
check_sanity_and_sync (p);
|
|
|
|
dll_type type;
|
|
|
|
/* If this function is called before cygwin has finished
|
|
initializing, then the DLL must be a cygwin-aware DLL
|
|
that was explicitly linked into the program rather than
|
|
a dlopened DLL. */
|
|
if (linked)
|
|
type = DLL_LINK;
|
|
else
|
|
{
|
|
type = DLL_LOAD;
|
|
dlls.reload_on_fork = 1;
|
|
}
|
|
|
|
/* Allocate and initialize space for the DLL. */
|
|
dll *d = dlls.alloc (h, p, type);
|
|
|
|
/* If d == NULL, then something is broken.
|
|
Otherwise, if we've finished initializing, it's ok to
|
|
initialize the DLL. If we haven't finished initializing,
|
|
it may not be safe to call the dll's "main" since not
|
|
all of cygwin's internal structures may have been set up. */
|
|
if (!d || (!linked && !d->init ()))
|
|
res = -1;
|
|
else
|
|
res = (DWORD) d;
|
|
}
|
|
|
|
/* OBSOLETE: This function is obsolete and will go away in the
|
|
future. Cygwin can now handle being loaded from a noncygwin app
|
|
using the same entry point. */
|
|
|
|
extern "C" int
|
|
dll_noncygwin_dllcrt0 (HMODULE h, per_process *p)
|
|
{
|
|
return dll_dllcrt0 (h, p);
|
|
}
|
|
|
|
extern "C" void
|
|
cygwin_detach_dll (dll *)
|
|
{
|
|
HANDLE retaddr;
|
|
if (_my_tls.isinitialized ())
|
|
retaddr = (void *) _my_tls.retaddr ();
|
|
else
|
|
retaddr = __builtin_return_address (0);
|
|
dlls.detach (retaddr);
|
|
}
|
|
|
|
extern "C" void
|
|
dlfork (int val)
|
|
{
|
|
dlls.reload_on_fork = val;
|
|
}
|
|
|
|
/* Called from various places to update all of the individual
|
|
ideas of the environ block. Explain to me again why we didn't
|
|
just import __cygwin_environ? */
|
|
void __stdcall
|
|
update_envptrs ()
|
|
{
|
|
for (dll *d = dlls.istart (DLL_ANY); d; d = dlls.inext ())
|
|
if (*(d->p.envptr) != __cygwin_environ)
|
|
*(d->p.envptr) = __cygwin_environ;
|
|
*main_environ = __cygwin_environ;
|
|
}
|