kubamracek created this revision.
kubamracek added reviewers: jingham, jasonmolenda, davide.

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 in very common and in those cases you currently 
don't have any access to the throw point backtrace.

This is WIP, as the patch is missing doc-comments, error handling, but I'm 
looking for early comments to the approach.


https://reviews.llvm.org/D44072

Files:
  include/lldb/Target/SystemRuntime.h
  packages/Python/lldbsuite/test/lang/objc/exceptions/TestObjCExceptions.py
  packages/Python/lldbsuite/test/lang/objc/exceptions/main.m
  scripts/interface/SBThread.i
  source/Plugins/SystemRuntime/MacOSX/SystemRuntimeMacOSX.cpp
  source/Plugins/SystemRuntime/MacOSX/SystemRuntimeMacOSX.h
  source/Target/Thread.cpp

Index: source/Target/Thread.cpp
===================================================================
--- source/Target/Thread.cpp
+++ source/Target/Thread.cpp
@@ -2212,17 +2212,37 @@
 
 ValueObjectSP Thread::GetCurrentException() {
   StackFrameSP frame_sp(GetStackFrameAtIndex(0));
-  if (!frame_sp) return ValueObjectSP();
-
-  RecognizedStackFrameSP recognized_frame(frame_sp->GetRecognizedFrame());
-  if (!recognized_frame) return ValueObjectSP();
+  if (frame_sp) {
+    RecognizedStackFrameSP recognized_frame(frame_sp->GetRecognizedFrame());
+    if (recognized_frame && recognized_frame->IsThrowingObjCException()) {
+      ValueObjectSP exception_from_recognizer =
+          recognized_frame->GetObjCExceptionObject();
+      if (exception_from_recognizer) {
+        return exception_from_recognizer;
+      }
+    }
+  }
 
-  if (!recognized_frame->IsThrowingObjCException()) return ValueObjectSP();
+  SystemRuntime *runtime = GetProcess()->GetSystemRuntime();
+  if (runtime) {
+    ValueObjectSP exception_from_runtime =
+        runtime->GetExceptionObjectForThread(shared_from_this());
+    if (exception_from_runtime) {
+      return exception_from_runtime;
+    }
+  }
 
-  return recognized_frame->GetObjCExceptionObject();
+  return ValueObjectSP();
 }
 
 ThreadSP Thread::GetCurrentExceptionBacktrace() {
-  // TODO
-  return ThreadSP();
+  ValueObjectSP exception = GetCurrentException();
+  if (!exception)
+    return ThreadSP();
+
+  SystemRuntime *runtime = GetProcess()->GetSystemRuntime();
+  if (!runtime)
+    return ThreadSP();
+
+  return runtime->GetBacktraceThreadFromException(exception);
 }
Index: source/Plugins/SystemRuntime/MacOSX/SystemRuntimeMacOSX.h
===================================================================
--- source/Plugins/SystemRuntime/MacOSX/SystemRuntimeMacOSX.h
+++ source/Plugins/SystemRuntime/MacOSX/SystemRuntimeMacOSX.h
@@ -103,6 +103,12 @@
   void AddThreadExtendedInfoPacketHints(
       lldb_private::StructuredData::ObjectSP dict) override;
 
+  virtual lldb::ValueObjectSP
+  GetExceptionObjectForThread(lldb::ThreadSP thread_sp) override;
+
+  virtual lldb::ThreadSP
+  GetBacktraceThreadFromException(lldb::ValueObjectSP thread_sp) override;
+
   bool SafeToCallFunctionsOnThisThread(lldb::ThreadSP thread_sp) override;
 
   //------------------------------------------------------------------
Index: source/Plugins/SystemRuntime/MacOSX/SystemRuntimeMacOSX.cpp
===================================================================
--- source/Plugins/SystemRuntime/MacOSX/SystemRuntimeMacOSX.cpp
+++ source/Plugins/SystemRuntime/MacOSX/SystemRuntimeMacOSX.cpp
@@ -13,6 +13,9 @@
 #include "lldb/Core/ModuleSpec.h"
 #include "lldb/Core/PluginManager.h"
 #include "lldb/Core/Section.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/Symbol/SymbolContext.h"
@@ -224,6 +227,116 @@
   }
 }
 
