[Lldb-commits] [lldb] r209912 - gdb-remote signal delivery test cleanup.

Todd Fiala todd.fiala at gmail.com
Fri May 30 10:59:47 PDT 2014


Author: tfiala
Date: Fri May 30 12:59:47 2014
New Revision: 209912

URL: http://llvm.org/viewvc/llvm-project?rev=209912&view=rev
Log:
gdb-remote signal delivery test cleanup.

Learned that MacOSX only accepts signal delivery on a thread that is
already signal handling.  Reworked the test exe to cause a SIGSEGV
and recover if either nothing intercepts the SIGSEGV handler, or
if a SIGUSR1 is inserted.  The test uses the latter part to test
signal delivery on continue using the SIGUSR1.

I still don't have this working on MacOSX.  I'm seeing the
signal get delivered to a different thread than the one I'm
specifying with $Hc{thread-id} + $C{signo}, or with
$vCont;C{signo}:{thread-id};c.  I'll come back to this
after getting it working on the llgs branch on Linux x86_64.


Modified:
    lldb/trunk/test/tools/lldb-gdbserver/TestLldbGdbServer.py
    lldb/trunk/test/tools/lldb-gdbserver/main.cpp
    lldb/trunk/test/tools/lldb-gdbserver/socket_packet_pump.py

Modified: lldb/trunk/test/tools/lldb-gdbserver/TestLldbGdbServer.py
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/test/tools/lldb-gdbserver/TestLldbGdbServer.py?rev=209912&r1=209911&r2=209912&view=diff
==============================================================================
--- lldb/trunk/test/tools/lldb-gdbserver/TestLldbGdbServer.py (original)
+++ lldb/trunk/test/tools/lldb-gdbserver/TestLldbGdbServer.py Fri May 30 12:59:47 2014
@@ -31,6 +31,14 @@ class LldbGdbServerTestCase(TestBase):
     _STARTUP_ATTACH = "attach"
     _STARTUP_LAUNCH = "launch"
 
+    # GDB Signal numbers that are not target-specific used for common exceptions
+    TARGET_EXC_BAD_ACCESS      = 0x91
+    TARGET_EXC_BAD_INSTRUCTION = 0x92
+    TARGET_EXC_ARITHMETIC      = 0x93
+    TARGET_EXC_EMULATION       = 0x94
+    TARGET_EXC_SOFTWARE        = 0x95
+    TARGET_EXC_BREAKPOINT      = 0x96
+
     def setUp(self):
         TestBase.setUp(self)
         FORMAT = '%(asctime)-15s %(levelname)-8s %(message)s'
@@ -42,8 +50,8 @@ class LldbGdbServerTestCase(TestBase):
 
         # Uncomment this code to force only a single test to run (by name).
         # if self._testMethodName != "test_Hc_then_Csignal_signals_correct_thread_launch_debugserver_dsym":
-        #     # print "skipping test {}".format(self._testMethodName)
-        #     self.skipTest("focusing on one test")
+        #   # print "skipping test {}".format(self._testMethodName)
+        #   self.skipTest("focusing on one test")
 
     def reset_test_sequence(self):
         self.test_sequence = GdbRemoteTestSequence(self.logger)
@@ -487,15 +495,16 @@ class LldbGdbServerTestCase(TestBase):
         self.add_verified_launch_packets(launch_args)
         self.test_sequence.add_log_lines(
             ["read packet: $vCont;c#00",
+             {"type":"output_match", "regex":r"^hello, world\r\n$" },
              "send packet: $W00#00"],
             True)
             
         context = self.expect_gdbremote_sequence()
         self.assertIsNotNone(context)
         
-        O_content = context.get("O_content")
-        self.assertIsNotNone(O_content)
-        self.assertEquals(O_content, "hello, world\r\n")
+        # O_content = context.get("O_content")
+        # self.assertIsNotNone(O_content)
+        # self.assertEquals(O_content, "hello, world\r\n")
 
     @debugserver_test
     @dsym_test
@@ -1116,37 +1125,61 @@ class LldbGdbServerTestCase(TestBase):
         NUM_THREADS = 3
         
         # Startup the inferior with three threads (main + NUM_THREADS-1 worker threads).
