llgs: corrected Linux signal reception notification for SIGABRT, SIGSEGV and their ilk.

Added llgs/debugserver gdb-remote tests around SIGABRT and SIGSEGV signal reception
notification.  Found a few bugs in exception signal handling in Linux llgs.  Fixed those.

llvm-svn: 215458
This commit is contained in:
Todd Fiala 2014-08-12 17:02:07 +00:00
parent 09d84addb7
commit 58a2f6692b
6 changed files with 237 additions and 93 deletions

View File

@ -2154,77 +2154,78 @@ NativeProcessLinux::MonitorSignal(const siginfo_t *info, lldb::pid_t pid, bool e
info->si_pid, info->si_pid,
(info->si_pid == getpid ()) ? "is monitor" : "is not monitor", (info->si_pid == getpid ()) ? "is monitor" : "is not monitor",
pid); pid);
}
if ((info->si_pid == 0) && info->si_code == SI_USER) // Check for new thread notification.
if ((info->si_pid == 0) && (info->si_code == SI_USER))
{
// A new thread creation is being signaled. This is one of two parts that come in
// a non-deterministic order. pid is the thread id.
if (log)
log->Printf ("NativeProcessLinux::%s() pid = %" PRIu64 " tid %" PRIu64 ": new thread notification",
__FUNCTION__, GetID (), pid);
// Did we already create the thread?
bool already_tracked = false;
thread_sp = GetOrCreateThread (pid, already_tracked);
assert (thread_sp.get() && "failed to get or create the tracking data for newly created inferior thread");
// If the thread was already tracked, it means the main thread already received its SIGTRAP for the create.
if (already_tracked)
{ {
// A new thread creation is being signaled. This is one of two parts that come in // We can now resume this thread up since it is fully created.
// a non-deterministic order. pid is the thread id. reinterpret_cast<NativeThreadLinux*> (thread_sp.get ())->SetRunning ();
if (log) Resume (thread_sp->GetID (), LLDB_INVALID_SIGNAL_NUMBER);
log->Printf ("NativeProcessLinux::%s() pid = %" PRIu64 " tid %" PRIu64 ": new thread notification",
__FUNCTION__, GetID (), pid);
// Did we already create the thread?
bool already_tracked = false;
thread_sp = GetOrCreateThread (pid, already_tracked);
assert (thread_sp.get() && "failed to get or create the tracking data for newly created inferior thread");
// If the thread was already tracked, it means the main thread already received its SIGTRAP for the create.
if (already_tracked)
{
// We can now resume this thread up since it is fully created.
reinterpret_cast<NativeThreadLinux*> (thread_sp.get ())->SetRunning ();
Resume (thread_sp->GetID (), LLDB_INVALID_SIGNAL_NUMBER);
}
else
{
// Mark the thread as currently launching. Need to wait for SIGTRAP clone on the main thread before
// this thread is ready to go.
reinterpret_cast<NativeThreadLinux*> (thread_sp.get ())->SetLaunching ();
}
}
else if (info->si_pid == getpid () && (signo == SIGSTOP))
{
// This is a tgkill()-based stop.
if (thread_sp)
{
// An inferior thread just stopped. Mark it as such.
reinterpret_cast<NativeThreadLinux*> (thread_sp.get ())->SetStoppedBySignal (signo);
SetCurrentThreadID (thread_sp->GetID ());
// Remove this tid from the wait-for-stop set.
Mutex::Locker locker (m_wait_for_stop_tids_mutex);
auto removed_count = m_wait_for_stop_tids.erase (thread_sp->GetID ());
if (removed_count < 1)
{
log->Printf ("NativeProcessLinux::%s() pid = %" PRIu64 " tid %" PRIu64 ": tgkill()-stopped thread not in m_wait_for_stop_tids",
__FUNCTION__, GetID (), thread_sp->GetID ());
}
// If this is the last thread in the m_wait_for_stop_tids, we need to notify
// the delegate that a stop has occurred now that every thread that was supposed
// to stop has stopped.
if (m_wait_for_stop_tids.empty ())
{
if (log)
{
log->Printf ("NativeProcessLinux::%s() pid %" PRIu64 " tid %" PRIu64 ", setting process state to stopped now that all tids marked for stop have completed",
__FUNCTION__,
GetID (),
pid);
}
SetState (StateType::eStateStopped, true);
}
}
} }
else else
{ {
// Hmm, not sure what to do with this. // Mark the thread as currently launching. Need to wait for SIGTRAP clone on the main thread before
if (log) // this thread is ready to go.
log->Printf ("NativeProcessLinux::%s() pid %" PRIu64 " unsure how to handle SI_KILL or SI_USER signal", __FUNCTION__, GetID ()); reinterpret_cast<NativeThreadLinux*> (thread_sp.get ())->SetLaunching ();
} }
// Done handling.
return;
}
// Check for thread stop notification.
if ((info->si_pid == getpid ()) && (info->si_code == SI_TKILL) && (signo == SIGSTOP))
{
// This is a tgkill()-based stop.
if (thread_sp)
{
// An inferior thread just stopped. Mark it as such.
reinterpret_cast<NativeThreadLinux*> (thread_sp.get ())->SetStoppedBySignal (signo);
SetCurrentThreadID (thread_sp->GetID ());
// Remove this tid from the wait-for-stop set.
Mutex::Locker locker (m_wait_for_stop_tids_mutex);
auto removed_count = m_wait_for_stop_tids.erase (thread_sp->GetID ());
if (removed_count < 1)
{
log->Printf ("NativeProcessLinux::%s() pid = %" PRIu64 " tid %" PRIu64 ": tgkill()-stopped thread not in m_wait_for_stop_tids",
__FUNCTION__, GetID (), thread_sp->GetID ());
}
// If this is the last thread in the m_wait_for_stop_tids, we need to notify
// the delegate that a stop has occurred now that every thread that was supposed
// to stop has stopped.
if (m_wait_for_stop_tids.empty ())
{
if (log)
{
log->Printf ("NativeProcessLinux::%s() pid %" PRIu64 " tid %" PRIu64 ", setting process state to stopped now that all tids marked for stop have completed",
__FUNCTION__,
GetID (),
pid);
}
SetState (StateType::eStateStopped, true);
}
}
// Done handling.
return; return;
} }
@ -2265,38 +2266,40 @@ NativeProcessLinux::MonitorSignal(const siginfo_t *info, lldb::pid_t pid, bool e
} }
break; break;
case SIGABRT:
case SIGILL: case SIGILL:
{
// lldb::addr_t fault_addr = reinterpret_cast<lldb::addr_t>(info->si_addr);
// Can get the reason from here.
// ProcessMessage::CrashReason reason = GetCrashReasonForSIGILL(info);
// FIXME save the crash reason
SetState (StateType::eStateCrashed, true);
}
break;
case SIGFPE: case SIGFPE:
{
// lldb::addr_t fault_addr = reinterpret_cast<lldb::addr_t>(info->si_addr);
// Can get the crash reason from below.
// ProcessMessage::CrashReason reason = GetCrashReasonForSIGFPE(info);
// FIXME save the crash reason
SetState (StateType::eStateCrashed, true);
}
break;
case SIGBUS: case SIGBUS:
{ {
// lldb::addr_t fault_addr = reinterpret_cast<lldb::addr_t>(info->si_addr); // Break these out into separate cases once I have more data for each type of signal.
// Can get the crash reason from below. lldb::addr_t fault_addr = reinterpret_cast<lldb::addr_t>(info->si_addr);
// ProcessMessage::CrashReason reason = GetCrashReasonForSIGBUS(info); if (!exited)
// FIXME save the crash reason {
SetState (StateType::eStateCrashed); // This is just a pre-signal-delivery notification of the incoming signal.
// Send a stop to the debugger.
if (thread_sp)
{
reinterpret_cast<NativeThreadLinux*> (thread_sp.get ())->SetStoppedBySignal (signo);
SetCurrentThreadID (thread_sp->GetID ());
}
SetState (StateType::eStateStopped, true);
}
else
{
if (thread_sp)
{
// FIXME figure out how to report exit by signal correctly.
const uint64_t exception_type = static_cast<uint64_t> (SIGABRT);
reinterpret_cast<NativeThreadLinux*> (thread_sp.get ())->SetCrashedWithException (exception_type, fault_addr);
}
SetState (StateType::eStateCrashed, true);
}
} }
break; break;
default: default:
// FIXME Stop all threads here. if (log)
log->Printf ("NativeProcessLinux::%s unhandled signal %s (%d)", __FUNCTION__, GetUnixSignals ().GetSignalAsCString (signo), signo);
break; break;
} }
} }

