/* * dlfcn-win32 * Copyright (c) 2007 Ramiro Polla * Copyright (c) 2015 Tiancheng "Timothy" Gu * * dlfcn-win32 is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * dlfcn-win32 is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with dlfcn-win32; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef _DEBUG #define _CRTDBG_MAP_ALLOC #include #include #endif #define PSAPI_VERSION 1 #include #include #include #ifdef SHARED #define DLFCN_WIN32_EXPORTS #endif #include "dlfcn.h" #if ((defined(_WIN32) || defined(WIN32)) && (defined(_MSC_VER)) ) #define snprintf sprintf_s #endif #ifdef UNICODE #include #define CHAR wchar_t #define UNICODE_L(s) L##s #else #define CHAR char #define UNICODE_L(s) s #endif /* Note: * MSDN says these functions are not thread-safe. We make no efforts to have * any kind of thread safety. */ typedef struct global_object { HMODULE hModule; struct global_object *previous; struct global_object *next; } global_object; static global_object first_object; static global_object first_automatic_object; static int auto_ref_count = 0; /* These functions implement a double linked list for the global objects. */ static global_object *global_search( global_object *start, HMODULE hModule ) { global_object *pobject; if( hModule == NULL ) return NULL; for( pobject = start; pobject; pobject = pobject->next ) if( pobject->hModule == hModule ) return pobject; return NULL; } static void global_add( global_object *start, HMODULE hModule ) { global_object *pobject; global_object *nobject; if( hModule == NULL ) return; pobject = global_search( start, hModule ); /* Do not add object again if it's already on the list */ if( pobject ) return; if( start == &first_automatic_object ) { pobject = global_search( &first_object, hModule ); if( pobject ) return; } for( pobject = start; pobject->next; pobject = pobject->next ); nobject = (global_object*) malloc( sizeof( global_object ) ); /* Should this be enough to fail global_add, and therefore also fail * dlopen? */ if( !nobject ) return; pobject->next = nobject; nobject->next = NULL; nobject->previous = pobject; nobject->hModule = hModule; } static void global_rem( global_object *start, HMODULE hModule ) { global_object *pobject; if( hModule == NULL ) return; pobject = global_search( start, hModule ); if( !pobject ) return; if( pobject->next ) pobject->next->previous = pobject->previous; if( pobject->previous ) pobject->previous->next = pobject->next; free( pobject ); } /* POSIX says dlerror( ) doesn't have to be thread-safe, so we use one * static buffer. * MSDN says the buffer cannot be larger than 64K bytes, so we set it to * the limit. */ static CHAR error_buffer[65535]; static CHAR *current_error; static char dlerror_buffer[65536]; static int copy_string( CHAR *dest, int dest_size, const CHAR *src ) { int i = 0; /* gcc should optimize this out */ if( !src || !dest ) return 0; for( i = 0 ; i < dest_size-1 ; i++ ) { if( !src[i] ) break; else dest[i] = src[i]; } dest[i] = '\0'; return i; } static void save_err_str( const CHAR *str ) { DWORD dwMessageId; DWORD pos; dwMessageId = GetLastError( ); if( dwMessageId == 0 ) return; /* Format error message to: * "": */ pos = copy_string( error_buffer, sizeof(error_buffer), UNICODE_L("\"") ); pos += copy_string( error_buffer+pos, sizeof(error_buffer)-pos, str ); pos += copy_string( error_buffer+pos, sizeof(error_buffer)-pos, UNICODE_L("\": ") ); pos += FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwMessageId, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), error_buffer+pos, sizeof(error_buffer)-pos, NULL ); if( pos > 1 ) { /* POSIX says the string must not have trailing */ if( error_buffer[pos-2] == '\r' && error_buffer[pos-1] == '\n' ) error_buffer[pos-2] = '\0'; } current_error = error_buffer; } static void save_err_ptr_str( const void *ptr ) { CHAR ptr_buf[19]; /* 0x up to 64 bits. */ #ifdef UNICODE # if ((defined(_WIN32) || defined(WIN32)) && (defined(_MSC_VER)) ) swprintf_s( ptr_buf, 19, UNICODE_L("0x%p"), ptr ); # else swprintf(ptr_buf, 19, UNICODE_L("0x%p"), ptr); # endif #else snprintf( ptr_buf, 19, "0x%p", ptr ); #endif save_err_str( ptr_buf ); } void *dlopen( const char *file, int mode ) { HMODULE hModule; UINT uMode; current_error = NULL; /* Do not let Windows display the critical-error-handler message box */ uMode = SetErrorMode( SEM_FAILCRITICALERRORS ); if( file == 0 ) { HMODULE hAddtnlMods[1024]; // Already loaded modules HANDLE hCurrentProc = GetCurrentProcess( ); DWORD cbNeeded; /* POSIX says that if the value of file is 0, a handle on a global * symbol object must be provided. That object must be able to access * all symbols from the original program file, and any objects loaded * with the RTLD_GLOBAL flag. * The return value from GetModuleHandle( ) allows us to retrieve * symbols only from the original program file. For objects loaded with * the RTLD_GLOBAL flag, we create our own list later on. For objects * outside of the program file but already loaded (e.g. linked DLLs) * they are added below. */ hModule = GetModuleHandle( NULL ); if( !hModule ) save_err_ptr_str( file ); /* GetModuleHandle( NULL ) only returns the current program file. So * if we want to get ALL loaded module including those in linked DLLs, * we have to use EnumProcessModules( ). */ if( EnumProcessModules( hCurrentProc, hAddtnlMods, sizeof( hAddtnlMods ), &cbNeeded ) != 0 ) { DWORD i; for( i = 0; i < cbNeeded / sizeof( HMODULE ); i++ ) { global_add( &first_automatic_object, hAddtnlMods[i] ); } } auto_ref_count++; } else { CHAR lpFileName[MAX_PATH]; int i; /* MSDN says backslashes *must* be used instead of forward slashes. */ for( i = 0 ; i < sizeof(lpFileName) - 1 ; i ++ ) { if( !file[i] ) break; else if( file[i] == '/' ) lpFileName[i] = '\\'; else lpFileName[i] = file[i]; } lpFileName[i] = '\0'; /* POSIX says the search path is implementation-defined. * LOAD_WITH_ALTERED_SEARCH_PATH is used to make it behave more closely * to UNIX's search paths (start with system folders instead of current * folder). */ hModule = LoadLibraryEx(lpFileName, NULL, LOAD_WITH_ALTERED_SEARCH_PATH ); /* If the object was loaded with RTLD_GLOBAL, add it to list of global * objects, so that its symbols may be retrieved even if the handle for * the original program file is passed. POSIX says that if the same * file is specified in multiple invocations, and any of them are * RTLD_GLOBAL, even if any further invocations use RTLD_LOCAL, the * symbols will remain global. */ if( !hModule ) save_err_str( lpFileName ); else if( (mode & RTLD_GLOBAL) ) global_add( &first_object, hModule ); } /* Return to previous state of the error-mode bit flags. */ SetErrorMode( uMode ); return (void *) hModule; } static void free_auto( ) { global_object *pobject = first_automatic_object.next; if( pobject ) { global_object *next; for ( ; pobject; pobject = next ) { next = pobject->next; free( pobject ); } first_automatic_object.next = NULL; } } int dlclose( void *handle ) { HMODULE hModule = (HMODULE) handle; BOOL ret; current_error = NULL; ret = FreeLibrary( hModule ); /* If the object was loaded with RTLD_GLOBAL, remove it from list of global * objects. */ if( ret ) { HMODULE cur = GetModuleHandle( NULL ); global_rem( &first_object, hModule ); if( hModule == cur ) { auto_ref_count--; if( auto_ref_count < 0 ) auto_ref_count = 0; if( !auto_ref_count ) free_auto( ); } } else save_err_ptr_str( handle ); /* dlclose's return value in inverted in relation to FreeLibrary's. */ ret = !ret; return (int) ret; } void *dlsym( void *handle, const char *name ) { FARPROC symbol; HMODULE hModule; #ifdef UNICODE wchar_t namew[MAX_PATH]; wmemset(namew, 0, MAX_PATH); #endif current_error = NULL; symbol = GetProcAddress( (HMODULE) handle, name ); if( symbol != NULL ) goto end; /* If the handle for the original program file is passed, also search * in all globally loaded objects. */ hModule = GetModuleHandle( NULL ); if( hModule == handle ) { global_object *pobject; for( pobject = &first_object; pobject; pobject = pobject->next ) { if( pobject->hModule ) { symbol = GetProcAddress( pobject->hModule, name ); if( symbol != NULL ) goto end; } } for( pobject = &first_automatic_object; pobject; pobject = pobject->next ) { if( pobject->hModule ) { symbol = GetProcAddress( pobject->hModule, name ); if( symbol != NULL ) goto end; } } } end: if( symbol == NULL ) { #ifdef UNICODE size_t converted_chars; size_t str_len = strlen(name) + 1; #if ((defined(_WIN32) || defined(WIN32)) && (defined(_MSC_VER)) ) errno_t err = mbstowcs_s(&converted_chars, namew, str_len, name, str_len); if (err != 0) return NULL; #else mbstowcs(namew, name, str_len); #endif save_err_str( namew ); #else save_err_str( name ); #endif } // warning C4054: 'type cast' : from function pointer 'FARPROC' to data pointer 'void *' #ifdef _MSC_VER #pragma warning( suppress: 4054 ) #endif return (void*) symbol; } char *dlerror( void ) { char *error_pointer = dlerror_buffer; /* If this is the second consecutive call to dlerror, return NULL */ if (current_error == NULL) { return NULL; } #ifdef UNICODE errno_t err = 0; size_t converted_chars = 0; size_t str_len = wcslen(current_error) + 1; memset(error_pointer, 0, 65535); # if ((defined(_WIN32) || defined(WIN32)) && (defined(_MSC_VER)) ) err = wcstombs_s(&converted_chars, error_pointer, str_len * sizeof(char), current_error, str_len * sizeof(wchar_t)); if (err != 0) return NULL; # else wcstombs(error_pointer, current_error, str_len); # endif #else memcpy(error_pointer, current_error, strlen(current_error) + 1); #endif /* POSIX says that invoking dlerror( ) a second time, immediately following * a prior invocation, shall result in NULL being returned. */ current_error = NULL; return error_pointer; } #ifdef SHARED BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved ) { (void) hinstDLL; /* * https://msdn.microsoft.com/en-us/library/windows/desktop/ms682583(v=vs.85).aspx * * When handling DLL_PROCESS_DETACH, a DLL should free resources such as heap * memory only if the DLL is being unloaded dynamically (the lpReserved * parameter is NULL). */ if( fdwReason == DLL_PROCESS_DETACH && !lpvReserved ) { auto_ref_count = 0; free_auto( ); } return TRUE; } #endif