-        inferior_args=["thread:print-ids"]
+        # inferior_args=["thread:print-ids"]
+        inferior_args=["thread:segfault"]
         for i in range(NUM_THREADS - 1):
+            # if i > 0:
+                # Give time between thread creation/segfaulting for the handler to work.
+                # inferior_args.append("sleep:1")
             inferior_args.append("thread:new")
-        inferior_args.append("sleep:20")
-        
+        inferior_args.append("sleep:10")
+
+        # Launch/attach.  (In our case, this should only ever be launched since we need inferior stdout/stderr).
         procs = self.prep_debug_monitor_and_inferior(inferior_args=inferior_args)
+        self.test_sequence.add_log_lines(["read packet: $c#00"], True)
+        context = self.expect_gdbremote_sequence()
 
         # Let the inferior process have a few moments to start up the thread when launched.
-        context = self.run_process_then_stop(run_seconds=1)
+        # context = self.run_process_then_stop(run_seconds=1)
 
         # Wait at most x seconds for all threads to be present.
-        threads = self.wait_for_thread_count(NUM_THREADS, timeout_seconds=5)
-        self.assertEquals(len(threads), NUM_THREADS)
+        # threads = self.wait_for_thread_count(NUM_THREADS, timeout_seconds=5)
+        # self.assertEquals(len(threads), NUM_THREADS)
 
-        # print_thread_ids = {}
+        signaled_tids = {}
 
         # Switch to each thread, deliver a signal, and verify signal delivery
-        for thread_id in threads:
-            # Change to each thread, verify current thread id.
+        for i in range(NUM_THREADS - 1):
+            # Run until SIGSEGV comes in.
+            self.reset_test_sequence()
+            self.test_sequence.add_log_lines(
+                [ # "read packet: $c#00",
+                 {"direction":"send", "regex":r"^\$T([0-9a-fA-F]{2})thread:([0-9a-fA-F]+);", "capture":{1:"signo", 2:"thread_id"} }
+                 ], True)
+            context = self.expect_gdbremote_sequence()
+            
+            self.assertIsNotNone(context)
+            signo = context.get("signo")
+            self.assertEqual(int(signo, 16), self.TARGET_EXC_BAD_ACCESS)
+            
+            # Ensure we haven't seen this tid yet.
+            thread_id = int(context.get("thread_id"), 16)
+            self.assertFalse(thread_id in signaled_tids)
+            signaled_tids[thread_id] = 1
+            
+            # Send SIGUSR1 to the thread that signaled the SIGSEGV.
             self.reset_test_sequence()
             self.test_sequence.add_log_lines(
-                ["read packet: $Hc{0:x}#00".format(thread_id),  # Set current thread.
+                [
+                 "read packet: $Hc{0:x}#00".format(thread_id),  # Set current thread.
                  "send packet: $OK#00",
                  "read packet: $C{0:x}#00".format(signal.SIGUSR1),
-                 {"direction":"send", "regex":r"^\$T([0-9a-fA-F]{2})thread:([0-9a-fA-F]+);", "capture":{1:"stop_signo", 2:"stop_thread_id"} },
                  # "read packet: $vCont;C{0:x}:{1:x};c#00".format(signal.SIGUSR1, thread_id),
-                 # "read packet: $vCont;C{0:x};c#00".format(signal.SIGUSR1, thread_id),
+                 # "read packet: $c#00",
+                 {"direction":"send", "regex":r"^\$T([0-9a-fA-F]{2})thread:([0-9a-fA-F]+);", "capture":{1:"stop_signo", 2:"stop_thread_id"} },
+                  "read packet: $c#00",
                  # { "type":"output_match", "regex":r"^received SIGUSR1 on thread id: ([0-9a-fA-F]+)\r\n$", "capture":{ 1:"print_thread_id"} },
-                 "read packet: $c#00",
-                 "read packet: {}".format(chr(03)),
-                 {"direction":"send", "regex":r"^\$T([0-9a-fA-F]{2})thread:([0-9a-fA-F]+);", "capture":{1:"intr_signo", 2:"intr_thread_id"} }
+                 # "read packet: {}".format(chr(03)),
                 ],
                 True)
 
@@ -1174,7 +1207,7 @@ class LldbGdbServerTestCase(TestBase):
 
     @debugserver_test
     @dsym_test
-    @unittest2.expectedFailure() # this test is failing on MacOSX 10.9
+    @unittest2.expectedFailure()
     def test_Hc_then_Csignal_signals_correct_thread_launch_debugserver_dsym(self):
         self.init_debugserver_test()
         self.buildDsym()

Modified: lldb/trunk/test/tools/lldb-gdbserver/main.cpp
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/test/tools/lldb-gdbserver/main.cpp?rev=209912&r1=209911&r2=209912&view=diff
==============================================================================
--- lldb/trunk/test/tools/lldb-gdbserver/main.cpp (original)
+++ lldb/trunk/test/tools/lldb-gdbserver/main.cpp Fri May 30 12:59:47 2014
@@ -3,13 +3,17 @@
 #include <errno.h>
 #include <inttypes.h>
 #include <pthread.h>
+#include <setjmp.h>
 #include <signal.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <unistd.h>
 #include <vector>
 
-#if defined(__linux__)
+#if defined(__APPLE__)
+__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2)
+int pthread_threadid_np(pthread_t,__uint64_t*);
+#elif defined(__linux__)
 #include <sys/syscall.h>
 #endif
 
