diff --git a/lldb/bindings/interface/SBProcess.i b/lldb/bindings/interface/SBProcess.i --- a/lldb/bindings/interface/SBProcess.i +++ b/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 diff --git a/lldb/include/lldb/API/SBProcess.h b/lldb/include/lldb/API/SBProcess.h --- a/lldb/include/lldb/API/SBProcess.h +++ b/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 diff --git a/lldb/include/lldb/API/SBStructuredData.h b/lldb/include/lldb/API/SBStructuredData.h --- a/lldb/include/lldb/API/SBStructuredData.h +++ b/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; diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h --- a/lldb/include/lldb/Target/Process.h +++ b/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; + 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 m_target_wp; ///< The target that owns this process. ThreadSafeValue m_public_state; diff --git a/lldb/packages/Python/lldbsuite/test/commands/process/crash-info/Makefile b/lldb/packages/Python/lldbsuite/test/commands/process/crash-info/Makefile new file mode 100644 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/commands/process/crash-info/Makefile @@ -0,0 +1,4 @@ +C_SOURCES := main.c + +include Makefile.rules + diff --git a/lldb/packages/Python/lldbsuite/test/commands/process/crash-info/TestProcessCrashInfo.py b/lldb/packages/Python/lldbsuite/test/commands/process/crash-info/TestProcessCrashInfo.py new file mode 100644 --- /dev/null +++ b/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()) diff --git a/lldb/packages/Python/lldbsuite/test/commands/process/crash-info/main.c b/lldb/packages/Python/lldbsuite/test/commands/process/crash-info/main.c new file mode 100644 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/commands/process/crash-info/main.c @@ -0,0 +1,7 @@ +#include +int main() { + int *var = malloc(sizeof(int)); + free(var); + free(var); + return 0; +} diff --git a/lldb/source/API/SBProcess.cpp b/lldb/source/API/SBProcess.cpp --- a/lldb/source/API/SBProcess.cpp +++ b/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, ()); diff --git a/lldb/source/Commands/CommandObjectProcess.cpp b/lldb/source/Commands/CommandObjectProcess.cpp --- a/lldb/source/Commands/CommandObjectProcess.cpp +++ b/lldb/source/Commands/CommandObjectProcess.cpp @@ -1472,6 +1472,45 @@ 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 +1527,9 @@ interpreter))); LoadSubCommand("connect", CommandObjectSP(new CommandObjectProcessConnect(interpreter))); + LoadSubCommand( + "crash-info", + CommandObjectSP(new CommandObjectProcessCrashInfo(interpreter))); LoadSubCommand("detach", CommandObjectSP(new CommandObjectProcessDetach(interpreter))); LoadSubCommand("load", diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp --- a/lldb/source/Target/Process.cpp +++ b/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(); + + 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(); + + 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 guard(m_exit_status_mutex);