/* * A Win32 based proxy implementing the GBD remote protocol * This allows to debug Wine (and any "emulated" program) under * Linux using GDB * * Copyright (c) Eric Pouech 2002 * * 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 */ /* Protocol specification can be found here: * http://sources.redhat.com/gdb/onlinedocs/gdb_32.html */ #include "config.h" #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_SOCKET_H # include #endif #include #include #include #include "windef.h" #include "winbase.h" #include "tlhelp32.h" /* those two are needed only for the SHOWNORMAL flag */ #include "wingdi.h" #include "winuser.h" #include "debugger.h" #define GDBPXY_TRC_LOWLEVEL 0x01 #define GDBPXY_TRC_PACKET 0x02 #define GDBPXY_TRC_COMMAND 0x04 #define GDBPXY_TRC_COMMAND_ERROR 0x08 #define GDBPXY_TRC_WIN32_EVENT 0x10 #define GDBPXY_TRC_WIN32_ERROR 0x20 struct gdb_ctx_Xpoint { int type; /* -1 means free */ void* addr; unsigned long val; }; struct gdb_context { /* gdb information */ int sock; /* incoming buffer */ char* in_buf; int in_buf_alloc; int in_len; /* split into individual packet */ char* in_packet; int in_packet_len; /* outgoing buffer */ char* out_buf; int out_buf_alloc; int out_len; int out_curr_packet; /* generic GDB thread information */ unsigned exec_thread; /* thread used in step & continue */ unsigned other_thread; /* thread to be used in any other operation */ unsigned trace; /* current Win32 trap env */ unsigned last_sig; BOOL in_trap; CONTEXT context; /* Win32 information */ DBG_PROCESS* process; #define NUM_XPOINT 32 struct gdb_ctx_Xpoint Xpoints[NUM_XPOINT]; /* Unix environment */ unsigned long wine_segs[3]; /* load addresses of the ELF wine exec segments (text, bss and data) */ }; extern int read_elf_info(const char* filename, unsigned long tab[]); /* =============================================== * * B A S I C M A N I P U L A T I O N S * * =============================================== * */ static inline int hex_from0(char ch) { if (ch >= '0' && ch <= '9') return ch - '0'; if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; assert(0); return 0; } static inline unsigned char hex_to0(int x) { assert(x >= 0 && x < 16); return "0123456789abcdef"[x]; } static void hex_from(void* dst, const char* src, size_t len) { unsigned char *p = dst; while (len--) { *p++ = (hex_from0(src[0]) << 4) | hex_from0(src[1]); src += 2; } } static void hex_to(char* dst, const void* src, size_t len) { const unsigned char *p = src; while (len--) { *dst++ = hex_to0(*p >> 4); *dst++ = hex_to0(*p & 0x0F); p++; } } static unsigned char checksum(const char* ptr, int len) { unsigned cksum = 0; while (len-- > 0) cksum += (unsigned char)*ptr++; return cksum; } /* =============================================== * * C P U H A N D L E R S * * =============================================== * */ #define OFFSET_OF(__c,__f) ((int)(((char*)&(((__c*)0)->__f))-((char*)0))) #ifdef __i386__ static size_t cpu_register_map[] = { OFFSET_OF(CONTEXT, Eax), OFFSET_OF(CONTEXT, Ecx), OFFSET_OF(CONTEXT, Edx), OFFSET_OF(CONTEXT, Ebx), OFFSET_OF(CONTEXT, Esp), OFFSET_OF(CONTEXT, Ebp), OFFSET_OF(CONTEXT, Esi), OFFSET_OF(CONTEXT, Edi), OFFSET_OF(CONTEXT, Eip), OFFSET_OF(CONTEXT, EFlags), OFFSET_OF(CONTEXT, SegCs), OFFSET_OF(CONTEXT, SegSs), OFFSET_OF(CONTEXT, SegDs), OFFSET_OF(CONTEXT, SegEs), OFFSET_OF(CONTEXT, SegFs), OFFSET_OF(CONTEXT, SegGs), }; #else # ifdef __powerpc__ static size_t cpu_register_map[] = { OFFSET_OF(CONTEXT, Gpr0), OFFSET_OF(CONTEXT, Gpr1), OFFSET_OF(CONTEXT, Gpr2), OFFSET_OF(CONTEXT, Gpr3), OFFSET_OF(CONTEXT, Gpr4), OFFSET_OF(CONTEXT, Gpr5), OFFSET_OF(CONTEXT, Gpr6), OFFSET_OF(CONTEXT, Gpr7), OFFSET_OF(CONTEXT, Gpr8), OFFSET_OF(CONTEXT, Gpr9), OFFSET_OF(CONTEXT, Gpr10), OFFSET_OF(CONTEXT, Gpr11), OFFSET_OF(CONTEXT, Gpr12), OFFSET_OF(CONTEXT, Gpr13), OFFSET_OF(CONTEXT, Gpr14), OFFSET_OF(CONTEXT, Gpr15), OFFSET_OF(CONTEXT, Gpr16), OFFSET_OF(CONTEXT, Gpr17), OFFSET_OF(CONTEXT, Gpr18), OFFSET_OF(CONTEXT, Gpr19), OFFSET_OF(CONTEXT, Gpr20), OFFSET_OF(CONTEXT, Gpr21), OFFSET_OF(CONTEXT, Gpr22), OFFSET_OF(CONTEXT, Gpr23), OFFSET_OF(CONTEXT, Gpr24), OFFSET_OF(CONTEXT, Gpr25), OFFSET_OF(CONTEXT, Gpr26), OFFSET_OF(CONTEXT, Gpr27), OFFSET_OF(CONTEXT, Gpr28), OFFSET_OF(CONTEXT, Gpr29), OFFSET_OF(CONTEXT, Gpr30), OFFSET_OF(CONTEXT, Gpr31), OFFSET_OF(CONTEXT, Fpr0), OFFSET_OF(CONTEXT, Fpr1), OFFSET_OF(CONTEXT, Fpr2), OFFSET_OF(CONTEXT, Fpr3), OFFSET_OF(CONTEXT, Fpr4), OFFSET_OF(CONTEXT, Fpr5), OFFSET_OF(CONTEXT, Fpr6), OFFSET_OF(CONTEXT, Fpr7), OFFSET_OF(CONTEXT, Fpr8), OFFSET_OF(CONTEXT, Fpr9), OFFSET_OF(CONTEXT, Fpr10), OFFSET_OF(CONTEXT, Fpr11), OFFSET_OF(CONTEXT, Fpr12), OFFSET_OF(CONTEXT, Fpr13), OFFSET_OF(CONTEXT, Fpr14), OFFSET_OF(CONTEXT, Fpr15), OFFSET_OF(CONTEXT, Fpr16), OFFSET_OF(CONTEXT, Fpr17), OFFSET_OF(CONTEXT, Fpr18), OFFSET_OF(CONTEXT, Fpr19), OFFSET_OF(CONTEXT, Fpr20), OFFSET_OF(CONTEXT, Fpr21), OFFSET_OF(CONTEXT, Fpr22), OFFSET_OF(CONTEXT, Fpr23), OFFSET_OF(CONTEXT, Fpr24), OFFSET_OF(CONTEXT, Fpr25), OFFSET_OF(CONTEXT, Fpr26), OFFSET_OF(CONTEXT, Fpr27), OFFSET_OF(CONTEXT, Fpr28), OFFSET_OF(CONTEXT, Fpr29), OFFSET_OF(CONTEXT, Fpr30), OFFSET_OF(CONTEXT, Fpr31), OFFSET_OF(CONTEXT, Iar), OFFSET_OF(CONTEXT, Msr), OFFSET_OF(CONTEXT, Cr), OFFSET_OF(CONTEXT, Lr), OFFSET_OF(CONTEXT, Ctr), OFFSET_OF(CONTEXT, Xer), /* FIXME: MQ is missing? OFFSET_OF(CONTEXT, Mq), */ /* see gdb/nlm/ppc.c */ }; # else # error "Define the registers map for your CPU" # endif #endif #undef OFFSET_OF static const size_t cpu_num_regs = (sizeof(cpu_register_map) / sizeof(cpu_register_map[0])); static inline unsigned long* cpu_register(struct gdb_context* gdbctx, unsigned idx) { assert(idx < cpu_num_regs); return (unsigned long*)((char*)&gdbctx->context + cpu_register_map[idx]); } static inline BOOL cpu_enter_stepping(struct gdb_context* gdbctx) { #ifdef __i386__ gdbctx->context.EFlags |= 0x100; return TRUE; #elif __powerpc__ #ifndef MSR_SE # define MSR_SE (1<<10) #endif gdbctx->context.Msr |= MSR_SE; return TRUE; #else #error "Define step mode enter for your CPU" #endif return FALSE; } static inline BOOL cpu_leave_stepping(struct gdb_context* gdbctx) { #ifdef __i386__ /* The Win32 debug API always resets the Step bit in EFlags after * a single step instruction, so we don't need to clear when the * step is done. */ return TRUE; #elif __powerpc__ gdbctx->context.Msr &= MSR_SE; return TRUE; #else #error "Define step mode leave for your CPU" #endif return FALSE; } #ifdef __i386__ #define DR7_CONTROL_SHIFT 16 #define DR7_CONTROL_SIZE 4 #define DR7_RW_EXECUTE (0x0) #define DR7_RW_WRITE (0x1) #define DR7_RW_READ (0x3) #define DR7_LEN_1 (0x0) #define DR7_LEN_2 (0x4) #define DR7_LEN_4 (0xC) #define DR7_LOCAL_ENABLE_SHIFT 0 #define DR7_GLOBAL_ENABLE_SHIFT 1 #define DR7_ENABLE_SIZE 2 #define DR7_LOCAL_ENABLE_MASK (0x55) #define DR7_GLOBAL_ENABLE_MASK (0xAA) #define DR7_CONTROL_RESERVED (0xFC00) #define DR7_LOCAL_SLOWDOWN (0x100) #define DR7_GLOBAL_SLOWDOWN (0x200) #define DR7_ENABLE_MASK(dr) (1<<(DR7_LOCAL_ENABLE_SHIFT+DR7_ENABLE_SIZE*(dr))) #define IS_DR7_SET(ctrl,dr) ((ctrl)&DR7_ENABLE_MASK(dr)) static inline int i386_get_unused_DR(struct gdb_context* gdbctx, unsigned long** r) { if (!IS_DR7_SET(gdbctx->context.Dr7, 0)) { *r = &gdbctx->context.Dr0; return 0; } if (!IS_DR7_SET(gdbctx->context.Dr7, 1)) { *r = &gdbctx->context.Dr1; return 1; } if (!IS_DR7_SET(gdbctx->context.Dr7, 2)) { *r = &gdbctx->context.Dr2; return 2; } if (!IS_DR7_SET(gdbctx->context.Dr7, 3)) { *r = &gdbctx->context.Dr3; return 3; } return -1; } #endif /****************************************************************** * cpu_insert_Xpoint * * returns 1 if ok * 0 if error * -1 if operation isn't supported by CPU */ static inline int cpu_insert_Xpoint(struct gdb_context* gdbctx, struct gdb_ctx_Xpoint* xpt, size_t len) { #ifdef __i386__ unsigned char ch; unsigned long sz; unsigned long* pr; int reg; unsigned long bits; switch (xpt->type) { case '0': if (len != 1) return 0; if (!ReadProcessMemory(gdbctx->process->handle, xpt->addr, &ch, 1, &sz) || sz != 1) return 0; xpt->val = ch; ch = 0xcc; if (!WriteProcessMemory(gdbctx->process->handle, xpt->addr, &ch, 1, &sz) || sz != 1) return 0; break; case '1': bits = DR7_RW_EXECUTE; goto hw_bp; case '2': bits = DR7_RW_READ; goto hw_bp; case '3': bits = DR7_RW_WRITE; hw_bp: if ((reg = i386_get_unused_DR(gdbctx, &pr)) == -1) return 0; *pr = (unsigned long)xpt->addr; if (xpt->type != '1') switch (len) { case 4: bits |= DR7_LEN_4; break; case 2: bits |= DR7_LEN_2; break; case 1: bits |= DR7_LEN_1; break; default: return 0; } xpt->val = reg; /* clear old values */ gdbctx->context.Dr7 &= ~(0x0F << (DR7_CONTROL_SHIFT + DR7_CONTROL_SIZE * reg)); /* set the correct ones */ gdbctx->context.Dr7 |= bits << (DR7_CONTROL_SHIFT + DR7_CONTROL_SIZE * reg); gdbctx->context.Dr7 |= DR7_ENABLE_MASK(reg) | DR7_LOCAL_SLOWDOWN; break; default: fprintf(stderr, "Unknown bp type %c\n", xpt->type); return 0; } return 1; #elif defined(__powerpc__) unsigned long xbp; unsigned long sz; switch (xpt->type) { case '0': if (len != 4) return 0; if (!ReadProcessMemory(gdbctx->process->handle, xpt->addr, &xbp, 4, &sz) || sz != 4) return 0; xpt->val = xbp; xbp = 0x7d821008; /* 7d 82 10 08 ... in big endian */ if (!WriteProcessMemory(gdbctx->process->handle, xpt->addr, &xbp, 4, &sz) || sz != 4) return 0; break; default: fprintf(stderr, "Unknown/unsupported bp type %c\n", xpt->type); return 0; } return 1; #else #error "Define insert Xpoint for your CPU" #endif return -1; } /****************************************************************** * cpu_remove_Xpoint * * returns 1 if ok * 0 if error * -1 if operation isn't supported by CPU */ static inline BOOL cpu_remove_Xpoint(struct gdb_context* gdbctx, struct gdb_ctx_Xpoint* xpt, size_t len) { #ifdef __i386__ unsigned long sz; unsigned char ch; switch (xpt->type) { case '0': if (len != 1) return 0; ch = (unsigned char)xpt->val; if (!WriteProcessMemory(gdbctx->process->handle, xpt->addr, &ch, 1, &sz) || sz != 1) return 0; break; case '1': case '2': case '3': /* simply disable the entry */ gdbctx->context.Dr7 &= ~DR7_ENABLE_MASK(xpt->val); break; default: fprintf(stderr, "Unknown bp type %c\n", xpt->type); return 0; } return 1; #elif defined(__powerpc__) unsigned long sz; unsigned long xbp; switch (xpt->type) { case '0': if (len != 4) return 0; xbp = xpt->val; if (!WriteProcessMemory(gdbctx->process->handle, xpt->addr, &xbp, 4, &sz) || sz != 4) return 0; break; case '1': case '2': case '3': default: fprintf(stderr, "Unknown/unsupported bp type %c\n", xpt->type); return 0; } return 1; #else #error "Define remove Xpoint for your CPU" #endif return -1; } /* =============================================== * * W I N 3 2 D E B U G I N T E R F A C E * * =============================================== * */ static BOOL handle_exception(struct gdb_context* gdbctx, EXCEPTION_DEBUG_INFO* exc) { EXCEPTION_RECORD* rec = &exc->ExceptionRecord; BOOL ret = FALSE; switch (rec->ExceptionCode) { case EXCEPTION_ACCESS_VIOLATION: case EXCEPTION_PRIV_INSTRUCTION: case EXCEPTION_STACK_OVERFLOW: case EXCEPTION_GUARD_PAGE: gdbctx->last_sig = SIGSEGV; ret = TRUE; break; case EXCEPTION_DATATYPE_MISALIGNMENT: gdbctx->last_sig = SIGBUS; ret = TRUE; break; case EXCEPTION_SINGLE_STEP: /* fall thru */ case EXCEPTION_BREAKPOINT: gdbctx->last_sig = SIGTRAP; ret = TRUE; break; case EXCEPTION_FLT_DENORMAL_OPERAND: case EXCEPTION_FLT_DIVIDE_BY_ZERO: case EXCEPTION_FLT_INEXACT_RESULT: case EXCEPTION_FLT_INVALID_OPERATION: case EXCEPTION_FLT_OVERFLOW: case EXCEPTION_FLT_STACK_CHECK: case EXCEPTION_FLT_UNDERFLOW: gdbctx->last_sig = SIGFPE; ret = TRUE; break; case EXCEPTION_INT_DIVIDE_BY_ZERO: case EXCEPTION_INT_OVERFLOW: gdbctx->last_sig = SIGFPE; ret = TRUE; break; case EXCEPTION_ILLEGAL_INSTRUCTION: gdbctx->last_sig = SIGILL; ret = TRUE; break; case CONTROL_C_EXIT: gdbctx->last_sig = SIGINT; ret = TRUE; break; case EXCEPTION_CRITICAL_SECTION_WAIT: gdbctx->last_sig = SIGALRM; ret = TRUE; /* FIXME: we could also add here a O packet with additional information */ break; default: if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT) fprintf(stderr, "unhandled exception code %08lx\n", rec->ExceptionCode); gdbctx->last_sig = SIGABRT; ret = TRUE; break; } return ret; } static void handle_debug_event(struct gdb_context* gdbctx, DEBUG_EVENT* de) { char buffer[256]; DEBUG_CurrThread = DEBUG_GetThread(gdbctx->process, de->dwThreadId); switch (de->dwDebugEventCode) { case CREATE_PROCESS_DEBUG_EVENT: DEBUG_ProcessGetStringIndirect(buffer, sizeof(buffer), de->u.CreateProcessInfo.hProcess, de->u.CreateProcessInfo.lpImageName); /* FIXME unicode ? de->u.CreateProcessInfo.fUnicode */ if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT) fprintf(stderr, "%08lx:%08lx: create process '%s'/%p @%08lx (%ld<%ld>)\n", de->dwProcessId, de->dwThreadId, buffer, de->u.CreateProcessInfo.lpImageName, (unsigned long)(LPVOID)de->u.CreateProcessInfo.lpStartAddress, de->u.CreateProcessInfo.dwDebugInfoFileOffset, de->u.CreateProcessInfo.nDebugInfoSize); gdbctx->process = DEBUG_AddProcess(de->dwProcessId, de->u.CreateProcessInfo.hProcess, buffer); /* de->u.CreateProcessInfo.lpStartAddress; */ if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT) fprintf(stderr, "%08lx:%08lx: create thread I @%08lx\n", de->dwProcessId, de->dwThreadId, (unsigned long)(LPVOID)de->u.CreateProcessInfo.lpStartAddress); assert(DEBUG_CurrThread == NULL); /* shouldn't be there */ DEBUG_AddThread(gdbctx->process, de->dwThreadId, de->u.CreateProcessInfo.hThread, de->u.CreateProcessInfo.lpStartAddress, de->u.CreateProcessInfo.lpThreadLocalBase); #if 0 DEBUG_LoadModule32(DEBUG_CurrProcess->imageName, de->u.CreateProcessInfo.hFile, (DWORD)de->u.CreateProcessInfo.lpBaseOfImage); if (buffer[0]) /* we got a process name */ { DWORD type; if (!GetBinaryTypeA( buffer, &type )) { /* not a Windows binary, assume it's a Unix executable then */ char unixname[MAX_PATH]; /* HACK!! should fix DEBUG_ReadExecutableDbgInfo to accept DOS filenames */ if (wine_get_unix_file_name( buffer, unixname, sizeof(unixname) )) { DEBUG_ReadExecutableDbgInfo( unixname ); break; } } } /* if it is a Windows binary, or an invalid or missing file name, * we use wine itself as the main executable */ DEBUG_ReadExecutableDbgInfo( "wine" ); #endif break; case LOAD_DLL_DEBUG_EVENT: assert(DEBUG_CurrThread); DEBUG_ProcessGetStringIndirect(buffer, sizeof(buffer), gdbctx->process->handle, de->u.LoadDll.lpImageName); /* FIXME unicode: de->u.LoadDll.fUnicode */ if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT) fprintf(stderr, "%08lx:%08lx: loads DLL %s @%08lx (%ld<%ld>)\n", de->dwProcessId, de->dwThreadId, buffer, (unsigned long)de->u.LoadDll.lpBaseOfDll, de->u.LoadDll.dwDebugInfoFileOffset, de->u.LoadDll.nDebugInfoSize); #if 0 _strupr(buffer); DEBUG_LoadModule32(buffer, de->u.LoadDll.hFile, (DWORD)de->u.LoadDll.lpBaseOfDll); DEBUG_CheckDelayedBP(); if (DBG_IVAR(BreakOnDllLoad)) { DEBUG_Printf(DBG_CHN_MESG, "Stopping on DLL %s loading at %08lx\n", buffer, (unsigned long)de->u.LoadDll.lpBaseOfDll); DEBUG_Parser(); } #endif break; case UNLOAD_DLL_DEBUG_EVENT: if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT) fprintf(stderr, "%08lx:%08lx: unload DLL @%08lx\n", de->dwProcessId, de->dwThreadId, (unsigned long)de->u.UnloadDll.lpBaseOfDll); break; case EXCEPTION_DEBUG_EVENT: assert(DEBUG_CurrThread); if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT) fprintf(stderr, "%08lx:%08lx: exception code=%08lx\n", de->dwProcessId, de->dwThreadId, de->u.Exception.ExceptionRecord.ExceptionCode); gdbctx->context.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER #ifdef CONTEXT_SEGMENTS | CONTEXT_SEGMENTS #endif #ifdef CONTEXT_DEBUG_REGISTERS | CONTEXT_DEBUG_REGISTERS #endif ; if (!GetThreadContext(DEBUG_CurrThread->handle, &gdbctx->context)) { if (gdbctx->trace & GDBPXY_TRC_WIN32_ERROR) fprintf(stderr, "Can't get thread's context\n"); break; } gdbctx->in_trap = handle_exception(gdbctx, &de->u.Exception); break; case CREATE_THREAD_DEBUG_EVENT: if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT) fprintf(stderr, "%08lx:%08lx: create thread D @%08lx\n", de->dwProcessId, de->dwThreadId, (unsigned long)(LPVOID)de->u.CreateThread.lpStartAddress); DEBUG_AddThread(gdbctx->process, de->dwThreadId, de->u.CreateThread.hThread, de->u.CreateThread.lpStartAddress, de->u.CreateThread.lpThreadLocalBase); break; case EXIT_THREAD_DEBUG_EVENT: if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT) fprintf(stderr, "%08lx:%08lx: exit thread (%ld)\n", de->dwProcessId, de->dwThreadId, de->u.ExitThread.dwExitCode); assert(DEBUG_CurrThread); DEBUG_DelThread(DEBUG_CurrThread); break; case EXIT_PROCESS_DEBUG_EVENT: if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT) fprintf(stderr, "%08lx:%08lx: exit process (%ld)\n", de->dwProcessId, de->dwThreadId, de->u.ExitProcess.dwExitCode); DEBUG_DelProcess(gdbctx->process); gdbctx->process = NULL; /* now signal gdb that we're done */ gdbctx->last_sig = SIGTERM; gdbctx->in_trap = TRUE; break; case OUTPUT_DEBUG_STRING_EVENT: assert(DEBUG_CurrThread); DEBUG_ProcessGetString(buffer, sizeof(buffer), gdbctx->process->handle, de->u.DebugString.lpDebugStringData); /* FIXME unicode de->u.DebugString.fUnicode ? */ if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT) fprintf(stderr, "%08lx:%08lx: output debug string (%s)\n", de->dwProcessId, de->dwThreadId, buffer); break; case RIP_EVENT: if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT) fprintf(stderr, "%08lx:%08lx: rip error=%ld type=%ld\n", de->dwProcessId, de->dwThreadId, de->u.RipInfo.dwError, de->u.RipInfo.dwType); break; default: if (gdbctx->trace & GDBPXY_TRC_WIN32_EVENT) fprintf(stderr, "%08lx:%08lx: unknown event (%ld)\n", de->dwProcessId, de->dwThreadId, de->dwDebugEventCode); } } static void resume_debuggee(struct gdb_context* gdbctx, unsigned long cont) { if (DEBUG_CurrThread) { if (!SetThreadContext(DEBUG_CurrThread->handle, &gdbctx->context)) if (gdbctx->trace & GDBPXY_TRC_WIN32_ERROR) fprintf(stderr, "cannot set ctx on %lu\n", DEBUG_CurrThread->tid); if (!ContinueDebugEvent(gdbctx->process->pid, DEBUG_CurrThread->tid, cont)) if (gdbctx->trace & GDBPXY_TRC_WIN32_ERROR) fprintf(stderr, "cannot continue on %lu (%lu)\n", DEBUG_CurrThread->tid, cont); } else if (gdbctx->trace & GDBPXY_TRC_WIN32_ERROR) fprintf(stderr, "cannot find last thread (%lu)\n", DEBUG_CurrThread->tid); } static void wait_for_debuggee(struct gdb_context* gdbctx) { DEBUG_EVENT de; gdbctx->in_trap = FALSE; while (WaitForDebugEvent(&de, INFINITE)) { handle_debug_event(gdbctx, &de); assert(!gdbctx->process || gdbctx->process->pid == 0 || de.dwProcessId == gdbctx->process->pid); assert(!DEBUG_CurrThread || de.dwThreadId == DEBUG_CurrThread->tid); if (gdbctx->in_trap) break; ContinueDebugEvent(de.dwProcessId, de.dwThreadId, DBG_CONTINUE); } } static void detach_debuggee(struct gdb_context* gdbctx, BOOL kill) { cpu_leave_stepping(gdbctx); resume_debuggee(gdbctx, DBG_CONTINUE); if (!kill) DebugActiveProcessStop(gdbctx->process->pid); DEBUG_DelProcess(gdbctx->process); gdbctx->process = NULL; } static void get_process_info(struct gdb_context* gdbctx, char* buffer, size_t len) { unsigned long status; if (!GetExitCodeProcess(gdbctx->process->handle, &status)) { strcpy(buffer, "Unknown process"); return; } if (status == STILL_ACTIVE) { strcpy(buffer, "Running"); } else snprintf(buffer, len, "Terminated (%lu)", status); switch (GetPriorityClass(gdbctx->process->handle)) { case 0: break; #ifdef ABOVE_NORMAL_PRIORITY_CLASS case ABOVE_NORMAL_PRIORITY_CLASS: strcat(buffer, ", above normal priority"); break; #endif #ifdef BELOW_NORMAL_PRIORITY_CLASS case BELOW_NORMAL_PRIORITY_CLASS: strcat(buffer, ", below normal priotity"); break; #endif case HIGH_PRIORITY_CLASS: strcat(buffer, ", high priority"); break; case IDLE_PRIORITY_CLASS: strcat(buffer, ", idle priority"); break; case NORMAL_PRIORITY_CLASS: strcat(buffer, ", normal priority"); break; case REALTIME_PRIORITY_CLASS: strcat(buffer, ", realtime priority"); break; } strcat(buffer, "\n"); } static void get_thread_info(struct gdb_context* gdbctx, unsigned tid, char* buffer, size_t len) { DBG_THREAD* thd; unsigned long status; int prio; /* FIXME: use the size of buffer */ thd = DEBUG_GetThread(gdbctx->process, tid); if (thd == NULL) { strcpy(buffer, "No information"); return; } if (GetExitCodeThread(thd->handle, &status)) { if (status == STILL_ACTIVE) { /* FIXME: this is a bit brutal... some nicer way shall be found */ switch (status = SuspendThread(thd->handle)) { case -1: break; case 0: strcpy(buffer, "Running"); break; default: snprintf(buffer, len, "Suspended (%lu)", status - 1); } ResumeThread(thd->handle); } else snprintf(buffer, len, "Terminated (exit code = %lu)", status); } else { strcpy(buffer, "Unknown threadID"); } switch (prio = GetThreadPriority(thd->handle)) { case THREAD_PRIORITY_ERROR_RETURN: break; case THREAD_PRIORITY_ABOVE_NORMAL: strcat(buffer, ", priority +1 above normal"); break; case THREAD_PRIORITY_BELOW_NORMAL: strcat(buffer, ", priority -1 below normal"); break; case THREAD_PRIORITY_HIGHEST: strcat(buffer, ", priority +2 above normal"); break; case THREAD_PRIORITY_LOWEST: strcat(buffer, ", priority -2 below normal"); break; case THREAD_PRIORITY_IDLE: strcat(buffer, ", priority idle"); break; case THREAD_PRIORITY_NORMAL: strcat(buffer, ", priority normal"); break; case THREAD_PRIORITY_TIME_CRITICAL: strcat(buffer, ", priority time-critical"); break; default: snprintf(buffer + strlen(buffer), len - strlen(buffer), ", priority = %d", prio); } assert(strlen(buffer) < len); } /* =============================================== * * P A C K E T U T I L S * * =============================================== * */ enum packet_return {packet_error = 0x00, packet_ok = 0x01, packet_done = 0x02, packet_last_f = 0x80}; static void packet_reply_grow(struct gdb_context* gdbctx, size_t size) { if (gdbctx->out_buf_alloc < gdbctx->out_len + size) { gdbctx->out_buf_alloc = ((gdbctx->out_len + size) / 32 + 1) * 32; gdbctx->out_buf = realloc(gdbctx->out_buf, gdbctx->out_buf_alloc); } } static void packet_reply_hex_to(struct gdb_context* gdbctx, const void* src, int len) { packet_reply_grow(gdbctx, len * 2); hex_to(&gdbctx->out_buf[gdbctx->out_len], src, len); gdbctx->out_len += len * 2; } static inline void packet_reply_hex_to_str(struct gdb_context* gdbctx, const char* src) { packet_reply_hex_to(gdbctx, src, strlen(src)); } static void packet_reply_val(struct gdb_context* gdbctx, unsigned long val, int len) { int i, shift; shift = (len - 1) * 8; packet_reply_grow(gdbctx, len * 2); for (i = 0; i < len; i++, shift -= 8) { gdbctx->out_buf[gdbctx->out_len++] = hex_to0((val >> (shift + 4)) & 0x0F); gdbctx->out_buf[gdbctx->out_len++] = hex_to0((val >> shift ) & 0x0F); } } static inline void packet_reply_add(struct gdb_context* gdbctx, const char* str, int len) { packet_reply_grow(gdbctx, len); memcpy(&gdbctx->out_buf[gdbctx->out_len], str, len); gdbctx->out_len += len; } static inline void packet_reply_cat(struct gdb_context* gdbctx, const char* str) { packet_reply_add(gdbctx, str, strlen(str)); } static inline void packet_reply_catc(struct gdb_context* gdbctx, char ch) { packet_reply_add(gdbctx, &ch, 1); } static void packet_reply_open(struct gdb_context* gdbctx) { assert(gdbctx->out_curr_packet == -1); packet_reply_catc(gdbctx, '$'); gdbctx->out_curr_packet = gdbctx->out_len; } static void packet_reply_close(struct gdb_context* gdbctx) { unsigned char cksum; int plen; plen = gdbctx->out_len - gdbctx->out_curr_packet; packet_reply_catc(gdbctx, '#'); cksum = checksum(&gdbctx->out_buf[gdbctx->out_curr_packet], plen); packet_reply_hex_to(gdbctx, &cksum, 1); if (gdbctx->trace & GDBPXY_TRC_PACKET) fprintf(stderr, "Reply : %*.*s\n", plen, plen, &gdbctx->out_buf[gdbctx->out_curr_packet]); gdbctx->out_curr_packet = -1; } static enum packet_return packet_reply(struct gdb_context* gdbctx, const char* packet, int len) { packet_reply_open(gdbctx); if (len == -1) len = strlen(packet); assert(memchr(packet, '$', len) == NULL && memchr(packet, '#', len) == NULL); packet_reply_add(gdbctx, packet, len); packet_reply_close(gdbctx); return packet_done; } static enum packet_return packet_reply_error(struct gdb_context* gdbctx, int error) { packet_reply_open(gdbctx); packet_reply_add(gdbctx, "E", 1); packet_reply_val(gdbctx, error, 1); packet_reply_close(gdbctx); return packet_done; } /* =============================================== * * P A C K E T H A N D L E R S * * =============================================== * */ static enum packet_return packet_reply_status(struct gdb_context* gdbctx) { enum packet_return ret = packet_done; packet_reply_open(gdbctx); if (gdbctx->process != NULL) { unsigned char sig; unsigned i; packet_reply_catc(gdbctx, 'T'); sig = gdbctx->last_sig; packet_reply_val(gdbctx, sig, 1); packet_reply_add(gdbctx, "thread:", 7); packet_reply_val(gdbctx, DEBUG_CurrThread->tid, 4); packet_reply_catc(gdbctx, ';'); for (i = 0; i < cpu_num_regs; i++) { /* FIXME: this call will also grow the buffer... * unneeded, but not harmful */ packet_reply_val(gdbctx, i, 1); packet_reply_catc(gdbctx, ':'); packet_reply_hex_to(gdbctx, cpu_register(gdbctx, i), 4); packet_reply_catc(gdbctx, ';'); } } else { /* Try to put an exit code * Cannot use GetExitCodeProcess, wouldn't fit in a 8 bit value, so * just indicate the end of process and exit */ packet_reply_add(gdbctx, "W00", 3); /*if (!gdbctx->extended)*/ ret |= packet_last_f; } packet_reply_close(gdbctx); return ret; } #if 0 static enum packet_return packet_extended(struct gdb_context* gdbctx) { gdbctx->extended = 1; return packet_ok; } #endif static enum packet_return packet_last_signal(struct gdb_context* gdbctx) { assert(gdbctx->in_packet_len == 0); return packet_reply_status(gdbctx); } static enum packet_return packet_continue(struct gdb_context* gdbctx) { /* FIXME: add support for address in packet */ assert(gdbctx->in_packet_len == 0); if (DEBUG_CurrThread->tid != gdbctx->exec_thread && gdbctx->exec_thread) if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR) fprintf(stderr, "NIY: cont on %u, while last thd is %lu\n", gdbctx->exec_thread, DEBUG_CurrThread->tid); resume_debuggee(gdbctx, DBG_CONTINUE); wait_for_debuggee(gdbctx); return packet_reply_status(gdbctx); } static enum packet_return packet_continue_signal(struct gdb_context* gdbctx) { unsigned char sig; /* FIXME: add support for address in packet */ assert(gdbctx->in_packet_len == 2); if (DEBUG_CurrThread->tid != gdbctx->exec_thread && gdbctx->exec_thread) if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR) fprintf(stderr, "NIY: cont/sig on %u, while last thd is %lu\n", gdbctx->exec_thread, DEBUG_CurrThread->tid); hex_from(&sig, gdbctx->in_packet, 1); /* cannot change signals on the fly */ if (gdbctx->trace & GDBPXY_TRC_COMMAND) fprintf(stderr, "sigs: %u %u\n", sig, gdbctx->last_sig); if (sig != gdbctx->last_sig) return packet_error; resume_debuggee(gdbctx, DBG_EXCEPTION_NOT_HANDLED); wait_for_debuggee(gdbctx); return packet_reply_status(gdbctx); } static enum packet_return packet_detach(struct gdb_context* gdbctx) { detach_debuggee(gdbctx, FALSE); return packet_ok | packet_last_f; } static enum packet_return packet_read_registers(struct gdb_context* gdbctx) { int i; assert(gdbctx->in_trap); if (DEBUG_CurrThread->tid != gdbctx->other_thread && gdbctx->other_thread) if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR) fprintf(stderr, "NIY: read regs on %u, while last thd is %lu\n", gdbctx->other_thread, DEBUG_CurrThread->tid); packet_reply_open(gdbctx); for (i = 0; i < cpu_num_regs; i++) { packet_reply_hex_to(gdbctx, cpu_register(gdbctx, i), 4); } packet_reply_close(gdbctx); return packet_done; } static enum packet_return packet_write_registers(struct gdb_context* gdbctx) { unsigned i; assert(gdbctx->in_trap); if (DEBUG_CurrThread->tid != gdbctx->other_thread && gdbctx->other_thread) if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR) fprintf(stderr, "NIY: write regs on %u, while last thd is %lu\n", gdbctx->other_thread, DEBUG_CurrThread->tid); if (gdbctx->in_packet_len < cpu_num_regs * 2) return packet_error; for (i = 0; i < cpu_num_regs; i++) hex_from(cpu_register(gdbctx, i), &gdbctx->in_packet[8 * i], 4); return packet_ok; } static enum packet_return packet_kill(struct gdb_context* gdbctx) { detach_debuggee(gdbctx, TRUE); #if 0 if (!gdbctx->extended) /* dunno whether GDB cares or not */ #endif wait(NULL); exit(0); /* assume we can't really answer something here */ /* return packet_done; */ } static enum packet_return packet_thread(struct gdb_context* gdbctx) { char* end; unsigned thread; switch (gdbctx->in_packet[0]) { case 'c': case 'g': if (gdbctx->in_packet[1] == '-') thread = -strtol(gdbctx->in_packet + 2, &end, 16); else thread = strtol(gdbctx->in_packet + 1, &end, 16); if (end == NULL || end > gdbctx->in_packet + gdbctx->in_packet_len) { if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR) fprintf(stderr, "Cannot get threadid %*.*s\n", gdbctx->in_packet_len - 1, gdbctx->in_packet_len - 1, gdbctx->in_packet + 1); return packet_error; } if (gdbctx->in_packet[0] == 'c') gdbctx->exec_thread = thread; else gdbctx->other_thread = thread; return packet_ok; default: if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR) fprintf(stderr, "Unknown thread sub-command %c\n", gdbctx->in_packet[0]); return packet_error; } } static enum packet_return packet_read_memory(struct gdb_context* gdbctx) { char *addr; size_t len, blk_len, nread; char buffer[32]; unsigned long r = 0; assert(gdbctx->in_trap); /* FIXME:check in_packet_len for reading %p,%x */ if (sscanf(gdbctx->in_packet, "%p,%x", &addr, &len) != 2) return packet_error; if (gdbctx->trace & GDBPXY_TRC_COMMAND) fprintf(stderr, "read mem at %p for %u bytes\n", addr, len); for (nread = 0; nread < len > 0; nread += r, addr += r) { blk_len = min(sizeof(buffer), len - nread); if (!ReadProcessMemory(gdbctx->process->handle, addr, buffer, blk_len, &r) || r == 0) { /* fail at first address, return error */ if (nread == 0) return packet_reply_error(gdbctx, EFAULT); /* something has already been read, return partial information */ break; } if (nread == 0) packet_reply_open(gdbctx); packet_reply_hex_to(gdbctx, buffer, r); } packet_reply_close(gdbctx); return packet_done; } static enum packet_return packet_write_memory(struct gdb_context* gdbctx) { char* addr; size_t len, blk_len; char* ptr; char buffer[32]; unsigned long w; assert(gdbctx->in_trap); ptr = memchr(gdbctx->in_packet, ':', gdbctx->in_packet_len); if (ptr == NULL) { if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR) fprintf(stderr, "cannot find ':' in %*.*s\n", gdbctx->in_packet_len, gdbctx->in_packet_len, gdbctx->in_packet); return packet_error; } *ptr++ = '\0'; if (sscanf(gdbctx->in_packet, "%p,%x", &addr, &len) != 2) { if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR) fprintf(stderr, "cannot scan addr,len in %s\n", gdbctx->in_packet); return packet_error; } if (ptr - gdbctx->in_packet + len * 2 != gdbctx->in_packet_len) { if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR) fprintf(stderr, "wrong sizes %u <> %u\n", ptr - gdbctx->in_packet + len * 2, gdbctx->in_packet_len); return packet_error; } if (gdbctx->trace & GDBPXY_TRC_COMMAND) fprintf(stderr, "write %u bytes at %p\n", len, addr); while (len > 0) { blk_len = min(sizeof(buffer), len); hex_from(buffer, ptr, blk_len); { BOOL ret; ret = WriteProcessMemory(gdbctx->process->handle, addr, buffer, blk_len, &w); if (!ret || w != blk_len) break; } addr += w; len -= w; ptr += w; } return packet_ok; /* FIXME: error while writing ? */ } static enum packet_return packet_write_register(struct gdb_context* gdbctx) { unsigned reg; char* ptr; char* end; assert(gdbctx->in_trap); if (DEBUG_CurrThread->tid != gdbctx->other_thread && gdbctx->other_thread) if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR) fprintf(stderr, "NIY: read reg on %u, while last thd is %lu\n", gdbctx->other_thread, DEBUG_CurrThread->tid); ptr = memchr(gdbctx->in_packet, '=', gdbctx->in_packet_len); *ptr++ = '\0'; reg = strtoul(gdbctx->in_packet, &end, 16); if (end == NULL || reg > cpu_num_regs) { if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR) fprintf(stderr, "invalid register index %s\n", gdbctx->in_packet); /* FIXME: if just the reg is above cpu_num_regs, don't tell gdb * it wouldn't matter too much, and it fakes our support for all regs */ return (end == NULL) ? packet_error : packet_ok; } if (ptr + 8 - gdbctx->in_packet != gdbctx->in_packet_len) { if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR) fprintf(stderr, "wrong sizes %u <> %u\n", ptr + 8 - gdbctx->in_packet, gdbctx->in_packet_len); return packet_error; } if (gdbctx->trace & GDBPXY_TRC_COMMAND) fprintf(stderr, "Writing reg %u <= %*.*s\n", reg, gdbctx->in_packet_len - (ptr - gdbctx->in_packet), gdbctx->in_packet_len - (ptr - gdbctx->in_packet), ptr); hex_from(cpu_register(gdbctx, reg), ptr, 4); return packet_ok; } static void packet_query_monitor_wnd_helper(struct gdb_context* gdbctx, HWND hWnd, int indent) { char buffer[128]; char clsName[128]; char wndName[128]; HWND child; do { if (!GetClassName(hWnd, clsName, sizeof(clsName))) strcpy(clsName, "-- Unknown --"); if (!GetWindowText(hWnd, wndName, sizeof(wndName))) strcpy(wndName, "-- Empty --"); packet_reply_open(gdbctx); packet_reply_catc(gdbctx, 'O'); snprintf(buffer, sizeof(buffer), "%*s%04x%*s%-17.17s %08lx %08lx %.14s\n", indent, "", (UINT)hWnd, 13 - indent, "", clsName, GetWindowLong(hWnd, GWL_STYLE), GetWindowLong(hWnd, GWL_WNDPROC), wndName); packet_reply_hex_to_str(gdbctx, buffer); packet_reply_close(gdbctx); if ((child = GetWindow(hWnd, GW_CHILD)) != 0) packet_query_monitor_wnd_helper(gdbctx, child, indent + 1); } while ((hWnd = GetWindow(hWnd, GW_HWNDNEXT)) != 0); } static void packet_query_monitor_wnd(struct gdb_context* gdbctx, int len, const char* str) { char buffer[128]; /* we do the output in several 'O' packets, with the last one being just OK for * marking the end of the output */ packet_reply_open(gdbctx); packet_reply_catc(gdbctx, 'O'); snprintf(buffer, sizeof(buffer), "%-16.16s %-17.17s %-8.8s %s\n", "hwnd", "Class Name", " Style", " WndProc Text"); packet_reply_hex_to_str(gdbctx, buffer); packet_reply_close(gdbctx); /* FIXME: could also add a pmt to this command in str... */ packet_query_monitor_wnd_helper(gdbctx, GetDesktopWindow(), 0); packet_reply(gdbctx, "OK", 2); } static void packet_query_monitor_process(struct gdb_context* gdbctx, int len, const char* str) { HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); char buffer[128]; char deco; PROCESSENTRY32 entry; BOOL ok; if (snap == INVALID_HANDLE_VALUE) return; entry.dwSize = sizeof(entry); ok = Process32First( snap, &entry ); /* we do the output in several 'O' packets, with the last one being just OK for * marking the end of the output */ packet_reply_open(gdbctx); packet_reply_catc(gdbctx, 'O'); snprintf(buffer, sizeof(buffer), " %-8.8s %-8.8s %-8.8s %s\n", "pid", "threads", "parent", "executable" ); packet_reply_hex_to_str(gdbctx, buffer); packet_reply_close(gdbctx); while (ok) { deco = ' '; if (entry.th32ProcessID == gdbctx->process->pid) deco = '>'; packet_reply_open(gdbctx); packet_reply_catc(gdbctx, 'O'); snprintf(buffer, sizeof(buffer), "%c%08lx %-8ld %08lx '%s'\n", deco, entry.th32ProcessID, entry.cntThreads, entry.th32ParentProcessID, entry.szExeFile); packet_reply_hex_to_str(gdbctx, buffer); packet_reply_close(gdbctx); ok = Process32Next(snap, &entry); } CloseHandle(snap); packet_reply(gdbctx, "OK", 2); } static void packet_query_monitor_mem(struct gdb_context* gdbctx, int len, const char* str) { MEMORY_BASIC_INFORMATION mbi; char* addr = 0; char* state; char* type; char prot[3+1]; char buffer[128]; /* we do the output in several 'O' packets, with the last one being just OK for * marking the end of the output */ packet_reply_open(gdbctx); packet_reply_catc(gdbctx, 'O'); packet_reply_hex_to_str(gdbctx, "Address Size State Type RWX\n"); packet_reply_close(gdbctx); while (VirtualQueryEx(gdbctx->process->handle, addr, &mbi, sizeof(mbi)) >= sizeof(mbi)) { switch (mbi.State) { case MEM_COMMIT: state = "commit "; break; case MEM_FREE: state = "free "; break; case MEM_RESERVE: state = "reserve"; break; default: state = "??? "; break; } if (mbi.State != MEM_FREE) { switch (mbi.Type) { case MEM_IMAGE: type = "image "; break; case MEM_MAPPED: type = "mapped "; break; case MEM_PRIVATE: type = "private"; break; case 0: type = " "; break; default: type = "??? "; break; } memset(prot, ' ' , sizeof(prot)-1); prot[sizeof(prot)-1] = '\0'; if (mbi.AllocationProtect & (PAGE_READONLY|PAGE_READWRITE|PAGE_EXECUTE_READ|PAGE_EXECUTE_READWRITE)) prot[0] = 'R'; if (mbi.AllocationProtect & (PAGE_READWRITE|PAGE_EXECUTE_READWRITE)) prot[1] = 'W'; if (mbi.AllocationProtect & (PAGE_WRITECOPY|PAGE_EXECUTE_WRITECOPY)) prot[1] = 'C'; if (mbi.AllocationProtect & (PAGE_EXECUTE|PAGE_EXECUTE_READ|PAGE_EXECUTE_READWRITE)) prot[2] = 'X'; } else { type = ""; prot[0] = '\0'; } packet_reply_open(gdbctx); snprintf(buffer, sizeof(buffer), "%08lx %08lx %s %s %s\n", (DWORD)addr, mbi.RegionSize, state, type, prot); packet_reply_catc(gdbctx, 'O'); packet_reply_hex_to_str(gdbctx, buffer); packet_reply_close(gdbctx); if (addr + mbi.RegionSize < addr) /* wrap around ? */ break; addr += mbi.RegionSize; } packet_reply(gdbctx, "OK", 2); } static void packet_query_monitor_trace(struct gdb_context* gdbctx, int len, const char* str) { char buffer[128]; if (len == 0) { snprintf(buffer, sizeof(buffer), "trace=%x\n", gdbctx->trace); } else if (len >= 2 && str[0] == '=') { unsigned val = atoi(&str[1]); snprintf(buffer, sizeof(buffer), "trace: %x => %x\n", gdbctx->trace, val); gdbctx->trace = val; } else { /* FIXME: ugly but can use error packet here */ packet_reply_cat(gdbctx, "E00"); return; } packet_reply_open(gdbctx); packet_reply_hex_to_str(gdbctx, buffer); packet_reply_close(gdbctx); } #ifdef __i386__ static void packet_query_monitor_linear(struct gdb_context* gdbctx, int len, const char* str) { unsigned seg, ofs; LDT_ENTRY le; unsigned linear; char buffer[32]; while (len > 0 && (*str == ' ' || *str == '\t')) { str++; len--; } /* FIXME: do a better scanning (allow both decimal and hex numbers) */ if (!len || sscanf(str, "%x:%x", &seg, &ofs) != 2) { packet_reply_error(gdbctx, 0); return; } /* V86 mode ? */ if (gdbctx->context.EFlags & 0x00020000) linear = (LOWORD(seg) << 4) + ofs; /* linux system selector ? */ else if (!(seg & 4) || ((seg >> 3) < 17)) linear = ofs; /* standard selector */ else if (GetThreadSelectorEntry(gdbctx->process->threads->handle, seg, &le)) linear = (le.HighWord.Bits.BaseHi << 24) + (le.HighWord.Bits.BaseMid << 16) + le.BaseLow + ofs; /* error */ else linear = 0; snprintf(buffer, sizeof(buffer), "0x%x", linear); packet_reply_open(gdbctx); packet_reply_hex_to_str(gdbctx, buffer); packet_reply_close(gdbctx); } #endif struct query_detail { int with_arg; const char* name; size_t len; void (*handler)(struct gdb_context*, int, const char*); } query_details[] = { {0, "wnd", 3, packet_query_monitor_wnd}, {0, "window", 6, packet_query_monitor_wnd}, {0, "proc", 4, packet_query_monitor_process}, {0, "process", 7, packet_query_monitor_process}, {0, "mem", 3, packet_query_monitor_mem}, {1, "trace", 5, packet_query_monitor_trace}, #ifdef __i386__ {1, "linear", 6, packet_query_monitor_linear}, #endif {0, NULL, 0, NULL}, }; static enum packet_return packet_query_remote_command(struct gdb_context* gdbctx, const char* hxcmd, size_t len) { char buffer[128]; struct query_detail* qd; assert((len & 1) == 0 && len < 2 * sizeof(buffer)); len /= 2; hex_from(buffer, hxcmd, len); for (qd = &query_details[0]; qd->name != NULL; qd++) { if (len < qd->len || strncmp(buffer, qd->name, qd->len) != 0) continue; if (!qd->with_arg && len != qd->len) continue; (qd->handler)(gdbctx, len - qd->len, buffer + qd->len); return packet_done; } return packet_reply_error(gdbctx, EINVAL); } static enum packet_return packet_query(struct gdb_context* gdbctx) { switch (gdbctx->in_packet[0]) { case 'f': if (strncmp(gdbctx->in_packet + 1, "ThreadInfo", gdbctx->in_packet_len - 1) == 0) { DBG_THREAD* thd; packet_reply_open(gdbctx); packet_reply_add(gdbctx, "m", 1); for (thd = gdbctx->process->threads; thd; thd = thd->next) { packet_reply_val(gdbctx, thd->tid, 4); if (thd->next != NULL) packet_reply_add(gdbctx, ",", 1); } packet_reply_close(gdbctx); return packet_done; } else if (strncmp(gdbctx->in_packet + 1, "ProcessInfo", gdbctx->in_packet_len - 1) == 0) { char result[128]; packet_reply_open(gdbctx); packet_reply_catc(gdbctx, 'O'); get_process_info(gdbctx, result, sizeof(result)); packet_reply_hex_to_str(gdbctx, result); packet_reply_close(gdbctx); return packet_done; } break; case 's': if (strncmp(gdbctx->in_packet + 1, "ThreadInfo", gdbctx->in_packet_len - 1) == 0) { packet_reply(gdbctx, "l", 1); return packet_done; } else if (strncmp(gdbctx->in_packet + 1, "ProcessInfo", gdbctx->in_packet_len - 1) == 0) { packet_reply(gdbctx, "l", 1); return packet_done; } break; case 'C': if (gdbctx->in_packet_len == 1) { DBG_THREAD* thd; /* FIXME: doc says 16 bit val ??? */ /* grab first created thread, aka last in list */ assert(gdbctx->process && gdbctx->process->threads); for (thd = gdbctx->process->threads; thd->next; thd = thd->next); packet_reply_open(gdbctx); packet_reply_add(gdbctx, "QC", 2); packet_reply_val(gdbctx, thd->tid, 4); packet_reply_close(gdbctx); return packet_done; } break; case 'O': if (strncmp(gdbctx->in_packet, "Offsets", gdbctx->in_packet_len) == 0) { char buf[64]; if (gdbctx->wine_segs[0] == 0 && gdbctx->wine_segs[1] == 0 && gdbctx->wine_segs[2] == 0) return packet_error; snprintf(buf, sizeof(buf), "Text=%08lx;Data=%08lx;Bss=%08lx", gdbctx->wine_segs[0], gdbctx->wine_segs[1], gdbctx->wine_segs[2]); return packet_reply(gdbctx, buf, -1); } break; case 'R': if (gdbctx->in_packet_len > 5 && strncmp(gdbctx->in_packet, "Rcmd,", 5) == 0) { return packet_query_remote_command(gdbctx, gdbctx->in_packet + 5, gdbctx->in_packet_len - 5); } break; case 'S': if (strncmp(gdbctx->in_packet, "Symbol::", gdbctx->in_packet_len) == 0) return packet_ok; break; case 'T': if (gdbctx->in_packet_len > 15 && strncmp(gdbctx->in_packet, "ThreadExtraInfo", 15) == 0 && gdbctx->in_packet[15] == ',') { unsigned tid; char* end; char result[128]; tid = strtol(gdbctx->in_packet + 16, &end, 16); if (end == NULL) break; get_thread_info(gdbctx, tid, result, sizeof(result)); packet_reply_open(gdbctx); packet_reply_hex_to_str(gdbctx, result); packet_reply_close(gdbctx); return packet_done; } break; } if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR) fprintf(stderr, "Unknown or malformed query %*.*s\n", gdbctx->in_packet_len, gdbctx->in_packet_len, gdbctx->in_packet); return packet_error; } static enum packet_return packet_step(struct gdb_context* gdbctx) { /* FIXME: add support for address in packet */ assert(gdbctx->in_packet_len == 0); if (DEBUG_CurrThread->tid != gdbctx->exec_thread && gdbctx->exec_thread) if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR) fprintf(stderr, "NIY: step on %u, while last thd is %lu\n", gdbctx->exec_thread, DEBUG_CurrThread->tid); if (!cpu_enter_stepping(gdbctx)) return packet_error; resume_debuggee(gdbctx, DBG_CONTINUE); wait_for_debuggee(gdbctx); if (!cpu_leave_stepping(gdbctx)) return packet_error; return packet_reply_status(gdbctx); } #if 0 static enum packet_return packet_step_signal(struct gdb_context* gdbctx) { unsigned char sig; /* FIXME: add support for address in packet */ assert(gdbctx->in_packet_len == 2); if (DEBUG_CurrThread->tid != gdbctx->exec_thread && gdbctx->exec_thread) if (gdbctx->trace & GDBPXY_TRC_COMMAND_ERROR) fprintf(stderr, "NIY: step/sig on %u, while last thd is %u\n", gdbctx->exec_thread, DEBUG_CurrThread->tid); hex_from(&sig, gdbctx->in_packet, 1); /* cannot change signals on the fly */ if (gdbctx->trace & GDBPXY_TRC_COMMAND) fprintf(stderr, "sigs: %u %u\n", sig, gdbctx->last_sig); if (sig != gdbctx->last_sig) return packet_error; resume_debuggee(gdbctx, DBG_EXCEPTION_NOT_HANDLED); wait_for_debuggee(gdbctx); return packet_reply_status(gdbctx); } #endif static enum packet_return packet_thread_alive(struct gdb_context* gdbctx) { char* end; unsigned tid; tid = strtol(gdbctx->in_packet, &end, 16); if (tid == -1 || tid == 0) return packet_reply_error(gdbctx, EINVAL); if (DEBUG_GetThread(gdbctx->process, tid) != NULL) return packet_ok; return packet_reply_error(gdbctx, ESRCH); } static enum packet_return packet_remove_breakpoint(struct gdb_context* gdbctx) { void* addr; unsigned len; struct gdb_ctx_Xpoint* xpt; /* FIXME: check packet_len */ if (gdbctx->in_packet[0] < '0' || gdbctx->in_packet[0] > '4' || gdbctx->in_packet[1] != ',' || sscanf(gdbctx->in_packet + 2, "%p,%x", &addr, &len) != 2) return packet_error; if (gdbctx->trace & GDBPXY_TRC_COMMAND) fprintf(stderr, "remove bp %p[%u] typ=%c\n", addr, len, gdbctx->in_packet[0]); for (xpt = &gdbctx->Xpoints[NUM_XPOINT - 1]; xpt >= gdbctx->Xpoints; xpt--) { if (xpt->addr == addr && xpt->type == gdbctx->in_packet[0]) { switch (cpu_remove_Xpoint(gdbctx, xpt, len)) { case 1: xpt->type = -1; return packet_ok; case 0: return packet_error; case -1: return packet_done; default: assert(0); } } } return packet_error; } static enum packet_return packet_set_breakpoint(struct gdb_context* gdbctx) { void* addr; unsigned len; struct gdb_ctx_Xpoint* xpt; /* FIXME: check packet_len */ if (gdbctx->in_packet[0] < '0' || gdbctx->in_packet[0] > '4' || gdbctx->in_packet[1] != ',' || sscanf(gdbctx->in_packet + 2, "%p,%x", &addr, &len) != 2) return packet_error; if (gdbctx->trace & GDBPXY_TRC_COMMAND) fprintf(stderr, "set bp %p[%u] typ=%c\n", addr, len, gdbctx->in_packet[0]); /* because of packet command handling, this should be made idempotent */ for (xpt = &gdbctx->Xpoints[NUM_XPOINT - 1]; xpt >= gdbctx->Xpoints; xpt--) { if (xpt->addr == addr && xpt->type == gdbctx->in_packet[0]) return packet_ok; /* nothing to do */ } /* really set the Xpoint */ for (xpt = &gdbctx->Xpoints[NUM_XPOINT - 1]; xpt >= gdbctx->Xpoints; xpt--) { if (xpt->type == -1) { xpt->addr = addr; xpt->type = gdbctx->in_packet[0]; switch (cpu_insert_Xpoint(gdbctx, xpt, len)) { case 1: return packet_ok; case 0: return packet_error; case -1: return packet_done; default: assert(0); } } } /* no more entries... eech */ fprintf(stderr, "Running out of spots for {break|watch}points\n"); return packet_error; } /* =============================================== * * P A C K E T I N F R A S T R U C T U R E * * =============================================== * */ struct packet_entry { char key; enum packet_return (*handler)(struct gdb_context* gdbctx); }; static struct packet_entry packet_entries[] = { /* {'!', packet_extended}, */ {'?', packet_last_signal}, {'c', packet_continue}, {'C', packet_continue_signal}, {'D', packet_detach}, {'g', packet_read_registers}, {'G', packet_write_registers}, {'k', packet_kill}, {'H', packet_thread}, {'m', packet_read_memory}, {'M', packet_write_memory}, /* {'p', packet_read_register}, doesn't seem needed */ {'P', packet_write_register}, {'q', packet_query}, {'s', packet_step}, /*{'S', packet_step_signal}, hard(er) to implement */ {'T', packet_thread_alive}, {'z', packet_remove_breakpoint}, {'Z', packet_set_breakpoint}, }; static BOOL extract_packets(struct gdb_context* gdbctx) { char* end; int plen; unsigned char in_cksum, loc_cksum; char* ptr; enum packet_return ret = packet_error; int num_packet = 0; while ((ret & packet_last_f) == 0) { if (gdbctx->in_len && (gdbctx->trace & GDBPXY_TRC_LOWLEVEL)) fprintf(stderr, "in-buf: %*.*s\n", gdbctx->in_len, gdbctx->in_len, gdbctx->in_buf); ptr = memchr(gdbctx->in_buf, '$', gdbctx->in_len); if (ptr == NULL) return FALSE; if (ptr != gdbctx->in_buf) { int glen = ptr - gdbctx->in_buf; /* garbage len */ if (gdbctx->trace & GDBPXY_TRC_LOWLEVEL) fprintf(stderr, "removing garbage: %*.*s\n", glen, glen, gdbctx->in_buf); gdbctx->in_len -= glen; memmove(gdbctx->in_buf, ptr, gdbctx->in_len); } end = memchr(gdbctx->in_buf + 1, '#', gdbctx->in_len); if (end == NULL) return FALSE; /* no checksum yet */ if (end + 3 > gdbctx->in_buf + gdbctx->in_len) return FALSE; plen = end - gdbctx->in_buf - 1; hex_from(&in_cksum, end + 1, 1); loc_cksum = checksum(gdbctx->in_buf + 1, plen); if (loc_cksum == in_cksum) { if (num_packet == 0) { int i; ret = packet_error; write(gdbctx->sock, "+", 1); assert(plen); /* FIXME: should use bsearch if packet_entries was sorted */ for (i = 0; i < sizeof(packet_entries)/sizeof(packet_entries[0]); i++) { if (packet_entries[i].key == gdbctx->in_buf[1]) break; } if (i == sizeof(packet_entries)/sizeof(packet_entries[0])) { if (gdbctx->trace & GDBPXY_TRC_PACKET) fprintf(stderr, "Unknown packet request %*.*s\n", plen, plen, &gdbctx->in_buf[1]); } else { gdbctx->in_packet = gdbctx->in_buf + 2; gdbctx->in_packet_len = plen - 1; if (gdbctx->trace & GDBPXY_TRC_PACKET) fprintf(stderr, "Packet: %c%*.*s\n", gdbctx->in_buf[1], gdbctx->in_packet_len, gdbctx->in_packet_len, gdbctx->in_packet); ret = (packet_entries[i].handler)(gdbctx); } switch (ret & ~packet_last_f) { case packet_error: packet_reply(gdbctx, "", 0); break; case packet_ok: packet_reply(gdbctx, "OK", 2); break; case packet_done: break; } if (gdbctx->trace & GDBPXY_TRC_LOWLEVEL) fprintf(stderr, "reply-full: %*.*s\n", gdbctx->out_len, gdbctx->out_len, gdbctx->out_buf); i = write(gdbctx->sock, gdbctx->out_buf, gdbctx->out_len); assert(i == gdbctx->out_len); /* if this fails, we'll have to use POLLOUT... */ gdbctx->out_len = 0; num_packet++; } else { /* FIXME: if we have in our input buffer more than one packet, * it's very likely that we took too long to answer to a given packet * and gdb is sending us again the same packet * We simply drop the second packet. This will lower the risk of error, * but there's still some race conditions here * A better fix (yet not perfect) would be to have two threads: * - one managing the packets for gdb * - the second one managing the commands... * This would allow us also the reply with the '+' character (Ack of * the command) way sooner than what we do know */ if (gdbctx->trace & GDBPXY_TRC_LOWLEVEL) fprintf(stderr, "dropping packet, I was too slow to respond\n"); } } else { write(gdbctx->sock, "+", 1); if (gdbctx->trace & GDBPXY_TRC_LOWLEVEL) fprintf(stderr, "dropping packet, invalid checksum %d <> %d\n", in_cksum, loc_cksum); } gdbctx->in_len -= plen + 4; memmove(gdbctx->in_buf, end + 3, gdbctx->in_len); } return TRUE; } static int fetch_data(struct gdb_context* gdbctx) { int len, in_len = gdbctx->in_len; assert(gdbctx->in_len <= gdbctx->in_buf_alloc); for (;;) { #define STEP 128 if (gdbctx->in_len + STEP > gdbctx->in_buf_alloc) gdbctx->in_buf = realloc(gdbctx->in_buf, gdbctx->in_buf_alloc += STEP); #undef STEP if (gdbctx->trace & GDBPXY_TRC_LOWLEVEL) fprintf(stderr, "%d %d %*.*s\n", gdbctx->in_len, gdbctx->in_buf_alloc, gdbctx->in_len, gdbctx->in_len, gdbctx->in_buf); len = read(gdbctx->sock, gdbctx->in_buf + gdbctx->in_len, gdbctx->in_buf_alloc - gdbctx->in_len); if (len <= 0) break; gdbctx->in_len += len; assert(gdbctx->in_len <= gdbctx->in_buf_alloc); if (len < gdbctx->in_buf_alloc - gdbctx->in_len) break; } if (gdbctx->trace & GDBPXY_TRC_LOWLEVEL) fprintf(stderr, "=> %d\n", gdbctx->in_len - in_len); return gdbctx->in_len - in_len; } static BOOL gdb_startup(struct gdb_context* gdbctx, DEBUG_EVENT* de, unsigned flags) { int sock; struct sockaddr_in s_addrs; socklen_t s_len = sizeof(s_addrs); struct pollfd pollfd; char wine_path[MAX_PATH]; char* ptr; /* step 1: create socket for gdb connection request */ if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) { if (gdbctx->trace & GDBPXY_TRC_LOWLEVEL) fprintf(stderr, "Can't create socket"); return FALSE; } if (listen(sock, 1) == -1 || getsockname(sock, (struct sockaddr*)&s_addrs, &s_len) == -1) return FALSE; /* step 2: find out wine executable location (as a Unix filename) */ ptr = getenv("WINELOADER"); strcpy(wine_path, ptr ? ptr : "wine"); fprintf(stderr, "using wine_path: %s\n", wine_path); read_elf_info(wine_path, gdbctx->wine_segs); /* step 3: fire up gdb (if requested) */ if (flags & 1) fprintf(stderr, "target remote localhost:%d\n", ntohs(s_addrs.sin_port)); else switch (fork()) { case -1: /* error in parent... */ fprintf(stderr, "Cannot create gdb\n"); return FALSE; break; default: /* in parent... success */ break; case 0: /* in child... and alive */ { char buf[MAX_PATH]; int fd; char* gdb_path; FILE* f; if (!(gdb_path = getenv("WINE_GDB"))) gdb_path = "gdb"; strcpy(buf,"/tmp/winegdb.XXXXXX"); fd = mkstemp(buf); if (fd == -1) return FALSE; if ((f = fdopen(fd, "w+")) == NULL) return FALSE; fprintf(f, "file %s\n", wine_path); fprintf(f, "target remote localhost:%d\n", ntohs(s_addrs.sin_port)); fprintf(f, "monitor trace=0\n"); fprintf(f, "set prompt Wine-gdb>\\ \n"); /* gdb 5.1 seems to require it, won't hurt anyway */ fprintf(f, "sharedlibrary\n"); /* tell gdb to delete this file when done handling it... */ fprintf(f, "shell rm -f \"%s\"\n", buf); fclose(f); if (flags & 2) execlp("xterm", "xterm", "-e", gdb_path, "-x", buf, NULL); else execlp(gdb_path, gdb_path, "-x", buf, NULL); assert(0); /* never reached */ break; } break; } /* step 4: do the process internal creation */ handle_debug_event(gdbctx, de); /* step 5: wait for gdb to connect actually */ pollfd.fd = sock; pollfd.events = POLLIN; pollfd.revents = 0; switch (poll(&pollfd, 1, -1)) { case 1: if (pollfd.revents & POLLIN) { int dummy = 1; gdbctx->sock = accept(sock, (struct sockaddr*)&s_addrs, &s_len); if (gdbctx->sock == -1) break; if (gdbctx->trace & GDBPXY_TRC_LOWLEVEL) fprintf(stderr, "Connected on %d\n", gdbctx->sock); /* don't keep our small packets too long: send them ASAP back to GDB * without this, GDB really crawls */ setsockopt(gdbctx->sock, IPPROTO_TCP, TCP_NODELAY, (char*)&dummy, sizeof(dummy)); } break; case 0: if (gdbctx->trace & GDBPXY_TRC_LOWLEVEL) fprintf(stderr, "poll for cnx failed (timeout)\n"); return FALSE; case -1: if (gdbctx->trace & GDBPXY_TRC_LOWLEVEL) fprintf(stderr, "poll for cnx failed (error)\n"); return FALSE; default: assert(0); } close(sock); return TRUE; } static BOOL gdb_init_context(struct gdb_context* gdbctx, unsigned flags) { DEBUG_EVENT de; int i; gdbctx->sock = -1; gdbctx->in_buf = NULL; gdbctx->in_buf_alloc = 0; gdbctx->in_len = 0; gdbctx->out_buf = NULL; gdbctx->out_buf_alloc = 0; gdbctx->out_len = 0; gdbctx->out_curr_packet = -1; gdbctx->exec_thread = gdbctx->other_thread = 0; gdbctx->last_sig = 0; gdbctx->in_trap = FALSE; gdbctx->trace = /*GDBPXY_TRC_PACKET | GDBPXY_TRC_COMMAND |*/ GDBPXY_TRC_COMMAND_ERROR | GDBPXY_TRC_WIN32_EVENT; gdbctx->process = NULL; for (i = 0; i < NUM_XPOINT; i++) gdbctx->Xpoints[i].type = -1; /* wait for first trap */ while (WaitForDebugEvent(&de, INFINITE)) { if (de.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) { /* this should be the first event we get, * and the only one of this type */ assert(gdbctx->process == NULL && de.dwProcessId == DEBUG_CurrPid); /*gdbctx->dwProcessId = pid; */ if (!gdb_startup(gdbctx, &de, flags)) return FALSE; assert(!gdbctx->in_trap); } else { handle_debug_event(gdbctx, &de); if (gdbctx->in_trap) break; } ContinueDebugEvent(de.dwProcessId, de.dwThreadId, DBG_CONTINUE); } return TRUE; } BOOL DEBUG_GdbRemote(unsigned flags) { struct pollfd pollfd; struct gdb_context gdbctx; BOOL doLoop; for (doLoop = gdb_init_context(&gdbctx, flags); doLoop;) { pollfd.fd = gdbctx.sock; pollfd.events = POLLIN; pollfd.revents = 0; switch (poll(&pollfd, 1, -1)) { case 1: /* got something */ if (pollfd.revents & (POLLHUP | POLLERR)) { if (gdbctx.trace & GDBPXY_TRC_LOWLEVEL) fprintf(stderr, "Gdb hung up\n"); /* kill also debuggee process - questionnable - */ detach_debuggee(&gdbctx, TRUE); doLoop = FALSE; break; } if ((pollfd.revents & POLLIN) && fetch_data(&gdbctx) > 0) { if (extract_packets(&gdbctx)) doLoop = FALSE; } break; case 0: /* timeout, should never happen (infinite timeout) */ break; case -1: if (gdbctx.trace & GDBPXY_TRC_LOWLEVEL) fprintf(stderr, "poll failed\n"); doLoop = FALSE; break; } } wait(NULL); return 0; }