mib created this revision.
mib added reviewers: JDevlieghere, friss, jingham.
mib added a project: LLDB.
Herald added a subscriber: lldb-commits.

Currently, in macOS, when a process crashes, lldb halts inside the 
implementation
disassembly without yielding any useful information. The only way to get more
information is to detach from the process, then wait
for ReportCrash to generate a report, find the report, then see what error
message was included in it. Instead of waiting for this to happen, lldb could
locate the error_string and make it available to the user.

This patch addresses this issue by introducing the command `process crash-info`.

When invoked, lldb will iterate over each of the target's images, extract their
`__crash_info` section and generated a StructuredData::Array (mostly a JSON
Array) containing, in each entry, the module spec, its UUID, the crash messages
and the abort cause.

This crash information can also be fetched using the SB API or lldb-rpc protocol
using SBProcess::GetCrashInfo().

rdar://37736535

Signed-off-by: Med Ismail Bennani <medismail.benn...@gmail.com>


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D74657

Files:
  lldb/bindings/interface/SBProcess.i
  lldb/include/lldb/API/SBProcess.h
  lldb/include/lldb/API/SBStructuredData.h
  lldb/include/lldb/Target/Process.h
  lldb/packages/Python/lldbsuite/test/commands/process/crash-info/Makefile
  
lldb/packages/Python/lldbsuite/test/commands/process/crash-info/TestProcessCrashInfo.py
  lldb/packages/Python/lldbsuite/test/commands/process/crash-info/main.c
  lldb/source/API/SBProcess.cpp
  lldb/source/Commands/CommandObjectProcess.cpp
  lldb/source/Target/Process.cpp

Index: lldb/source/Target/Process.cpp
===================================================================
--- lldb/source/Target/Process.cpp
+++ lldb/source/Target/Process.cpp
@@ -19,6 +19,7 @@
 #include "lldb/Core/Module.h"
 #include "lldb/Core/ModuleSpec.h"
 #include "lldb/Core/PluginManager.h"
+#include "lldb/Core/Section.h"
 #include "lldb/Core/StreamFile.h"
 #include "lldb/Expression/DiagnosticManager.h"
 #include "lldb/Expression/DynamicCheckerFunctions.h"
@@ -36,6 +37,7 @@
 #include "lldb/Interpreter/OptionArgParser.h"
 #include "lldb/Interpreter/OptionValueProperties.h"
 #include "lldb/Symbol/Function.h"
+#include "lldb/Symbol/ObjectFile.h"
 #include "lldb/Symbol/Symbol.h"
 #include "lldb/Target/ABI.h"
 #include "lldb/Target/AssertFrameRecognizer.h"
@@ -1095,6 +1097,112 @@
   return nullptr;
 }
 
