diff options
-rw-r--r-- | lldb/test/tools/lldb-gdbserver/TestLldbGdbServer.py | 75 | ||||
-rw-r--r-- | lldb/test/tools/lldb-gdbserver/main.cpp | 135 | ||||
-rw-r--r-- | lldb/test/tools/lldb-gdbserver/socket_packet_pump.py | 38 |
3 files changed, 199 insertions, 49 deletions
diff --git a/lldb/test/tools/lldb-gdbserver/TestLldbGdbServer.py b/lldb/test/tools/lldb-gdbserver/TestLldbGdbServer.py index 97c855193e4..8aae233dbbf 100644 --- a/lldb/test/tools/lldb-gdbserver/TestLldbGdbServer.py +++ b/lldb/test/tools/lldb-gdbserver/TestLldbGdbServer.py @@ -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() diff --git a/lldb/test/tools/lldb-gdbserver/main.cpp b/lldb/test/tools/lldb-gdbserver/main.cpp index f32e428593c..e7ae168c2b3 100644 --- a/lldb/test/tools/lldb-gdbserver/main.cpp +++ b/lldb/test/tools/lldb-gdbserver/main.cpp @@ -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 = "stderr:"; 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. diff --git a/lldb/test/tools/lldb-gdbserver/socket_packet_pump.py b/lldb/test/tools/lldb-gdbserver/socket_packet_pump.py index 2ec2419026a..2d35898fd5d 100644 --- a/lldb/test/tools/lldb-gdbserver/socket_packet_pump.py +++ b/lldb/test/tools/lldb-gdbserver/socket_packet_pump.py @@ -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_contents): 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 |