forked from OSchip/llvm-project
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:
parent
09d84addb7
commit
58a2f6692b
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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()
|
||||
|
|
@ -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()
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue