Index: include/lldb/API/SBThread.h =================================================================== --- include/lldb/API/SBThread.h +++ include/lldb/API/SBThread.h @@ -200,8 +200,7 @@ SBValue GetCurrentException(); - // TODO(kubamracek): Extract backtrace from SBValue into SBThread - // SBThread GetCurrentExceptionBacktrace(); + SBThread GetCurrentExceptionBacktrace(); bool SafeToCallFunctions(); Index: include/lldb/Target/LanguageRuntime.h =================================================================== --- include/lldb/Target/LanguageRuntime.h +++ include/lldb/Target/LanguageRuntime.h @@ -119,6 +119,17 @@ static Breakpoint::BreakpointPreconditionSP CreateExceptionPrecondition(lldb::LanguageType language, bool catch_bp, bool throw_bp); + + virtual lldb::ValueObjectSP GetExceptionObjectForThread( + lldb::ThreadSP thread_sp) { + return lldb::ValueObjectSP(); + } + + virtual lldb::ThreadSP GetBacktraceThreadFromException( + lldb::ValueObjectSP thread_sp) { + return lldb::ThreadSP(); + } + Process *GetProcess() { return m_process; } Target &GetTargetRef() { return m_process->GetTarget(); } Index: include/lldb/Target/Thread.h =================================================================== --- include/lldb/Target/Thread.h +++ include/lldb/Target/Thread.h @@ -1255,8 +1255,7 @@ lldb::ValueObjectSP GetCurrentException(); - // TODO(kubamracek): Extract backtrace from ValueObjectSP into ThreadSP - // lldb::ThreadSP GetCurrentExceptionBacktrace(); + lldb::ThreadSP GetCurrentExceptionBacktrace(); protected: friend class ThreadPlan; Index: packages/Python/lldbsuite/test/lang/objc/exceptions/TestObjCExceptions.py =================================================================== --- packages/Python/lldbsuite/test/lang/objc/exceptions/TestObjCExceptions.py +++ packages/Python/lldbsuite/test/lang/objc/exceptions/TestObjCExceptions.py @@ -17,13 +17,14 @@ mydir = TestBase.compute_mydir(__file__) @skipUnlessDarwin - def test_objc_exceptions_1(self): + def test_objc_exceptions_at_throw(self): self.build() target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) self.assertTrue(target, VALID_TARGET) - lldbutil.run_to_name_breakpoint(self, "objc_exception_throw") + launch_info = lldb.SBLaunchInfo(["a.out", "0"]) + lldbutil.run_to_name_breakpoint(self, "objc_exception_throw", launch_info=launch_info) self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, substrs=['stopped', 'stop reason = breakpoint']) @@ -33,7 +34,7 @@ 'name: "ThrownException" - reason: "SomeReason"', ]) - lldbutil.run_to_source_breakpoint(self, "// Set break point at this line.", lldb.SBFileSpec("main.m")) + lldbutil.run_to_source_breakpoint(self, "// Set break point at this line.", lldb.SBFileSpec("main.m"), launch_info=launch_info) self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, substrs=['stopped', 'stop reason = breakpoint']) @@ -42,6 +43,10 @@ thread = target.GetProcess().GetSelectedThread() frame = thread.GetSelectedFrame() + # No exception being currently thrown/caught at this point + self.assertEqual(thread.GetCurrentException().IsValid(), False) + self.assertEqual(thread.GetCurrentExceptionBacktrace().IsValid(), False) + self.expect( 'frame variable e1', substrs=[ @@ -108,3 +113,60 @@ names = [target.ResolveSymbolContextForAddress(lldb.SBAddress(pc, target), lldb.eSymbolContextSymbol).GetSymbol().name for pc in pcs] for n in ["objc_exception_throw", "foo", "main"]: self.assertTrue(n in names, "%s is in the exception backtrace (%s)" % (n, names)) + + @skipUnlessDarwin + def test_objc_exceptions_at_abort(self): + self.build() + + target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + self.assertTrue(target, VALID_TARGET) + + self.runCmd("run 0") + + # We should be stopped at pthread_kill because of an unhandled exception + self.expect("thread list", + substrs=['stopped', 'stop reason = signal SIGABRT']) + + self.expect('thread exception', substrs=[ + '(NSException *) exception = ', + 'name: "ThrownException" - reason: "SomeReason"', + 'libobjc.A.dylib`objc_exception_throw', + 'a.out`foo', 'at main.m:22', + 'a.out`rethrow', 'at main.m:31', + 'a.out`main', + ]) + + process = self.dbg.GetSelectedTarget().process + thread = process.GetSelectedThread() + + # There is an exception being currently processed at this point + self.assertEqual(thread.GetCurrentException().IsValid(), True) + self.assertEqual(thread.GetCurrentExceptionBacktrace().IsValid(), True) + + history_thread = thread.GetCurrentExceptionBacktrace() + self.assertGreaterEqual(history_thread.num_frames, 4) + for n in ["objc_exception_throw", "foo", "rethrow", "main"]: + self.assertEqual(len([f for f in history_thread.frames if f.GetFunctionName() == n]), 1) + + self.runCmd("kill") + + self.runCmd("run 1") + # We should be stopped at pthread_kill because of an unhandled exception + self.expect("thread list", + substrs=['stopped', 'stop reason = signal SIGABRT']) + + self.expect('thread exception', substrs=[ + '(MyCustomException *) exception = ', + 'libobjc.A.dylib`objc_exception_throw', + 'a.out`foo', 'at main.m:24', + 'a.out`rethrow', 'at main.m:31', + 'a.out`main', + ]) + + process = self.dbg.GetSelectedTarget().process + thread = process.GetSelectedThread() + + history_thread = thread.GetCurrentExceptionBacktrace() + self.assertGreaterEqual(history_thread.num_frames, 4) + for n in ["objc_exception_throw", "foo", "rethrow", "main"]: + self.assertEqual(len([f for f in history_thread.frames if f.GetFunctionName() == n]), 1) Index: packages/Python/lldbsuite/test/lang/objc/exceptions/main.m =================================================================== --- packages/Python/lldbsuite/test/lang/objc/exceptions/main.m +++ packages/Python/lldbsuite/test/lang/objc/exceptions/main.m @@ -9,10 +9,29 @@ #import -void foo() +@interface MyCustomException: NSException +@end +@implementation MyCustomException +@end + +void foo(int n) { NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:@"some_value", @"some_key", nil]; - @throw [[NSException alloc] initWithName:@"ThrownException" reason:@"SomeReason" userInfo:info]; + switch (n) { + case 0: + @throw [[NSException alloc] initWithName:@"ThrownException" reason:@"SomeReason" userInfo:info]; + case 1: + @throw [[MyCustomException alloc] initWithName:@"ThrownException" reason:@"SomeReason" userInfo:info]; + } +} + +void rethrow(int n) +{ + @try { + foo(n); + } @catch(NSException *e) { + @throw; + } } int main (int argc, const char * argv[]) @@ -24,12 +43,15 @@ NSException *e2; @try { - foo(); + foo(atoi(argv[1])); } @catch(NSException *e) { e2 = e; } NSLog(@"1"); // Set break point at this line. + + rethrow(atoi(argv[1])); + [pool drain]; return 0; } Index: scripts/interface/SBThread.i =================================================================== --- scripts/interface/SBThread.i +++ scripts/interface/SBThread.i @@ -397,6 +397,24 @@ ") GetExtendedBacktraceOriginatingIndexID; uint32_t GetExtendedBacktraceOriginatingIndexID(); + + %feature("autodoc"," + Returns an SBValue object represeting the current exception for the thread, + if there is any. Currently, this works for Obj-C code and returns an SBValue + representing the NSException object at the throw site or that's currently + being processes. + ") GetCurrentException; + lldb::SBValue + GetCurrentException(); + + %feature("autodoc"," + Returns a historical (fake) SBThread representing the stack trace of an + exception, if there is one for the thread. Currently, this works for Obj-C + code, and can retrieve the throw-site backtrace of an NSException object + even when the program is no longer at the throw site. + ") GetCurrentExceptionBacktrace; + lldb::SBThread + GetCurrentExceptionBacktrace(); %feature("autodoc"," Takes no arguments, returns a bool. Index: source/API/SBThread.cpp =================================================================== --- source/API/SBThread.cpp +++ source/API/SBThread.cpp @@ -1491,13 +1491,12 @@ return SBValue(thread_sp->GetCurrentException()); } -/* TODO(kubamracek) SBThread SBThread::GetCurrentExceptionBacktrace() { ThreadSP thread_sp(m_opaque_sp->GetThreadSP()); if (!thread_sp) return SBThread(); return SBThread(thread_sp->GetCurrentExceptionBacktrace()); -}*/ +} bool SBThread::SafeToCallFunctions() { ThreadSP thread_sp(m_opaque_sp->GetThreadSP()); Index: source/Commands/CommandObjectThread.cpp =================================================================== --- source/Commands/CommandObjectThread.cpp +++ source/Commands/CommandObjectThread.cpp @@ -1552,14 +1552,13 @@ exception_object_sp->Dump(strm); } - /* TODO(kubamracek) ThreadSP exception_thread_sp = thread_sp->GetCurrentExceptionBacktrace(); if (exception_thread_sp && exception_thread_sp->IsValid()) { const uint32_t num_frames_with_source = 0; const bool stop_format = false; - exception_thread_sp->GetStatus(strm, m_options.m_start, m_options.m_count, + exception_thread_sp->GetStatus(strm, 0, UINT32_MAX, num_frames_with_source, stop_format); - }*/ + } return true; } Index: source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.h =================================================================== --- source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.h +++ source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.h @@ -65,6 +65,9 @@ bool throw_bp) override; lldb::SearchFilterSP CreateExceptionSearchFilter() override; + + lldb::ValueObjectSP GetExceptionObjectForThread( + lldb::ThreadSP thread_sp) override; //------------------------------------------------------------------ // PluginInterface protocol Index: source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.cpp =================================================================== --- source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.cpp +++ source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.cpp @@ -16,6 +16,9 @@ #include "lldb/Core/PluginManager.h" #include "lldb/Core/ValueObject.h" #include "lldb/Core/ValueObjectMemory.h" +#include "lldb/DataFormatters/FormattersHelpers.h" +#include "lldb/Expression/DiagnosticManager.h" +#include "lldb/Expression/FunctionCaller.h" #include "lldb/Interpreter/CommandObject.h" #include "lldb/Interpreter/CommandObjectMultiword.h" #include "lldb/Interpreter/CommandReturnObject.h" @@ -549,6 +552,61 @@ break_site_id, m_cxx_exception_bp_sp->GetID()); } +ValueObjectSP ItaniumABILanguageRuntime::GetExceptionObjectForThread( + ThreadSP thread_sp) { + ClangASTContext *clang_ast_context = + m_process->GetTarget().GetScratchClangASTContext(); + CompilerType voidstar = + clang_ast_context->GetBasicType(eBasicTypeVoid).GetPointerType(); + + DiagnosticManager diagnostics; + ExecutionContext exe_ctx; + EvaluateExpressionOptions options; + + options.SetUnwindOnError(true); + options.SetIgnoreBreakpoints(true); + options.SetStopOthers(true); + options.SetTimeout(std::chrono::milliseconds(500)); + options.SetTryAllThreads(false); + thread_sp->CalculateExecutionContext(exe_ctx); + + const ModuleList &modules = m_process->GetTarget().GetImages(); + SymbolContextList contexts; + SymbolContext context; + + modules.FindSymbolsWithNameAndType( + ConstString("__cxa_current_exception_type"), eSymbolTypeCode, contexts); + contexts.GetContextAtIndex(0, context); + Address addr = context.symbol->GetAddress(); + + Status error; + FunctionCaller *function_caller = + m_process->GetTarget().GetFunctionCallerForLanguage( + eLanguageTypeC, voidstar, addr, ValueList(), "caller", error); + + ExpressionResults func_call_ret; + Value results; + func_call_ret = function_caller->ExecuteFunction(exe_ctx, nullptr, options, + diagnostics, results); + if (func_call_ret != eExpressionCompleted || !error.Success()) { + return ValueObjectSP(); + } + + size_t ptr_size = m_process->GetAddressByteSize(); + addr_t result_ptr = results.GetScalar().ULongLong(LLDB_INVALID_ADDRESS); + addr_t exception_addr = + m_process->ReadPointerFromMemory(result_ptr - ptr_size, error); + + lldb_private::formatters::InferiorSizedWord exception_isw(exception_addr, + *m_process); + ValueObjectSP exception = ValueObject::CreateValueObjectFromData( + "exception", exception_isw.GetAsData(m_process->GetByteOrder()), exe_ctx, + voidstar); + exception = exception->GetDynamicValue(eDynamicDontRunTarget); + + return exception; +} + TypeAndOrName ItaniumABILanguageRuntime::GetDynamicTypeInfo( const lldb_private::Address &vtable_addr) { std::lock_guard locker(m_dynamic_type_map_mutex); Index: source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.h =================================================================== --- source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.h +++ source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.h @@ -89,6 +89,12 @@ static std::tuple GetExceptionThrowLocation(); + lldb::ValueObjectSP GetExceptionObjectForThread( + lldb::ThreadSP thread_sp) override; + + lldb::ThreadSP GetBacktraceThreadFromException( + lldb::ValueObjectSP thread_sp) override; + uint32_t GetFoundationVersion(); virtual void GetValuesForGlobalCFBooleans(lldb::addr_t &cf_true, Index: source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.cpp =================================================================== --- source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.cpp +++ source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.cpp @@ -19,10 +19,13 @@ #include "lldb/Core/PluginManager.h" #include "lldb/Core/Section.h" #include "lldb/Core/ValueObject.h" +#include "lldb/Core/ValueObjectConstResult.h" +#include "lldb/DataFormatters/FormattersHelpers.h" #include "lldb/Expression/DiagnosticManager.h" #include "lldb/Expression/FunctionCaller.h" #include "lldb/Symbol/ClangASTContext.h" #include "lldb/Symbol/ObjectFile.h" +#include "lldb/Target/CPPLanguageRuntime.h" #include "lldb/Target/ExecutionContext.h" #include "lldb/Target/Process.h" #include "lldb/Target/RegisterContext.h" @@ -35,6 +38,9 @@ #include "lldb/Utility/Status.h" #include "lldb/Utility/StreamString.h" +#include "Plugins/Process/Utility/HistoryThread.h" +#include "Plugins/Language/ObjC/NSString.h" + #include using namespace lldb; @@ -459,6 +465,102 @@ } } +ValueObjectSP AppleObjCRuntime::GetExceptionObjectForThread( + ThreadSP thread_sp) { + auto cpp_runtime = m_process->GetCPPLanguageRuntime(); + if (!cpp_runtime) return ValueObjectSP(); + auto cpp_exception = cpp_runtime->GetExceptionObjectForThread(thread_sp); + if (!cpp_exception) return ValueObjectSP(); + + auto descriptor = GetClassDescriptor(*cpp_exception.get()); + if (!descriptor || !descriptor->IsValid()) return ValueObjectSP(); + + while (descriptor) { + ConstString class_name(descriptor->GetClassName()); + if (class_name == ConstString("NSException")) return cpp_exception; + descriptor = descriptor->GetSuperclass(); + } + + return ValueObjectSP(); +} + +ThreadSP AppleObjCRuntime::GetBacktraceThreadFromException( + lldb::ValueObjectSP exception_sp) { + ValueObjectSP reserved_dict = + exception_sp->GetChildMemberWithName(ConstString("reserved"), true); + if (!reserved_dict) return ThreadSP(); + + reserved_dict = reserved_dict->GetSyntheticValue(); + if (!reserved_dict) return ThreadSP(); + + CompilerType objc_id = + exception_sp->GetTargetSP()->GetScratchClangASTContext()->GetBasicType( + lldb::eBasicTypeObjCID); + ValueObjectSP return_addresses; + + auto objc_object_from_address = [&exception_sp, &objc_id](uint64_t addr, + const char *name) { + Value value(addr); + value.SetCompilerType(objc_id); + auto object = ValueObjectConstResult::Create( + exception_sp->GetTargetSP().get(), value, ConstString(name)); + object = object->GetDynamicValue(eDynamicDontRunTarget); + return object; + }; + + for (size_t idx = 0; idx < reserved_dict->GetNumChildren(); idx++) { + ValueObjectSP dict_entry = reserved_dict->GetChildAtIndex(idx, true); + + DataExtractor data; + data.SetAddressByteSize(dict_entry->GetProcessSP()->GetAddressByteSize()); + Status error; + dict_entry->GetData(data, error); + if (error.Fail()) return ThreadSP(); + + lldb::offset_t data_offset = 0; + auto dict_entry_key = data.GetPointer(&data_offset); + auto dict_entry_value = data.GetPointer(&data_offset); + + auto key_nsstring = objc_object_from_address(dict_entry_key, "key"); + StreamString key_summary; + if (lldb_private::formatters::NSStringSummaryProvider( + *key_nsstring, key_summary, TypeSummaryOptions()) && + !key_summary.Empty()) { + if (key_summary.GetString() == "\"callStackReturnAddresses\"") { + return_addresses = objc_object_from_address(dict_entry_value, + "callStackReturnAddresses"); + break; + } + } + } + + if (!return_addresses) return ThreadSP(); + auto frames_value = + return_addresses->GetChildMemberWithName(ConstString("_frames"), true); + addr_t frames_addr = frames_value->GetValueAsUnsigned(0); + auto count_value = + return_addresses->GetChildMemberWithName(ConstString("_cnt"), true); + size_t count = count_value->GetValueAsUnsigned(0); + auto ignore_value = + return_addresses->GetChildMemberWithName(ConstString("_ignore"), true); + size_t ignore = ignore_value->GetValueAsUnsigned(0); + + size_t ptr_size = m_process->GetAddressByteSize(); + std::vector pcs; + for (size_t idx = 0; idx < count; idx++) { + Status error; + addr_t pc = m_process->ReadPointerFromMemory( + frames_addr + (ignore + idx) * ptr_size, error); + pcs.push_back(pc); + } + + if (pcs.empty()) return ThreadSP(); + + ThreadSP new_thread_sp(new HistoryThread(*m_process, 0, pcs, 0, false)); + m_process->GetExtendedThreadList().AddThread(new_thread_sp); + return new_thread_sp; +} + std::tuple AppleObjCRuntime::GetExceptionThrowLocation() { return std::make_tuple( Index: source/Target/Thread.cpp =================================================================== --- source/Target/Thread.cpp +++ source/Target/Thread.cpp @@ -23,6 +23,7 @@ #include "lldb/Target/ABI.h" #include "lldb/Target/DynamicLoader.h" #include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/ObjCLanguageRuntime.h" #include "lldb/Target/Process.h" #include "lldb/Target/RegisterContext.h" #include "lldb/Target/StackFrameRecognizer.h" @@ -2202,16 +2203,30 @@ } ValueObjectSP Thread::GetCurrentException() { - StackFrameSP frame_sp(GetStackFrameAtIndex(0)); - if (!frame_sp) return ValueObjectSP(); + if (auto frame_sp = GetStackFrameAtIndex(0)) + if (auto recognized_frame = frame_sp->GetRecognizedFrame()) + if (auto e = recognized_frame->GetExceptionObject()) + return e; + + // FIXME: For now, only ObjC exceptions are supported. This should really + // iterate over all language runtimes and ask them all to give us the current + // exception. + if (auto runtime = GetProcess()->GetObjCLanguageRuntime()) + if (auto e = runtime->GetExceptionObjectForThread(shared_from_this())) + return e; - RecognizedStackFrameSP recognized_frame(frame_sp->GetRecognizedFrame()); - if (!recognized_frame) return ValueObjectSP(); - - return recognized_frame->GetExceptionObject(); + return ValueObjectSP(); } -/* TODO(kubamracek) ThreadSP Thread::GetCurrentExceptionBacktrace() { - return ThreadSP(); -}*/ + ValueObjectSP exception = GetCurrentException(); + if (!exception) return ThreadSP(); + + // FIXME: For now, only ObjC exceptions are supported. This should really + // iterate over all language runtimes and ask them all to give us the current + // exception. + auto runtime = GetProcess()->GetObjCLanguageRuntime(); + if (!runtime) return ThreadSP(); + + return runtime->GetBacktraceThreadFromException(exception); +}