This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[RFC] New gdbserver Win32 interrupt code


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.

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 <sys/cygwin.h>
 #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, &current_event);
     }
@@ -265,24 +284,178 @@
       && th->suspend_count)
     {
       if (th->context.ContextFlags)
-	{
-	  (*the_low_target.set_thread_context) (th, &current_event);
-	  th->context.ContextFlags = 0;
-	}
+        {
+          (*the_low_target.set_thread_context) (th, &current_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 *


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]