[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