[lldb] Retrieve currently handled Obj-C exception via __cxa_current_exception_type...
authorKuba Mracek <mracek@apple.com>
Thu, 20 Dec 2018 02:01:59 +0000 (02:01 +0000)
committerKuba Mracek <mracek@apple.com>
Thu, 20 Dec 2018 02:01:59 +0000 (02:01 +0000)
This builds on https://reviews.llvm.org/D43884 and https://reviews.llvm.org/D43886 and extends LLDB support of Obj-C exceptions to also look for a "current exception" for a thread in the C++ exception handling runtime metadata (via call to __cxa_current_exception_type). We also construct an actual historical SBThread/ThreadSP that contains frames from the backtrace in the Obj-C exception object.

The high level goal this achieves is that when we're already crashed (because an unhandled exception occurred), we can still access the exception object and retrieve the backtrace from the throw point. In Obj-C, this is particularly useful because a catch+rethrow is very common and in those cases you currently don't have any access to the throw point backtrace.

Differential Revision: https://reviews.llvm.org/D44072

llvm-svn: 349718

14 files changed:
lldb/include/lldb/API/SBThread.h
lldb/include/lldb/Target/LanguageRuntime.h
lldb/include/lldb/Target/Thread.h
lldb/packages/Python/lldbsuite/test/lang/objc/exceptions/Makefile
lldb/packages/Python/lldbsuite/test/lang/objc/exceptions/TestObjCExceptions.py
lldb/packages/Python/lldbsuite/test/lang/objc/exceptions/main.mm [moved from lldb/packages/Python/lldbsuite/test/lang/objc/exceptions/main.m with 57% similarity]
lldb/scripts/interface/SBThread.i
lldb/source/API/SBThread.cpp
lldb/source/Commands/CommandObjectThread.cpp
lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.cpp
lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.h
lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.cpp
lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.h
lldb/source/Target/Thread.cpp

index 24224cd..63816ee 100644 (file)
@@ -200,8 +200,7 @@ public:
 
   SBValue GetCurrentException();
 
-  // TODO(kubamracek): Extract backtrace from SBValue into SBThread
-  // SBThread GetCurrentExceptionBacktrace();
+  SBThread GetCurrentExceptionBacktrace();
 
   bool SafeToCallFunctions();
 
index d330d6d..2a2f47b 100644 (file)
@@ -119,6 +119,17 @@ public:
   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 4a697e3..0d14b10 100644 (file)
@@ -1255,8 +1255,7 @@ public:
 
   lldb::ValueObjectSP GetCurrentException();
 
-  // TODO(kubamracek): Extract backtrace from ValueObjectSP into ThreadSP
-  // lldb::ThreadSP GetCurrentExceptionBacktrace();
+  lldb::ThreadSP GetCurrentExceptionBacktrace();
 
 protected:
   friend class ThreadPlan;
index 6959f2e..b551154 100644 (file)
@@ -17,13 +17,14 @@ class ObjCExceptionsTestCase(TestBase):
     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 @@ class ObjCExceptionsTestCase(TestBase):
                 '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.mm"), launch_info=launch_info)
 
         self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT,
                     substrs=['stopped', 'stop reason = breakpoint'])
@@ -42,19 +43,23 @@ class ObjCExceptionsTestCase(TestBase):
         thread = target.GetProcess().GetSelectedThread()
         frame = thread.GetSelectedFrame()
 
+        # No exception being currently thrown/caught at this point
+        self.assertFalse(thread.GetCurrentException().IsValid())
+        self.assertFalse(thread.GetCurrentExceptionBacktrace().IsValid())
+
         self.expect(
             'frame variable e1',
             substrs=[
                 '(NSException *) e1 = ',
-                'name: @"ExceptionName" - reason: @"SomeReason"'
+                'name: "ExceptionName" - reason: "SomeReason"'
             ])
 
         self.expect(
             'frame variable --dynamic-type no-run-target *e1',
             substrs=[
                 '(NSException) *e1 = ',
-                'name = ', '@"ExceptionName"',
-                'reason = ', '@"SomeReason"',
+                'name = ', '"ExceptionName"',
+                'reason = ', '"SomeReason"',
                 'userInfo = ', '1 key/value pair',
                 'reserved = ', 'nil',
             ])
@@ -62,7 +67,7 @@ class ObjCExceptionsTestCase(TestBase):
         e1 = frame.FindVariable("e1")
         self.assertTrue(e1)
         self.assertEqual(e1.type.name, "NSException *")
-        self.assertEqual(e1.GetSummary(), 'name: @"ExceptionName" - reason: @"SomeReason"')
+        self.assertEqual(e1.GetSummary(), 'name: "ExceptionName" - reason: "SomeReason"')
         self.assertEqual(e1.GetChildMemberWithName("name").description, "ExceptionName")
         self.assertEqual(e1.GetChildMemberWithName("reason").description, "SomeReason")
         userInfo = e1.GetChildMemberWithName("userInfo").dynamic
@@ -75,15 +80,15 @@ class ObjCExceptionsTestCase(TestBase):
             'frame variable e2',
             substrs=[
                 '(NSException *) e2 = ',
-                'name: @"ThrownException" - reason: @"SomeReason"'
+                'name: "ThrownException" - reason: "SomeReason"'
             ])
 
         self.expect(
             'frame variable --dynamic-type no-run-target *e2',
             substrs=[
                 '(NSException) *e2 = ',
-                'name = ', '@"ThrownException"',
-                'reason = ', '@"SomeReason"',
+                'name = ', '"ThrownException"',
+                'reason = ', '"SomeReason"',
                 'userInfo = ', '1 key/value pair',
                 'reserved = ',
             ])
