wine/debugger/winedbg.c
Eric Pouech ac11a68f3e improved exception handling
merged all module handling code in new module.c file
reenabled 'walk module' and 'info module' commands
added ability to drive break on thread startup
2000-03-26 13:37:39 +00:00

599 lines
16 KiB
C

/* -*- tab-width: 8; c-basic-offset: 4 -*- */
/* Wine internal debugger
* Interface to Windows debugger API
* Eric Pouech (c) 2000
*/
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#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;
}