From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 11598 invoked by alias); 4 Nov 2007 01:28:35 -0000 Received: (qmail 11586 invoked by uid 22791); 4 Nov 2007 01:28:33 -0000 X-Spam-Check-By: sourceware.org Received: from gw.sprintaddict.net (HELO champenstudios.com) (80.91.89.73) by sourceware.org (qpsmtpd/0.31) with ESMTP; Sun, 04 Nov 2007 01:28:28 +0000 Received: from [192.168.1.5] (164.Red-80-36-45.staticIP.rima-tde.net [80.36.45.164]) (authenticated bits=0) by champenstudios.com (8.13.8/8.13.8) with ESMTP id lA41LNEw028173 (version=TLSv1/SSLv3 cipher=DHE-DSS-AES256-SHA bits=256 verify=NO) for ; Sun, 4 Nov 2007 02:21:24 +0100 Message-ID: <472D2035.5070004@champenstudios.com> Date: Sun, 04 Nov 2007 01:28:00 -0000 From: Lerele User-Agent: Thunderbird 2.0.0.6 (Windows/20070728) MIME-Version: 1.0 To: gdb-patches@sourceware.org Subject: [RFC] New gdbserver Win32 interrupt code Content-Type: multipart/mixed; boundary="------------050404070208060508010907" X-IsSubscribed: yes Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org X-SW-Source: 2007-11/txt/msg00035.txt.bz2 This is a multi-part message in MIME format. --------------050404070208060508010907 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit Content-length: 1378 Hello, Sorry for the delay, didn't get the time to continue with gdbserver Win32 coding. Some time ago I suggested a method to better support remote interrupt of debugged process, in this thread: http://sourceware.org/ml/gdb-patches/2007-03/msg00026.html The attached patch does just what I described there, with all the benefits I discussed back then. I have tried to code the bits with WinCE in mind by copying how it's already done for WinCE (LoadLibrary basically), but I have not compiled nor tested it under Windows CE. However, it does work fine with Cygwin. Also I fixed what I think is a slight bug in thread suspend/resume in win32-low.c, and something that is also related with this new interrupt thread pause/resume functionality. The thread_rec function was setting the gdbserver internal suspend_count to the number of suspends a thread actually has, as reported by Windows. However I think this is erroneous to keep this way because a thread may already be paused by the application being debugged, and as such when gdbserver pauses/resumes threads, it could wake up a child thread that should actually stay paused. I have also kept old functionality with the macro NEW_INT in case someone needs/want to fallback to the old code. This can also be removed of course. Any thoughts about this new functionality? Hope patch is Ok. Regards, Leo. --------------050404070208060508010907 Content-Type: text/plain; name="diff-newint" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="diff-newint" Content-length: 12057 ChangeLog: 2007-11-04 Leo Zayas * win32-low.c [NEW_INT]: New macro to allow conditional compilation with new or old interrupt code. * win32-low.c [NEW_INT] (interrupt_requested): New variable to indicate remote interrupt request. * win32-low.c [NEW_INT] (interrupt_thread_event): New variable to store interrupt code secondary gdbserver thread event. * win32-low.c [NEW_INT] (winapi_SetProcessPriorityBoost): New API typedef [NEW_INT] (winapi_GetProcessPriorityBoost): New API typedef [NEW_INT] (winapi_SetProcessAffinityMask): New API typedef [!NEW_INT] (winapi_DebugBreakProcess): Old typedef being replaced [!NEW_INT] (winapi_GenerateConsoleCtrlEvent): Old typedef being replaced * win32-low.c (thread_rec): set suspend_count to our internal suspends only. * win32-low.c (continue_one_thread): fix indentation. * win32-low.c [NEW_INT] (consume_cpu_interrupt_thread): Thread function that consumes cpu, and exits upon signal. * win32-low.c [NEW_INT] (pause_child_inferior_thread): Pause a single thread. (resume_child_inferior_thread): Resume a single thread. * win32-low.c [NEW_INT] (synthetic_child_interrupt): New function to pause or resume all child threads. * win32-low.c (child_continue) [NEW_INT]: Resume the child process threads. * win32-low.c (get_child_debug_event) [NEW_INT]: Pause child process threads upon interrupt request, and return signal. * win32-low.c (win32_request_interrupt) [NEW_INT]: Set interrupt request flag. (win32_request_interrupt) [!NEW_INT]: Keep old code around. diff -u src_orig/gdb/gdbserver/win32-low.c src/gdb/gdbserver/win32-low.c --- src_orig/gdb/gdbserver/win32-low.c 2007-09-04 00:17:27.000000000 +0200 +++ src/gdb/gdbserver/win32-low.c 2007-11-04 00:54:53.218750000 +0100 @@ -37,6 +37,8 @@ #include #endif +#define NEW_INT 1 + #define LOG 0 #define OUTMSG(X) do { printf X; fflush (stdout); } while (0) @@ -64,6 +66,11 @@ int using_threads = 1; +#if NEW_INT +static int interrupt_requested = 0; +static HANDLE interrupt_thread_event = NULL; +#endif + /* Globals. */ static HANDLE current_process_handle = NULL; static DWORD current_process_id = 0; @@ -76,8 +83,14 @@ typedef BOOL WINAPI (*winapi_DebugActiveProcessStop) (DWORD dwProcessId); typedef BOOL WINAPI (*winapi_DebugSetProcessKillOnExit) (BOOL KillOnExit); +#if NEW_INT +typedef BOOL WINAPI (*winapi_SetProcessPriorityBoost) (HANDLE, BOOL); +typedef BOOL WINAPI (*winapi_GetProcessPriorityBoost) (HANDLE, PBOOL); +typedef BOOL WINAPI (*winapi_SetProcessAffinityMask) (HANDLE, DWORD_PTR); +#else typedef BOOL WINAPI (*winapi_DebugBreakProcess) (HANDLE); typedef BOOL WINAPI (*winapi_GenerateConsoleCtrlEvent) (DWORD, DWORD); +#endif static DWORD main_thread_id = 0; @@ -92,7 +105,7 @@ return th->tid; } -/* Find a thread record given a thread id. If GET_CONTEXT is set then +/* Find a thread record given a thread id. If get_context is set then also retrieve the context for this thread. */ static win32_thread_info * thread_rec (DWORD id, int get_context) @@ -108,7 +121,13 @@ if (!th->suspend_count && get_context) { if (id != current_event.dwThreadId) - th->suspend_count = SuspendThread (th->h) + 1; + { + /* Set suspend count to *our* internal thread suspends, so that we + do not interfere with already thread suspend state. */ + th->suspend_count++; + SuspendThread (th->h); + /* th->suspend_count = SuspendThread (th->h) + 1; */ + } (*the_low_target.get_thread_context) (th, ¤t_event); } @@ -265,24 +284,178 @@ && th->suspend_count) { if (th->context.ContextFlags) - { - (*the_low_target.set_thread_context) (th, ¤t_event); - th->context.ContextFlags = 0; - } + { + (*the_low_target.set_thread_context) (th, ¤t_event); + th->context.ContextFlags = 0; + } for (i = 0; i < th->suspend_count; i++) - (void) ResumeThread (th->h); + (void) ResumeThread (th->h); th->suspend_count = 0; } return 0; } +/* thread function that consumes cpu while interrupt code pauses + child threads. */ +#if NEW_INT +static DWORD WINAPI +consume_cpu_interrupt_thread (LPVOID data) +{ + HANDLE *event = (HANDLE *) data; + while (WaitForSingleObject (interrupt_thread_event, 0) != WAIT_OBJECT_0) + { + } + if (event != 0) + SetEvent (*event); + return 0; +} +#endif + +/* Pause child thread. */ +#if NEW_INT +static void pause_child_inferior_thread (struct inferior_list_entry *inf) +{ + struct thread_info *thr = (struct thread_info *) inf; + + if (thr != NULL) + { + win32_thread_info *th = inferior_target_data (thr); + + /* Pause and get context from thread if necessary. */ + int tid = th->tid; + thread_rec (tid, 1); + } +} +#endif + +/* Resume child thread. */ +#if NEW_INT +static void resume_child_inferior_thread (struct inferior_list_entry *inf) +{ + struct thread_info *thr = (struct thread_info *) inf; + + if (thr != NULL) + { + win32_thread_info *th = inferior_target_data (thr); + + /* Resume thread and set context if necessary. */ + int tid = th->tid; + continue_one_thread (inf, &tid); + } +} +#endif + +/* Pause/resume all child threads. This function attempts to perform the + required operation in a more portable manner across Windows versions and also + bypass some problems with GenerateConcoleCtrlEvent and DebugBreakProcess + functions. */ +#if NEW_INT +static void +synthetic_child_interrupt (int pause) +{ + DWORD old_process_priority_class; + DWORD old_process_affinity_mask; + BOOL old_process_priority_boost; + DWORD system_affinity_mask; + HANDLE current_process_handle; + SYSTEM_INFO sys_info; + int thr; + + winapi_SetProcessPriorityBoost SetProcessPriorityBoost; + winapi_GetProcessPriorityBoost GetProcessPriorityBoost; + winapi_SetProcessAffinityMask SetProcessAffinityMask; + +#ifdef _WIN32_WCE + HMODULE dll = GetModuleHandle (_T("COREDLL.DLL")); +#else + HMODULE dll = GetModuleHandle (_T("KERNEL32.DLL")); +#endif + + SetProcessPriorityBoost = GETPROCADDRESS (dll, SetProcessPriorityBoost); + GetProcessPriorityBoost = GETPROCADDRESS (dll, GetProcessPriorityBoost); + SetProcessAffinityMask = GETPROCADDRESS (dll, SetProcessAffinityMask); + + /* As we are going to play around with gdbserver scheduling, use system + reported current process, in case gdbserver own data gets corrupted. */ + current_process_handle = OpenProcess (PROCESS_ALL_ACCESS, FALSE, + GetCurrentProcessId ()); + + old_process_priority_class = GetPriorityClass (current_process_handle); + + /* Go real-time so we can pause child as atomically as possible. */ + SetPriorityClass (current_process_handle, REALTIME_PRIORITY_CLASS); + + if (GetProcessPriorityBoost != NULL) + GetProcessPriorityBoost (current_process_handle, + &old_process_priority_boost); + if (SetProcessPriorityBoost != NULL) + SetProcessPriorityBoost (current_process_handle, FALSE); + + GetProcessAffinityMask (current_process_handle, + &old_process_affinity_mask, + &system_affinity_mask); + + /* Set gdbserver to run on all CPUs. */ + if (SetProcessAffinityMask != NULL) + SetProcessAffinityMask (current_process_handle, system_affinity_mask); + + GetSystemInfo (&sys_info); + + interrupt_thread_event = CreateEvent(NULL, TRUE, FALSE, NULL); + + HANDLE *events; + events = malloc (sizeof (HANDLE) * (sys_info.dwNumberOfProcessors - 1)); + for (thr = 0; thr < sys_info.dwNumberOfProcessors - 1; thr++) + { + events[thr] = CreateEvent (NULL, TRUE, FALSE, NULL); + CreateThread(NULL, 0, consume_cpu_interrupt_thread, events+thr, 0, NULL); + } + + /* Now pause/resume child threads one by one. */ + if (pause) + for_each_inferior (&all_threads, pause_child_inferior_thread); + else + for_each_inferior (&all_threads, resume_child_inferior_thread); + + /* Once paused, signal gdbserver secondary cpu-consumption threads + to terminate, wait for them to do so, and gracefully + free all synchronization objects. */ + SetEvent (interrupt_thread_event); + WaitForMultipleObjects (sys_info.dwNumberOfProcessors - 1, + events, TRUE, INFINITE); + for (thr = 0; thr < sys_info.dwNumberOfProcessors - 1; thr++) + CloseHandle (events[thr]); + free (events); + CloseHandle (interrupt_thread_event); + + /* Restore gdbserver Windows scheduling state. */ + if (SetProcessPriorityBoost != NULL) + SetProcessPriorityBoost (current_process_handle, + old_process_priority_boost); + if (SetProcessAffinityMask != NULL) + SetProcessAffinityMask (current_process_handle, old_process_affinity_mask); + SetPriorityClass (current_process_handle, old_process_priority_class); + + FreeLibrary (dll); +} +#endif + static BOOL child_continue (DWORD continue_status, int thread_id) { BOOL res; +#if NEW_INT + if (interrupt_requested) + { + interrupt_requested = 0; + synthetic_child_interrupt(0); + return TRUE; + } +#endif + res = ContinueDebugEvent (current_event.dwProcessId, current_event.dwThreadId, continue_status); if (res) @@ -1243,6 +1416,33 @@ last_sig = TARGET_SIGNAL_0; ourstatus->kind = TARGET_WAITKIND_SPURIOUS; + +#if NEW_INT + /* Remote interrupt has been requested. In this case return a signal + * the client gdb can understand as the child being stopped. + * As we are checking this first, any pending OS exceptions arised + * between interrupt request and next call to WaitForDebugEvent should + * get stacked and pending. */ + if (interrupt_requested) + { + current_inferior = + (struct thread_info *) find_inferior_id (&all_threads, + main_thread_id); + ourstatus->kind = TARGET_WAITKIND_STOPPED; + ourstatus->value.sig = TARGET_SIGNAL_INT; + + /* Clear current event data, as this could interfere with thread_rec. */ + current_event.dwThreadId = 0; + current_event.dwProcessId = current_process_id; + + /* Simulate child stop. Basically give gdbserver process maximum priority + * so that child doesn't get a chance to run, then pause one by one all + * child threads, and finally return new signaled state. */ + synthetic_child_interrupt(1); + + return; + } +#endif /* Keep the wait time low enough for confortable remote interruption, but high enough so gdbserver doesn't become a bottleneck. */ @@ -1445,7 +1645,8 @@ 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) +win32_read_inferior_memory (CORE_ADDR memaddr, unsigned char *myaddr, + int len) { return child_xfer_memory (memaddr, (char *) myaddr, len, 0, 0) != len; } @@ -1455,8 +1656,9 @@ 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) +win32_write_inferior_memory (CORE_ADDR memaddr, + const unsigned char *myaddr, + int len) { return child_xfer_memory (memaddr, (char *) myaddr, len, 1, 0) != len; } @@ -1465,6 +1667,12 @@ static void win32_request_interrupt (void) { +#if NEW_INT + + interrupt_requested = 1; + +#else + winapi_DebugBreakProcess DebugBreakProcess; winapi_GenerateConsoleCtrlEvent GenerateConsoleCtrlEvent; @@ -1492,6 +1700,8 @@ return; OUTMSG (("Could not interrupt process.\n")); + +#endif } static const char * --------------050404070208060508010907--