@@ -20,9 +24,15 @@ static const char *const STDERR_PREFIX =
 static const char *const THREAD_PREFIX = "thread:";
 static const char *const THREAD_COMMAND_NEW = "new"; 
 static const char *const THREAD_COMMAND_PRINT_IDS = "print-ids"; 
+static const char *const THREAD_COMMAND_SEGFAULT = "segfault"; 
 
 static bool g_print_thread_ids = false;
 static pthread_mutex_t g_print_mutex = PTHREAD_MUTEX_INITIALIZER;
+static bool g_threads_do_segfault = false;
+
+static pthread_mutex_t g_jump_buffer_mutex = PTHREAD_MUTEX_INITIALIZER;
+static jmp_buf g_jump_buffer;
+static bool g_is_segfaulting = false;
 
 static void
 print_thread_id ()
@@ -30,7 +40,9 @@ print_thread_id ()
 	// Put in the right magic here for your platform to spit out the thread id (tid) that debugserver/lldb-gdbserver would see as a TID.
 	// Otherwise, let the else clause print out the unsupported text so that the unit test knows to skip verifying thread ids.
 #if defined(__APPLE__)
-	printf ("%" PRIx64, static_cast<uint64_t> (pthread_mach_thread_np(pthread_self())));
+	__uint64_t tid = 0;
+	pthread_threadid_np(pthread_self(), &tid);
+	printf ("%" PRIx64, tid);
 #elif defined (__linux__)
 	// This is a call to gettid() via syscall.
 	printf ("%" PRIx64, static_cast<uint64_t> (syscall (__NR_gettid)));
