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 == getpid ()) ? "is monitor" : "is not monitor",
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
// 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)
{
// 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);
}
}
// 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
{
// Hmm, not sure what to do with this.
if (log)
log->Printf ("NativeProcessLinux::%s() pid %" PRIu64 " unsure how to handle SI_KILL or SI_USER signal", __FUNCTION__, GetID ());
// 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 ();
}
// 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;
}
@ -2265,38 +2266,40 @@ NativeProcessLinux::MonitorSignal(const siginfo_t *info, lldb::pid_t pid, bool e
}
break;
case SIGABRT:
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:
{
// 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:
{
// lldb::addr_t fault_addr = reinterpret_cast<lldb::addr_t>(info->si_addr);
// Can get the crash reason from below.
// ProcessMessage::CrashReason reason = GetCrashReasonForSIGBUS(info);
// FIXME save the crash reason
SetState (StateType::eStateCrashed);
// Break these out into separate cases once I have more data for each type of signal.
lldb::addr_t fault_addr = reinterpret_cast<lldb::addr_t>(info->si_addr);
if (!exited)
{
// 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;
default:
// FIXME Stop all threads here.
if (log)
log->Printf ("NativeProcessLinux::%s unhandled signal %s (%d)", __FUNCTION__, GetUnixSignals ().GetSignalAsCString (signo), signo);
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)
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.
# This process needs to be started so that it just hangs around for a while. We'll
# have it sleep.
exe_path = os.path.abspath("a.out")
if not exe_path:
exe_path = os.path.abspath("a.out")
args = [exe_path]
if inferior_args:
@ -298,7 +299,7 @@ class GdbRemoteTestCaseBase(TestBase):
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.
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:
# 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.assertTrue(inferior.pid > 0)
if self._inferior_startup == self._STARTUP_ATTACH:
@ -336,7 +337,9 @@ class GdbRemoteTestCaseBase(TestBase):
if self._inferior_startup == self._STARTUP_LAUNCH:
# 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:
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;
}