+StructuredData::ArraySP Process::FetchCrashInfo() {
+  Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS));
+
+  StructuredData::ArraySP array_sp = std::make_shared<StructuredData::Array>();
+
+  for (ModuleSP module : GetTarget().GetImages().Modules()) {
+    SectionList *sections = module->GetSectionList();
+
+    std::string module_name = module->GetSpecificationDescription();
+
+    if (!sections) {
+      LLDB_LOG(log, "Module {0} doesn't have any section!", module_name);
+      continue;
+    }
+
+    ConstString section_name("__crash_info");
+    SectionSP crash_info = sections->FindSectionByName(section_name);
+    if (!crash_info) {
+      LLDB_LOG(log, "Module {0} doesn't have section {1}!", module_name,
+               section_name);
+      continue;
+    }
+
+    addr_t load_addr = crash_info->GetLoadBaseAddress(&GetTarget());
+
+    if (load_addr == LLDB_INVALID_ADDRESS)
+      continue;
+
+    CrashInfoExtractor extractor = {};
+    extractor.load_addr = load_addr;
+
+    if (!ExtractCrashInfoAnnotations(extractor)) {
+      LLDB_LOG(log, "{Couldn't extract crash info from Module {0}: {1}}",
+               module_name, extractor.error.AsCString());
+      continue;
+    }
+
+    StructuredData::DictionarySP entry_sp =
+        std::make_shared<StructuredData::Dictionary>();
+
+    entry_sp->AddStringItem("image", module->GetFileSpec().GetPath(false));
+    entry_sp->AddStringItem("uuid", module->GetUUID().GetAsString());
+    entry_sp->AddStringItem("message", extractor.message);
+    entry_sp->AddStringItem("message2", extractor.message2);
+    entry_sp->AddIntegerItem("abort-cause", extractor.annotations.abort_cause);
+
+    array_sp->AddItem(entry_sp);
+  }
+
+  return array_sp;
+}
+
+bool Process::ExtractCrashInfoAnnotations(CrashInfoExtractor &extractor) {
+  CrashInfoAnnotations annotations;
+  size_t expected_size = sizeof(CrashInfoAnnotations);
+  size_t bytes_read = ReadMemoryFromInferior(extractor.load_addr, &annotations,
+                                             expected_size, extractor.error);
+
+  if (expected_size != bytes_read || extractor.error.Fail())
+    return false;
+
+  // initial support added for version 5
+  if (annotations.version < 5) {
+    extractor.error.SetErrorString(
+        "Annotation version lower than 5 unsupported!");
+    return false;
+  }
+
+  if (!annotations.message) {
+    extractor.error.SetErrorString("No message available.");
+    return false;
+  }
+
+  std::string message;
+  bytes_read =
+      ReadCStringFromMemory(annotations.message, message, extractor.error);
+
+  if (message.empty() || bytes_read != message.size() ||
+      extractor.error.Fail()) {
+    extractor.error.SetErrorString("Failed to read the message from memory.");
+    return false;
+  }
+
+  // Remove trailing newline from message
+  if (message[message.size() - 1] == '\n')
+    message.pop_back();
+
+  extractor.annotations = annotations;
+  extractor.message = message;
+
+  if (annotations.message2) {
+    std::string message2;
+    bytes_read =
+        ReadCStringFromMemory(annotations.message2, message2, extractor.error);
+
+    if (!message2.empty() && bytes_read == message2.size() &&
+        extractor.error.Success()) {
+      if (message2[message2.size() - 1] == '\n')
+        message2.pop_back();
+      extractor.message2 = message2;
+    }
+  }
+
+  return true;
+}
+
 bool Process::SetExitStatus(int status, const char *cstr) {
   // Use a mutex to protect setting the exit status.
   std::lock_guard<std::mutex> guard(m_exit_status_mutex);
Index: lldb/source/Commands/CommandObjectProcess.cpp
===================================================================
--- lldb/source/Commands/CommandObjectProcess.cpp
+++ lldb/source/Commands/CommandObjectProcess.cpp
@@ -1472,6 +1472,44 @@
   CommandOptions m_options;
 };
 
+// CommandObjectProcessCrashInfo
+#pragma mark CommandObjectProcessCrashInfo
+
+class CommandObjectProcessCrashInfo : public CommandObjectParsed {
+public:
+  CommandObjectProcessCrashInfo(CommandInterpreter &interpreter)
+      : CommandObjectParsed(interpreter, "process crash-info",
+                            "Fetch process' crash information from the module.",
+                            "process crash-info",
+                            eCommandRequiresProcess |
+                                eCommandTryTargetAPILock) {}
+
+  ~CommandObjectProcessCrashInfo() override = default;
+
+  bool DoExecute(Args &command, CommandReturnObject &result) override {
+    Stream &strm = result.GetOutputStream();
+    result.SetStatus(eReturnStatusSuccessFinishNoResult);
+    // No need to check "process" for validity as eCommandRequiresProcess
+    // ensures it is valid
+    Process *process = m_exe_ctx.GetProcessPtr();
+    llvm::Triple::OSType os = process->GetTarget().GetArchitecture().GetTriple().getOS();
+    
+    if (os != llvm::Triple::MacOSX || os != llvm::Triple::Darwin) {
+      result.AppendError("Unsupported OS");
+      return result.Succeeded();
+    }
+    
+    StructuredData::ArraySP crash_info_sp = process->FetchCrashInfo();
+
+    if (!crash_info_sp)
+      result.AppendError("Couldn't fetch crash info.");
+
+    crash_info_sp->Dump(strm);
+
+    return result.Succeeded();
+  }
+};
+
 // CommandObjectMultiwordProcess
 
 CommandObjectMultiwordProcess::CommandObjectMultiwordProcess(
@@ -1488,6 +1526,9 @@
                                  interpreter)));
   LoadSubCommand("connect",
                  CommandObjectSP(new CommandObjectProcessConnect(interpreter)));
