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;
 | 
						|
}
 |