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


_______________________________________________
lldb-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/lldb-commits

Reply via email to