@@ -91,7 +96,7 @@ class ObjCExceptionsTestCase(TestBase):
         e2 = frame.FindVariable("e2")
         self.assertTrue(e2)
         self.assertEqual(e2.type.name, "NSException *")
-        self.assertEqual(e2.GetSummary(), 'name: @"ThrownException" - reason: @"SomeReason"')
+        self.assertEqual(e2.GetSummary(), 'name: "ThrownException" - reason: "SomeReason"')
         self.assertEqual(e2.GetChildMemberWithName("name").description, "ThrownException")
         self.assertEqual(e2.GetChildMemberWithName("reason").description, "SomeReason")
         userInfo = e2.GetChildMemberWithName("userInfo").dynamic
@@ -106,5 +111,84 @@ class ObjCExceptionsTestCase(TestBase):
 
         pcs = [i.unsigned for i in children]
         names = [target.ResolveSymbolContextForAddress(lldb.SBAddress(pc, target), lldb.eSymbolContextSymbol).GetSymbol().name for pc in pcs]
-        for n in ["objc_exception_throw", "foo", "main"]:
+        for n in ["objc_exception_throw", "foo(int)", "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.mm:25',
+                'a.out`rethrow', 'at main.mm:36',
+                'a.out`main',
+            ])
+
+        process = self.dbg.GetSelectedTarget().process
+        thread = process.GetSelectedThread()
+
+        # There is an exception being currently processed at this point
+        self.assertTrue(thread.GetCurrentException().IsValid())
+        self.assertTrue(thread.GetCurrentExceptionBacktrace().IsValid())
+
+        history_thread = thread.GetCurrentExceptionBacktrace()
+        self.assertGreaterEqual(history_thread.num_frames, 4)
+        for n in ["objc_exception_throw", "foo(int)", "rethrow(int)", "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.mm:27',
+                'a.out`rethrow', 'at main.mm:36',
+                '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(int)", "rethrow(int)", "main"]:
+            self.assertEqual(len([f for f in history_thread.frames if f.GetFunctionName() == n]), 1)
+
+    @skipUnlessDarwin
+    def test_cxx_exceptions_at_abort(self):
+        self.build()
+
+        target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
+        self.assertTrue(target, VALID_TARGET)
+
+        self.runCmd("run 2")
+
+        # 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=[])
+
+        process = self.dbg.GetSelectedTarget().process
+        thread = process.GetSelectedThread()
+
+        # C++ exceptions are not exposed in the API (yet).
+        self.assertFalse(thread.GetCurrentException().IsValid())
+        self.assertFalse(thread.GetCurrentExceptionBacktrace().IsValid())
@@ -9,13 +9,37 @@
 
 #import <Foundation/Foundation.h>
 
-void foo()
+#import <exception>
+#import <stdexcept>
+
+@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];
+        case 2:
+            throw std::runtime_error("C++ exception");
+    }
 }
 
-int main (int argc, const char * argv[])
+void rethrow(int n)
+{
+    @try {
+        foo(n);
+    } @catch(NSException *e) {
+        @throw;
+    }
+}
+
+int main(int argc, const char * argv[])
 {
     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 
@@ -24,12 +48,15 @@ int main (int argc, const char * argv[])
     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 60d77c7..c12f176 100644 (file)
@@ -397,6 +397,24 @@ public:
     ") 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 97a9df6..2c859d5 100644 (file)
@@ -1491,13 +1491,12 @@ SBValue SBThread::GetCurrentException() {
   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 57dd2a3..e792887 100644 (file)
@@ -1552,14 +1552,13 @@ class CommandObjectThreadException : public CommandObjectIterateOverThreads {
       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 3f9ae93..8fcf33c 100644 (file)
@@ -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 @@ bool ItaniumABILanguageRuntime::ExceptionBreakpointsExplainStop(
       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<std::mutex> locker(m_dynamic_type_map_mutex);
index 149fe05..abed370 100644 (file)
@@ -65,6 +65,9 @@ public:
                                                      bool throw_bp) override;
 
   lldb::SearchFilterSP CreateExceptionSearchFilter() override;
+  
+  lldb::ValueObjectSP GetExceptionObjectForThread(
+      lldb::ThreadSP thread_sp) override;
 
   //------------------------------------------------------------------
   // PluginInterface protocol
index f55a7e8..ed47b48 100644 (file)
 #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 <vector>
 
 using namespace lldb;
@@ -459,6 +465,102 @@ lldb::SearchFilterSP AppleObjCRuntime::CreateExceptionSearchFilter() {
   }
 }
 
+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<lldb::addr_t> 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<FileSpec, ConstString>
 AppleObjCRuntime::GetExceptionThrowLocation() {
   return std::make_tuple(
index ac2f5c8..8660646 100644 (file)
@@ -89,6 +89,12 @@ public:
   
   static std::tuple<FileSpec, ConstString> 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 65aca86..3662f1b 100644 (file)
@@ -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 @@ Status Thread::StepOut() {
 }
 
 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);
+}