View File

@ -284,11 +284,12 @@ class GdbRemoteTestCaseBase(TestBase):
raise Exception("failed to create a socket to the launched debug monitor after %d tries" % attempts) raise Exception("failed to create a socket to the launched debug monitor after %d tries" % attempts)
def launch_process_for_attach(self,inferior_args=None, sleep_seconds=3): def launch_process_for_attach(self,inferior_args=None, sleep_seconds=3, exe_path=None):
# We're going to start a child process that the debug monitor stub can later attach to. # We're going to start a child process that the debug monitor stub can later attach to.
# This process needs to be started so that it just hangs around for a while. We'll # This process needs to be started so that it just hangs around for a while. We'll
# have it sleep. # have it sleep.
exe_path = os.path.abspath("a.out") if not exe_path:
exe_path = os.path.abspath("a.out")
args = [exe_path] args = [exe_path]
if inferior_args: if inferior_args:
@ -298,7 +299,7 @@ class GdbRemoteTestCaseBase(TestBase):
return subprocess.Popen(args) return subprocess.Popen(args)
def prep_debug_monitor_and_inferior(self, inferior_args=None, inferior_sleep_seconds=3): def prep_debug_monitor_and_inferior(self, inferior_args=None, inferior_sleep_seconds=3, inferior_exe_path=None):
"""Prep the debug monitor, the inferior, and the expected packet stream. """Prep the debug monitor, the inferior, and the expected packet stream.
Handle the separate cases of using the debug monitor in attach-to-inferior mode Handle the separate cases of using the debug monitor in attach-to-inferior mode
@ -323,7 +324,7 @@ class GdbRemoteTestCaseBase(TestBase):
if self._inferior_startup == self._STARTUP_ATTACH or self._inferior_startup == self._STARTUP_ATTACH_MANUALLY: if self._inferior_startup == self._STARTUP_ATTACH or self._inferior_startup == self._STARTUP_ATTACH_MANUALLY:
# Launch the process that we'll use as the inferior. # Launch the process that we'll use as the inferior.
inferior = self.launch_process_for_attach(inferior_args=inferior_args, sleep_seconds=inferior_sleep_seconds) inferior = self.launch_process_for_attach(inferior_args=inferior_args, sleep_seconds=inferior_sleep_seconds, exe_path=inferior_exe_path)
self.assertIsNotNone(inferior) self.assertIsNotNone(inferior)
self.assertTrue(inferior.pid > 0) self.assertTrue(inferior.pid > 0)
if self._inferior_startup == self._STARTUP_ATTACH: if self._inferior_startup == self._STARTUP_ATTACH:
@ -336,7 +337,9 @@ class GdbRemoteTestCaseBase(TestBase):
if self._inferior_startup == self._STARTUP_LAUNCH: if self._inferior_startup == self._STARTUP_LAUNCH:
# Build launch args # Build launch args
launch_args = [os.path.abspath('a.out')] if not inferior_exe_path:
inferior_exe_path = os.path.abspath("a.out")
launch_args = [inferior_exe_path]
if inferior_args: if inferior_args:
launch_args.extend(inferior_args) launch_args.extend(inferior_args)