+  LoadSubCommand(
+      "crash-info",
+      CommandObjectSP(new CommandObjectProcessCrashInfo(interpreter)));
   LoadSubCommand("detach",
                  CommandObjectSP(new CommandObjectProcessDetach(interpreter)));
   LoadSubCommand("load",
Index: lldb/source/API/SBProcess.cpp
===================================================================
--- lldb/source/API/SBProcess.cpp
+++ lldb/source/API/SBProcess.cpp
@@ -18,6 +18,7 @@
 #include "lldb/Core/Module.h"
 #include "lldb/Core/PluginManager.h"
 #include "lldb/Core/StreamFile.h"
+#include "lldb/Core/StructuredDataImpl.h"
 #include "lldb/Target/MemoryRegionInfo.h"
 #include "lldb/Target/Process.h"
 #include "lldb/Target/RegisterContext.h"
@@ -555,6 +556,19 @@
   return exit_desc;
 }
 
+SBStructuredData SBProcess::GetCrashInfo() {
+  LLDB_RECORD_METHOD_NO_ARGS(lldb::SBStructuredData, SBProcess, GetCrashInfo);
+
+  SBStructuredData data;
+  ProcessSP process_sp(GetSP());
+  if (!process_sp)
+    return LLDB_RECORD_RESULT(data);
+
+  StructuredData::ArraySP fetched_data = process_sp->FetchCrashInfo();
+  data.m_impl_up->SetObjectSP(fetched_data);
+  return LLDB_RECORD_RESULT(data);
+}
+
 lldb::pid_t SBProcess::GetProcessID() {
   LLDB_RECORD_METHOD_NO_ARGS(lldb::pid_t, SBProcess, GetProcessID);
 
@@ -1338,6 +1352,7 @@
   LLDB_REGISTER_METHOD(lldb::StateType, SBProcess, GetState, ());
   LLDB_REGISTER_METHOD(int, SBProcess, GetExitStatus, ());
   LLDB_REGISTER_METHOD(const char *, SBProcess, GetExitDescription, ());
+  LLDB_REGISTER_METHOD(SBStructuredData, SBProcess, GetCrashInfo, ());
   LLDB_REGISTER_METHOD(lldb::pid_t, SBProcess, GetProcessID, ());
   LLDB_REGISTER_METHOD(uint32_t, SBProcess, GetUniqueID, ());
   LLDB_REGISTER_METHOD_CONST(lldb::ByteOrder, SBProcess, GetByteOrder, ());
Index: lldb/packages/Python/lldbsuite/test/commands/process/crash-info/main.c
===================================================================
--- /dev/null
+++ lldb/packages/Python/lldbsuite/test/commands/process/crash-info/main.c
@@ -0,0 +1,7 @@
+#include <stdlib.h>
+int main() {
+  int *var = malloc(sizeof(int));
+  free(var);
+  free(var);
+  return 0;
+}
Index: lldb/packages/Python/lldbsuite/test/commands/process/crash-info/TestProcessCrashInfo.py
===================================================================
--- /dev/null
+++ lldb/packages/Python/lldbsuite/test/commands/process/crash-info/TestProcessCrashInfo.py
@@ -0,0 +1,65 @@
+"""
+Test lldb process crash info.
+"""
+
+from __future__ import print_function
+
+import os
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+class ProcessCrashInfoTestCase(TestBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    def setUp(self):
+        # Call super's setUp().
+        TestBase.setUp(self)
+        self.runCmd("settings set auto-confirm true")
+
+    def tearDown(self):
+        self.runCmd("settings clear auto-confirm")
+        TestBase.tearDown(self)
+
+    @skipUnlessDarwin
+    def test_cli(self):
+        """Test that process crash-info fetches an annotation message from the
+        command-line properly."""
+        self.build()
+        exe = self.getBuildArtifact("a.out")
+        self.expect("file " + exe,
+                    patterns=["Current executable set to .*a.out"])
+
+        self.expect('process launch',
+                    patterns=["Process .* launched: .*a.out"])
+
+        self.expect('process crash-info',
+                    patterns=["\"message\".*pointer being freed was not allocated"])
+
+
+    @skipUnlessDarwin
+    def test_api(self):
+        """Test that process crash-info fetches an annotation message from the
+        api properly."""
+        self.build()
+        target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
+        self.assertTrue(target, VALID_TARGET)
+
+        process = target.LaunchSimple(None, None, os.getcwd())
+
+        stream = lldb.SBStream()
+        self.assertTrue(stream)
+
+        crash_info = process.GetCrashInfo()
+
+        error = crash_info.GetAsJSON(stream)
+
+        self.assertTrue(error.Success())
+
+        self.assertTrue(crash_info.IsValid())
+
+        self.assertTrue("pointer being freed was not allocated" in
+                        stream.GetData())
Index: lldb/packages/Python/lldbsuite/test/commands/process/crash-info/Makefile
===================================================================
--- /dev/null
+++ lldb/packages/Python/lldbsuite/test/commands/process/crash-info/Makefile
@@ -0,0 +1,4 @@
+C_SOURCES := main.c
+
+include Makefile.rules
+
Index: lldb/include/lldb/Target/Process.h
===================================================================
--- lldb/include/lldb/Target/Process.h
+++ lldb/include/lldb/Target/Process.h
@@ -1267,7 +1267,7 @@
   ///     LLDB_INVALID_ADDRESS.
   ///
   /// \return
-  ///     A StructureDataSP object which, if non-empty, will contain the
+  ///     A StructuredDataSP object which, if non-empty, will contain the
   ///     information the DynamicLoader needs to get the initial scan of
   ///     solibs resolved.
   virtual lldb_private::StructuredData::ObjectSP
@@ -1330,6 +1330,8 @@
 
   virtual void DidExit() {}
 
+  lldb_private::StructuredData::ArraySP FetchCrashInfo();
+
   /// Get the Modification ID of the process.
   ///
   /// \return
@@ -2632,6 +2634,28 @@
   using StructuredDataPluginMap =
       std::map<ConstString, lldb::StructuredDataPluginSP>;
 
+  typedef struct {
+    uint64_t version;          // unsigned long
+    uint64_t message;          // char *
+    uint64_t signature_string; // char *
+    uint64_t backtrace;        // char *
+    uint64_t message2;         // char *
+    uint64_t thread;           // uint64_t
+    uint64_t dialog_mode;      // unsigned int
+    uint64_t abort_cause;      // unsigned int
+  } CrashInfoAnnotations;
+
+  typedef struct {
+    lldb::addr_t load_addr;
+    CrashInfoAnnotations annotations;
+    std::string message;
+    std::string message2;
+    uint64_t abort_cause;
+    Status error;
+  } CrashInfoExtractor;
+
+  bool ExtractCrashInfoAnnotations(CrashInfoExtractor &extractor);
+
   // Member variables
   std::weak_ptr<Target> m_target_wp; ///< The target that owns this process.
   ThreadSafeValue<lldb::StateType> m_public_state;
Index: lldb/include/lldb/API/SBStructuredData.h
===================================================================
--- lldb/include/lldb/API/SBStructuredData.h
+++ lldb/include/lldb/API/SBStructuredData.h
@@ -91,6 +91,7 @@
   friend class SBTraceOptions;
   friend class SBDebugger;
   friend class SBTarget;
+  friend class SBProcess;
   friend class SBThread;
   friend class SBThreadPlan;
   friend class SBBreakpoint;
Index: lldb/include/lldb/API/SBProcess.h
===================================================================
--- lldb/include/lldb/API/SBProcess.h
+++ lldb/include/lldb/API/SBProcess.h
@@ -121,6 +121,8 @@
 
   const char *GetExitDescription();
 
+  SBStructuredData GetCrashInfo();
+
   /// Gets the process ID
   ///
   /// Returns the process identifier for the process as it is known
Index: lldb/bindings/interface/SBProcess.i
===================================================================
--- lldb/bindings/interface/SBProcess.i
+++ lldb/bindings/interface/SBProcess.i
@@ -192,6 +192,9 @@
     const char *
     GetExitDescription ();
 
+    SBStructuredData
+    GetCrashInfo ();
+
     %feature("autodoc", "
     Returns the process ID of the process.") GetProcessID;
     lldb::pid_t
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to