/* * File dbghelp.c - generic routines (process) for dbghelp DLL * * Copyright (C) 2004, Eric Pouech * * This library 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. * * This library 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 this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #include "dbghelp_private.h" #include "winerror.h" #include "psapi.h" #include "wine/debug.h" #include "wdbgexts.h" #include "winnls.h" WINE_DEFAULT_DEBUG_CHANNEL(dbghelp); /* TODO * - support for symbols' types is still partly missing * + C++ support * + we should store the underlying type for an enum in the symt_enum struct * + for enums, we store the names & values (associated to the enum type), * but those values are not directly usable from a debugger (that's why, I * assume, that we have also to define constants for enum values, as * Codeview does BTW. * + SymEnumTypes should only return *user* defined types (UDT, typedefs...) not * all the types stored/used in the modules (like char*) * - SymGetLine{Next|Prev} don't work as expected (they don't seem to work across * functions, and even across function blocks...). Basically, for *Next* to work * it requires an address after the prolog of the func (the base address of the * func doesn't work) * - most options (dbghelp_options) are not used (loading lines...) * - in symbol lookup by name, we don't use RE everywhere we should. Moreover, when * we're supposed to use RE, it doesn't make use of our hash tables. Therefore, * we could use hash if name isn't a RE, and fall back to a full search when we * get a full RE * - msc: * + we should add parameters' types to the function's signature * while processing a function's parameters * + add support for function-less labels (as MSC seems to define them) * + C++ management * - stabs: * + when, in a same module, the same definition is used in several compilation * units, we get several definitions of the same object (especially * struct/union). we should find a way not to duplicate them * + in some cases (dlls/user/dialog16.c DIALOG_GetControl16), the same static * global variable is defined several times (at different scopes). We are * getting several of those while looking for a unique symbol. Part of the * issue is that we don't give a scope to a static variable inside a function * + C++ management */ unsigned dbghelp_options = SYMOPT_UNDNAME; HANDLE hMsvcrt = NULL; /*********************************************************************** * DllMain (DEBUGHLP.@) */ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { switch (fdwReason) { case DLL_PROCESS_ATTACH: break; case DLL_PROCESS_DETACH: if (hMsvcrt) FreeLibrary(hMsvcrt); break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; default: break; } return TRUE; } static struct process* process_first /* = NULL */; /****************************************************************** * process_find_by_handle * */ struct process* process_find_by_handle(HANDLE hProcess) { struct process* p; for (p = process_first; p && p->handle != hProcess; p = p->next); if (!p) SetLastError(ERROR_INVALID_HANDLE); return p; } /****************************************************************** * validate_addr64 (internal) * */ BOOL validate_addr64(DWORD64 addr) { if (addr >> 32) { FIXME("Unsupported address %s\n", wine_dbgstr_longlong(addr)); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } return TRUE; } /****************************************************************** * SymSetSearchPathW (DBGHELP.@) * */ BOOL WINAPI SymSetSearchPathW(HANDLE hProcess, PCWSTR searchPath) { struct process* pcs = process_find_by_handle(hProcess); if (!pcs) return FALSE; if (!searchPath) return FALSE; HeapFree(GetProcessHeap(), 0, pcs->search_path); pcs->search_path = lstrcpyW(HeapAlloc(GetProcessHeap(), 0, (lstrlenW(searchPath) + 1) * sizeof(WCHAR)), searchPath); return TRUE; } /****************************************************************** * SymSetSearchPath (DBGHELP.@) * */ BOOL WINAPI SymSetSearchPath(HANDLE hProcess, PCSTR searchPath) { BOOL ret = FALSE; unsigned len; WCHAR* sp; len = MultiByteToWideChar(CP_ACP, 0, searchPath, -1, NULL, 0); if ((sp = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)))) { MultiByteToWideChar(CP_ACP, 0, searchPath, -1, sp, len); ret = SymSetSearchPathW(hProcess, sp); HeapFree(GetProcessHeap(), 0, sp); } return ret; } /*********************************************************************** * SymGetSearchPathW (DBGHELP.@) */ BOOL WINAPI SymGetSearchPathW(HANDLE hProcess, LPWSTR szSearchPath, DWORD SearchPathLength) { struct process* pcs = process_find_by_handle(hProcess); if (!pcs) return FALSE; lstrcpynW(szSearchPath, pcs->search_path, SearchPathLength); return TRUE; } /*********************************************************************** * SymGetSearchPath (DBGHELP.@) */ BOOL WINAPI SymGetSearchPath(HANDLE hProcess, LPSTR szSearchPath, DWORD SearchPathLength) { WCHAR* buffer = HeapAlloc(GetProcessHeap(), 0, SearchPathLength); BOOL ret = FALSE; if (buffer) { ret = SymGetSearchPathW(hProcess, buffer, SearchPathLength * sizeof(WCHAR)); if (ret) WideCharToMultiByte(CP_ACP, 0, buffer, SearchPathLength, szSearchPath, SearchPathLength, NULL, NULL); HeapFree(GetProcessHeap(), 0, buffer); } return ret; } /****************************************************************** * invade_process * * SymInitialize helper: loads in dbghelp all known (and loaded modules) * this assumes that hProcess is a handle on a valid process */ static BOOL WINAPI process_invade_cb(char* name, DWORD base, DWORD size, void* user) { char tmp[MAX_PATH]; HANDLE hProcess = (HANDLE)user; if (!GetModuleFileNameExA(hProcess, (HMODULE)base, tmp, sizeof(tmp))) lstrcpynA(tmp, name, sizeof(tmp)); SymLoadModule(hProcess, 0, tmp, name, base, size); return TRUE; } /****************************************************************** * check_live_target * */ static BOOL check_live_target(struct process* pcs) { if (!GetProcessId(pcs->handle)) return FALSE; if (GetEnvironmentVariableA("DBGHELP_NOLIVE", NULL, 0)) return FALSE; elf_read_wine_loader_dbg_info(pcs); return TRUE; } /****************************************************************** * SymInitializeW (DBGHELP.@) * * The initialisation of a dbghelp's context. * Note that hProcess doesn't need to be a valid process handle (except * when fInvadeProcess is TRUE). * Since, we're also allow to load ELF (pure) libraries and Wine ELF libraries * containing PE (and NE) module(s), here's how we handle it: * - we load every module (ELF, NE, PE) passed in SymLoadModule * - in fInvadeProcess (in SymInitialize) is TRUE, we set up what is called ELF * synchronization: hProcess should be a valid process handle, and we hook * ourselves on hProcess's loaded ELF-modules, and keep this list in sync with * our internal ELF modules representation (loading / unloading). This way, * we'll pair every loaded builtin PE module with its ELF counterpart (and * access its debug information). * - if fInvadeProcess (in SymInitialize) is FALSE, we check anyway if the * hProcess refers to a running process. We use some heuristics here, so YMMV. * If we detect a live target, then we get the same handling as if * fInvadeProcess is TRUE (except that the modules are not loaded). Otherwise, * we won't be able to make the peering between a builtin PE module and its ELF * counterpart. Hence we won't be able to provide the requested debug * information. We'll however be able to load native PE modules (and their * debug information) without any trouble. * Note also that this scheme can be intertwined with the deferred loading * mechanism (ie only load the debug information when we actually need it). */ BOOL WINAPI SymInitializeW(HANDLE hProcess, PCWSTR UserSearchPath, BOOL fInvadeProcess) { struct process* pcs; TRACE("(%p %s %u)\n", hProcess, debugstr_w(UserSearchPath), fInvadeProcess); if (process_find_by_handle(hProcess)) FIXME("what to do ??\n"); pcs = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*pcs)); if (!pcs) return FALSE; pcs->handle = hProcess; if (UserSearchPath) { pcs->search_path = lstrcpyW(HeapAlloc(GetProcessHeap(), 0, (lstrlenW(UserSearchPath) + 1) * sizeof(WCHAR)), UserSearchPath); } else { unsigned size; unsigned len; static const WCHAR sym_path[] = {'_','N','T','_','S','Y','M','B','O','L','_','P','A','T','H',0}; static const WCHAR alt_sym_path[] = {'_','N','T','_','A','L','T','E','R','N','A','T','E','_','S','Y','M','B','O','L','_','P','A','T','H',0}; pcs->search_path = HeapAlloc(GetProcessHeap(), 0, (len = MAX_PATH) * sizeof(WCHAR)); while ((size = GetCurrentDirectoryW(len, pcs->search_path)) >= len) pcs->search_path = HeapReAlloc(GetProcessHeap(), 0, pcs->search_path, (len *= 2) * sizeof(WCHAR)); pcs->search_path = HeapReAlloc(GetProcessHeap(), 0, pcs->search_path, (size + 1) * sizeof(WCHAR)); len = GetEnvironmentVariableW(sym_path, NULL, 0); if (len) { pcs->search_path = HeapReAlloc(GetProcessHeap(), 0, pcs->search_path, (size + 1 + len + 1) * sizeof(WCHAR)); pcs->search_path[size] = ';'; GetEnvironmentVariableW(sym_path, pcs->search_path + size + 1, len); size += 1 + len; } len = GetEnvironmentVariableW(alt_sym_path, NULL, 0); if (len) { pcs->search_path = HeapReAlloc(GetProcessHeap(), 0, pcs->search_path, (size + 1 + len + 1) * sizeof(WCHAR)); pcs->search_path[size] = ';'; GetEnvironmentVariableW(alt_sym_path, pcs->search_path + size + 1, len); size += 1 + len; } } pcs->lmodules = NULL; pcs->dbg_hdr_addr = 0; pcs->next = process_first; process_first = pcs; if (check_live_target(pcs)) { if (fInvadeProcess) EnumerateLoadedModules(hProcess, process_invade_cb, (void*)hProcess); elf_synchronize_module_list(pcs); } else if (fInvadeProcess) { SymCleanup(hProcess); SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } return TRUE; } /****************************************************************** * SymInitialize (DBGHELP.@) * * */ BOOL WINAPI SymInitialize(HANDLE hProcess, PCSTR UserSearchPath, BOOL fInvadeProcess) { WCHAR* sp = NULL; BOOL ret; if (UserSearchPath) { unsigned len; len = MultiByteToWideChar(CP_ACP, 0, UserSearchPath, -1, NULL, 0); sp = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); MultiByteToWideChar(CP_ACP, 0, UserSearchPath, -1, sp, len); } ret = SymInitializeW(hProcess, sp, fInvadeProcess); HeapFree(GetProcessHeap(), 0, sp); return ret; } /****************************************************************** * SymCleanup (DBGHELP.@) * */ BOOL WINAPI SymCleanup(HANDLE hProcess) { struct process** ppcs; struct process* next; for (ppcs = &process_first; *ppcs; ppcs = &(*ppcs)->next) { if ((*ppcs)->handle == hProcess) { while ((*ppcs)->lmodules) module_remove(*ppcs, (*ppcs)->lmodules); HeapFree(GetProcessHeap(), 0, (*ppcs)->search_path); next = (*ppcs)->next; HeapFree(GetProcessHeap(), 0, *ppcs); *ppcs = next; return TRUE; } } return FALSE; } /****************************************************************** * SymSetOptions (DBGHELP.@) * */ DWORD WINAPI SymSetOptions(DWORD opts) { struct process* pcs; for (pcs = process_first; pcs; pcs = pcs->next) { pcs_callback(pcs, CBA_SET_OPTIONS, &opts); } return dbghelp_options = opts; } /****************************************************************** * SymGetOptions (DBGHELP.@) * */ DWORD WINAPI SymGetOptions(void) { return dbghelp_options; } /****************************************************************** * SymSetParentWindow (DBGHELP.@) * */ BOOL WINAPI SymSetParentWindow(HWND hwnd) { /* Save hwnd so it can be used as parent window */ FIXME("(%p): stub\n", hwnd); return TRUE; } /****************************************************************** * SymSetContext (DBGHELP.@) * */ BOOL WINAPI SymSetContext(HANDLE hProcess, PIMAGEHLP_STACK_FRAME StackFrame, PIMAGEHLP_CONTEXT Context) { struct process* pcs = process_find_by_handle(hProcess); if (!pcs) return FALSE; if (pcs->ctx_frame.ReturnOffset == StackFrame->ReturnOffset && pcs->ctx_frame.FrameOffset == StackFrame->FrameOffset && pcs->ctx_frame.StackOffset == StackFrame->StackOffset) { TRACE("Setting same frame {rtn=%s frm=%s stk=%s}\n", wine_dbgstr_longlong(pcs->ctx_frame.ReturnOffset), wine_dbgstr_longlong(pcs->ctx_frame.FrameOffset), wine_dbgstr_longlong(pcs->ctx_frame.StackOffset)); SetLastError(ERROR_ACCESS_DENIED); /* latest MSDN says ERROR_SUCCESS */ return FALSE; } pcs->ctx_frame = *StackFrame; /* MSDN states that Context is not (no longer?) used */ return TRUE; } /****************************************************************** * reg_cb64to32 (internal) * * Registered callback for converting information from 64 bit to 32 bit */ static BOOL CALLBACK reg_cb64to32(HANDLE hProcess, ULONG action, ULONG64 data, ULONG64 user) { PSYMBOL_REGISTERED_CALLBACK cb32 = (PSYMBOL_REGISTERED_CALLBACK)(DWORD)(user >> 32); DWORD user32 = (DWORD)user; void* data32; IMAGEHLP_DEFERRED_SYMBOL_LOAD64* idsl64; IMAGEHLP_DEFERRED_SYMBOL_LOAD idsl; switch (action) { case CBA_DEBUG_INFO: case CBA_DEFERRED_SYMBOL_LOAD_CANCEL: case CBA_SET_OPTIONS: case CBA_SYMBOLS_UNLOADED: data32 = (void*)(DWORD)data; break; case CBA_DEFERRED_SYMBOL_LOAD_COMPLETE: case CBA_DEFERRED_SYMBOL_LOAD_FAILURE: case CBA_DEFERRED_SYMBOL_LOAD_PARTIAL: case CBA_DEFERRED_SYMBOL_LOAD_START: idsl64 = (IMAGEHLP_DEFERRED_SYMBOL_LOAD64*)(DWORD)data; if (!validate_addr64(idsl64->BaseOfImage)) return FALSE; idsl.SizeOfStruct = sizeof(idsl); idsl.BaseOfImage = (DWORD)idsl64->BaseOfImage; idsl.CheckSum = idsl64->CheckSum; idsl.TimeDateStamp = idsl64->TimeDateStamp; memcpy(idsl.FileName, idsl64->FileName, sizeof(idsl.FileName)); idsl.Reparse = idsl64->Reparse; data32 = &idsl; break; case CBA_DUPLICATE_SYMBOL: case CBA_EVENT: case CBA_READ_MEMORY: default: FIXME("No mapping for action %lu\n", action); return FALSE; } return cb32(hProcess, action, (PVOID)data32, (PVOID)user32); } /****************************************************************** * pcs_callback (internal) */ BOOL pcs_callback(const struct process* pcs, ULONG action, void* data) { TRACE("%p %lu %p\n", pcs, action, data); if (!pcs->reg_cb) return FALSE; if (pcs->reg_is_unicode) { IMAGEHLP_DEFERRED_SYMBOL_LOAD64* idsl; IMAGEHLP_DEFERRED_SYMBOL_LOADW64 idslW; switch (action) { case CBA_DEBUG_INFO: case CBA_DEFERRED_SYMBOL_LOAD_CANCEL: case CBA_SET_OPTIONS: case CBA_SYMBOLS_UNLOADED: break; case CBA_DEFERRED_SYMBOL_LOAD_COMPLETE: case CBA_DEFERRED_SYMBOL_LOAD_FAILURE: case CBA_DEFERRED_SYMBOL_LOAD_PARTIAL: case CBA_DEFERRED_SYMBOL_LOAD_START: idsl = (IMAGEHLP_DEFERRED_SYMBOL_LOAD64*)(DWORD)data; idslW.SizeOfStruct = sizeof(idslW); idslW.BaseOfImage = idsl->BaseOfImage; idslW.CheckSum = idsl->CheckSum; idslW.TimeDateStamp = idsl->TimeDateStamp; MultiByteToWideChar(CP_ACP, 0, idsl->FileName, -1, idslW.FileName, sizeof(idslW.FileName) / sizeof(WCHAR)); idslW.Reparse = idsl->Reparse; data = &idslW; break; case CBA_DUPLICATE_SYMBOL: case CBA_EVENT: case CBA_READ_MEMORY: default: FIXME("No mapping for action %lu\n", action); return FALSE; } } return pcs->reg_cb(pcs->handle, action, (ULONG64)(DWORD_PTR)data, pcs->reg_user); } /****************************************************************** * sym_register_cb * * Helper for registering a callback. */ static BOOL sym_register_cb(HANDLE hProcess, PSYMBOL_REGISTERED_CALLBACK64 cb, DWORD64 user, BOOL unicode) { struct process* pcs = process_find_by_handle(hProcess); if (!pcs) return FALSE; pcs->reg_cb = cb; pcs->reg_is_unicode = unicode; pcs->reg_user = user; return TRUE; } /*********************************************************************** * SymRegisterCallback (DBGHELP.@) */ BOOL WINAPI SymRegisterCallback(HANDLE hProcess, PSYMBOL_REGISTERED_CALLBACK CallbackFunction, PVOID UserContext) { DWORD64 tmp = ((ULONGLONG)(DWORD)CallbackFunction << 32) | (DWORD)UserContext; TRACE("(%p, %p, %p)\n", hProcess, CallbackFunction, UserContext); return sym_register_cb(hProcess, reg_cb64to32, tmp, FALSE); } /*********************************************************************** * SymRegisterCallback64 (DBGHELP.@) */ BOOL WINAPI SymRegisterCallback64(HANDLE hProcess, PSYMBOL_REGISTERED_CALLBACK64 CallbackFunction, ULONG64 UserContext) { TRACE("(%p, %p, %s)\n", hProcess, CallbackFunction, wine_dbgstr_longlong(UserContext)); return sym_register_cb(hProcess, CallbackFunction, UserContext, FALSE); } /*********************************************************************** * SymRegisterCallbackW64 (DBGHELP.@) */ BOOL WINAPI SymRegisterCallbackW64(HANDLE hProcess, PSYMBOL_REGISTERED_CALLBACK64 CallbackFunction, ULONG64 UserContext) { TRACE("(%p, %p, %s)\n", hProcess, CallbackFunction, wine_dbgstr_longlong(UserContext)); return sym_register_cb(hProcess, CallbackFunction, UserContext, TRUE); } /* This is imagehlp version not dbghelp !! */ static API_VERSION api_version = { 4, 0, 2, 0 }; /*********************************************************************** * ImagehlpApiVersion (DBGHELP.@) */ LPAPI_VERSION WINAPI ImagehlpApiVersion(VOID) { return &api_version; } /*********************************************************************** * ImagehlpApiVersionEx (DBGHELP.@) */ LPAPI_VERSION WINAPI ImagehlpApiVersionEx(LPAPI_VERSION AppVersion) { if (!AppVersion) return NULL; AppVersion->MajorVersion = api_version.MajorVersion; AppVersion->MinorVersion = api_version.MinorVersion; AppVersion->Revision = api_version.Revision; AppVersion->Reserved = api_version.Reserved; return AppVersion; } /****************************************************************** * ExtensionApiVersion (DBGHELP.@) */ LPEXT_API_VERSION WINAPI ExtensionApiVersion(void) { static EXT_API_VERSION eav = {5, 5, 5, 0}; return &eav; } /****************************************************************** * WinDbgExtensionDllInit (DBGHELP.@) */ void WINAPI WinDbgExtensionDllInit(PWINDBG_EXTENSION_APIS lpExtensionApis, unsigned short major, unsigned short minor) { }