+ValueObjectSP
+SystemRuntimeMacOSX::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;
+}
+
+ThreadSP SystemRuntimeMacOSX::GetBacktraceThreadFromException(
+    lldb::ValueObjectSP exception_sp) {
+  ValueObjectSP reserved =
+      exception_sp->GetChildMemberWithName(ConstString("reserved"), true);
+  if (!reserved)
+    return ThreadSP();
+
+  reserved = reserved->GetSyntheticValue();
+  if (!reserved)
+    return ThreadSP();
+
+  ValueObjectSP return_addresses;
+  for (size_t idx = 0; idx < reserved->GetNumChildren(); idx++) {
+    ValueObjectSP pair = reserved->GetChildAtIndex(idx, true);
+    auto key = pair->GetChildAtIndex(0, true);
+    std::string key_string;
+    key->GetSummaryAsCString(key_string, TypeSummaryOptions());
+    if (key_string == "\"callStackReturnAddresses\"") {
+      auto value = pair->GetChildAtIndex(1, true);
+      return_addresses = value;
+      break;
+    }
+  }
+
+  if (!return_addresses)
+    return ThreadSP();
+
+  return_addresses = return_addresses->GetDynamicValue(eDynamicDontRunTarget);
+  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;
+}
+
 bool SystemRuntimeMacOSX::SafeToCallFunctionsOnThisThread(ThreadSP thread_sp) {
   if (thread_sp && thread_sp->GetStackFrameCount() > 0 &&
       thread_sp->GetFrameWithConcreteFrameIndex(0)) {
Index: scripts/interface/SBThread.i
===================================================================
--- scripts/interface/SBThread.i
+++ scripts/interface/SBThread.i
@@ -368,6 +368,12 @@
     uint32_t
     GetExtendedBacktraceOriginatingIndexID();
 
+    lldb::SBThread
+    GetCurrentExceptionBacktrace();
+
+    lldb::SBValue
+    GetCurrentException();
+
     %feature("autodoc","
     Takes no arguments, returns a bool.
     lldb may be able to detect that function calls should not be executed
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
@@ -15,6 +15,15 @@
     @throw [[NSException alloc] initWithName:@"ThrownException" reason:@"SomeReason" userInfo:info];
 }
 
+void rethrow()
+{
+    @try {
+        foo();
+    } @catch(NSException *e) {
+        @throw;
+    }
+}
+
 int main (int argc, const char * argv[])
 {
     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
@@ -30,6 +39,9 @@
     }
 
     NSLog(@"1"); // Set break point at this line.
+
+    rethrow();
+
     [pool drain];
     return 0;
 }
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
@@ -26,11 +26,12 @@
     def test_objc_exceptions_1(self):
         self.build()
 
-        self.runCmd("file a.out", CURRENT_EXECUTABLE_SET)
+        target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
+        self.assertTrue(target, VALID_TARGET)
 
         lldbutil.run_break_set_by_file_and_line(
             self, "main.m", self.line, num_expected_locations=1, loc_exact=True)
-        lldbutil.run_break_set_by_symbol(self, "objc_exception_throw")
+        bp = lldbutil.run_break_set_by_symbol(self, "objc_exception_throw")
 
         self.runCmd("run", RUN_SUCCEEDED)
 
@@ -77,3 +78,29 @@
                 'userInfo = ', '1 key/value pair',
                 'reserved = ',
             ])
+
+        self.runCmd("breakpoint delete %d" % bp)
+
+        self.runCmd("continue")
+
+        # 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:15',
+                'a.out`rethrow at main.m:21',
+                '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: include/lldb/Target/SystemRuntime.h
===================================================================
--- include/lldb/Target/SystemRuntime.h
+++ include/lldb/Target/SystemRuntime.h
@@ -327,6 +327,16 @@
   virtual void AddThreadExtendedInfoPacketHints(
       lldb_private::StructuredData::ObjectSP dict) {}
 
+  virtual lldb::ValueObjectSP GetExceptionObjectForThread(
+      lldb::ThreadSP thread_sp) {
+    return lldb::ValueObjectSP();
+  }
+
+  virtual lldb::ThreadSP GetBacktraceThreadFromException(
+      lldb::ValueObjectSP thread_sp) {
+    return lldb::ThreadSP();
+  }
+
   /// Determine whether it is safe to run an expression on a given thread
   ///
   /// If a system must not run functions on a thread in some particular state,
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to