/* Low level interface to Windows debugging, for gdbserver. Copyright (C) 2006 Free Software Foundation, Inc. Contributed by Leo Zayas. Based on "win32-nat.c" from GDB. This file is part of GDB. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "server.h" #include "regcache.h" #include "gdb/signals.h" #include #include #include #include #include #include #ifndef USE_WIN32API #include #endif #define LOG 0 #define OUTMSG(X) do { printf X; fflush (stdout); } while (0) #if LOG #define OUTMSG2(X) do { printf X; fflush (stdout); } while (0) #else #define OUTMSG2(X) #endif int debug_threads; int using_threads = 1; /* Globals. */ static HANDLE current_process_handle = NULL; static DWORD current_process_id = 0; static enum target_signal last_sig = TARGET_SIGNAL_0; /* The current debug event from WaitForDebugEvent. */ static DEBUG_EVENT current_event; static int debug_registers_changed = 0; static int debug_registers_used = 0; static unsigned dr[8]; typedef BOOL winapi_DebugActiveProcessStop (DWORD dwProcessId); typedef BOOL winapi_DebugSetProcessKillOnExit (BOOL KillOnExit); #define FLAG_TRACE_BIT 0x100 #define CONTEXT_DEBUGGER (CONTEXT_FULL | CONTEXT_FLOATING_POINT) #define CONTEXT_DEBUGGER_DR CONTEXT_DEBUGGER | CONTEXT_DEBUG_REGISTERS \ | CONTEXT_EXTENDED_REGISTERS /* Thread information structure used to track extra information about each thread. */ typedef struct thread_info_struct { DWORD tid; HANDLE h; int suspend_count; CONTEXT context; } thread_info; static DWORD main_thread_id = 0; /* Get the thread ID from the current selected inferior (the current thread). */ static DWORD current_inferior_tid (void) { thread_info *th = inferior_target_data (current_inferior); return th->tid; } /* Find a thread record given a thread id. If GET_CONTEXT is set then also retrieve the context for this thread. */ static thread_info * thread_rec (DWORD id, int get_context) { struct thread_info *thread; thread_info *th; thread = (struct thread_info *) find_inferior_id (&all_threads, id); if (thread == NULL) return NULL; th = inferior_target_data (thread); if (!th->suspend_count && get_context) { if (get_context > 0 && id != current_event.dwThreadId) th->suspend_count = SuspendThread (th->h) + 1; else if (get_context < 0) th->suspend_count = -1; th->context.ContextFlags = CONTEXT_DEBUGGER_DR; GetThreadContext (th->h, &th->context); if (id == current_event.dwThreadId) { /* Copy dr values from that thread. */ dr[0] = th->context.Dr0; dr[1] = th->context.Dr1; dr[2] = th->context.Dr2; dr[3] = th->context.Dr3; dr[6] = th->context.Dr6; dr[7] = th->context.Dr7; } } return th; } /* Add a thread to the thread list. */ static thread_info * child_add_thread (DWORD tid, HANDLE h) { thread_info *th; if ((th = thread_rec (tid, FALSE))) return th; th = (thread_info *) malloc (sizeof (*th)); memset (th, 0, sizeof (*th)); th->tid = tid; th->h = h; add_thread (tid, th, (unsigned int) tid); set_inferior_regcache_data ((struct thread_info *) find_inferior_id (&all_threads, tid), new_register_cache ()); /* Set the debug registers for the new thread if they are used. */ if (debug_registers_used) { /* Only change the value of the debug registers. */ th->context.ContextFlags = CONTEXT_DEBUGGER_DR; GetThreadContext (th->h, &th->context); th->context.Dr0 = dr[0]; th->context.Dr1 = dr[1]; th->context.Dr2 = dr[2]; th->context.Dr3 = dr[3]; /* th->context.Dr6 = dr[6]; FIXME: should we set dr6 also ?? */ th->context.Dr7 = dr[7]; SetThreadContext (th->h, &th->context); th->context.ContextFlags = 0; } return th; } /* Delete a thread from the list of threads. */ static void delete_thread_info (struct inferior_list_entry *thread) { thread_info *th = inferior_target_data ((struct thread_info *) thread); remove_thread ((struct thread_info *) thread); CloseHandle (th->h); free (th); } /* Delete a thread from the list of threads. */ static void child_delete_thread (DWORD id) { struct inferior_list_entry *thread; /* If the last thread is exiting, just return. */ if (all_threads.head == all_threads.tail) return; thread = find_inferior_id (&all_threads, id); if (thread == NULL) return; delete_thread_info (thread); } /* Transfer memory from/to the debugged process. */ static int child_xfer_memory (CORE_ADDR memaddr, char *our, int len, int write, struct target_ops *target) { SIZE_T done; long addr = (long) memaddr; if (write) { WriteProcessMemory (current_process_handle, (LPVOID) addr, (LPCVOID) our, len, &done); FlushInstructionCache (current_process_handle, (LPCVOID) addr, len); } else { ReadProcessMemory (current_process_handle, (LPCVOID) addr, (LPVOID) our, len, &done); } return done; } /* Generally, what has the program done? */ enum target_waitkind { /* The program has exited. The exit status is in value.integer. */ TARGET_WAITKIND_EXITED, /* The program has stopped with a signal. Which signal is in value.sig. */ TARGET_WAITKIND_STOPPED, /* The program is letting us know that it dynamically loaded something (e.g. it called load(2) on AIX). */ TARGET_WAITKIND_LOADED, /* The program has exec'ed a new executable file. The new file's pathname is pointed to by value.execd_pathname. */ TARGET_WAITKIND_EXECD, /* Nothing happened, but we stopped anyway. This perhaps should be handled within target_wait, but I'm not sure target_wait should be resuming the inferior. */ TARGET_WAITKIND_SPURIOUS, }; struct target_waitstatus { enum target_waitkind kind; /* Forked child pid, execd pathname, exit status or signal number. */ union { int integer; enum target_signal sig; int related_pid; char *execd_pathname; int syscall_id; } value; }; #define NUM_REGS 41 #define FCS_REGNUM 27 #define FOP_REGNUM 31 #define context_offset(x) ((int)&(((CONTEXT *)NULL)->x)) static const int mappings[] = { context_offset (Eax), context_offset (Ecx), context_offset (Edx), context_offset (Ebx), context_offset (Esp), context_offset (Ebp), context_offset (Esi), context_offset (Edi), context_offset (Eip), context_offset (EFlags), context_offset (SegCs), context_offset (SegSs), context_offset (SegDs), context_offset (SegEs), context_offset (SegFs), context_offset (SegGs), context_offset (FloatSave.RegisterArea[0 * 10]), context_offset (FloatSave.RegisterArea[1 * 10]), context_offset (FloatSave.RegisterArea[2 * 10]), context_offset (FloatSave.RegisterArea[3 * 10]), context_offset (FloatSave.RegisterArea[4 * 10]), context_offset (FloatSave.RegisterArea[5 * 10]), context_offset (FloatSave.RegisterArea[6 * 10]), context_offset (FloatSave.RegisterArea[7 * 10]), context_offset (FloatSave.ControlWord), context_offset (FloatSave.StatusWord), context_offset (FloatSave.TagWord), context_offset (FloatSave.ErrorSelector), context_offset (FloatSave.ErrorOffset), context_offset (FloatSave.DataSelector), context_offset (FloatSave.DataOffset), context_offset (FloatSave.ErrorSelector), /* XMM0-7 */ context_offset (ExtendedRegisters[10 * 16]), context_offset (ExtendedRegisters[11 * 16]), context_offset (ExtendedRegisters[12 * 16]), context_offset (ExtendedRegisters[13 * 16]), context_offset (ExtendedRegisters[14 * 16]), context_offset (ExtendedRegisters[15 * 16]), context_offset (ExtendedRegisters[16 * 16]), context_offset (ExtendedRegisters[17 * 16]), /* MXCSR */ context_offset (ExtendedRegisters[24]) }; #undef context_offset /* Clear out any old thread list and reintialize it to a pristine state. */ static void child_init_thread_list (void) { for_each_inferior (&all_threads, delete_thread_info); } static void do_initial_child_stuff (DWORD pid) { int i; last_sig = TARGET_SIGNAL_0; debug_registers_changed = 0; debug_registers_used = 0; for (i = 0; i < sizeof (dr) / sizeof (dr[0]); i++) dr[i] = 0; memset (¤t_event, 0, sizeof (current_event)); child_init_thread_list (); } /* Resume all artificially suspended threads if we are continuing execution. */ static int continue_one_thread (struct inferior_list_entry *this_thread, void *id_ptr) { struct thread_info *thread = (struct thread_info *) this_thread; int thread_id = * (int *) id_ptr; thread_info *th = inferior_target_data (thread); int i; if ((thread_id == -1 || thread_id == th->tid) && th->suspend_count) { for (i = 0; i < th->suspend_count; i++) (void) ResumeThread (th->h); th->suspend_count = 0; if (debug_registers_changed) { /* Only change the value of the debug registers. */ th->context.ContextFlags = CONTEXT_DEBUG_REGISTERS; th->context.Dr0 = dr[0]; th->context.Dr1 = dr[1]; th->context.Dr2 = dr[2]; th->context.Dr3 = dr[3]; /* th->context.Dr6 = dr[6]; FIXME: should we set dr6 also ?? */ th->context.Dr7 = dr[7]; SetThreadContext (th->h, &th->context); th->context.ContextFlags = 0; } } return 0; } static BOOL child_continue (DWORD continue_status, int thread_id) { BOOL res; res = ContinueDebugEvent (current_event.dwProcessId, current_event.dwThreadId, continue_status); continue_status = 0; if (res) find_inferior (&all_threads, continue_one_thread, &thread_id); debug_registers_changed = 0; return res; } /* Fetch register(s) from gdbserver regcache data. */ static void do_child_fetch_inferior_registers (thread_info *th, int r) { char *context_offset = ((char *) &th->context) + mappings[r]; long l; if (r == FCS_REGNUM) { l = *((long *) context_offset) & 0xffff; supply_register (r, (char *) &l); } else if (r == FOP_REGNUM) { l = (*((long *) context_offset) >> 16) & ((1 << 11) - 1); supply_register (r, (char *) &l); } else supply_register (r, context_offset); } /* Fetch register(s) from the current thread context. */ static void child_fetch_inferior_registers (int r) { int regno; thread_info *th = thread_rec (current_inferior_tid (), TRUE); if (r == -1 || r == 0 || r > NUM_REGS) child_fetch_inferior_registers (NUM_REGS); else for (regno = 0; regno < r; regno++) do_child_fetch_inferior_registers (th, regno); } /* Get register from gdbserver regcache data. */ static void do_child_store_inferior_registers (thread_info *th, int r) { collect_register (r, ((char *) &th->context) + mappings[r]); } /* Store a new register value into the current thread context. We don't change the program's context until later, when we resume it. */ static void child_store_inferior_registers (int r) { int regno; thread_info *th = thread_rec (current_inferior_tid (), TRUE); if (r == -1 || r == 0 || r > NUM_REGS) child_store_inferior_registers (NUM_REGS); else for (regno = 0; regno < r; regno++) do_child_store_inferior_registers (th, regno); } /* Start a new process. PROGRAM is a path to the program to execute. ARGS is a standard NULL-terminated array of arguments, to be passed to the inferior as ``argv''. Returns the new PID on success, -1 on failure. Registers the new process with the process list. */ static int win32_create_inferior (char *program, char **program_args) { #ifndef USE_WIN32API char real_path[MAXPATHLEN]; char *orig_path, *new_path, *path_ptr; #endif char *winenv = NULL; STARTUPINFO si; PROCESS_INFORMATION pi; BOOL ret; DWORD flags; char *args; int argslen; int argc; if (!program) error ("No executable specified, specify executable to debug.\n"); memset (&si, 0, sizeof (si)); si.cb = sizeof (si); flags = DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS; #ifndef USE_WIN32API orig_path = NULL; path_ptr = getenv ("PATH"); if (path_ptr) { orig_path = alloca (strlen (path_ptr) + 1); new_path = alloca (cygwin_posix_to_win32_path_list_buf_size (path_ptr)); strcpy (orig_path, path_ptr); cygwin_posix_to_win32_path_list (path_ptr, new_path); setenv ("PATH", new_path, 1); } cygwin_conv_to_win32_path (program, real_path); program = real_path; #endif argslen = strlen (program) + 1; for (argc = 1; program_args[argc]; argc++) argslen += strlen (program_args[argc]) + 1; args = alloca (argslen); strcpy (args, program); for (argc = 1; program_args[argc]; argc++) { /* FIXME: Can we do better about quoting? How does Cygwin handle this? */ strcat (args, " "); strcat (args, program_args[argc]); } OUTMSG2 (("Command line is %s\n", args)); flags |= CREATE_NEW_PROCESS_GROUP; ret = CreateProcess (0, args, /* command line */ NULL, /* Security */ NULL, /* thread */ TRUE, /* inherit handles */ flags, /* start flags */ winenv, NULL, /* current directory */ &si, &pi); #ifndef USE_WIN32API if (orig_path) setenv ("PATH", orig_path, 1); #endif if (!ret) { error ("Error creating process %s, (error %d): %s\n", args, (int) GetLastError (), strerror (GetLastError ())); } else { OUTMSG2 (("Process created: %s\n", (char *) args)); } CloseHandle (pi.hThread); current_process_handle = pi.hProcess; current_process_id = pi.dwProcessId; do_initial_child_stuff (current_process_id); return current_process_id; } /* Attach to a running process. PID is the process ID to attach to, specified by the user or a higher layer. */ static int win32_attach (unsigned long pid) { int res = 0; HMODULE kernel32 = LoadLibrary ("KERNEL32.DLL"); winapi_DebugActiveProcessStop *DebugActiveProcessStop = NULL; winapi_DebugSetProcessKillOnExit *DebugSetProcessKillOnExit = NULL; DebugActiveProcessStop = (winapi_DebugActiveProcessStop *) GetProcAddress (kernel32, "DebugActiveProcessStop"); DebugSetProcessKillOnExit = (winapi_DebugSetProcessKillOnExit *) GetProcAddress (kernel32, "DebugSetProcessKillOnExit"); res = DebugActiveProcess (pid) ? 1 : 0; if (!res) error ("Attach to process failed."); if (DebugSetProcessKillOnExit != NULL) DebugSetProcessKillOnExit (FALSE); current_process_id = pid; current_process_handle = OpenProcess (PROCESS_ALL_ACCESS, FALSE, pid); if (current_process_handle == NULL) { res = 0; if (DebugActiveProcessStop != NULL) DebugActiveProcessStop (current_process_id); } if (res) do_initial_child_stuff (pid); FreeLibrary (kernel32); return res; } /* Kill all inferiors. */ static void win32_kill (void) { if (current_process_handle == NULL) return; TerminateProcess (current_process_handle, 0); for (;;) { if (!child_continue (DBG_CONTINUE, -1)) break; if (!WaitForDebugEvent (¤t_event, INFINITE)) break; if (current_event.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) break; } } /* Detach from all inferiors. */ static void win32_detach (void) { HMODULE kernel32 = LoadLibrary ("KERNEL32.DLL"); winapi_DebugActiveProcessStop *DebugActiveProcessStop = NULL; winapi_DebugSetProcessKillOnExit *DebugSetProcessKillOnExit = NULL; DebugActiveProcessStop = (winapi_DebugActiveProcessStop *) GetProcAddress (kernel32, "DebugActiveProcessStop"); DebugSetProcessKillOnExit = (winapi_DebugSetProcessKillOnExit *) GetProcAddress (kernel32, "DebugSetProcessKillOnExit"); if (DebugSetProcessKillOnExit != NULL) DebugSetProcessKillOnExit (FALSE); if (DebugActiveProcessStop != NULL) DebugActiveProcessStop (current_process_id); else win32_kill (); FreeLibrary (kernel32); } /* Return 1 iff the thread with thread ID TID is alive. */ static int win32_thread_alive (unsigned long tid) { int res; /* Our thread list is reliable; don't bother to poll target threads. */ if (find_inferior_id (&all_threads, tid) != NULL) res = 1; else res = 0; return res; } /* Resume the inferior process. RESUME_INFO describes how we want to resume. */ static void win32_resume (struct thread_resume *resume_info) { DWORD tid; enum target_signal sig; int step; thread_info *th; DWORD continue_status = DBG_CONTINUE; /* This handles the very limited set of resume packets that GDB can currently produce. */ if (resume_info[0].thread == -1) tid = -1; else if (resume_info[1].thread == -1 && !resume_info[1].leave_stopped) tid = -1; else /* Yes, we're ignoring resume_info[0].thread. It'd be tricky to make the Windows resume code do the right thing for thread switching. */ tid = current_event.dwThreadId; if (resume_info[0].thread != -1) { sig = resume_info[0].sig; step = resume_info[0].step; } else { sig = 0; step = 0; } if (sig != TARGET_SIGNAL_0) { if (current_event.dwDebugEventCode != EXCEPTION_DEBUG_EVENT) { OUTMSG (("Cannot continue with signal %d here.\n", sig)); } else if (sig == last_sig) continue_status = DBG_EXCEPTION_NOT_HANDLED; else OUTMSG (("Can only continue with recieved signal %d.\n", last_sig)); } last_sig = TARGET_SIGNAL_0; /* Get context for the currently selected thread. */ th = thread_rec (current_event.dwThreadId, FALSE); if (th) { if (th->context.ContextFlags) { if (debug_registers_changed) { th->context.Dr0 = dr[0]; th->context.Dr1 = dr[1]; th->context.Dr2 = dr[2]; th->context.Dr3 = dr[3]; /* th->context.Dr6 = dr[6]; FIXME: should we set dr6 also ?? */ th->context.Dr7 = dr[7]; } /* Move register values from the inferior into the thread context structure. */ regcache_invalidate (); if (step) th->context.EFlags |= FLAG_TRACE_BIT; SetThreadContext (th->h, &th->context); th->context.ContextFlags = 0; } } /* Allow continuing with the same signal that interrupted us. Otherwise complain. */ child_continue (continue_status, tid); } static int handle_exception (struct target_waitstatus *ourstatus) { thread_info *th; DWORD code = current_event.u.Exception.ExceptionRecord.ExceptionCode; ourstatus->kind = TARGET_WAITKIND_STOPPED; /* Record the context of the current thread. */ th = thread_rec (current_event.dwThreadId, -1); switch (code) { case EXCEPTION_ACCESS_VIOLATION: OUTMSG2 (("EXCEPTION_ACCESS_VIOLATION")); ourstatus->value.sig = TARGET_SIGNAL_SEGV; break; case STATUS_STACK_OVERFLOW: OUTMSG2 (("STATUS_STACK_OVERFLOW")); ourstatus->value.sig = TARGET_SIGNAL_SEGV; break; case STATUS_FLOAT_DENORMAL_OPERAND: OUTMSG2 (("STATUS_FLOAT_DENORMAL_OPERAND")); ourstatus->value.sig = TARGET_SIGNAL_FPE; break; case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: OUTMSG2 (("EXCEPTION_ARRAY_BOUNDS_EXCEEDED")); ourstatus->value.sig = TARGET_SIGNAL_FPE; break; case STATUS_FLOAT_INEXACT_RESULT: OUTMSG2 (("STATUS_FLOAT_INEXACT_RESULT")); ourstatus->value.sig = TARGET_SIGNAL_FPE; break; case STATUS_FLOAT_INVALID_OPERATION: OUTMSG2 (("STATUS_FLOAT_INVALID_OPERATION")); ourstatus->value.sig = TARGET_SIGNAL_FPE; break; case STATUS_FLOAT_OVERFLOW: OUTMSG2 (("STATUS_FLOAT_OVERFLOW")); ourstatus->value.sig = TARGET_SIGNAL_FPE; break; case STATUS_FLOAT_STACK_CHECK: OUTMSG2 (("STATUS_FLOAT_STACK_CHECK")); ourstatus->value.sig = TARGET_SIGNAL_FPE; break; case STATUS_FLOAT_UNDERFLOW: OUTMSG2 (("STATUS_FLOAT_UNDERFLOW")); ourstatus->value.sig = TARGET_SIGNAL_FPE; break; case STATUS_FLOAT_DIVIDE_BY_ZERO: OUTMSG2 (("STATUS_FLOAT_DIVIDE_BY_ZERO")); ourstatus->value.sig = TARGET_SIGNAL_FPE; break; case STATUS_INTEGER_DIVIDE_BY_ZERO: OUTMSG2 (("STATUS_INTEGER_DIVIDE_BY_ZERO")); ourstatus->value.sig = TARGET_SIGNAL_FPE; break; case STATUS_INTEGER_OVERFLOW: OUTMSG2 (("STATUS_INTEGER_OVERFLOW")); ourstatus->value.sig = TARGET_SIGNAL_FPE; break; case EXCEPTION_BREAKPOINT: OUTMSG2 (("EXCEPTION_BREAKPOINT")); ourstatus->value.sig = TARGET_SIGNAL_TRAP; break; case DBG_CONTROL_C: OUTMSG2 (("DBG_CONTROL_C")); ourstatus->value.sig = TARGET_SIGNAL_INT; break; case DBG_CONTROL_BREAK: OUTMSG2 (("DBG_CONTROL_BREAK")); ourstatus->value.sig = TARGET_SIGNAL_INT; break; case EXCEPTION_SINGLE_STEP: OUTMSG2 (("EXCEPTION_SINGLE_STEP")); ourstatus->value.sig = TARGET_SIGNAL_TRAP; break; case EXCEPTION_ILLEGAL_INSTRUCTION: OUTMSG2 (("EXCEPTION_ILLEGAL_INSTRUCTION")); ourstatus->value.sig = TARGET_SIGNAL_ILL; break; case EXCEPTION_PRIV_INSTRUCTION: OUTMSG2 (("EXCEPTION_PRIV_INSTRUCTION")); ourstatus->value.sig = TARGET_SIGNAL_ILL; break; case EXCEPTION_NONCONTINUABLE_EXCEPTION: OUTMSG2 (("EXCEPTION_NONCONTINUABLE_EXCEPTION")); ourstatus->value.sig = TARGET_SIGNAL_ILL; break; default: if (current_event.u.Exception.dwFirstChance) return 0; OUTMSG2 (("gdbserver: unknown target exception 0x%08lx at 0x%08lx", current_event.u.Exception.ExceptionRecord.ExceptionCode, (DWORD) current_event.u.Exception.ExceptionRecord. ExceptionAddress)); ourstatus->value.sig = TARGET_SIGNAL_UNKNOWN; break; } OUTMSG2 (("\n")); last_sig = ourstatus->value.sig; return 1; } /* Get the next event from the child. Return 1 if the event requires handling. */ static int get_child_debug_event (struct target_waitstatus *ourstatus) { BOOL debug_event; DWORD continue_status, event_code; thread_info *th = NULL; static thread_info dummy_thread_info; int retval = 0; in: last_sig = TARGET_SIGNAL_0; ourstatus->kind = TARGET_WAITKIND_SPURIOUS; if (!(debug_event = WaitForDebugEvent (¤t_event, 1000))) goto out; current_inferior = (struct thread_info *) find_inferior_id (&all_threads, current_event.dwThreadId); continue_status = DBG_CONTINUE; event_code = current_event.dwDebugEventCode; switch (event_code) { case CREATE_THREAD_DEBUG_EVENT: OUTMSG2 (("gdbserver: kernel event CREATE_THREAD_DEBUG_EVENT " "for pid=%d tid=%x)\n", (unsigned) current_event.dwProcessId, (unsigned) current_event.dwThreadId)); /* Record the existence of this thread. */ th = child_add_thread (current_event.dwThreadId, current_event.u.CreateThread.hThread); retval = current_event.dwThreadId; break; case EXIT_THREAD_DEBUG_EVENT: OUTMSG2 (("gdbserver: kernel event EXIT_THREAD_DEBUG_EVENT " "for pid=%d tid=%x\n", (unsigned) current_event.dwProcessId, (unsigned) current_event.dwThreadId)); child_delete_thread (current_event.dwThreadId); th = &dummy_thread_info; break; case CREATE_PROCESS_DEBUG_EVENT: OUTMSG2 (("gdbserver: kernel event CREATE_PROCESS_DEBUG_EVENT " "for pid=%d tid=%x\n", (unsigned) current_event.dwProcessId, (unsigned) current_event.dwThreadId)); CloseHandle (current_event.u.CreateProcessInfo.hFile); current_process_handle = current_event.u.CreateProcessInfo.hProcess; main_thread_id = current_event.dwThreadId; ourstatus->kind = TARGET_WAITKIND_EXECD; ourstatus->value.execd_pathname = "Main executable"; /* Add the main thread. */ th = child_add_thread (main_thread_id, current_event.u.CreateProcessInfo.hThread); retval = ourstatus->value.related_pid = current_event.dwThreadId; break; case EXIT_PROCESS_DEBUG_EVENT: OUTMSG2 (("gdbserver: kernel event EXIT_PROCESS_DEBUG_EVENT " "for pid=%d tid=%x\n", (unsigned) current_event.dwProcessId, (unsigned) current_event.dwThreadId)); ourstatus->kind = TARGET_WAITKIND_EXITED; ourstatus->value.integer = current_event.u.ExitProcess.dwExitCode; CloseHandle (current_process_handle); current_process_handle = NULL; retval = main_thread_id; break; case LOAD_DLL_DEBUG_EVENT: OUTMSG2 (("gdbserver: kernel event LOAD_DLL_DEBUG_EVENT " "for pid=%d tid=%x\n", (unsigned) current_event.dwProcessId, (unsigned) current_event.dwThreadId)); CloseHandle (current_event.u.LoadDll.hFile); ourstatus->kind = TARGET_WAITKIND_LOADED; ourstatus->value.integer = 0; retval = main_thread_id; break; case UNLOAD_DLL_DEBUG_EVENT: OUTMSG2 (("gdbserver: kernel event UNLOAD_DLL_DEBUG_EVENT " "for pid=%d tid=%x\n", (unsigned) current_event.dwProcessId, (unsigned) current_event.dwThreadId)); break; case EXCEPTION_DEBUG_EVENT: OUTMSG2 (("gdbserver: kernel event EXCEPTION_DEBUG_EVENT " "for pid=%d tid=%x\n", (unsigned) current_event.dwProcessId, (unsigned) current_event.dwThreadId)); retval = handle_exception (ourstatus); break; case OUTPUT_DEBUG_STRING_EVENT: /* A message from the kernel (or Cygwin). */ OUTMSG2 (("gdbserver: kernel event OUTPUT_DEBUG_STRING_EVENT " "for pid=%d tid=%x\n", (unsigned) current_event.dwProcessId, (unsigned) current_event.dwThreadId)); break; default: OUTMSG2 (("gdbserver: kernel event unknown " "for pid=%d tid=%x code=%ld\n", (unsigned) current_event.dwProcessId, (unsigned) current_event.dwThreadId, current_event.dwDebugEventCode)); break; } current_inferior = (struct thread_info *) find_inferior_id (&all_threads, current_event.dwThreadId); if (!retval || (event_code != EXCEPTION_DEBUG_EVENT && event_code != EXIT_PROCESS_DEBUG_EVENT)) { child_continue (continue_status, -1); goto in; } if (th == NULL) thread_rec (current_event.dwThreadId, TRUE); out: return retval; } /* Wait for the inferior process to change state. STATUS will be filled in with a response code to send to GDB. Returns the signal which caused the process to stop. */ static unsigned char win32_wait (char *status) { struct target_waitstatus our_status; *status = 'T'; while (1) { get_child_debug_event (&our_status); if (our_status.kind == TARGET_WAITKIND_EXITED) { OUTMSG2 (("Child exited with retcode = %x\n", our_status.value.integer)); *status = 'W'; child_fetch_inferior_registers (-1); return our_status.value.integer; } else if (our_status.kind == TARGET_WAITKIND_STOPPED) { OUTMSG2 (("Child Stopped with signal = %x \n", WSTOPSIG (our_status.value.sig))); *status = 'T'; child_fetch_inferior_registers (-1); return our_status.value.sig; } else OUTMSG (("Ignoring unknown internal event, %d\n", our_status.kind)); { struct thread_resume resume; resume.thread = -1; resume.step = 0; resume.sig = 0; resume.leave_stopped = 0; win32_resume (&resume); } } } /* Fetch registers from the inferior process. If REGNO is -1, fetch all registers; otherwise, fetch at least REGNO. */ static void win32_fetch_inferior_registers (int regno) { child_fetch_inferior_registers (regno); } /* Store registers to the inferior process. If REGNO is -1, store all registers; otherwise, store at least REGNO. */ static void win32_store_inferior_registers (int regno) { child_store_inferior_registers (regno); } /* Read memory from the inferior process. This should generally be called through read_inferior_memory, which handles breakpoint shadowing. Read LEN bytes at MEMADDR into a buffer at MYADDR. */ static int win32_read_inferior_memory (CORE_ADDR memaddr, unsigned char *myaddr, int len) { return child_xfer_memory (memaddr, myaddr, len, 0, 0) != len; } /* Write memory to the inferior process. This should generally be called through write_inferior_memory, which handles breakpoint shadowing. Write LEN bytes from the buffer at MYADDR to MEMADDR. Returns 0 on success and errno on failure. */ static int win32_write_inferior_memory (CORE_ADDR memaddr, const unsigned char *myaddr, int len) { return child_xfer_memory (memaddr, (char *) myaddr, len, 1, 0) != len; } static struct target_ops win32_target_ops = { win32_create_inferior, win32_attach, win32_kill, win32_detach, win32_thread_alive, win32_resume, win32_wait, win32_fetch_inferior_registers, win32_store_inferior_registers, win32_read_inferior_memory, win32_write_inferior_memory, 0, 0 }; /* Initialize the Win32 backend. */ void initialize_low (void) { set_target_ops (&win32_target_ops); init_registers (); }