View File

@ -0,0 +1,8 @@
LEVEL = ../../../make
CFLAGS_EXTRAS := -D__STDC_LIMIT_MACROS -D__STDC_FORMAT_MACROS -std=c++11
# LD_EXTRAS := -lpthread
CXX_SOURCES := main.cpp
MAKE_DSYM :=NO
include $(LEVEL)/Makefile.rules

View File

@ -0,0 +1,45 @@
import unittest2
# Add the directory above ours to the python library path since we
# will import from there.
import os.path
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
import gdbremote_testcase
import signal
from lldbtest import *
class TestGdbRemoteAbort(gdbremote_testcase.GdbRemoteTestCaseBase):
mydir = TestBase.compute_mydir(__file__)
def inferior_abort_received(self):
procs = self.prep_debug_monitor_and_inferior(inferior_args=["abort"])
self.assertIsNotNone(procs)
self.test_sequence.add_log_lines([
"read packet: $vCont;c#00",
{"direction":"send", "regex":r"^\$T([0-9a-fA-F]{2}).*#[0-9a-fA-F]{2}$", "capture":{ 1:"hex_exit_code"} },
], True)
context = self.expect_gdbremote_sequence()
self.assertIsNotNone(context)
hex_exit_code = context.get("hex_exit_code")
self.assertIsNotNone(hex_exit_code)
self.assertEquals(int(hex_exit_code, 16), signal.SIGABRT)
@debugserver_test
@dsym_test
def test_inferior_abort_received_debugserver_dsym(self):
self.init_debugserver_test()
self.buildDsym()
self.inferior_abort_received()
@llgs_test
@dwarf_test
def test_inferior_abort_received_llgs_dwarf(self):
self.init_llgs_test()
self.buildDwarf()
self.inferior_abort_received()

