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,
|
||||||
(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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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