@@ -42,36 +54,62 @@ print_thread_id ()
 static void
 signal_handler (int signo)
 {
+	const char *signal_name = NULL;
+	switch (signo)
+	{
+		case SIGUSR1: signal_name = "SIGUSR1"; break;
+		case SIGSEGV: signal_name = "SIGSEGV"; break;
+		default:      signal_name = NULL;
+	}
+
+	// Print notice that we received the signal on a given thread.
+	pthread_mutex_lock (&g_print_mutex);
+	if (signal_name)
+		printf ("received %s on thread id: ", signal_name);
+	else
+		printf ("received signo %d (%s) on thread id: ", signo, strsignal (signo));
+	print_thread_id ();
+	printf ("\n");
+	pthread_mutex_unlock (&g_print_mutex);
+
+	// Reset the signal handler if we're one of the expected signal handlers.
 	switch (signo)
 	{
+	case SIGSEGV:
+		// Fix up the pointer we're writing to.  This needs to happen if nothing intercepts the SIGSEGV
+		// (i.e. if somebody runs this from the command line).
+		longjmp(g_jump_buffer, 1);
+		break;
 	case SIGUSR1:
-		// Print notice that we received the signal on a given thread.
-		pthread_mutex_lock (&g_print_mutex);
-		printf ("received SIGUSR1 on thread id: ");
-		print_thread_id ();
-		printf ("\n");
-		pthread_mutex_unlock (&g_print_mutex);
-		
-		// Reset the signal handler.
-		sig_t sig_result = signal (SIGUSR1, signal_handler);
-		if (sig_result == SIG_ERR)
+		if (g_is_segfaulting)
 		{
-			fprintf(stderr, "failed to set signal handler: errno=%d\n", errno);
-			exit (1);
+			// Fix up the pointer we're writing to.  This is used to test gdb remote signal delivery.
+			// A SIGSEGV will be raised when the thread is created, switched out for a SIGUSR1, and
+			// then this code still needs to fix the seg fault.
+			// (i.e. if somebody runs this from the command line).
+			longjmp(g_jump_buffer, 1);
 		}
-
 		break;
 	}
+
+	// Reset the signal handler.
+	sig_t sig_result = signal (signo, signal_handler);
+	if (sig_result == SIG_ERR)
+	{
+		fprintf(stderr, "failed to set signal handler: errno=%d\n", errno);
+		exit (1);
+	}
 }
 
 static void*
 thread_func (void *arg)
 {
+	static pthread_mutex_t s_thread_index_mutex = PTHREAD_MUTEX_INITIALIZER;
 	static int s_thread_index = 1;
-	// For now, just sleep for a few seconds.
-	// std::cout << "thread " << pthread_self() << ": created" << std::endl;
 
+	pthread_mutex_lock (&s_thread_index_mutex);
 	const int this_thread_index = s_thread_index++;
+	pthread_mutex_unlock (&s_thread_index_mutex);
 
 	if (g_print_thread_ids)
 	{
@@ -82,13 +120,50 @@ thread_func (void *arg)
 		pthread_mutex_unlock (&g_print_mutex);
 	}
 
-	int sleep_seconds_remaining = 20;
+	if (g_threads_do_segfault)
+	{
+		// Sleep for a number of seconds based on the thread index.
+		// TODO add ability to send commands to test exe so we can
+		// handle timing more precisely.  This is clunky.  All we're
+		// trying to do is add predictability as to the timing of
+		// signal generation by created threads.
+		int sleep_seconds = 2 * (this_thread_index - 1);
+		while (sleep_seconds > 0)
+			sleep_seconds = sleep(sleep_seconds);
+		
+		// Test creating a SEGV.
+		pthread_mutex_lock (&g_jump_buffer_mutex);
+		g_is_segfaulting = true;
+		int *bad_p = NULL;
+		if (setjmp(g_jump_buffer) == 0)
+		{
+			// Force a seg fault signal on this thread.
+			*bad_p = 0;
+		}
+		else
+		{
+			// Tell the system we're no longer seg faulting.
+			// Used by the SIGUSR1 signal handler that we inject
+			// in place of the SIGSEGV so it only tries to
+			// recover from the SIGSEGV if this seg fault code
+			// was in play.
+			g_is_segfaulting = false;
+		}
+		pthread_mutex_unlock (&g_jump_buffer_mutex);
+
+		pthread_mutex_lock (&g_print_mutex);
+		printf ("thread ");
+		print_thread_id ();
+		printf (": past SIGSEGV\n");
+		pthread_mutex_unlock (&g_print_mutex);
+	}
+	
+	int sleep_seconds_remaining = 5;
 	while (sleep_seconds_remaining > 0)
 	{
 		sleep_seconds_remaining = sleep (sleep_seconds_remaining);
 	}
 
-	// std::cout << "thread " << pthread_self() << ": exiting" << std::endl;
 	return NULL;
 }
 
@@ -98,10 +173,24 @@ int main (int argc, char **argv)
     int return_value = 0;
 
 	// Set the signal handler.
-	sig_t sig_result = signal (SIGUSR1, signal_handler);
+	sig_t sig_result = signal (SIGALRM, signal_handler);
 	if (sig_result == SIG_ERR)
 	{
-		fprintf(stderr, "failed to set signal handler: errno=%d\n", errno);
+		fprintf(stderr, "failed to set SIGALRM signal handler: errno=%d\n", errno);
+		exit (1);
+	}
+
+	sig_result = signal (SIGUSR1, signal_handler);
+	if (sig_result == SIG_ERR)
+	{
+		fprintf(stderr, "failed to set SIGUSR1 handler: errno=%d\n", errno);
+		exit (1);
+	}
+
+	sig_result = signal (SIGSEGV, signal_handler);
+	if (sig_result == SIG_ERR)
+	{
+		fprintf(stderr, "failed to set SIGUSR1 handler: errno=%d\n", errno);
 		exit (1);
 	}
 	
