Index: include/lldb/Target/StopInfo.h =================================================================== --- include/lldb/Target/StopInfo.h +++ include/lldb/Target/StopInfo.h @@ -164,7 +164,7 @@ CreateStopReasonWithWatchpointID (Thread &thread, lldb::break_id_t watch_id); static lldb::StopInfoSP - CreateStopReasonWithSignal (Thread &thread, int signo); + CreateStopReasonWithSignal (Thread &thread, int signo, const char *description = nullptr); static lldb::StopInfoSP CreateStopReasonToTrace (Thread &thread); Index: source/Plugins/Process/Linux/NativeProcessLinux.cpp =================================================================== --- source/Plugins/Process/Linux/NativeProcessLinux.cpp +++ source/Plugins/Process/Linux/NativeProcessLinux.cpp @@ -2177,7 +2177,7 @@ // leave the signal intact if this is the thread that was chosen as the // triggering thread. if (m_pending_notification_up && m_pending_notification_up->triggering_tid == pid) - linux_thread_sp->SetStoppedBySignal(SIGSTOP); + linux_thread_sp->SetStoppedBySignal(SIGSTOP, info); else linux_thread_sp->SetStoppedBySignal(0); @@ -2217,22 +2217,8 @@ // This thread is stopped. ThreadDidStop (pid, false); - switch (signo) - { - case SIGSEGV: - case SIGILL: - case SIGFPE: - case SIGBUS: - if (thread_sp) - std::static_pointer_cast (thread_sp)->SetCrashedWithException (*info); - break; - default: - // This is just a pre-signal-delivery notification of the incoming signal. - if (thread_sp) - std::static_pointer_cast (thread_sp)->SetStoppedBySignal (signo); - - break; - } + if (thread_sp) + std::static_pointer_cast (thread_sp)->SetStoppedBySignal(signo, info); // Send a stop to the debugger after we get all other threads to stop. StopRunningThreads (pid); Index: source/Plugins/Process/Linux/NativeThreadLinux.h =================================================================== --- source/Plugins/Process/Linux/NativeThreadLinux.h +++ source/Plugins/Process/Linux/NativeThreadLinux.h @@ -60,7 +60,7 @@ SetStepping (); void - SetStoppedBySignal (uint32_t signo); + SetStoppedBySignal(uint32_t signo, const siginfo_t *info = nullptr); /// Return true if the thread is stopped. /// If stopped by a signal, indicate the signo in the signo argument. Index: source/Plugins/Process/Linux/NativeThreadLinux.cpp =================================================================== --- source/Plugins/Process/Linux/NativeThreadLinux.cpp +++ source/Plugins/Process/Linux/NativeThreadLinux.cpp @@ -125,15 +125,7 @@ if (log) LogThreadStopInfo (*log, m_stop_info, "m_stop_info in thread:"); stop_info = m_stop_info; - switch (m_stop_info.reason) - { - case StopReason::eStopReasonException: - case StopReason::eStopReasonBreakpoint: - case StopReason::eStopReasonWatchpoint: - description = m_stop_description; - default: - break; - } + description = m_stop_description; if (log) LogThreadStopInfo (*log, stop_info, "returned stop_info:"); @@ -250,7 +242,7 @@ } void -NativeThreadLinux::SetStoppedBySignal (uint32_t signo) +NativeThreadLinux::SetStoppedBySignal(uint32_t signo, const siginfo_t *info) { Log *log (GetLogIfAllCategoriesSet (LIBLLDB_LOG_THREAD)); if (log) @@ -262,6 +254,20 @@ m_stop_info.reason = StopReason::eStopReasonSignal; m_stop_info.details.signal.signo = signo; + + m_stop_description.clear(); + switch (signo) + { + case SIGSEGV: + case SIGBUS: + case SIGFPE: + case SIGILL: + if (! info) + break; + const auto reason = GetCrashReason(*info); + m_stop_description = GetCrashReasonString(reason, reinterpret_cast(info->si_addr)); + break; + } } bool @@ -356,20 +362,6 @@ } void -NativeThreadLinux::SetCrashedWithException (const siginfo_t& info) -{ - const StateType new_state = StateType::eStateCrashed; - MaybeLogStateChange (new_state); - m_state = new_state; - - m_stop_info.reason = StopReason::eStopReasonException; - m_stop_info.details.signal.signo = info.si_signo; - - const auto reason = GetCrashReason (info); - m_stop_description = GetCrashReasonString (reason, reinterpret_cast(info.si_addr)); -} - -void NativeThreadLinux::SetSuspended () { const StateType new_state = StateType::eStateSuspended; Index: source/Plugins/Process/POSIX/CrashReason.cpp =================================================================== --- source/Plugins/Process/POSIX/CrashReason.cpp +++ source/Plugins/Process/POSIX/CrashReason.cpp @@ -134,69 +134,69 @@ break; case CrashReason::eInvalidAddress: - str = "invalid address"; + str = "signal SIGSEGV: invalid address"; AppendFaultAddr (str, fault_addr); break; case CrashReason::ePrivilegedAddress: - str = "address access protected"; + str = "signal SIGSEGV: address access protected"; AppendFaultAddr (str, fault_addr); break; case CrashReason::eIllegalOpcode: - str = "illegal instruction"; + str = "signal SIGILL: illegal instruction"; break; case CrashReason::eIllegalOperand: - str = "illegal instruction operand"; + str = "signal SIGILL: illegal instruction operand"; break; case CrashReason::eIllegalAddressingMode: - str = "illegal addressing mode"; + str = "signal SIGILL: illegal addressing mode"; break; case CrashReason::eIllegalTrap: - str = "illegal trap"; + str = "signal SIGILL: illegal trap"; break; case CrashReason::ePrivilegedOpcode: - str = "privileged instruction"; + str = "signal SIGILL: privileged instruction"; break; case CrashReason::ePrivilegedRegister: - str = "privileged register"; + str = "signal SIGILL: privileged register"; break; case CrashReason::eCoprocessorError: - str = "coprocessor error"; + str = "signal SIGILL: coprocessor error"; break; case CrashReason::eInternalStackError: - str = "internal stack error"; + str = "signal SIGILL: internal stack error"; break; case CrashReason::eIllegalAlignment: - str = "illegal alignment"; + str = "signal SIGBUS: illegal alignment"; break; case CrashReason::eIllegalAddress: - str = "illegal address"; + str = "signal SIGBUS: illegal address"; break; case CrashReason::eHardwareError: - str = "hardware error"; + str = "signal SIGBUS: hardware error"; break; case CrashReason::eIntegerDivideByZero: - str = "integer divide by zero"; + str = "signal SIGFPE: integer divide by zero"; break; case CrashReason::eIntegerOverflow: - str = "integer overflow"; + str = "signal SIGFPE: integer overflow"; break; case CrashReason::eFloatDivideByZero: - str = "floating point divide by zero"; + str = "signal SIGFPE: floating point divide by zero"; break; case CrashReason::eFloatOverflow: - str = "floating point overflow"; + str = "signal SIGFPE: floating point overflow"; break; case CrashReason::eFloatUnderflow: - str = "floating point underflow"; + str = "signal SIGFPE: floating point underflow"; break; case CrashReason::eFloatInexactResult: - str = "inexact floating point result"; + str = "signal SIGFPE: inexact floating point result"; break; case CrashReason::eFloatInvalidOperation: - str = "invalid floating point operation"; + str = "signal SIGFPE: invalid floating point operation"; break; case CrashReason::eFloatSubscriptRange: - str = "invalid floating point subscript range"; + str = "signal SIGFPE: invalid floating point subscript range"; break; } Index: source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp =================================================================== --- source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -2081,11 +2081,11 @@ if (thread_sp->GetTemporaryResumeState() == eStateStepping) thread_sp->SetStopInfo (StopInfo::CreateStopReasonToTrace (*thread_sp)); else - thread_sp->SetStopInfo (StopInfo::CreateStopReasonWithSignal(*thread_sp, signo)); + thread_sp->SetStopInfo (StopInfo::CreateStopReasonWithSignal(*thread_sp, signo, description.c_str())); } } if (!handled) - thread_sp->SetStopInfo (StopInfo::CreateStopReasonWithSignal (*thread_sp, signo)); + thread_sp->SetStopInfo (StopInfo::CreateStopReasonWithSignal (*thread_sp, signo, description.c_str())); } if (!description.empty()) Index: source/Target/StopInfo.cpp =================================================================== --- source/Target/StopInfo.cpp +++ source/Target/StopInfo.cpp @@ -871,9 +871,10 @@ { public: - StopInfoUnixSignal (Thread &thread, int signo) : + StopInfoUnixSignal (Thread &thread, int signo, const char *description) : StopInfo (thread, signo) { + SetDescription (description); } virtual ~StopInfoUnixSignal () @@ -1161,9 +1162,9 @@ } StopInfoSP -StopInfo::CreateStopReasonWithSignal (Thread &thread, int signo) +StopInfo::CreateStopReasonWithSignal (Thread &thread, int signo, const char *description) { - return StopInfoSP (new StopInfoUnixSignal (thread, signo)); + return StopInfoSP (new StopInfoUnixSignal (thread, signo, description)); } StopInfoSP Index: test/functionalities/inferior-changed/TestInferiorChanged.py =================================================================== --- test/functionalities/inferior-changed/TestInferiorChanged.py +++ test/functionalities/inferior-changed/TestInferiorChanged.py @@ -49,20 +49,14 @@ self.runCmd("run", RUN_SUCCEEDED) - if self.platformIsDarwin(): - stop_reason = 'stop reason = EXC_BAD_ACCESS' - else: - stop_reason = 'stop reason = invalid address' - - # The stop reason of the thread should be a bad access exception. - self.expect("thread list", STOPPED_DUE_TO_EXC_BAD_ACCESS, - substrs = ['stopped', - stop_reason]) + # We should have one crashing thread + self.assertEquals( + len(lldbutil.get_crashed_threads(self, self.dbg.GetSelectedTarget().GetProcess())), + 1, + STOPPED_DUE_TO_EXC_BAD_ACCESS) # And it should report the correct line number. - self.expect("thread backtrace all", - substrs = [stop_reason, - 'main.c:%d' % self.line1]) + self.expect("thread backtrace all", substrs = ['main.c:%d' % self.line1]) def inferior_not_crashing(self): """Test lldb reloads the inferior after it was changed during the session.""" @@ -73,13 +67,10 @@ self.runCmd("run", RUN_SUCCEEDED) self.runCmd("process status") - if self.platformIsDarwin(): - stop_reason = 'EXC_BAD_ACCESS' - else: - stop_reason = 'invalid address' - - if stop_reason in self.res.GetOutput(): - self.fail("Inferior changed, but lldb did not perform a reload") + self.assertNotEquals( + len(lldbutil.get_crashed_threads(self, self.dbg.GetSelectedTarget().GetProcess())), + 1, + "Inferior changed, but lldb did not perform a reload") # Break inside the main. lldbutil.run_break_set_by_file_and_line (self, "main2.c", self.line2, num_expected_locations=1, loc_exact=True) Index: test/functionalities/inferior-crashing/TestInferiorCrashing.py =================================================================== --- test/functionalities/inferior-crashing/TestInferiorCrashing.py +++ test/functionalities/inferior-crashing/TestInferiorCrashing.py @@ -79,7 +79,7 @@ self.inferior_crashing_expr_step_expr() @expectedFailureFreeBSD('llvm.org/pr15989') # Couldn't allocate space for the stack frame - @expectedFailureAll("llvm.org/pr23139", oslist=["linux"], compiler="gcc", compiler_version=[">=","4.9"], archs=["i386"]) + @skipIfLinux # Inferior exits after stepping after a segfault. This is working as intended IMHO. def test_inferior_crashing_expr_step_and_expr_dwarf(self): """Test that lldb expressions work before and after stepping after a crash.""" self.buildDwarf() @@ -89,17 +89,11 @@ lldbutil.run_break_set_by_file_and_line (self, "main.c", line, num_expected_locations=1, loc_exact=True) def check_stop_reason(self): - if self.platformIsDarwin(): - stop_reason = 'stop reason = EXC_BAD_ACCESS' - else: - stop_reason = 'stop reason = invalid address' - - # The stop reason of the thread should be a bad access exception. - self.expect("thread list", STOPPED_DUE_TO_EXC_BAD_ACCESS, - substrs = ['stopped', - stop_reason]) - - return stop_reason + # We should have one crashing thread + self.assertEquals( + len(lldbutil.get_crashed_threads(self, self.dbg.GetSelectedTarget().GetProcess())), + 1, + STOPPED_DUE_TO_EXC_BAD_ACCESS) def get_api_stop_reason(self): return lldb.eStopReasonException @@ -116,7 +110,16 @@ self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) self.runCmd("run", RUN_SUCCEEDED) - stop_reason = self.check_stop_reason() + # The exact stop reason depends on the platform + if self.platformIsDarwin(): + stop_reason = 'stop reason = EXC_BAD_ACCESS' + elif self.getPlatform() == "linux": + stop_reason = 'stop reason = signal SIGSEGV' + else: + stop_reason = 'stop reason = invalid address' + self.expect("thread list", STOPPED_DUE_TO_EXC_BAD_ACCESS, + substrs = ['stopped', + stop_reason]) # And it should report the correct line number. self.expect("thread backtrace all", @@ -139,12 +142,12 @@ "instead the actual state is: '%s'" % lldbutil.state_type_to_str(process.GetState())) - thread = lldbutil.get_stopped_thread(process, self.get_api_stop_reason()) - if not thread: + threads = lldbutil.get_crashed_threads(self, process) + if len(threads) != 1: self.fail("Fail to stop the thread upon bad access exception") if self.TraceOn(): - lldbutil.print_stacktrace(thread) + lldbutil.print_stacktrace(threads[0]) def inferior_crashing_registers(self): """Test that lldb can read registers after crashing.""" @@ -185,7 +188,7 @@ 'stop reason = breakpoint']) self.runCmd("next") - stop_reason = self.check_stop_reason() + self.check_stop_reason() # The lldb expression interpreter should be able to read from addresses of the inferior after a crash. self.expect("p argv[0]", @@ -198,8 +201,7 @@ # And it should report the correct line number. self.expect("thread backtrace all", - substrs = [stop_reason, - 'main.c:%d' % self.line]) + substrs = ['main.c:%d' % self.line]) def inferior_crashing_step_after_break(self): """Test that lldb behaves correctly when stepping after a crash.""" @@ -209,8 +211,17 @@ self.runCmd("run", RUN_SUCCEEDED) self.check_stop_reason() - self.runCmd("next") - self.check_stop_reason() + expected_state = 'exited' # Provide the exit code. + if self.platformIsDarwin(): + expected_state = 'stopped' # TODO: Determine why 'next' and 'continue' have no effect after a crash. + + self.expect("next", + substrs = ['Process', expected_state]) + + if expected_state == 'exited': + self.expect("thread list", error=True,substrs = ['Process must be launched']) + else: + self.check_stop_reason() def inferior_crashing_expr_step_expr(self): """Test that lldb expressions work before and after stepping after a crash.""" Index: test/functionalities/inferior-crashing/recursive-inferior/TestRecursiveInferior.py =================================================================== --- test/functionalities/inferior-crashing/recursive-inferior/TestRecursiveInferior.py +++ test/functionalities/inferior-crashing/recursive-inferior/TestRecursiveInferior.py @@ -79,6 +79,7 @@ self.recursive_inferior_crashing_expr_step_expr() @expectedFailureFreeBSD('llvm.org/pr15989') # Couldn't allocate space for the stack frame + @skipIfLinux # Inferior exits after stepping after a segfault. This is working as intended IMHO. def test_recursive_inferior_crashing_expr_step_and_expr_dwarf(self): """Test that lldb expressions work before and after stepping after a crash.""" self.buildDwarf() @@ -88,17 +89,11 @@ lldbutil.run_break_set_by_file_and_line (self, "main.c", line, num_expected_locations=1, loc_exact=True) def check_stop_reason(self): - if self.platformIsDarwin(): - stop_reason = 'stop reason = EXC_BAD_ACCESS' - else: - stop_reason = 'stop reason = invalid address' - - # The stop reason of the thread should be a bad access exception. - self.expect("thread list", STOPPED_DUE_TO_EXC_BAD_ACCESS, - substrs = ['stopped', - stop_reason]) - - return stop_reason + # We should have one crashing thread + self.assertEquals( + len(lldbutil.get_crashed_threads(self, self.dbg.GetSelectedTarget().GetProcess())), + 1, + STOPPED_DUE_TO_EXC_BAD_ACCESS) def setUp(self): # Call super's setUp(). @@ -112,7 +107,17 @@ self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) self.runCmd("run", RUN_SUCCEEDED) - stop_reason = self.check_stop_reason() + + # The exact stop reason depends on the platform + if self.platformIsDarwin(): + stop_reason = 'stop reason = EXC_BAD_ACCESS' + elif self.getPlatform() == "linux": + stop_reason = 'stop reason = signal SIGSEGV' + else: + stop_reason = 'stop reason = invalid address' + self.expect("thread list", STOPPED_DUE_TO_EXC_BAD_ACCESS, + substrs = ['stopped', + stop_reason]) # And it should report a backtrace that includes main and the crash site. self.expect("thread backtrace all", @@ -139,12 +144,12 @@ "instead the actual state is: '%s'" % lldbutil.state_type_to_str(process.GetState())) - thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonException) - if not thread: + threads = lldbutil.get_crashed_threads(self, process) + if len(threads) != 1: self.fail("Fail to stop the thread upon bad access exception") if self.TraceOn(): - lldbutil.print_stacktrace(thread) + lldbutil.print_stacktrace(threads[0]) def recursive_inferior_crashing_registers(self): """Test that lldb can read registers after crashing.""" @@ -182,7 +187,7 @@ 'stop reason = breakpoint']) self.runCmd("next") - stop_reason = self.check_stop_reason() + self.check_stop_reason() # The lldb expression interpreter should be able to read from addresses of the inferior after a crash. self.expect("p i", @@ -193,8 +198,7 @@ # And it should report the correct line number. self.expect("thread backtrace all", - substrs = [stop_reason, - 'main.c:%d' % self.line]) + substrs = ['main.c:%d' % self.line]) def recursive_inferior_crashing_step_after_break(self): """Test that lldb behaves correctly when stepping after a crash.""" @@ -205,7 +209,7 @@ self.check_stop_reason() expected_state = 'exited' # Provide the exit code. - if self.platformIsDarwin() or self.getPlatform() == "linux": + if self.platformIsDarwin(): expected_state = 'stopped' # TODO: Determine why 'next' and 'continue' have no effect after a crash. self.expect("next", Index: test/functionalities/signal/handle-segv/Makefile =================================================================== --- /dev/null +++ test/functionalities/signal/handle-segv/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules Index: test/functionalities/signal/handle-segv/TestHandleSegv.py =================================================================== --- /dev/null +++ test/functionalities/signal/handle-segv/TestHandleSegv.py @@ -0,0 +1,45 @@ +"""Test that we can debug inferiors that handle SIGSEGV by themselves""" + +import os +import unittest2 +import lldb +from lldbtest import * +import lldbutil +import re + + +class HandleSegvTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + @skipIfWindows # signals do not exist on Windows + @skipIfDarwin + def test_inferior_handle_sigsegv_with_dwarf(self): + self.buildDefault() + exe = os.path.join(os.getcwd(), "a.out") + + # Create a target by the debugger. + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + + # launch + process = target.LaunchSimple(None, None, self.get_process_working_directory()) + self.assertTrue(process, PROCESS_IS_VALID) + self.assertEqual(process.GetState(), lldb.eStateStopped) + signo = process.GetUnixSignals().GetSignalNumberFromName("SIGSEGV") + + thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonSignal) + self.assertTrue(thread.IsValid(), "Thread should be stopped due to a signal") + self.assertTrue(thread.GetStopReasonDataCount() >= 1, "There was data in the event.") + self.assertEqual(thread.GetStopReasonDataAtIndex(0), signo, "The stop signal was SIGSEGV") + + # Continue until we exit. + process.Continue() + self.assertEqual(process.GetState(), lldb.eStateExited) + self.assertEqual(process.GetExitStatus(), 0) + +if __name__ == '__main__': + import atexit + lldb.SBDebugger.Initialize() + atexit.register(lambda: lldb.SBDebugger.Terminate()) + unittest2.main() Index: test/functionalities/signal/handle-segv/main.c =================================================================== --- /dev/null +++ test/functionalities/signal/handle-segv/main.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include + +void *address; +size_t size = 0x1000; +volatile sig_atomic_t signaled = 0; + +void handler(int sig) +{ + signaled = 1; + if (munmap(address, size) != 0) + { + perror("munmap"); + _exit(5); + } + + void* newaddr = mmap(address, size, PROT_READ | PROT_WRITE, + MAP_ANON | MAP_FIXED | MAP_PRIVATE, -1, 0); + if (newaddr != address) + { + fprintf(stderr, "Newly mmaped address (%p) does not equal old address (%p).\n", + newaddr, address); + _exit(6); + } + *(int*)newaddr = 47; +} + +int main() +{ + if (signal(SIGSEGV, handler) == SIG_ERR) + { + perror("signal"); + return 1; + } + + address = mmap(NULL, size, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0); + if (address == MAP_FAILED) + { + perror("mmap"); + return 2; + } + + if (*(int*)address != 47) + return 3; + + if (! signaled) + return 4; + + return 0; +} Index: test/functionalities/thread/concurrent_events/TestConcurrentEvents.py =================================================================== --- test/functionalities/thread/concurrent_events/TestConcurrentEvents.py +++ test/functionalities/thread/concurrent_events/TestConcurrentEvents.py @@ -390,6 +390,13 @@ self.crash_count > 0 or \ self.inferior_process.GetState() == lldb.eStateExited + def count_signaled_threads(self): + count = 0 + for thread in self.inferior_process: + if thread.GetStopReason() == lldb.eStopReasonSignal and thread.GetStopReasonDataAtIndex(0) == self.inferior_process.GetUnixSignals().GetSignalNumberFromName('SIGUSR1'): + count += 1 + return count + def do_thread_actions(self, num_breakpoint_threads = 0, num_signal_threads = 0, @@ -470,16 +477,16 @@ num_threads, "\n\t".join(self.describe_threads()))) - self.signal_count = len(lldbutil.get_stopped_threads(self.inferior_process, lldb.eStopReasonSignal)) - self.crash_count = len(lldbutil.get_stopped_threads(self.inferior_process, lldb.eStopReasonException)) + self.signal_count = self.count_signaled_threads() + self.crash_count = len(lldbutil.get_crashed_threads(self, self.inferior_process)) # Run to completion (or crash) while not self.inferior_done(): if self.TraceOn(): self.runCmd("thread backtrace all") self.runCmd("continue") - self.signal_count += len(lldbutil.get_stopped_threads(self.inferior_process, lldb.eStopReasonSignal)) - self.crash_count += len(lldbutil.get_stopped_threads(self.inferior_process, lldb.eStopReasonException)) + self.signal_count += self.count_signaled_threads() + self.crash_count += len(lldbutil.get_crashed_threads(self, self.inferior_process)) if num_crash_threads > 0 or num_delay_crash_threads > 0: # Expecting a crash Index: test/lldbutil.py =================================================================== --- test/lldbutil.py +++ test/lldbutil.py @@ -553,6 +553,25 @@ return threads +def is_thread_crashed (test, thread): + """In the test suite we dereference a null pointer to simulate a crash. The way this is + reported depends on the platform.""" + if test.platformIsDarwin(): + return thread.GetStopReason() == lldb.eStopReasonException and "EXC_BAD_ACCESS" in thread.GetStopDescription(100) + elif test.getPlatform() == "linux": + return thread.GetStopReason() == lldb.eStopReasonSignal and thread.GetStopReasonDataAtIndex(0) == thread.GetProcess().GetUnixSignals().GetSignalNumberFromName("SIGSEGV") + else: + return "invalid address" in thread.GetStopDescription(100) + +def get_crashed_threads (test, process): + threads = [] + if process.GetState() != lldb.eStateStopped: + return threads + for thread in process: + if is_thread_crashed(test, thread): + threads.append(thread) + return threads + def continue_to_breakpoint (process, bkpt): """ Continues the process, if it stops, returns the threads stopped at bkpt; otherwise, returns None""" process.Continue()