From ecbd51ca8aa0ff5715f3ff863e1af441674a6a88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan-Iulian=20Alecu?= <165364995+pascalecu@users.noreply.github.com> Date: Fri, 20 Jun 2025 13:53:08 +0300 Subject: [PATCH 01/12] feat: make dladdr more reliable and safe --- source/dladdr.c | 236 +++++++++++++++++++++++++++--------------------- 1 file changed, 134 insertions(+), 102 deletions(-) diff --git a/source/dladdr.c b/source/dladdr.c index 90cf291..3619946 100644 --- a/source/dladdr.c +++ b/source/dladdr.c @@ -6,140 +6,172 @@ static PIMAGE_NT_HEADERS ImageNtHeader (PVOID Base) { - PIMAGE_DOS_HEADER imageDosHeader = Base; - return (PIMAGE_NT_HEADERS) ((char *) (imageDosHeader) + - imageDosHeader->e_lfanew); + PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER) Base; + if (!dos || dos->e_magic != IMAGE_DOS_SIGNATURE) + return NULL; + + PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS) ((char *) Base + dos->e_lfanew); + if (nt->Signature != IMAGE_NT_SIGNATURE) + return NULL; + + return nt; } int dladdr (const void *addr, Dl_info * info) { - DWORD dwErrCode = GetLastError (); - pid_t mepid = getpid (); - HANDLE hProcess = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, mepid); - const char *root = "\\\\?\\GLOBALROOT"; - LPSTR lpFilename; - DWORD nSize = 1; - HANDLE hFile; - MEMORY_BASIC_INFORMATION lpBuffer; - LPSTR lpszFilePath; - PIMAGE_NT_HEADERS imageNtHeaders; - PIMAGE_EXPORT_DIRECTORY imageExportDirectory; - DWORD i; - DWORD NumberOfNames; - DWORD *AddressOfNames; - DWORD *AddressOfFunctions; - DWORD AddressOfFunction; - char *dli_sname; - void *dli_saddr; - SetLastError (ERROR_SUCCESS); - nSize += strlen (root) / sizeof (*lpFilename); - lpFilename = malloc (nSize); - lpFilename += strlen (root) / sizeof (*lpFilename); - nSize -= strlen (root) / sizeof (*lpFilename); - GetMappedFileName (hProcess, (LPVOID) addr, lpFilename, nSize); - while (GetLastError () == ERROR_INSUFFICIENT_BUFFER) + if (!addr || !info) { - nSize += 4096; - lpFilename -= strlen (root) / sizeof (*lpFilename); - nSize += strlen (root) / sizeof (*lpFilename); - free (lpFilename); - lpFilename = malloc (nSize); - if (lpFilename == 0) - { - return 0; - } - lpFilename += strlen (root) / sizeof (*lpFilename); - nSize -= strlen (root) / sizeof (*lpFilename); - GetMappedFileName (hProcess, (LPVOID) addr, lpFilename, nSize); + SetLastError (ERROR_INVALID_PARAMETER); + return 0; } - if (GetLastError () != ERROR_SUCCESS) + + DWORD oldErr = GetLastError (); + int resultCode = 0; + HANDLE hProcess = NULL; + LPSTR lpFilename = NULL; + LPSTR resolvedPath = NULL; + + HANDLE hProcess = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, getpid ()); + if (!hProcess) { + SetLastError (ERROR_INVALID_HANDLE); return 0; } - lpFilename -= strlen (root) / sizeof (*lpFilename); - memcpy (lpFilename, root, strlen (root)); - nSize = 1; - hFile = CreateFile (lpFilename, GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); - if (hFile != INVALID_HANDLE_VALUE) + LPCSTR root = "\\\\?\\GLOBALROOT"; + size_t rootlen = strlen (root); + DWORD nSize = MAX_PATH; + lpFilename = (LPSTR) malloc (nSize + rootlen + 1); + if (!lpFilename) { - nSize = GetFinalPathNameByHandle (hFile, 0, 0, VOLUME_NAME_DOS); + SetLastError (ERROR_NOT_ENOUGH_MEMORY); + goto cleanup; + } - if (nSize != 0) + LPSTR filenamePtr = lpFilename + rootlen; + DWORD result = + GetMappedFileNameA (hProcess, (LPVOID) addr, filenamePtr, nSize); + if (result == 0) + { + goto cleanup; + } + + memcpy (lpFilename, root, rootlen); + lpFilename[rootlen + result] = '\0'; + + HANDLE hFile = CreateFileA (lpFilename, + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | + FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + + resolvedPath = lpFilename; + if (hFile != INVALID_HANDLE_VALUE) + { + DWORD finalSize = + GetFinalPathNameByHandleA (hFile, NULL, 0, VOLUME_NAME_DOS); + if (finalSize) { - nSize++; - lpszFilePath = (char *) malloc (nSize); - if (!lpszFilePath || !GetFinalPathNameByHandle (hFile, - lpszFilePath, nSize, - VOLUME_NAME_DOS)) + LPSTR tmpPath = (LPSTR) malloc (finalSize + 1); + if (tmpPath + && GetFinalPathNameByHandleA (hFile, tmpPath, finalSize, + VOLUME_NAME_DOS)) { - free (lpszFilePath); - lpszFilePath = lpFilename; + free (lpFilename); + resolvedPath = tmpPath; } else { - free (lpFilename); + free (tmpPath); } } - else - { - lpszFilePath = lpFilename; - } - } - else - { - lpszFilePath = lpFilename; + CloseHandle (hFile); } - if (!VirtualQuery (addr, &lpBuffer, sizeof (lpBuffer))) + MEMORY_BASIC_INFORMATION mbi; + if (!VirtualQuery (addr, &mbi, sizeof (mbi))) { - lpBuffer.AllocationBase = (void *) addr; + mbi.AllocationBase = (LPVOID) addr; } - imageNtHeaders = ImageNtHeader (lpBuffer.AllocationBase); - imageExportDirectory = - (PIMAGE_EXPORT_DIRECTORY) ((size_t) lpBuffer.AllocationBase + - imageNtHeaders->OptionalHeader.DataDirectory - [IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); - if (imageExportDirectory->NumberOfNames < - imageExportDirectory->NumberOfFunctions) + PIMAGE_NT_HEADERS ntHeaders = ImageNtHeader (mbi.AllocationBase); + if (!ntHeaders) { - NumberOfNames = imageExportDirectory->NumberOfNames; + goto cleanup; } - else + + DWORD exportDirRVA = + ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]. + VirtualAddress; + if (!exportDirRVA) { - NumberOfNames = imageExportDirectory->NumberOfFunctions; + goto cleanup; } - AddressOfNames = - (DWORD *) ((size_t) lpBuffer.AllocationBase + - imageExportDirectory->AddressOfNames); - AddressOfFunctions = - (DWORD *) ((size_t) lpBuffer.AllocationBase + - imageExportDirectory->AddressOfFunctions); - AddressOfFunction = (size_t) addr - (size_t) lpBuffer.AllocationBase; - dli_sname = 0; - dli_saddr = 0; - - for (i = 0; i != NumberOfNames; i++) + + PIMAGE_EXPORT_DIRECTORY exportDir = + (PIMAGE_EXPORT_DIRECTORY) ((size_t) mbi.AllocationBase + exportDirRVA); + + DWORD *nameRVAs = + (DWORD *) ((size_t) mbi.AllocationBase + exportDir->AddressOfNames); + DWORD *funcRVAs = + (DWORD *) ((size_t) mbi.AllocationBase + exportDir->AddressOfFunctions); + WORD *ordinals = + (WORD *) ((size_t) mbi.AllocationBase + exportDir->AddressOfNameOrdinals); + + LPSTR closestName = NULL; + LPVOID closestAddr = NULL; + size_t addrOffset = (size_t) addr - (size_t) mbi.AllocationBase; + DWORD bestDistance = (DWORD) - 1; + + for (DWORD i = 0; i < exportDir->NumberOfNames; ++i) { - if (AddressOfFunctions[i] <= AddressOfFunction) + WORD ordinal = ordinals[i]; + DWORD funcRVA = funcRVAs[ordinal]; + if (funcRVA <= addrOffset) { - dli_sname = - (char *) ((size_t) lpBuffer.AllocationBase + AddressOfNames[i]); - dli_saddr = - (void *) ((size_t) lpBuffer.AllocationBase + - AddressOfFunctions[i]); - i = NumberOfNames - 1; + DWORD distance = addrOffset - funcRVA; + if (distance < bestDistance) + { + bestDistance = distance; + closestName = + (LPSTR) ((size_t) mbi.AllocationBase + nameRVAs[i]); + closestAddr = (LPVOID) ((size_t) mbi.AllocationBase + funcRVA); + } } } - info->dli_fname = lpszFilePath; - info->dli_fbase = lpBuffer.AllocationBase; - info->dli_sname = dli_sname; - info->dli_saddr = dli_saddr; - SetLastError (dwErrCode); - return 1; + info->dli_fname = resolvedPath; + info->dli_fbase = mbi.AllocationBase; + info->dli_sname = closestName; + info->dli_saddr = closestAddr; + + resultCode = 1; + +cleanup: + SetLastError (oldErr); + if (hProcess) + { + CloseHandle (hProcess); + } + + if (resultCode == 0 && resolvedPath != lpFilename) + { + free (resolvedPath); + } + + if ((resultCode == 1 && resolvedPath != lpFilename) || resultCode == 0) + { + // User takes ownership of resolvedPath + } + + else if (lpFilename) + { + free (lpFilename); + } + + return resultCode; } From 2e90fde8ad4d42cb4940c916e3742786f4a34d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan-Iulian=20Alecu?= <165364995+pascalecu@users.noreply.github.com> Date: Fri, 20 Jun 2025 14:26:16 +0300 Subject: [PATCH 02/12] feat: refactor dladdr function and improve symbol resolution --- source/dladdr.c | 194 +++++++++++++----------------------------------- 1 file changed, 52 insertions(+), 142 deletions(-) diff --git a/source/dladdr.c b/source/dladdr.c index 3619946..07e0b20 100644 --- a/source/dladdr.c +++ b/source/dladdr.c @@ -2,176 +2,86 @@ #include #include #include +#include -static PIMAGE_NT_HEADERS -ImageNtHeader (PVOID Base) -{ - PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER) Base; - if (!dos || dos->e_magic != IMAGE_DOS_SIGNATURE) - return NULL; - - PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS) ((char *) Base + dos->e_lfanew); - if (nt->Signature != IMAGE_NT_SIGNATURE) - return NULL; +#pragma comment(lib, "dbghelp.lib") - return nt; +static BOOL CALLBACK +SymInitOnceCallback (PINIT_ONCE InitOnce, PVOID Parameter, PVOID * Context) +{ + HANDLE process = GetCurrentProcess (); + SymSetOptions (SYMOPT_DEFERRED_LOADS | SYMOPT_UNDNAME | SYMOPT_NO_PROMPTS); + return SymInitialize (process, NULL, TRUE); } int -dladdr (const void *addr, Dl_info * info) +dladdr (const LPVOID addr, Dl_info * info) { + int ret = 0; + DWORD lastErr = GetLastError (); + if (!addr || !info) { - SetLastError (ERROR_INVALID_PARAMETER); - return 0; + lastErr = ERROR_INVALID_PARAMETER; + goto out; } - DWORD oldErr = GetLastError (); - int resultCode = 0; - HANDLE hProcess = NULL; - LPSTR lpFilename = NULL; - LPSTR resolvedPath = NULL; + memset (info, 0, sizeof (*info)); + HANDLE process = GetCurrentProcess (); - HANDLE hProcess = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, getpid ()); - if (!hProcess) - { - SetLastError (ERROR_INVALID_HANDLE); - return 0; - } + static INIT_ONCE initOnce = INIT_ONCE_STATIC_INIT; - LPCSTR root = "\\\\?\\GLOBALROOT"; - size_t rootlen = strlen (root); - DWORD nSize = MAX_PATH; - lpFilename = (LPSTR) malloc (nSize + rootlen + 1); - if (!lpFilename) + // SymInitialize failed or already initialized improperly + if (!InitOnceExecuteOnce (&initOnce, SymInitOnceCallback, NULL, NULL)) { - SetLastError (ERROR_NOT_ENOUGH_MEMORY); - goto cleanup; + goto out; } - LPSTR filenamePtr = lpFilename + rootlen; - DWORD result = - GetMappedFileNameA (hProcess, (LPVOID) addr, filenamePtr, nSize); - if (result == 0) - { - goto cleanup; - } + DWORD64 displacement = 0; + BYTE symbolBuffer[sizeof (SYMBOL_INFO) + MAX_SYM_NAME * sizeof (TCHAR)] = + { 0 }; + PSYMBOL_INFO symbol = (PSYMBOL_INFO) symbolBuffer; + symbol->SizeOfStruct = sizeof (SYMBOL_INFO); + symbol->MaxNameLen = MAX_SYM_NAME; - memcpy (lpFilename, root, rootlen); - lpFilename[rootlen + result] = '\0'; + BOOL hasSymbol = + SymFromAddr (process, (DWORD64) (uintptr_t) addr, &displacement, symbol); - HANDLE hFile = CreateFileA (lpFilename, - GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE | - FILE_SHARE_DELETE, - NULL, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - NULL); + IMAGEHLP_MODULE64 moduleInfo = { 0 }; + moduleInfo.SizeOfStruct = sizeof (moduleInfo); - resolvedPath = lpFilename; - if (hFile != INVALID_HANDLE_VALUE) - { - DWORD finalSize = - GetFinalPathNameByHandleA (hFile, NULL, 0, VOLUME_NAME_DOS); - if (finalSize) - { - LPSTR tmpPath = (LPSTR) malloc (finalSize + 1); - if (tmpPath - && GetFinalPathNameByHandleA (hFile, tmpPath, finalSize, - VOLUME_NAME_DOS)) - { - free (lpFilename); - resolvedPath = tmpPath; - } - else - { - free (tmpPath); - } - } - CloseHandle (hFile); - } - - MEMORY_BASIC_INFORMATION mbi; - if (!VirtualQuery (addr, &mbi, sizeof (mbi))) - { - mbi.AllocationBase = (LPVOID) addr; - } + if (!SymGetModuleInfo64 (process, (DWORD64) (uintptr_t) addr, &moduleInfo)) + goto out; - PIMAGE_NT_HEADERS ntHeaders = ImageNtHeader (mbi.AllocationBase); - if (!ntHeaders) - { - goto cleanup; - } + info->dli_fname = _strdup (moduleInfo.ImageName); + info->dli_fbase = (LPVOID) (uintptr_t) moduleInfo.BaseOfImage; - DWORD exportDirRVA = - ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]. - VirtualAddress; - if (!exportDirRVA) + if (hasSymbol) { - goto cleanup; - } - - PIMAGE_EXPORT_DIRECTORY exportDir = - (PIMAGE_EXPORT_DIRECTORY) ((size_t) mbi.AllocationBase + exportDirRVA); - - DWORD *nameRVAs = - (DWORD *) ((size_t) mbi.AllocationBase + exportDir->AddressOfNames); - DWORD *funcRVAs = - (DWORD *) ((size_t) mbi.AllocationBase + exportDir->AddressOfFunctions); - WORD *ordinals = - (WORD *) ((size_t) mbi.AllocationBase + exportDir->AddressOfNameOrdinals); - - LPSTR closestName = NULL; - LPVOID closestAddr = NULL; - size_t addrOffset = (size_t) addr - (size_t) mbi.AllocationBase; - DWORD bestDistance = (DWORD) - 1; + char demangled[MAX_SYM_NAME]; + if (UnDecorateSymbolName + (symbol->Name, demangled, MAX_SYM_NAME, + UNDNAME_COMPLETE | UNDNAME_NO_LEADING_UNDERSCORES)) + { - for (DWORD i = 0; i < exportDir->NumberOfNames; ++i) - { - WORD ordinal = ordinals[i]; - DWORD funcRVA = funcRVAs[ordinal]; - if (funcRVA <= addrOffset) + info->dli_sname = _strdup (demangled); + } + else { - DWORD distance = addrOffset - funcRVA; - if (distance < bestDistance) - { - bestDistance = distance; - closestName = - (LPSTR) ((size_t) mbi.AllocationBase + nameRVAs[i]); - closestAddr = (LPVOID) ((size_t) mbi.AllocationBase + funcRVA); - } + info->dli_sname = _strdup (symbol->Name); } - } - - info->dli_fname = resolvedPath; - info->dli_fbase = mbi.AllocationBase; - info->dli_sname = closestName; - info->dli_saddr = closestAddr; - resultCode = 1; - -cleanup: - SetLastError (oldErr); - if (hProcess) - { - CloseHandle (hProcess); - } - - if (resultCode == 0 && resolvedPath != lpFilename) - { - free (resolvedPath); + info->dli_saddr = (LPVOID) (uintptr_t) symbol->Address; } - - if ((resultCode == 1 && resolvedPath != lpFilename) || resultCode == 0) + else { - // User takes ownership of resolvedPath + info->dli_sname = NULL; + info->dli_saddr = NULL; } - else if (lpFilename) - { - free (lpFilename); - } + ret = 1; - return resultCode; +out: + SetLastError (lastErr); + return ret; } From 2a8d8bea6e9a88f6c4019acb7ecb0a6abce77dea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan-Iulian=20Alecu?= <165364995+pascalecu@users.noreply.github.com> Date: Fri, 20 Jun 2025 14:31:26 +0300 Subject: [PATCH 03/12] feat: make dlclose more reliable --- source/dlclose.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/source/dlclose.c b/source/dlclose.c index dd12126..b498bd2 100644 --- a/source/dlclose.c +++ b/source/dlclose.c @@ -2,7 +2,20 @@ #include int -dlclose (void *__handle) +dlclose (LPVOID __handle) { - return !FreeLibrary (__handle); + if (!__handle) + { + SetLastError (ERROR_INVALID_PARAMETER); + return -1; + } + + BOOL success = FreeLibrary ((HMODULE) __handle); + if (!success) + { + SetLastError (GetLastError ()); + return -1; + } + + return 0; } From 3424bc7fba257dcd292ff61051aa78ee34a1655f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan-Iulian=20Alecu?= <165364995+pascalecu@users.noreply.github.com> Date: Fri, 20 Jun 2025 14:33:09 +0300 Subject: [PATCH 04/12] feat: implement dlerror --- source/dlerror.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/source/dlerror.c b/source/dlerror.c index 07d5b3a..d36ecf7 100644 --- a/source/dlerror.c +++ b/source/dlerror.c @@ -1,5 +1,24 @@ +#include +#include + +static char dlerror_buffer[512] = { 0 }; + char * dlerror (void) { - return 0; + DWORD err = GetLastError (); + if (err == 0) + { + return NULL; + } + + FormatMessageA (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + err, + MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), + dlerror_buffer, sizeof (dlerror_buffer), NULL); + + SetLastError (0); + + return dlerror_buffer; } From a38d3192f8ae2cfd29e70fad7a36f6f3d143cc0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan-Iulian=20Alecu?= <165364995+pascalecu@users.noreply.github.com> Date: Fri, 20 Jun 2025 14:36:51 +0300 Subject: [PATCH 05/12] feat: enhance dlopen function with error handling --- source/dlopen.c | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/source/dlopen.c b/source/dlopen.c index 6cd47e3..ca58f54 100644 --- a/source/dlopen.c +++ b/source/dlopen.c @@ -1,13 +1,48 @@ #include #include +static char dlerror_buffer[512] = { 0 }; + +static void +set_dlerror_from_last_error (void) +{ + DWORD err = GetLastError (); + if (err == 0) + { + dlerror_buffer[0] = '\0'; + return; + } + FormatMessageA (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + err, + MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), + dlerror_buffer, sizeof (dlerror_buffer), NULL); + SetLastError (0); +} + void * dlopen (const char *__file, int __mode) { DWORD dwFlags = 0; - if (__mode == RTLD_LAZY) + if (!__file) + { + SetLastError (ERROR_INVALID_PARAMETER); + return NULL; + } + + if (__mode & RTLD_LAZY) { dwFlags = DONT_RESOLVE_DLL_REFERENCES; } - return LoadLibraryExA (__file, NULL, dwFlags); + + HMODULE module = LoadLibraryExA (__file, NULL, dwFlags); + if (!module) + { + set_dlerror_from_last_error (); + } + else + { + dlerror_buffer[0] = '\0'; + } + return (LPVOID) module; } From 487ce849fc89f5d00a9ecdcc760b0445cd48a591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan-Iulian=20Alecu?= <165364995+pascalecu@users.noreply.github.com> Date: Fri, 20 Jun 2025 14:50:21 +0300 Subject: [PATCH 06/12] feat: improve dlsym + make it thread safe --- source/dlsym.c | 188 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 134 insertions(+), 54 deletions(-) diff --git a/source/dlsym.c b/source/dlsym.c index 1845846..099a236 100644 --- a/source/dlsym.c +++ b/source/dlsym.c @@ -1,74 +1,154 @@ #include #include +#include + +static SRWLOCK g_imports_lock = SRWLOCK_INIT; static PIMAGE_NT_HEADERS -ImageNtHeader (PVOID Base) +ImageNtHeader (void *base) { - PIMAGE_DOS_HEADER imageDosHeader = Base; - return (PIMAGE_NT_HEADERS) ((char *) (imageDosHeader) + - imageDosHeader->e_lfanew); + if (!base) + return NULL; + + PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER) base; + if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) + return NULL; + + PIMAGE_NT_HEADERS ntHeaders = + (PIMAGE_NT_HEADERS) ((BYTE *) base + dosHeader->e_lfanew); + if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) + return NULL; + + return ntHeaders; } +// TODO: make this use a thread-safe hash/map +static BOOL +HasFixedImports (HMODULE module) +{ + static HMODULE patchedModules[64]; + static size_t count = 0; + + AcquireSRWLockExclusive (&g_imports_lock); + + for (size_t i = 0; i < count; i++) + { + if (patchedModules[i] == module) + { + ReleaseSRWLockExclusive (&g_imports_lock); + return TRUE; + } + } + + if (count < sizeof (patchedModules) / sizeof (patchedModules[0])) + patchedModules[count++] = module; + + ReleaseSRWLockExclusive (&g_imports_lock); + return FALSE; +} + +static inline void +PatchIATEntry (PIMAGE_THUNK_DATA thunk, FARPROC procAddress) +{ +#ifdef _WIN64 + thunk->u1.Function = (ULONGLONG) procAddress; +#else + thunk->u1.Function = (DWORD) procAddress; +#endif +} + +typedef BOOL (WINAPI * DllEntryProc) (HINSTANCE, DWORD, LPVOID); + void * dlsym (void *__restrict __handle, const char *__restrict __name) { - PIMAGE_NT_HEADERS imageNtHeaders = ImageNtHeader (__handle); + if (!__handle || !__name) + { + SetLastError (ERROR_INVALID_PARAMETER); + return NULL; + } + + HMODULE module = (HMODULE) __handle; + + PIMAGE_NT_HEADERS ntHeaders = ImageNtHeader (__handle); + if (!ntHeaders) + { + return NULL; + } + + IMAGE_DATA_DIRECTORY importDir = + ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; + if (importDir.VirtualAddress == 0 || importDir.Size == 0) + return NULL; + + BYTE *baseAddr = (BYTE *) __handle; + PIMAGE_IMPORT_DESCRIPTOR importDesc = + (PIMAGE_IMPORT_DESCRIPTOR) (baseAddr + importDir.VirtualAddress); + MEMORY_BASIC_INFORMATION mbi; - VirtualQuery ((char *) (imageNtHeaders) + - imageNtHeaders->OptionalHeader. - DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress, - &mbi, sizeof (mbi)); - if (mbi.Protect & PAGE_WRITECOPY) + if (VirtualQuery (importDesc, &mbi, sizeof (mbi)) == 0) + return NULL; + + if ((mbi.Protect & PAGE_WRITECOPY) && !HasFixedImports (module)) { - PIMAGE_IMPORT_DESCRIPTOR imageImportDescriptor; - imageImportDescriptor = - (PIMAGE_IMPORT_DESCRIPTOR) ((char *) (__handle) + - imageNtHeaders->OptionalHeader. - DataDirectory - [IMAGE_DIRECTORY_ENTRY_IMPORT]. - VirtualAddress); - LPCSTR libFileName; - HMODULE hModule; - LPCSTR procName; - PIMAGE_THUNK_DATA imageThunkData; - while (imageImportDescriptor->Name != 0) + for (; importDesc->Name != 0; importDesc++) { - libFileName = (char *) __handle + imageImportDescriptor->Name; - hModule = LoadLibraryA (libFileName); - if (hModule) + LPCSTR libName = (LPCSTR) (baseAddr + importDesc->Name); + HMODULE hModule = LoadLibraryA (libName); + if (!hModule) + continue; + + PIMAGE_THUNK_DATA importLookupTable = + (PIMAGE_THUNK_DATA) (baseAddr + importDesc->OriginalFirstThunk); + PIMAGE_THUNK_DATA importAddressTable = + (PIMAGE_THUNK_DATA) (baseAddr + importDesc->FirstThunk); + + if (!importLookupTable) + importLookupTable = importAddressTable; + + for (; importLookupTable->u1.AddressOfData != 0; + importLookupTable++, importAddressTable++) { - imageThunkData = - (PIMAGE_THUNK_DATA) ((char *) (__handle) + - imageImportDescriptor->FirstThunk); - while (imageThunkData->u1.AddressOfData != 0) + FARPROC procAddress = NULL; + + if (importLookupTable->u1.Ordinal & IMAGE_ORDINAL_FLAG) + { + WORD ordinal = + (WORD) (importLookupTable->u1.Ordinal & 0xFFFF); + procAddress = + GetProcAddress (hModule, (LPCSTR) (uintptr_t) ordinal); + } + else { - if (IMAGE_SNAP_BY_ORDINAL (imageThunkData->u1.Ordinal)) - { - procName = - (LPCSTR) IMAGE_ORDINAL (imageThunkData->u1.Ordinal); - } - else - { - procName = - ((PIMAGE_IMPORT_BY_NAME) - ((char *) (__handle) + - imageThunkData->u1.AddressOfData))->Name; - } - imageThunkData->u1.Function = - (DWORD_PTR) GetProcAddress (hModule, procName); - ++imageThunkData; + PIMAGE_IMPORT_BY_NAME importByName = + (PIMAGE_IMPORT_BY_NAME) (baseAddr + + importLookupTable->u1. + AddressOfData); + procAddress = GetProcAddress (hModule, importByName->Name); } + + if (!procAddress) + { + procAddress = NULL; + } + + PatchIATEntry (importAddressTable, procAddress); + } + } + + FARPROC entryPoint = + (FARPROC) (baseAddr + ntHeaders->OptionalHeader.AddressOfEntryPoint); + if (entryPoint) + { + DllEntryProc DllEntry = (DllEntryProc) entryPoint; + + if (!DllEntry ((HINSTANCE) __handle, DLL_PROCESS_ATTACH, NULL)) + { + SetLastError (ERROR_DLL_INIT_FAILED); + return NULL; } - imageImportDescriptor++; } - BOOL WINAPI (*DllEntry) (HINSTANCE hinstDLL, DWORD fdwReason, - LPVOID lpvReserved) = - (BOOL - WINAPI (*)(HINSTANCE hinstDLL, DWORD fdwReason, - LPVOID lpvReserved)) ((char *) (__handle) + - imageNtHeaders->OptionalHeader. - AddressOfEntryPoint); - (*DllEntry) ((HINSTANCE) __handle, DLL_PROCESS_ATTACH, 0); } - return GetProcAddress (__handle, __name); + + return (LPVOID) GetProcAddress (module, __name); } From d6f154c6c02fe021f4c22555f8dab5019517a7e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan-Iulian=20Alecu?= <165364995+pascalecu@users.noreply.github.com> Date: Fri, 20 Jun 2025 14:56:00 +0300 Subject: [PATCH 07/12] feat: add thread safety to dladdr + some more cleanup --- source/dladdr.c | 47 +++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/source/dladdr.c b/source/dladdr.c index 07e0b20..0b4fd08 100644 --- a/source/dladdr.c +++ b/source/dladdr.c @@ -1,11 +1,14 @@ +#include #include -#include #include +#include #include -#include +#include #pragma comment(lib, "dbghelp.lib") +static SRWLOCK g_dbghelp_lock = SRWLOCK_INIT; + static BOOL CALLBACK SymInitOnceCallback (PINIT_ONCE InitOnce, PVOID Parameter, PVOID * Context) { @@ -17,13 +20,10 @@ SymInitOnceCallback (PINIT_ONCE InitOnce, PVOID Parameter, PVOID * Context) int dladdr (const LPVOID addr, Dl_info * info) { - int ret = 0; - DWORD lastErr = GetLastError (); - if (!addr || !info) { - lastErr = ERROR_INVALID_PARAMETER; - goto out; + SetLastError (ERROR_INVALID_PARAMETER); + return 0; } memset (info, 0, sizeof (*info)); @@ -31,14 +31,15 @@ dladdr (const LPVOID addr, Dl_info * info) static INIT_ONCE initOnce = INIT_ONCE_STATIC_INIT; - // SymInitialize failed or already initialized improperly if (!InitOnceExecuteOnce (&initOnce, SymInitOnceCallback, NULL, NULL)) { - goto out; + return 0; } + AcquireSRWLockShared (&g_dbghelp_lock); + DWORD64 displacement = 0; - BYTE symbolBuffer[sizeof (SYMBOL_INFO) + MAX_SYM_NAME * sizeof (TCHAR)] = + BYTE symbolBuffer[sizeof (SYMBOL_INFO) + MAX_SYM_NAME * sizeof (char)] = { 0 }; PSYMBOL_INFO symbol = (PSYMBOL_INFO) symbolBuffer; symbol->SizeOfStruct = sizeof (SYMBOL_INFO); @@ -49,28 +50,38 @@ dladdr (const LPVOID addr, Dl_info * info) IMAGEHLP_MODULE64 moduleInfo = { 0 }; moduleInfo.SizeOfStruct = sizeof (moduleInfo); + BOOL hasModule = + SymGetModuleInfo64 (process, (DWORD64) (uintptr_t) addr, &moduleInfo); - if (!SymGetModuleInfo64 (process, (DWORD64) (uintptr_t) addr, &moduleInfo)) - goto out; + ReleaseSRWLockShared (&g_dbghelp_lock); + + if (!hasModule) + return 0; info->dli_fname = _strdup (moduleInfo.ImageName); + if (!info->dli_fname) + return 0; info->dli_fbase = (LPVOID) (uintptr_t) moduleInfo.BaseOfImage; if (hasSymbol) { char demangled[MAX_SYM_NAME]; if (UnDecorateSymbolName - (symbol->Name, demangled, MAX_SYM_NAME, + (symbol->Name, demangled, sizeof (demangled), UNDNAME_COMPLETE | UNDNAME_NO_LEADING_UNDERSCORES)) { - info->dli_sname = _strdup (demangled); } else { info->dli_sname = _strdup (symbol->Name); } - + if (!info->dli_sname) + { + free (info->dli_fname); + info->dli_fname = NULL; + return 0; + } info->dli_saddr = (LPVOID) (uintptr_t) symbol->Address; } else @@ -79,9 +90,5 @@ dladdr (const LPVOID addr, Dl_info * info) info->dli_saddr = NULL; } - ret = 1; - -out: - SetLastError (lastErr); - return ret; + return 1; } From 8d724a0c4cab171febca197a138531747067f1d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan-Iulian=20Alecu?= <165364995+pascalecu@users.noreply.github.com> Date: Fri, 20 Jun 2025 14:57:29 +0300 Subject: [PATCH 08/12] fix: no need to reset the error because of FreeLibrary --- source/dlclose.c | 1 - 1 file changed, 1 deletion(-) diff --git a/source/dlclose.c b/source/dlclose.c index b498bd2..1a38876 100644 --- a/source/dlclose.c +++ b/source/dlclose.c @@ -13,7 +13,6 @@ dlclose (LPVOID __handle) BOOL success = FreeLibrary ((HMODULE) __handle); if (!success) { - SetLastError (GetLastError ()); return -1; } From 0024f3628d9890bbc0265b6d57cec4e44a7c9cc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan-Iulian=20Alecu?= <165364995+pascalecu@users.noreply.github.com> Date: Fri, 20 Jun 2025 14:58:42 +0300 Subject: [PATCH 09/12] feat: cleanup dlerror + make dlerror_buffer thread local --- source/dlerror.c | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/source/dlerror.c b/source/dlerror.c index d36ecf7..fe2386d 100644 --- a/source/dlerror.c +++ b/source/dlerror.c @@ -1,7 +1,9 @@ #include #include -static char dlerror_buffer[512] = { 0 }; +static +__declspec (thread) + char dlerror_buffer[512] = { 0 }; char * dlerror (void) @@ -12,11 +14,32 @@ dlerror (void) return NULL; } - FormatMessageA (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - err, - MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), - dlerror_buffer, sizeof (dlerror_buffer), NULL); + DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; + + DWORD length = FormatMessageA (flags, + NULL, + err, + MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), + dlerror_buffer, + sizeof (dlerror_buffer), + NULL); + + if (length == 0) + { + // Failed to format the message, fallback to numeric error code string + snprintf (dlerror_buffer, sizeof (dlerror_buffer), + "Unknown error 0x%08lx", err); + } + else + { + // Trim trailing newline characters (CRLF) + while (length > 0 + && (dlerror_buffer[length - 1] == '\n' + || dlerror_buffer[length - 1] == '\r')) + { + dlerror_buffer[--length] = '\0'; + } + } SetLastError (0); From d7226b08c0ca02e77a37906ee3f830124ad9ba54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan-Iulian=20Alecu?= <165364995+pascalecu@users.noreply.github.com> Date: Fri, 20 Jun 2025 15:01:37 +0300 Subject: [PATCH 10/12] feat: enhance thread safety of dlerror_buffer and improve error handling in dlopen --- source/dlopen.c | 48 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/source/dlopen.c b/source/dlopen.c index ca58f54..ddbb880 100644 --- a/source/dlopen.c +++ b/source/dlopen.c @@ -1,7 +1,9 @@ #include #include -static char dlerror_buffer[512] = { 0 }; +static +__declspec (thread) + char dlerror_buffer[512] = { 0 }; static void set_dlerror_from_last_error (void) @@ -12,24 +14,47 @@ set_dlerror_from_last_error (void) dlerror_buffer[0] = '\0'; return; } - FormatMessageA (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - err, - MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), - dlerror_buffer, sizeof (dlerror_buffer), NULL); - SetLastError (0); + + DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; + + DWORD length = FormatMessageA (flags, + NULL, + err, + MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), + dlerror_buffer, + sizeof (dlerror_buffer), + NULL); + + if (length == 0) + { + // Failed to format the message, fallback to numeric error code string + snprintf (dlerror_buffer, sizeof (dlerror_buffer), + "Unknown error 0x%08lx", err); + } + else + { + // Trim trailing newline characters (CRLF) + while (length > 0 + && (dlerror_buffer[length - 1] == '\n' + || dlerror_buffer[length - 1] == '\r')) + { + dlerror_buffer[--length] = '\0'; + } + } } void * dlopen (const char *__file, int __mode) { - DWORD dwFlags = 0; if (!__file) { SetLastError (ERROR_INVALID_PARAMETER); + set_dlerror_from_last_error (); return NULL; } + DWORD dwFlags = 0; + if (__mode & RTLD_LAZY) { dwFlags = DONT_RESOLVE_DLL_REFERENCES; @@ -39,10 +64,9 @@ dlopen (const char *__file, int __mode) if (!module) { set_dlerror_from_last_error (); + return NULL; } - else - { - dlerror_buffer[0] = '\0'; - } + + dlerror_buffer[0] = '\0'; return (LPVOID) module; } From 9d219beada28b6ca750d381398c493713d32bf3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan-Iulian=20Alecu?= <165364995+pascalecu@users.noreply.github.com> Date: Fri, 20 Jun 2025 15:05:15 +0300 Subject: [PATCH 11/12] feat: add logging to dlsym --- source/dlsym.c | 72 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 7 deletions(-) diff --git a/source/dlsym.c b/source/dlsym.c index 099a236..ec98fdf 100644 --- a/source/dlsym.c +++ b/source/dlsym.c @@ -2,27 +2,75 @@ #include #include +static +__declspec (thread) + char dlerror_buffer[512] = { 0 }; + static SRWLOCK g_imports_lock = SRWLOCK_INIT; +static void +set_dlerror_from_last_error (void) +{ + DWORD err = GetLastError (); + if (err == 0) + { + dlerror_buffer[0] = '\0'; + return; + } + + DWORD length = + FormatMessageA (FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + err, + MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), + dlerror_buffer, + sizeof (dlerror_buffer), + NULL); + + if (length == 0) + snprintf (dlerror_buffer, sizeof (dlerror_buffer), + "Unknown error 0x%08lx", err); + else + while (length > 0 + && (dlerror_buffer[length - 1] == '\n' + || dlerror_buffer[length - 1] == '\r')) + { + dlerror_buffer[--length] = '\0'; + } + SetLastError (0); +} + static PIMAGE_NT_HEADERS ImageNtHeader (void *base) { if (!base) - return NULL; + { + SetLastError (ERROR_INVALID_PARAMETER); + set_dlerror_from_last_error (); + return NULL; + } PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER) base; if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) - return NULL; + { + SetLastError (ERROR_BAD_FORMAT); + set_dlerror_from_last_error (); + return NULL; + } PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS) ((BYTE *) base + dosHeader->e_lfanew); if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) - return NULL; + { + SetLastError (ERROR_BAD_FORMAT); + set_dlerror_from_last_error (); + return NULL; + } return ntHeaders; } -// TODO: make this use a thread-safe hash/map static BOOL HasFixedImports (HMODULE module) { @@ -65,6 +113,7 @@ dlsym (void *__restrict __handle, const char *__restrict __name) if (!__handle || !__name) { SetLastError (ERROR_INVALID_PARAMETER); + set_dlerror_from_last_error (); return NULL; } @@ -78,8 +127,10 @@ dlsym (void *__restrict __handle, const char *__restrict __name) IMAGE_DATA_DIRECTORY importDir = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; + + // no imports to patch, just return symbol if (importDir.VirtualAddress == 0 || importDir.Size == 0) - return NULL; + return (LPVOID) GetProcAddress (module, __name); BYTE *baseAddr = (BYTE *) __handle; PIMAGE_IMPORT_DESCRIPTOR importDesc = @@ -87,7 +138,10 @@ dlsym (void *__restrict __handle, const char *__restrict __name) MEMORY_BASIC_INFORMATION mbi; if (VirtualQuery (importDesc, &mbi, sizeof (mbi)) == 0) - return NULL; + { + set_dlerror_from_last_error (); + return NULL; + } if ((mbi.Protect & PAGE_WRITECOPY) && !HasFixedImports (module)) { @@ -150,5 +204,9 @@ dlsym (void *__restrict __handle, const char *__restrict __name) } } - return (LPVOID) GetProcAddress (module, __name); + void *result = (LPVOID) GetProcAddress (module, __name); + if (!result) + set_dlerror_from_last_error (); + + return result; } From 19c3b9945b2fecc843260337611edde498b766f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan-Iulian=20Alecu?= <165364995+pascalecu@users.noreply.github.com> Date: Fri, 20 Jun 2025 15:20:17 +0300 Subject: [PATCH 12/12] docs: add docs to all dl* related things --- include/dlfcn.h | 177 +++++++++++++++++++++++++++++++++++------------ source/dladdr.c | 3 + source/dlerror.c | 1 + source/dlopen.c | 11 +++ source/dlsym.c | 34 ++++++++- 5 files changed, 182 insertions(+), 44 deletions(-) diff --git a/include/dlfcn.h b/include/dlfcn.h index db2915e..1f8f8fa 100644 --- a/include/dlfcn.h +++ b/include/dlfcn.h @@ -1,5 +1,5 @@ /* dlfcn.h -*/ + */ #ifndef _DLFCN_H #define _DLFCN_H @@ -7,49 +7,140 @@ #include #include -#ifdef __cplusplus -extern "C" -{ -#endif +__BEGIN_DECLS + +/// @brief Loads a DLL module into the process's address space. +/// +/// This function mimics the POSIX dlopen interface on Windows by using LoadLibraryExA. +/// +/// @param __file The path to the DLL file to load. +/// @param __mode Mode flags controlling symbol resolution behavior. Supports: +/// - RTLD_LAZY: Load the DLL without resolving references immediately (maps to DONT_RESOLVE_DLL_REFERENCES). +/// +/// @return Handle to the loaded module on success, or NULL on failure. +/// On failure, error details are available via dlerror(). +/// +/// @note If __file is NULL, sets ERROR_INVALID_PARAMETER and returns NULL. +extern void * +dlopen (const char *__file, int __mode); + +/// @brief Retrieves the address of a symbol in a loaded module. +/// +/// On first call for a module, this patches the module's Import Address Table +/// (IAT) if necessary, resolving imports manually if the module was loaded with +/// delayed or lazy loading. +/// +/// @param __handle Handle to the loaded module. +/// @param __name Name of the symbol to locate. +/// +/// @return Pointer to the symbol on success, or NULL on failure. +/// On failure, thread-local error message is set and retrievable via dlerror(). +extern void * +dlsym (void *__restrict __handle, const char *__restrict __name); + +/// @brief Unloads a dynamically loaded module. +/// +/// Calls FreeLibrary on the given handle, releasing the loaded module. +/// +/// @param __handle Handle to the module to unload (returned by dlopen). +/// +/// @return 0 on success, -1 on failure. +/// On failure, the Windows last error code is set. +/// +/// @note On failure, you can call dlerror() to get a textual error description. +extern int +dlclose (void *__handle); -/* declarations used for dynamic linking support routines */ - extern void *dlopen (const char *__file, int __mode); - extern void *dlsym (void *__restrict __handle, - const char *__restrict __name); - extern int dlclose (void *__handle); - extern char *dlerror (void); +/// @brief Returns a human-readable string describing the last error that occurred. +/// +/// Retrieves the Windows last error code via GetLastError(), formats it into a +/// descriptive message, and returns a thread-local buffer with this message. +/// +/// If there is no error (GetLastError returns 0), returns NULL. +/// +/// @return Pointer to a thread-local static buffer containing the error message, +/// or NULL if no error. +/// +/// @note Calling this function clears the Windows last error (sets it to 0). +extern char * +dlerror (void); -/* following doesn't exist in Win32 API ... */ +/** + * @def RTLD_DEFAULT + * POSIX macro not used in Windows, define as NULL. + */ #define RTLD_DEFAULT NULL -/* valid values for mode argument to dlopen */ -#define RTLD_LOCAL 0 /* Symbols in this dlopen'ed obj are not */ - /* visible to other dlopen'ed objs. */ -#define RTLD_LAZY 1 /* Lazy function call binding. */ -#define RTLD_NOW 2 /* Immediate function call binding. */ -#define RTLD_GLOBAL 4 /* Symbols in this dlopen'ed obj are visible */ - /* to other dlopen'ed objs. */ -/* Non-standard GLIBC extensions */ -#define RTLD_NODELETE 8 /* Don't unload lib in dlclose. */ -#define RTLD_NOLOAD 16 /* Don't load lib, just return handle if lib */ - /* is already loaded, NULL otherwise. */ -#define RTLD_DEEPBIND 32 /* Place lookup scope so that this lib is */ - /* preferred over global scope. */ - - typedef struct Dl_info Dl_info; - - struct Dl_info - { - const char *dli_fname; /* Filename of defining object */ - void *dli_fbase; /* Load address of that object */ - const char *dli_sname; /* Name of nearest lower symbol */ - void *dli_saddr; /* Exact value of nearest symbol */ - }; - - extern int dladdr (const void *addr, Dl_info * info); - -#ifdef __cplusplus -} -#endif - -#endif /* _DLFCN_H */ +/** + * @def RTLD_LOCAL + * The symbols defined in this library are not made available to resolve references in subsequently loaded libraries. + */ +#define RTLD_LOCAL 0 + +/** + * @def RTLD_LAZY + * Perform lazy binding. Only resolve symbols as the code that references them is executed. + */ +#define RTLD_LAZY 1 + +/** + * @def RTLD_NOW + * Perform all necessary relocations when `dlopen` is called. + */ +#define RTLD_NOW 2 + +/** + * @def RTLD_GLOBAL + * Make symbols available for symbol resolution of subsequently loaded libraries. + */ +#define RTLD_GLOBAL 4 + +/** + * @def RTLD_NODELETE + * Do not unload the library during dlclose. + * Non-standard GLIBC extension, no effect on Windows. + */ +#define RTLD_NODELETE 8 + +/** + * @def RTLD_NOLOAD + * Do not load the library if it is not already loaded. + * Non-standard GLIBC extension, not supported on Windows. + */ +#define RTLD_NOLOAD 16 + +/** + * @def RTLD_DEEPBIND + * Place the library at the top of the symbol resolution scope. + * Non-standard GLIBC extension, not supported on Windows. + */ +#define RTLD_DEEPBIND 32 + +/** + * @struct Dl_info + * Structure returned by dladdr with symbolic information about a pointer. + */ +typedef struct Dl_info +{ + const char *dli_fname; /**< Filename of the shared object that contains the address */ + void *dli_fbase; /**< Base address at which the object is loaded */ + const char *dli_sname; /**< Name of the nearest symbol with address lower than or equal to the specified address */ + void *dli_saddr; /**< Exact address of the symbol named in dli_sname */ +} Dl_info; + +/// @brief Provides symbolic information about a given address. +/// +/// If successful, info will contain the filename of the module, the base address, +/// the nearest symbol name, and the exact address of that symbol. +/// +/// @param addr Pointer/address to query. +/// @param info Pointer to Dl_info struct to fill. +/// @return Non-zero on success, zero on failure. +/// +/// @note The caller is responsible for freeing dli_fname and dli_sname in Dl_info. +extern int +dladdr (const void *addr, Dl_info *info); + +__END_DECLS + +#endif /* _DLFCN_H */ diff --git a/source/dladdr.c b/source/dladdr.c index 0b4fd08..fb9ce08 100644 --- a/source/dladdr.c +++ b/source/dladdr.c @@ -7,8 +7,10 @@ #pragma comment(lib, "dbghelp.lib") +/// @brief Shared Reader-Writer lock to guard dbghelp usage. static SRWLOCK g_dbghelp_lock = SRWLOCK_INIT; +/// @brief One-time initialization for dbghelp symbols per process static BOOL CALLBACK SymInitOnceCallback (PINIT_ONCE InitOnce, PVOID Parameter, PVOID * Context) { @@ -17,6 +19,7 @@ SymInitOnceCallback (PINIT_ONCE InitOnce, PVOID Parameter, PVOID * Context) return SymInitialize (process, NULL, TRUE); } + int dladdr (const LPVOID addr, Dl_info * info) { diff --git a/source/dlerror.c b/source/dlerror.c index fe2386d..d22a82c 100644 --- a/source/dlerror.c +++ b/source/dlerror.c @@ -1,6 +1,7 @@ #include #include +/// @brief Thread-local buffer to hold error messages for dlerror(). static __declspec (thread) char dlerror_buffer[512] = { 0 }; diff --git a/source/dlopen.c b/source/dlopen.c index ddbb880..5fe81b6 100644 --- a/source/dlopen.c +++ b/source/dlopen.c @@ -1,10 +1,21 @@ #include #include +/// @brief Thread-local buffer to hold error messages for dlerror(). static __declspec (thread) char dlerror_buffer[512] = { 0 }; +/// @brief Sets the thread-local dlerror buffer from the current Windows last error. +/// +/// Retrieves the last error code via GetLastError() and formats the error message +/// string into `dlerror_buffer`. If no error code is set (0), clears the buffer. +/// +/// Trims trailing newline characters from the formatted message. +/// +/// If `FormatMessageA` fails, stores a fallback string with the numeric error code. +/// +/// @note This function resets the Windows last error to 0 after capturing it. static void set_dlerror_from_last_error (void) { diff --git a/source/dlsym.c b/source/dlsym.c index ec98fdf..e6eed3e 100644 --- a/source/dlsym.c +++ b/source/dlsym.c @@ -2,12 +2,24 @@ #include #include +/// @brief Thread-local buffer to hold error messages for dlerror(). static __declspec (thread) char dlerror_buffer[512] = { 0 }; +/// @brief SRW lock to synchronize access to patched modules list. static SRWLOCK g_imports_lock = SRWLOCK_INIT; +/// @brief Sets the thread-local dlerror buffer from the current Windows last error. +/// +/// Retrieves the last error code via GetLastError() and formats the error message +/// string into `dlerror_buffer`. If no error code is set (0), clears the buffer. +/// +/// Trims trailing newline characters from the formatted message. +/// +/// If `FormatMessageA` fails, stores a fallback string with the numeric error code. +/// +/// @note This function resets the Windows last error to 0 after capturing it. static void set_dlerror_from_last_error (void) { @@ -41,6 +53,12 @@ set_dlerror_from_last_error (void) SetLastError (0); } +/// @brief Gets the NT Headers from the base address of a loaded module. +/// +/// @param base Pointer to the base address of a loaded module. +/// +/// @return Pointer to the IMAGE_NT_HEADERS if valid, otherwise NULL. +/// @note On failure, sets the thread-local dlerror buffer with an appropriate error message. static PIMAGE_NT_HEADERS ImageNtHeader (void *base) { @@ -71,6 +89,13 @@ ImageNtHeader (void *base) return ntHeaders; } +/// @brief Tracks modules that have had their import address tables fixed. +/// +/// Prevents repeated patching by maintaining a static list protected by SRW lock. +/// +/// @param module Handle of the loaded module. +/// +/// @return TRUE if imports have already been fixed for this module, FALSE otherwise. static BOOL HasFixedImports (HMODULE module) { @@ -95,6 +120,10 @@ HasFixedImports (HMODULE module) return FALSE; } +/// @brief Writes the actual function address into the import address table thunk. +/// +/// @param thunk Pointer to the import thunk to patch. +/// @param procAddress Address of the function to patch into the IAT. static inline void PatchIATEntry (PIMAGE_THUNK_DATA thunk, FARPROC procAddress) { @@ -105,6 +134,7 @@ PatchIATEntry (PIMAGE_THUNK_DATA thunk, FARPROC procAddress) #endif } +/// @brief Signature for DLL entry point function (DllMain). typedef BOOL (WINAPI * DllEntryProc) (HINSTANCE, DWORD, LPVOID); void * @@ -128,7 +158,7 @@ dlsym (void *__restrict __handle, const char *__restrict __name) IMAGE_DATA_DIRECTORY importDir = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; - // no imports to patch, just return symbol + // No imports to patch, just return symbol if (importDir.VirtualAddress == 0 || importDir.Size == 0) return (LPVOID) GetProcAddress (module, __name); @@ -145,6 +175,7 @@ dlsym (void *__restrict __handle, const char *__restrict __name) if ((mbi.Protect & PAGE_WRITECOPY) && !HasFixedImports (module)) { + // Patch IAT for all imported modules for (; importDesc->Name != 0; importDesc++) { LPCSTR libName = (LPCSTR) (baseAddr + importDesc->Name); @@ -190,6 +221,7 @@ dlsym (void *__restrict __handle, const char *__restrict __name) } } + // Call DLL entry point with DLL_PROCESS_ATTACH FARPROC entryPoint = (FARPROC) (baseAddr + ntHeaders->OptionalHeader.AddressOfEntryPoint); if (entryPoint)