/* -*- tab-width: 8; c-basic-offset: 4 -*- */ /* Wine internal debugger * Interface to Windows debugger API * Eric Pouech (c) 2000 */ #include #include #include #include "debugger.h" #include "thread.h" #include "process.h" #include "wingdi.h" #include "winuser.h" #include "winreg.h" #include "debugtools.h" #include "options.h" #ifdef DBG_need_heap HANDLE dbg_heap = 0; #endif DEFAULT_DEBUG_CHANNEL(winedbg); DBG_PROCESS* DEBUG_CurrProcess = NULL; DBG_THREAD* DEBUG_CurrThread = NULL; CONTEXT DEBUG_context; static DBG_PROCESS* proc = NULL; static BOOL bBreakAllThreads = FALSE; static BOOL DEBUG_Init(void) { HKEY hkey; DWORD type; DWORD val; DWORD count = sizeof(val); if (!RegOpenKeyA(HKEY_CURRENT_USER, "Software\\Wine\\WineDbg", &hkey)) { if (!RegQueryValueExA(hkey, "BreakAllThreadsStartup", 0, &type, (LPSTR)&val, &count)) { bBreakAllThreads = val; } RegCloseKey(hkey); } return TRUE; } static WINE_EXCEPTION_FILTER(wine_dbg) { DEBUG_ExternalDebugger(); fprintf(stderr, "\nwine_dbg: Exception %lx\n", GetExceptionCode()); return EXCEPTION_EXECUTE_HANDLER; } static DBG_PROCESS* DEBUG_GetProcess(DWORD pid) { DBG_PROCESS* p; for (p = proc; p; p = p->next) if (p->pid == pid) break; return p; } static DBG_PROCESS* DEBUG_AddProcess(DWORD pid, HANDLE h) { DBG_PROCESS* p = DBG_alloc(sizeof(DBG_PROCESS)); if (!p) return NULL; p->handle = h; p->pid = pid; p->threads = NULL; p->num_threads = 0; p->modules = NULL; p->next_index = 0; p->next = proc; p->prev = NULL; if (proc) proc->prev = p; proc = p; return p; } static void DEBUG_DelThread(DBG_THREAD* p); static void DEBUG_DelProcess(DBG_PROCESS* p) { if (p->threads != NULL) { ERR("Shouldn't happen\n"); while (p->threads) DEBUG_DelThread(p->threads); } if (p->prev) p->prev->next = p->next; if (p->next) p->next->prev = p->prev; if (p == proc) proc = p->next; DBG_free(p); } static void DEBUG_InitCurrProcess(void) { #ifdef DBG_need_heap /* * Initialize the debugger heap. */ dbg_heap = HeapCreate(HEAP_NO_SERIALIZE, 0x1000, 0x8000000); /* 128MB */ #endif /* * Initialize the type handling stuff. */ DEBUG_InitTypes(); DEBUG_InitCVDataTypes(); /* * In some cases we can read the stabs information directly * from the executable. If this is the case, we don't need * to bother with trying to read a symbol file, as the stabs * also have line number and local variable information. * As long as gcc is used for the compiler, stabs will * be the default. On SVr4, DWARF could be used, but we * don't grok that yet, and in this case we fall back to using * the wine.sym file. */ if( DEBUG_ReadExecutableDbgInfo() == FALSE ) { char* symfilename = "wine.sym"; struct stat statbuf; HKEY hWineConf, hkey; DWORD count; char symbolTableFile[256]; if (-1 == stat(symfilename, &statbuf) ) symfilename = LIBDIR "wine.sym"; strcpy(symbolTableFile, symfilename); if (!RegOpenKeyA(HKEY_LOCAL_MACHINE, "Software\\Wine\\Wine\\Config", &hWineConf)) { if (!RegOpenKeyA(hWineConf, "wine", &hkey)) { count = sizeof(symbolTableFile); RegQueryValueA(hkey, "SymbolTableFile", symbolTableFile, &count); RegCloseKey(hkey); } RegCloseKey(hWineConf); } DEBUG_ReadSymbolTable(symbolTableFile); } DEBUG_LoadEntryPoints(NULL); DEBUG_ProcessDeferredDebug(); } static BOOL DEBUG_ProcessGetString(char* buffer, int size, HANDLE hp, LPSTR addr) { DWORD sz; *(WCHAR*)buffer = 0; return (addr && ReadProcessMemory(hp, addr, buffer, size, &sz)); } static BOOL DEBUG_ProcessGetStringIndirect(char* buffer, int size, HANDLE hp, LPVOID addr) { LPVOID ad; DWORD sz; if ( addr && ReadProcessMemory(hp, addr, &ad, sizeof(ad), &sz) && sz == sizeof(ad) && ad && ReadProcessMemory(hp, ad, buffer, size, &sz)) return TRUE; *(WCHAR*)buffer = 0; return FALSE; } static DBG_THREAD* DEBUG_GetThread(DBG_PROCESS* p, DWORD tid) { DBG_THREAD* t; for (t = p->threads; t; t = t->next) if (t->tid == tid) break; return t; } static DBG_THREAD* DEBUG_AddThread(DBG_PROCESS* p, DWORD tid, HANDLE h, LPVOID start, LPVOID teb) { DBG_THREAD* t = DBG_alloc(sizeof(DBG_THREAD)); if (!t) return NULL; t->handle = h; t->tid = tid; t->start = start; t->teb = teb; t->process = p; t->wait_for_first_exception = 0; t->dbg_exec_mode = EXEC_CONT; t->dbg_exec_count = 0; p->num_threads++; t->next = p->threads; t->prev = NULL; if (p->threads) p->threads->prev = t; p->threads = t; return t; } static void DEBUG_InitCurrThread(void) { if (!Options.debug) return; if (DEBUG_CurrThread->start) { if (DEBUG_CurrThread->process->num_threads == 1 || bBreakAllThreads) { DBG_VALUE value; DEBUG_SetBreakpoints(FALSE); value.type = NULL; value.cookie = DV_TARGET; value.addr.seg = 0; value.addr.off = (DWORD)DEBUG_CurrThread->start; DEBUG_AddBreakpoint(&value); DEBUG_SetBreakpoints(TRUE); } } else { DEBUG_CurrThread->wait_for_first_exception = 1; } } static void DEBUG_DelThread(DBG_THREAD* t) { if (t->prev) t->prev->next = t->next; if (t->next) t->next->prev = t->prev; if (t == t->process->threads) t->process->threads = t->next; t->process->num_threads--; DBG_free(t); } static BOOL DEBUG_HandleException( EXCEPTION_RECORD *rec, BOOL first_chance, BOOL force ) { BOOL is_debug = FALSE; BOOL ret; if (first_chance && !Options.debug && !force ) return 0; /* pass to app first */ switch (rec->ExceptionCode) { case EXCEPTION_BREAKPOINT: case EXCEPTION_SINGLE_STEP: is_debug = TRUE; break; case CONTROL_C_EXIT: if (!Options.debug) DEBUG_Exit(0); break; } if (!is_debug) { /* print some infos */ fprintf( stderr, "%s: ", first_chance ? "First chance exception" : "Unhandled exception" ); switch(rec->ExceptionCode) { case EXCEPTION_INT_DIVIDE_BY_ZERO: fprintf( stderr, "divide by zero" ); break; case EXCEPTION_INT_OVERFLOW: fprintf( stderr, "overflow" ); break; case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: fprintf( stderr, "array bounds " ); break; case EXCEPTION_ILLEGAL_INSTRUCTION: fprintf( stderr, "illegal instruction" ); break; case EXCEPTION_STACK_OVERFLOW: fprintf( stderr, "stack overflow" ); break; case EXCEPTION_PRIV_INSTRUCTION: fprintf( stderr, "priviledged instruction" ); break; case EXCEPTION_ACCESS_VIOLATION: if (rec->NumberParameters == 2) fprintf( stderr, "page fault on %s access to 0x%08lx", rec->ExceptionInformation[0] ? "write" : "read", rec->ExceptionInformation[1] ); else fprintf( stderr, "page fault" ); break; case EXCEPTION_DATATYPE_MISALIGNMENT: fprintf( stderr, "Alignment" ); break; case CONTROL_C_EXIT: fprintf( stderr, "^C" ); break; case EXCEPTION_CRITICAL_SECTION_WAIT: fprintf( stderr, "critical section %08lx wait failed", rec->ExceptionInformation[0] ); break; default: fprintf( stderr, "%08lx", rec->ExceptionCode ); break; } } #if 1 fprintf(stderr, "Entering debugger PC=%lx EFL=%08lx mode=%d count=%d\n", DEBUG_context.Eip, DEBUG_context.EFlags, DEBUG_CurrThread->dbg_exec_mode, DEBUG_CurrThread->dbg_exec_count); #endif ret = DEBUG_Main( is_debug, force, rec->ExceptionCode ); #if 1 fprintf(stderr, "Exiting debugger PC=%lx EFL=%08lx mode=%d count=%d\n", DEBUG_context.Eip, DEBUG_context.EFlags, DEBUG_CurrThread->dbg_exec_mode, DEBUG_CurrThread->dbg_exec_count); #endif return ret; } static DWORD DEBUG_HandleDebugEvent(DEBUG_EVENT* de) { char buffer[256]; DWORD cont; __TRY { cont = 0L; if ((DEBUG_CurrProcess = DEBUG_GetProcess(de->dwProcessId)) != NULL) DEBUG_CurrThread = DEBUG_GetThread(DEBUG_CurrProcess, de->dwThreadId); else DEBUG_CurrThread = NULL; switch (de->dwDebugEventCode) { case EXCEPTION_DEBUG_EVENT: if (!DEBUG_CurrThread) break; TRACE("%08lx:%08lx: exception code=%08lx %d\n", de->dwProcessId, de->dwThreadId, de->u.Exception.ExceptionRecord.ExceptionCode, DEBUG_CurrThread->wait_for_first_exception); DEBUG_context.ContextFlags = CONTEXT_CONTROL|CONTEXT_INTEGER|CONTEXT_SEGMENTS|CONTEXT_DEBUG_REGISTERS; if (!GetThreadContext(DEBUG_CurrThread->handle, &DEBUG_context)) { WARN("Can't get thread's context\n"); break; } TRACE("%p:%p\n", de->u.Exception.ExceptionRecord.ExceptionAddress, (void*)DEBUG_context.Eip); cont = DEBUG_HandleException(&de->u.Exception.ExceptionRecord, de->u.Exception.dwFirstChance, DEBUG_CurrThread->wait_for_first_exception); if (DEBUG_CurrThread->wait_for_first_exception) { DEBUG_CurrThread->wait_for_first_exception = 0; #ifdef __i386__ DEBUG_context.Eip--; #endif } SetThreadContext(DEBUG_CurrThread->handle, &DEBUG_context); break; case CREATE_THREAD_DEBUG_EVENT: TRACE("%08lx:%08lx: create thread D @%p\n", de->dwProcessId, de->dwThreadId, de->u.CreateThread.lpStartAddress); if (DEBUG_CurrProcess == NULL) { ERR("Unknown process\n"); break; } if (DEBUG_GetThread(DEBUG_CurrProcess, de->dwThreadId) != NULL) { TRACE("Thread already listed, skipping\n"); break; } DEBUG_CurrThread = DEBUG_AddThread(DEBUG_CurrProcess, de->dwThreadId, de->u.CreateThread.hThread, de->u.CreateThread.lpStartAddress, de->u.CreateThread.lpThreadLocalBase); if (!DEBUG_CurrThread) { ERR("Couldn't create thread\n"); break; } DEBUG_InitCurrThread(); break; case CREATE_PROCESS_DEBUG_EVENT: DEBUG_ProcessGetStringIndirect(buffer, sizeof(buffer), de->u.CreateProcessInfo.hProcess, de->u.LoadDll.lpImageName); /* FIXME unicode ? de->u.CreateProcessInfo.fUnicode */ TRACE("%08lx:%08lx: create process %s @%p\n", de->dwProcessId, de->dwThreadId, buffer, de->u.CreateProcessInfo.lpStartAddress); if (DEBUG_GetProcess(de->dwProcessId) != NULL) { TRACE("Skipping already defined process\n"); break; } DEBUG_CurrProcess = DEBUG_AddProcess(de->dwProcessId, de->u.CreateProcessInfo.hProcess); if (DEBUG_CurrProcess == NULL) { ERR("Unknown process\n"); break; } TRACE("%08lx:%08lx: create thread I @%p\n", de->dwProcessId, de->dwThreadId, de->u.CreateProcessInfo.lpStartAddress); DEBUG_CurrThread = DEBUG_AddThread(DEBUG_CurrProcess, de->dwThreadId, de->u.CreateProcessInfo.hThread, de->u.CreateProcessInfo.lpStartAddress, de->u.CreateProcessInfo.lpThreadLocalBase); if (!DEBUG_CurrThread) { ERR("Couldn't create thread\n"); break; } DEBUG_InitCurrProcess(); DEBUG_InitCurrThread(); #ifdef _WE_SUPPORT_THE_STAB_TYPES_USED_BY_MINGW_TOO /* so far, process name is not set */ DEBUG_RegisterDebugInfo((DWORD)de->u.CreateProcessInfo.lpBaseOfImage, "wine-exec"); #endif break; case EXIT_THREAD_DEBUG_EVENT: TRACE("%08lx:%08lx: exit thread (%ld)\n", de->dwProcessId, de->dwThreadId, de->u.ExitThread.dwExitCode); if (DEBUG_CurrThread == NULL) { ERR("Unknown thread\n"); break; } /* FIXME: remove break point set on thread startup */ DEBUG_DelThread(DEBUG_CurrThread); break; case EXIT_PROCESS_DEBUG_EVENT: TRACE("%08lx:%08lx: exit process (%ld)\n", de->dwProcessId, de->dwThreadId, de->u.ExitProcess.dwExitCode); if (DEBUG_CurrProcess == NULL) { ERR("Unknown process\n"); break; } /* kill last thread */ DEBUG_DelThread(DEBUG_CurrProcess->threads); /* FIXME: remove break point set on thread startup */ DEBUG_DelProcess(DEBUG_CurrProcess); break; case LOAD_DLL_DEBUG_EVENT: if (DEBUG_CurrThread == NULL) { ERR("Unknown thread\n"); break; } DEBUG_ProcessGetStringIndirect(buffer, sizeof(buffer), DEBUG_CurrThread->process->handle, de->u.LoadDll.lpImageName); /* FIXME unicode: de->u.LoadDll.fUnicode */ TRACE("%08lx:%08lx: loads DLL %s @%p\n", de->dwProcessId, de->dwThreadId, buffer, de->u.LoadDll.lpBaseOfDll); CharUpperA(buffer); DEBUG_LoadModule32( buffer, (DWORD)de->u.LoadDll.lpBaseOfDll); break; case UNLOAD_DLL_DEBUG_EVENT: TRACE("%08lx:%08lx: unload DLL @%p\n", de->dwProcessId, de->dwThreadId, de->u.UnloadDll.lpBaseOfDll); break; case OUTPUT_DEBUG_STRING_EVENT: if (DEBUG_CurrThread == NULL) { ERR("Unknown thread\n"); break; } DEBUG_ProcessGetString(buffer, sizeof(buffer), DEBUG_CurrThread->process->handle, de->u.DebugString.lpDebugStringData); /* fixme unicode de->u.DebugString.fUnicode ? */ TRACE("%08lx:%08lx: output debug string (%s)\n", de->dwProcessId, de->dwThreadId, buffer); break; case RIP_EVENT: TRACE("%08lx:%08lx: rip error=%ld type=%ld\n", de->dwProcessId, de->dwThreadId, de->u.RipInfo.dwError, de->u.RipInfo.dwType); break; default: TRACE("%08lx:%08lx: unknown event (%ld)\n", de->dwProcessId, de->dwThreadId, de->dwDebugEventCode); } } __EXCEPT(wine_dbg) { cont = 0; } __ENDTRY; return cont; } static DWORD CALLBACK DEBUG_MainLoop(DWORD pid) { DEBUG_EVENT de; DWORD cont; DEBUG_Init(); while (WaitForDebugEvent(&de, INFINITE)) { cont = DEBUG_HandleDebugEvent(&de); ContinueDebugEvent(de.dwProcessId, de.dwThreadId, cont); } TRACE("WineDbg terminated on pid %ld\n", pid); return 0L; } static DWORD CALLBACK DEBUG_StarterFromPID(LPVOID pid) { TRACE("WineDbg started on pid %ld\n", (DWORD)pid); if (!DebugActiveProcess((DWORD)pid)) { TRACE("Can't debug process %ld: %ld\n", (DWORD)pid, GetLastError()); return 0; } return DEBUG_MainLoop((DWORD)pid); } void DEBUG_Attach(DWORD pid) { CreateThread(NULL, 0, DEBUG_StarterFromPID, (LPVOID)pid, 0, NULL); } struct dsfcl { HANDLE hEvent; LPSTR lpCmdLine; int showWindow; DWORD error; }; static DWORD CALLBACK DEBUG_StarterFromCmdLine(LPVOID p) { PROCESS_INFORMATION info; STARTUPINFOA startup; BOOL ok = TRUE; memset(&startup, 0, sizeof(startup)); startup.cb = sizeof(startup); startup.dwFlags = STARTF_USESHOWWINDOW; startup.wShowWindow = ((struct dsfcl*)p)->showWindow; /* any value >= 32 will do, simulate a correct handle value */ ((struct dsfcl*)p)->error = 0xFFFFFFFF; if (!CreateProcessA(NULL, ((struct dsfcl*)p)->lpCmdLine, NULL, NULL, FALSE, DEBUG_PROCESS, NULL, NULL, &startup, &info)) { ((struct dsfcl*)p)->error = GetLastError(); ok = FALSE; } SetEvent(((struct dsfcl*)p)->hEvent); if (ok) DEBUG_MainLoop(info.dwProcessId); return 0; } DWORD DEBUG_WinExec(LPSTR lpCmdLine, int sw) { struct dsfcl s; BOOL ret; if ((s.hEvent = CreateEventA(NULL, FALSE, FALSE, NULL))) { s.lpCmdLine = lpCmdLine; s.showWindow = sw; if (CreateThread(NULL, 0, DEBUG_StarterFromCmdLine, (LPVOID)&s, 0, NULL)) { WaitForSingleObject(s.hEvent, INFINITE); ret = s.error; } else { ret = 3; /* (dummy) error value for non created thread */ } CloseHandle(s.hEvent); } else { ret = 1; /* (dummy) error value for non created event */ } return ret; }