mirror of
git://sourceware.org/git/newlib-cygwin.git
synced 2025-01-19 12:59:21 +08:00
9a231073a5
The default PSAPI_VERSION is controlled by WIN32_WINNT, which we set to 0x0a00 when building utils since 48a76190 (and is the default in w32api >= 9.0.0) In order for the built executables to run on Windows Vista, we must also define PSAPI_VERSION as 1 (otherwise '#define GetModuleFileNameExA K32GetModuleFileNameExA' causes a 'The procedure entry point K32GetModuleFilenameExA could not be located in the dynamic link library kernel32.dll' error at run time). Also drop uneeded psapi.h from dlfcn.cc (31ddf45d), resource.cc (34a6eeab) and ps.cc (1def2148).
443 lines
11 KiB
C++
443 lines
11 KiB
C++
/* dlfcn.cc
|
|
|
|
This file is part of Cygwin.
|
|
|
|
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 <stdlib.h>
|
|
#include <dlfcn.h>
|
|
#include <ctype.h>
|
|
#include <wctype.h>
|
|
#include "path.h"
|
|
#include "fhandler.h"
|
|
#include "dtable.h"
|
|
#include "cygheap.h"
|
|
#include "perprocess.h"
|
|
#include "cygtls.h"
|
|
#include "tls_pbuf.h"
|
|
#include "ntdll.h"
|
|
#include "shared_info.h"
|
|
#include "dll_init.h"
|
|
#include "pathfinder.h"
|
|
|
|
/* Dumb allocator using memory from tmp_pathbuf.w_get ().
|
|
|
|
Does not reuse free'd memory areas. Instead, memory
|
|
is released when the tmp_pathbuf goes out of scope.
|
|
|
|
ATTENTION: Requesting memory from an instance of tmp_pathbuf breaks
|
|
when another instance on a newer stack frame has provided memory. */
|
|
class tmp_pathbuf_allocator
|
|
: public allocator_interface
|
|
{
|
|
tmp_pathbuf & tp_;
|
|
union
|
|
{
|
|
PWCHAR wideptr;
|
|
void * voidptr;
|
|
char * byteptr;
|
|
} freemem_;
|
|
size_t freesize_;
|
|
|
|
/* allocator_interface */
|
|
virtual void * alloc (size_t need)
|
|
{
|
|
if (need == 0)
|
|
need = 1; /* GNU-ish */
|
|
size_t chunksize = NT_MAX_PATH * sizeof (WCHAR);
|
|
if (need > chunksize)
|
|
api_fatal ("temporary buffer too small for %d bytes", need);
|
|
if (need > freesize_)
|
|
{
|
|
/* skip remaining, use next chunk */
|
|
freemem_.wideptr = tp_.w_get ();
|
|
freesize_ = chunksize;
|
|
}
|
|
|
|
void * ret = freemem_.voidptr;
|
|
|
|
/* adjust remaining, aligned at 8 byte boundary */
|
|
need = need + 7 - (need - 1) % 8;
|
|
freemem_.byteptr += need;
|
|
if (need > freesize_)
|
|
freesize_ = 0;
|
|
else
|
|
freesize_ -= need;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* allocator_interface */
|
|
virtual void free (void *)
|
|
{
|
|
/* no-op: released by tmp_pathbuf at end of scope */
|
|
}
|
|
|
|
tmp_pathbuf_allocator ();
|
|
tmp_pathbuf_allocator (tmp_pathbuf_allocator const &);
|
|
tmp_pathbuf_allocator & operator = (tmp_pathbuf_allocator const &);
|
|
|
|
public:
|
|
/* use tmp_pathbuf of current stack frame */
|
|
tmp_pathbuf_allocator (tmp_pathbuf & tp)
|
|
: allocator_interface ()
|
|
, tp_ (tp)
|
|
, freemem_ ()
|
|
, freesize_ (0)
|
|
{}
|
|
};
|
|
|
|
static void
|
|
set_dl_error (const char *str)
|
|
{
|
|
strcpy (_my_tls.locals.dl_buffer, strerror (get_errno ()));
|
|
_my_tls.locals.dl_error = 1;
|
|
}
|
|
|
|
/* Identify basename and baselen within name,
|
|
return true if there is a dir in name. */
|
|
static bool
|
|
spot_basename (const char * &basename, int &baselen, const char * name)
|
|
{
|
|
basename = strrchr (name, '/');
|
|
basename = basename ? basename + 1 : name;
|
|
baselen = name + strlen (name) - basename;
|
|
return basename > name;
|
|
}
|
|
|
|
/* Setup basenames using basename and baselen,
|
|
return true if basenames do have some suffix. */
|
|
static void
|
|
collect_basenames (pathfinder::basenamelist & basenames,
|
|
const char * basename, int baselen)
|
|
{
|
|
/* like strrchr (basename, '.'), but limited to baselen */
|
|
const char *suffix = basename + baselen;
|
|
while (--suffix >= basename)
|
|
if (*suffix == '.')
|
|
break;
|
|
|
|
int suffixlen;
|
|
if (suffix >= basename)
|
|
suffixlen = basename + baselen - suffix;
|
|
else
|
|
{
|
|
suffixlen = 0;
|
|
suffix = NULL;
|
|
}
|
|
|
|
char const * ext = "";
|
|
/* Without some suffix, reserve space for a trailing dot to override
|
|
GetModuleHandleExA's automatic adding of the ".dll" suffix. */
|
|
int extlen = suffix ? 0 : 1;
|
|
|
|
/* If we have the ".so" suffix, ... */
|
|
if (suffixlen == 3 && !strncmp (suffix, ".so", 3))
|
|
{
|
|
/* ... keep the basename with original suffix, before ... */
|
|
basenames.appendv (basename, baselen, NULL);
|
|
/* ... replacing the ".so" with the ".dll" suffix. */
|
|
baselen -= 3;
|
|
ext = ".dll";
|
|
extlen = 4;
|
|
}
|
|
/* If the basename starts with "lib", ... */
|
|
if (!strncmp (basename, "lib", 3))
|
|
{
|
|
/* ... replace "lib" with "cyg", before ... */
|
|
basenames.appendv ("cyg", 3, basename+3, baselen-3, ext, extlen, NULL);
|
|
}
|
|
/* ... using original basename with new suffix. */
|
|
basenames.appendv (basename, baselen, ext, extlen, NULL);
|
|
}
|
|
|
|
/* Identify dir of current executable into exedirbuf using wpathbuf buffer.
|
|
Return length of exedirbuf on success, or zero on error. */
|
|
static int
|
|
get_exedir (char * exedirbuf, wchar_t * wpathbuf)
|
|
{
|
|
/* Unless we have a special cygwin loader, there is no such thing like
|
|
DT_RUNPATH on Windows we can use to search for dlls, except for the
|
|
directory of the main executable. */
|
|
*exedirbuf = '\0';
|
|
|
|
wchar_t * wlastsep = wcpcpy (wpathbuf, global_progname);
|
|
/* like wcsrchr(L'\\'), but we know the wcslen already */
|
|
while (--wlastsep > wpathbuf)
|
|
if (*wlastsep == L'\\')
|
|
break;
|
|
if (wlastsep <= wpathbuf)
|
|
return 0;
|
|
*wlastsep = L'\0';
|
|
|
|
if (mount_table->conv_to_posix_path (wpathbuf, exedirbuf, 0))
|
|
return 0;
|
|
|
|
return strlen (exedirbuf);
|
|
}
|
|
|
|
extern "C" void *
|
|
dlopen (const char *name, int flags)
|
|
{
|
|
void *ret = NULL;
|
|
|
|
do
|
|
{
|
|
if (name == NULL || *name == '\0')
|
|
{
|
|
ret = (void *) GetModuleHandle (NULL); /* handle for the current module */
|
|
if (!ret)
|
|
__seterrno ();
|
|
break;
|
|
}
|
|
|
|
DWORD gmheflags = (flags & RTLD_NODELETE)
|
|
? GET_MODULE_HANDLE_EX_FLAG_PIN
|
|
: 0;
|
|
|
|
tmp_pathbuf tp; /* single one per stack frame */
|
|
tmp_pathbuf_allocator allocator (tp);
|
|
pathfinder::basenamelist basenames (allocator);
|
|
|
|
const char *basename;
|
|
int baselen;
|
|
bool have_dir = spot_basename (basename, baselen, name);
|
|
collect_basenames (basenames, basename, baselen);
|
|
|
|
/* handle for the named library */
|
|
path_conv real_filename;
|
|
wchar_t *wpath = tp.w_get ();
|
|
char *cpath = tp.c_get ();
|
|
|
|
pathfinder finder (allocator, basenames); /* eats basenames */
|
|
|
|
if (have_dir)
|
|
{
|
|
int dirlen = basename - 1 - name;
|
|
|
|
/* if the specified dir is x/lib, and the current executable
|
|
dir is x/bin, do the /lib -> /bin mapping, which is the
|
|
same actually as adding the executable dir */
|
|
if (dirlen >= 4 && !strncmp (name + dirlen - 4, "/lib", 4))
|
|
{
|
|
int exedirlen = get_exedir (cpath, wpath);
|
|
if (exedirlen == dirlen &&
|
|
!strncmp (cpath, name, dirlen - 4) &&
|
|
!strcmp (cpath + dirlen - 4, "/bin"))
|
|
finder.add_searchdir (cpath, exedirlen);
|
|
}
|
|
|
|
/* search the specified dir */
|
|
finder.add_searchdir (name, dirlen);
|
|
}
|
|
else
|
|
{
|
|
/* NOTE: The Windows loader (for linked dlls) does
|
|
not use the LD_LIBRARY_PATH environment variable. */
|
|
finder.add_envsearchpath ("LD_LIBRARY_PATH");
|
|
|
|
/* Finally we better have some fallback. */
|
|
finder.add_searchdir ("/usr/bin", 8);
|
|
finder.add_searchdir ("/usr/lib", 8);
|
|
}
|
|
|
|
/* now search the file system */
|
|
if (!finder.find (pathfinder::
|
|
exists_and_not_dir (real_filename,
|
|
PC_SYM_FOLLOW | PC_POSIX)))
|
|
{
|
|
/* If nothing worked, create a relative path from the original
|
|
incoming filename and let LoadLibrary search for it using the
|
|
system default DLL search path. */
|
|
real_filename.check (name, PC_SYM_FOLLOW | PC_NOFULL | PC_NULLEMPTY);
|
|
if (real_filename.error)
|
|
break;
|
|
}
|
|
|
|
real_filename.get_wide_win32_path (wpath);
|
|
/* Check if the last path component contains a dot. If so,
|
|
leave the filename alone. Otherwise add a trailing dot
|
|
to override LoadLibrary's automatic adding of a ".dll" suffix. */
|
|
wchar_t *last_bs = wcsrchr (wpath, L'\\') ?: wpath;
|
|
if (last_bs && !wcschr (last_bs, L'.'))
|
|
wcscat (last_bs, L".");
|
|
|
|
if (flags & RTLD_NOLOAD)
|
|
{
|
|
GetModuleHandleExW (gmheflags, wpath, (HMODULE *) &ret);
|
|
if (ret)
|
|
break;
|
|
}
|
|
|
|
#ifdef __i386__
|
|
/* Workaround for broken DLLs built against Cygwin versions 1.7.0-49
|
|
up to 1.7.0-57. They override the cxx_malloc pointer in their
|
|
DLL initialization code even if loaded dynamically. This is a
|
|
no-no since a later dlclose lets cxx_malloc point into nirvana.
|
|
The below kludge "fixes" that by reverting the original cxx_malloc
|
|
pointer after LoadLibrary. This implies that their overrides
|
|
won't be applied; that's OK. All overrides should be present at
|
|
final link time, as Windows doesn't allow undefined references;
|
|
it would actually be wrong for a dlopen'd DLL to opportunistically
|
|
override functions in a way that wasn't known then. We're not
|
|
going to try and reproduce the full ELF dynamic loader here! */
|
|
|
|
/* Store original cxx_malloc pointer. */
|
|
struct per_process_cxx_malloc *tmp_malloc;
|
|
tmp_malloc = __cygwin_user_data.cxx_malloc;
|
|
#endif
|
|
|
|
ret = (void *) LoadLibraryW (wpath);
|
|
/* reference counting */
|
|
if (ret)
|
|
{
|
|
dll *d = dlls.find (ret);
|
|
if (d)
|
|
++d->count;
|
|
}
|
|
|
|
#ifdef __i386__
|
|
/* Restore original cxx_malloc pointer. */
|
|
__cygwin_user_data.cxx_malloc = tmp_malloc;
|
|
#endif
|
|
|
|
if (ret && gmheflags)
|
|
GetModuleHandleExW (gmheflags, wpath, (HMODULE *) &ret);
|
|
|
|
if (!ret)
|
|
__seterrno ();
|
|
}
|
|
while (0);
|
|
|
|
if (!ret)
|
|
set_dl_error ("dlopen");
|
|
debug_printf ("ret %p", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
extern "C" void *
|
|
dlsym (void *handle, const char *name)
|
|
{
|
|
void *ret = NULL;
|
|
|
|
if (handle == RTLD_DEFAULT)
|
|
{ /* search all modules */
|
|
PDEBUG_BUFFER buf;
|
|
NTSTATUS status;
|
|
|
|
buf = RtlCreateQueryDebugBuffer (0, FALSE);
|
|
if (!buf)
|
|
{
|
|
set_errno (ENOMEM);
|
|
set_dl_error ("dlsym");
|
|
return NULL;
|
|
}
|
|
status = RtlQueryProcessDebugInformation (GetCurrentProcessId (),
|
|
PDI_MODULES, buf);
|
|
if (!NT_SUCCESS (status))
|
|
__seterrno_from_nt_status (status);
|
|
else
|
|
{
|
|
PDEBUG_MODULE_ARRAY mods = (PDEBUG_MODULE_ARRAY)
|
|
buf->ModuleInformation;
|
|
for (ULONG i = 0; i < mods->Count; ++i)
|
|
if ((ret = (void *)
|
|
GetProcAddress ((HMODULE) mods->Modules[i].Base, name)))
|
|
break;
|
|
if (!ret)
|
|
set_errno (ENOENT);
|
|
}
|
|
RtlDestroyQueryDebugBuffer (buf);
|
|
}
|
|
else
|
|
{
|
|
ret = (void *) GetProcAddress ((HMODULE) handle, name);
|
|
if (!ret)
|
|
__seterrno ();
|
|
}
|
|
if (!ret)
|
|
set_dl_error ("dlsym");
|
|
debug_printf ("ret %p", ret);
|
|
return ret;
|
|
}
|
|
|
|
extern "C" int
|
|
dlclose (void *handle)
|
|
{
|
|
int ret = 0;
|
|
if (handle != GetModuleHandle (NULL))
|
|
{
|
|
/* reference counting */
|
|
dll *d = dlls.find (handle);
|
|
if (!d || d->count <= 0)
|
|
{
|
|
errno = ENOENT;
|
|
ret = -1;
|
|
}
|
|
else
|
|
{
|
|
--d->count;
|
|
if (!FreeLibrary ((HMODULE) handle))
|
|
{
|
|
__seterrno ();
|
|
ret = -1;
|
|
}
|
|
}
|
|
}
|
|
if (ret)
|
|
set_dl_error ("dlclose");
|
|
return ret;
|
|
}
|
|
|
|
extern "C" char *
|
|
dlerror ()
|
|
{
|
|
char *res;
|
|
if (!_my_tls.locals.dl_error)
|
|
res = NULL;
|
|
else
|
|
{
|
|
_my_tls.locals.dl_error = 0;
|
|
res = _my_tls.locals.dl_buffer;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
extern "C" int
|
|
dladdr (const void *addr, Dl_info *info)
|
|
{
|
|
HMODULE hModule;
|
|
BOOL ret = GetModuleHandleEx (GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
|
|
(LPCSTR) addr,
|
|
&hModule);
|
|
if (!ret)
|
|
return 0;
|
|
|
|
/* Module handle happens to be equal to it's base load address. */
|
|
info->dli_fbase = hModule;
|
|
|
|
/* Get the module filename. This pathname may be in short-, long- or //?/
|
|
format, depending on how it was specified when loaded, but we assume this
|
|
is always an absolute pathname. */
|
|
WCHAR fname[MAX_PATH];
|
|
DWORD length = GetModuleFileNameW (hModule, fname, MAX_PATH);
|
|
if ((length == 0) || (length == MAX_PATH))
|
|
return 0;
|
|
|
|
/* Convert to a cygwin pathname */
|
|
ssize_t conv = cygwin_conv_path (CCP_WIN_W_TO_POSIX | CCP_ABSOLUTE, fname,
|
|
info->dli_fname, MAX_PATH);
|
|
if (conv)
|
|
return 0;
|
|
|
|
/* Always indicate no symbol matching addr could be found. */
|
|
info->dli_sname = NULL;
|
|
info->dli_saddr = NULL;
|
|
|
|
return 1;
|
|
}
|