View File

@ -0,0 +1,46 @@
import unittest2
# Add the directory above ours to the python library path since we
# will import from there.
import os.path
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
import gdbremote_testcase
# import signal
from lldbtest import *
class TestGdbRemoteSegFault(gdbremote_testcase.GdbRemoteTestCaseBase):
mydir = TestBase.compute_mydir(__file__)
GDB_REMOTE_STOP_CODE_BAD_ACCESS = 0x91
def inferior_seg_fault_received(self):
procs = self.prep_debug_monitor_and_inferior(inferior_args=["segfault"])
self.assertIsNotNone(procs)
self.test_sequence.add_log_lines([
"read packet: $vCont;c#00",
{"direction":"send", "regex":r"^\$T([0-9a-fA-F]{2}).*#[0-9a-fA-F]{2}$", "capture":{ 1:"hex_exit_code"} },
], True)
context = self.expect_gdbremote_sequence()
self.assertIsNotNone(context)
hex_exit_code = context.get("hex_exit_code")
self.assertIsNotNone(hex_exit_code)
self.assertEquals(int(hex_exit_code, 16), self.GDB_REMOTE_STOP_CODE_BAD_ACCESS)
@debugserver_test
@dsym_test
def test_inferior_seg_fault_received_debugserver_dsym(self):
self.init_debugserver_test()
self.buildDsym()
self.inferior_seg_fault_received()
@llgs_test
@dwarf_test
def test_inferior_seg_fault_received_llgs_dwarf(self):
self.init_llgs_test()
self.buildDwarf()
self.inferior_seg_fault_received()

View File

@ -0,0 +1,39 @@
#include <cstdlib>
#include <cstring>
#include <iostream>
namespace
{
const char *const SEGFAULT_COMMAND = "segfault";
const char *const ABORT_COMMAND = "abort";
}
int main (int argc, char **argv)
{
if (argc < 2)
{
std::cout << "expected at least one command provided on the command line" << std::endl;
}
// Process command line args.
for (int i = 1; i < argc; ++i)
{
const char *const command = argv[i];
if (std::strstr (command, SEGFAULT_COMMAND))
{
// Perform a null pointer access.
int *const null_int_ptr = nullptr;
*null_int_ptr = 0xDEAD;
}
else if (std::strstr (command, ABORT_COMMAND))
{
std::abort();
}
else
{
std::cout << "Unsupported command: " << command << std::endl;
}
}
return 0;
}