@@ -158,6 +247,10 @@ int main (int argc, char **argv)
 				printf ("\n");
 				pthread_mutex_unlock (&g_print_mutex);
 			}
+			else if (std::strstr (argv[i] + strlen(THREAD_PREFIX), THREAD_COMMAND_SEGFAULT))
+			{
+				g_threads_do_segfault = true;
+			}
 			else
 			{
 				// At this point we don't do anything else with threads.

Modified: lldb/trunk/test/tools/lldb-gdbserver/socket_packet_pump.py
URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/test/tools/lldb-gdbserver/socket_packet_pump.py?rev=209912&r1=209911&r2=209912&view=diff
==============================================================================
--- lldb/trunk/test/tools/lldb-gdbserver/socket_packet_pump.py (original)
+++ lldb/trunk/test/tools/lldb-gdbserver/socket_packet_pump.py Fri May 30 12:59:47 2014
@@ -2,6 +2,7 @@ import Queue
 import re
 import select
 import threading
+import traceback
 
 def _handle_output_packet_string(packet_contents):
     if (not packet_contents) or (len(packet_contents) < 1):
@@ -13,6 +14,11 @@ def _handle_output_packet_string(packet_
     else:
         return packet_contents[1:].decode("hex")
 
+def _dump_queue(the_queue):
+    while not the_queue.empty():
+        print the_queue.get(True)
+        print "\n"
+
 class SocketPacketPump(object):
     """A threaded packet reader that partitions packets into two streams.
 
@@ -47,12 +53,26 @@ class SocketPacketPump(object):
         self.start_pump_thread()
         return self
 
-    def __exit__(self, exit_type, value, traceback):
+    def __exit__(self, exit_type, value, the_traceback):
         """Support the python 'with' statement.
 
         Shut down the pump thread."""
         self.stop_pump_thread()
 
+        # Warn if there is any content left in any of the queues.
+        # That would represent unmatched packets.
+        if not self.output_queue().empty():
+            print "warning: output queue entries still exist:"
+            _dump_queue(self.output_queue())
+            print "from here:"
+            traceback.print_stack()
+
+        if not self.packet_queue().empty():
+            print "warning: packet queue entries still exist:"
+            _dump_queue(self.packet_queue())
+            print "from here:"
+            traceback.print_stack()
+
     def start_pump_thread(self):
         if self._thread:
             raise Exception("pump thread is already running")
@@ -115,9 +135,11 @@ class SocketPacketPump(object):
                     self._receive_buffer = self._receive_buffer[
                         len(packet_match.group(0)):]
                     if self._logger:
-                        self._logger.debug("parsed packet from stub: " +
+                        self._logger.debug(
+                            "parsed packet from stub: " +
                             packet_match.group(0))
-                        self._logger.debug("new receive_buffer: " +
+                        self._logger.debug(
+                            "new receive_buffer: " +
                             self._receive_buffer)
                 else:
                     # We don't have enough in the receive bufferto make a full
@@ -138,11 +160,13 @@ class SocketPacketPump(object):
                 try:
                     new_bytes = self._socket.recv(4096)
                     if self._logger and new_bytes and len(new_bytes) > 0:
-                        self._logger.debug("pump received bytes: {}".format(new_bytes))
+                        self._logger.debug(
+                            "pump received bytes: {}".format(new_bytes))
                 except:
                     # Likely a closed socket.  Done with the pump thread.
                     if self._logger:
-                        self._logger.debug("socket read failed, stopping pump read thread")
+                        self._logger.debug(
+                            "socket read failed, stopping pump read thread")
                     break
                 self._process_new_bytes(new_bytes)
 
@@ -151,6 +175,6 @@ class SocketPacketPump(object):
 
     def get_accumulated_output(self):
         return self._accumulated_output
-        
+
     def get_receive_buffer(self):
-        return self._receive_buffer
\ No newline at end of file
+        return self._receive_buffer





More information about the lldb-commits mailing list