Index: test/tools/lldb-mi/TestMiNotification.py =================================================================== --- test/tools/lldb-mi/TestMiNotification.py +++ test/tools/lldb-mi/TestMiNotification.py @@ -119,5 +119,89 @@ # Clean up debugserver_child.terminate(force = True) + @lldbmi_test + @expectedFailureWindows("llvm.org/pr22274: need a pexpect replacement for windows") + def test_lldbmi_stopped_when_segfault_local(self): + """Test that 'lldb-mi --interpreter' notifies after it was stopped when segfault occurred (local).""" + + self.spawnLldbMi(args = None) + + # Load executable + self.runCmd("-file-exec-and-symbols %s" % self.myexe) + self.expect("\^done") + + # Run to main + self.runCmd("-break-insert -f main") + self.expect("\^done,bkpt={number=\"1\"") + self.runCmd("-exec-run") + self.expect("\^running") + self.expect("\*stopped,reason=\"breakpoint-hit\"") + + # Set dosegfault=1 and run (to cause a segfault error) + self.runCmd("-data-evaluate-expression \"dosegfault=1\"") + self.expect("\^done,value=\"1\"") + self.runCmd("-exec-continue") + self.expect("\^running") + + # Test that *stopped is printed + self.expect("\*stopped,reason=\"exception-received\",exception=\"EXC_BAD_ACCESS \(code=1, address=0x0\)\",thread-id=\"1\",stopped-threads=\"all\"") + + @lldbmi_test + @expectedFailureWindows("llvm.org/pr22274: need a pexpect replacement for windows") + @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin") + def test_lldbmi_stopped_when_segfault_remote(self): + """Test that 'lldb-mi --interpreter' notifies after it was stopped when segfault occurred (remote).""" + + # Prepare debugserver + import os, sys + lldb_gdbserver_folder = os.path.abspath(os.path.join(os.path.dirname(os.getcwd()), "lldb-gdbserver")) + sys.path.append(lldb_gdbserver_folder) + import lldbgdbserverutils + debugserver_exe = lldbgdbserverutils.get_debugserver_exe() + if not debugserver_exe: + raise Exception("debugserver not found") + hostname = "localhost" + import random + port = 12000 + random.randint(0,3999) # the same as GdbRemoteTestCaseBase.get_next_port + import pexpect + debugserver_child = pexpect.spawn("%s %s:%d" % (debugserver_exe, hostname, port)) + + self.spawnLldbMi(args = None) + + # Connect to debugserver + self.runCmd("-interpreter-exec command \"platform select remote-macosx --sysroot /\"") + self.expect("\^done") + self.runCmd("-file-exec-and-symbols %s" % self.myexe) + self.expect("\^done") + self.runCmd("-interpreter-exec command \"process connect connect://%s:%d\"" % (hostname, port)) + self.expect("\^done") + + try: + # Run to main + self.runCmd("-break-insert -f main") + self.expect("\^done,bkpt={number=\"1\"") + #FIXME -exec-run doesn't work + self.runCmd("-interpreter-exec command \"process launch\"") #FIXME: self.runCmd("-exec-run") + self.expect("\^done") #FIXME: self.expect("\^running") + self.expect("\*stopped,reason=\"breakpoint-hit\"") + + # Set dosegfault=1 and run (to cause a segfault error) + self.runCmd("-data-evaluate-expression \"dosegfault=1\"") + self.expect("\^done,value=\"1\"") + self.runCmd("-exec-continue") + self.expect("\^running") + + # Test that *stopped is printed + self.expect("\*stopped,reason=\"exception-received\",exception=\"EXC_BAD_ACCESS \(code=1, address=0x0\)\",thread-id=\"1\",stopped-threads=\"all\"") + + # Exit + self.runCmd("-gdb-exit") + self.runCmd("") #FIXME lldb-mi hangs here on Linux; extra return is needed + self.expect("\^exit") + + finally: + # Clean up + debugserver_child.terminate(force = True) + if __name__ == '__main__': unittest2.main() Index: test/tools/lldb-mi/main.c =================================================================== --- test/tools/lldb-mi/main.c +++ test/tools/lldb-mi/main.c @@ -11,7 +11,7 @@ extern int a_MyFunction(); extern int b_MyFunction(); extern int infloop(); -int doloop; +int doloop, dosegfault; int g_MyVar = 3; static int s_MyVar = 4; int main (int argc, char const *argv[]) @@ -24,6 +24,8 @@ //BP_localstest -- it must be at line #24 (or fix it in main*.micmds) if (doloop) // BP_doloop infloop(); + if (dosegfault) + *(volatile int *)NULL = 1; if (argc > 1 && *argv[1] == 'l') { a++; printf("a=%d, argv[1]=%s\n", a, argv[1]); //BP_argtest Index: tools/lldb-mi/MICmnLLDBDebuggerHandleEvents.h =================================================================== --- tools/lldb-mi/MICmnLLDBDebuggerHandleEvents.h +++ tools/lldb-mi/MICmnLLDBDebuggerHandleEvents.h @@ -77,6 +77,7 @@ bool HandleProcessEventStopReasonTrace(void); bool HandleProcessEventStopReasonBreakpoint(void); bool HandleProcessEventStopSignal(bool &vwrbShouldBrk); + bool HandleProcessEventStopException(void); bool HandleProcessEventStateSuspended(const lldb::SBEvent &vEvent); bool MiHelpGetCurrentThreadFrame(CMICmnMIValueTuple &vwrMiValueTuple); bool MiResultRecordToStdout(const CMICmnMIResultRecord &vrMiResultRecord); Index: tools/lldb-mi/MICmnLLDBDebuggerHandleEvents.cpp =================================================================== --- tools/lldb-mi/MICmnLLDBDebuggerHandleEvents.cpp +++ tools/lldb-mi/MICmnLLDBDebuggerHandleEvents.cpp @@ -796,6 +796,7 @@ break; case lldb::eStopReasonException: pEventType = "eStopReasonException"; + bOk = HandleProcessEventStopException(); break; case lldb::eStopReasonExec: pEventType = "eStopReasonExec"; @@ -931,6 +932,44 @@ } //++ ------------------------------------------------------------------------------------ +// Details: Asynchronous event handler for LLDB Process stop exception. +// Type: Method. +// Args: None. +// Return: MIstatus::success - Functional succeeded. +// MIstatus::failure - Functional failed. +// Throws: None. +//-- +bool +CMICmnLLDBDebuggerHandleEvents::HandleProcessEventStopException(void) +{ + const lldb::SBProcess sbProcess = CMICmnLLDBDebugSessionInfo::Instance().GetProcess(); + lldb::SBThread sbThread = sbProcess.GetSelectedThread(); + const size_t nStopDescriptionLen = sbThread.GetStopDescription(nullptr, 0); + std::shared_ptr spStopDescription(new char[nStopDescriptionLen]); + sbThread.GetStopDescription(spStopDescription.get(), nStopDescriptionLen); + + // MI print "*stopped,reason=\"exception-received\",exception=\"%s\",thread-id=\"%d\",stopped-threads=\"all\"" + const CMICmnMIValueConst miValueConst("exception-received"); + const CMICmnMIValueResult miValueResult("reason", miValueConst); + CMICmnMIOutOfBandRecord miOutOfBandRecord(CMICmnMIOutOfBandRecord::eOutOfBand_Stopped, miValueResult); + const CMIUtilString strReason(spStopDescription.get()); + const CMICmnMIValueConst miValueConst2(strReason); + const CMICmnMIValueResult miValueResult2("exception", miValueConst2); + bool bOk = miOutOfBandRecord.Add(miValueResult2); + const CMIUtilString strThreadId(CMIUtilString::Format("%d", sbThread.GetIndexID())); + const CMICmnMIValueConst miValueConst3(strThreadId); + const CMICmnMIValueResult miValueResult3("thread-id", miValueConst3); + bOk = bOk && miOutOfBandRecord.Add(miValueResult3); + const CMICmnMIValueConst miValueConst4("all"); + const CMICmnMIValueResult miValueResult4("stopped-threads", miValueConst4); + bOk = bOk && miOutOfBandRecord.Add(miValueResult4); + bOk = bOk && MiOutOfBandRecordToStdout(miOutOfBandRecord); + bOk = bOk && TextToStdout("(gdb)"); + + return bOk; +} + +//++ ------------------------------------------------------------------------------------ // Details: Form partial MI response in a MI value tuple object. // Type: Method. // Args: vwrMiValueTuple - (W) MI value tuple object.