diff --git a/lldb/bindings/interface/SBTarget.i b/lldb/bindings/interface/SBTarget.i --- a/lldb/bindings/interface/SBTarget.i +++ b/lldb/bindings/interface/SBTarget.i @@ -949,6 +949,12 @@ void SetLaunchInfo (const lldb::SBLaunchInfo &launch_info); + %feature("autodoc", " + Returns the platform's process extended crash information.") GetExtendedCrashInformation; + lldb::SBStructuredData + GetExtendedCrashInformation (); + + void SetCollectingStats(bool v); bool GetCollectingStats(); diff --git a/lldb/include/lldb/API/SBTarget.h b/lldb/include/lldb/API/SBTarget.h --- a/lldb/include/lldb/API/SBTarget.h +++ b/lldb/include/lldb/API/SBTarget.h @@ -819,6 +819,8 @@ void SetLaunchInfo(const lldb::SBLaunchInfo &launch_info); + SBStructuredData GetExtendedCrashInformation(); + protected: friend class SBAddress; friend class SBBlock; @@ -829,6 +831,7 @@ friend class SBFunction; friend class SBInstruction; friend class SBModule; + friend class SBPlatform; friend class SBProcess; friend class SBSection; friend class SBSourceManager; diff --git a/lldb/include/lldb/Target/Platform.h b/lldb/include/lldb/Target/Platform.h --- a/lldb/include/lldb/Target/Platform.h +++ b/lldb/include/lldb/Target/Platform.h @@ -23,6 +23,7 @@ #include "lldb/Utility/ArchSpec.h" #include "lldb/Utility/ConstString.h" #include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/StructuredData.h" #include "lldb/Utility/Timeout.h" #include "lldb/Utility/UserIDResolver.h" #include "lldb/lldb-private-forward.h" @@ -823,6 +824,26 @@ virtual size_t ConnectToWaitingProcesses(lldb_private::Debugger &debugger, lldb_private::Status &error); + /// Gather all of crash informations into a structured data dictionnary. + /// + /// If the platform have a crashed process with crash information entries, + /// gather all the entries into an structured data dictionnary or return a + /// nullptr. This dictionnary is generic and extensible, as it contains an + /// array for each different type of crash information. + /// + /// \param[in] target + /// The target running the crashed process. + /// + /// \return + /// A structured data dictionnary containing at each entry, the crash + /// information type as the entry key and the matching an array as the + /// entry value. \b nullptr if not implemented or if the process has no + /// crash information entry. \b error if an error occured. + virtual llvm::Expected + FetchExtendedCrashInformation(lldb_private::Target &target) { + return nullptr; + } + protected: bool m_is_host; // Set to true when we are able to actually set the OS version while being 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 diff --git a/lldb/source/API/SBTarget.cpp b/lldb/source/API/SBTarget.cpp --- a/lldb/source/API/SBTarget.cpp +++ b/lldb/source/API/SBTarget.cpp @@ -2388,6 +2388,30 @@ m_opaque_sp->SetProcessLaunchInfo(launch_info.ref()); } +SBStructuredData SBTarget::GetExtendedCrashInformation() { + LLDB_RECORD_METHOD_NO_ARGS(lldb::SBStructuredData, SBTarget, + GetExtendedCrashInformation); + SBStructuredData data; + TargetSP target_sp(GetSP()); + if (!target_sp) + return LLDB_RECORD_RESULT(data); + + PlatformSP platform_sp = target_sp->GetPlatform(); + + if (!target_sp) + return LLDB_RECORD_RESULT(data); + + auto expected_data = + platform_sp->FetchExtendedCrashInformation(*target_sp.get()); + + if (!expected_data) + return LLDB_RECORD_RESULT(data); + + StructuredData::ObjectSP fetched_data = *expected_data; + data.m_impl_up->SetObjectSP(fetched_data); + return LLDB_RECORD_RESULT(data); +} + namespace lldb_private { namespace repro { @@ -2630,6 +2654,8 @@ LLDB_REGISTER_METHOD_CONST(lldb::SBLaunchInfo, SBTarget, GetLaunchInfo, ()); LLDB_REGISTER_METHOD(void, SBTarget, SetLaunchInfo, (const lldb::SBLaunchInfo &)); + LLDB_REGISTER_METHOD(lldb::SBStructuredData, SBTarget, + GetExtendedCrashInformation, ()); LLDB_REGISTER_METHOD( size_t, SBTarget, ReadMemory, (const lldb::SBAddress, void *, size_t, lldb::SBError &)); 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 @@ -1201,6 +1201,8 @@ // CommandObjectProcessStatus #pragma mark CommandObjectProcessStatus +#define LLDB_OPTIONS_process_status +#include "CommandOptions.inc" class CommandObjectProcessStatus : public CommandObjectParsed { public: @@ -1209,13 +1211,57 @@ interpreter, "process status", "Show status and stop location for the current target process.", "process status", - eCommandRequiresProcess | eCommandTryTargetAPILock) {} + eCommandRequiresProcess | eCommandTryTargetAPILock), + m_options() {} ~CommandObjectProcessStatus() override = default; + Options *GetOptions() override { return &m_options; } + + class CommandOptions : public Options { + public: + CommandOptions() : Options(), m_verbose(false) {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'v': + m_verbose = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return {}; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_verbose = false; + } + + llvm::ArrayRef GetDefinitions() override { + return llvm::makeArrayRef(g_process_status_options); + } + + // Instance variables to hold the values for command options. + bool m_verbose; + }; + +protected: bool DoExecute(Args &command, CommandReturnObject &result) override { Stream &strm = result.GetOutputStream(); result.SetStatus(eReturnStatusSuccessFinishNoResult); + + if (command.GetArgumentCount()) { + result.AppendError("'process status' takes no arguments"); + result.SetStatus(eReturnStatusFailed); + return result.Succeeded(); + } + // No need to check "process" for validity as eCommandRequiresProcess // ensures it is valid Process *process = m_exe_ctx.GetProcessPtr(); @@ -1227,8 +1273,37 @@ process->GetStatus(strm); process->GetThreadStatus(strm, only_threads_with_stop_reason, start_frame, num_frames, num_frames_with_source, stop_format); + + if (m_options.m_verbose) { + PlatformSP platform_sp = process->GetTarget().GetPlatform(); + if (!platform_sp) { + result.AppendError("Couldn'retrieve the target's platform"); + result.SetStatus(eReturnStatusFailed); + return result.Succeeded(); + } + + auto expected_crash_info = + platform_sp->FetchExtendedCrashInformation(process->GetTarget()); + + if (!expected_crash_info) { + result.AppendError(llvm::toString(expected_crash_info.takeError())); + result.SetStatus(eReturnStatusFailed); + return result.Succeeded(); + } + + StructuredData::DictionarySP crash_info_sp = *expected_crash_info; + + if (crash_info_sp) { + strm.PutCString("Extended Crash Information:\n"); + crash_info_sp->Dump(strm); + } + } + return result.Succeeded(); } + +private: + CommandOptions m_options; }; // CommandObjectProcessHandle diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -670,6 +670,11 @@ Desc<"Whether or not the signal should be passed to the process.">; } +let Command = "process status" in { + def process_status_verbose : Option<"verbose", "v">, Group<1>, + Desc<"Show verbose process status including extended crash information.">; +} + let Command = "script import" in { def script_import_allow_reload : Option<"allow-reload", "r">, Group<1>, Desc<"Allow the script to be loaded even if it was already loaded before. " diff --git a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h --- a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h +++ b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h @@ -12,6 +12,7 @@ #include "Plugins/Platform/POSIX/PlatformPOSIX.h" #include "lldb/Host/FileSystem.h" #include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/StructuredData.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/FileSystem.h" @@ -84,7 +85,38 @@ iPhoneOS, }; + llvm::Expected + FetchExtendedCrashInformation(lldb_private::Target &target) override; + protected: + struct CrashInfoAnnotations { + 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 + }; + + /// Extract the `__crash_info` annotations from each of of the target's + /// modules. + /// + /// If the platform have a crashed processes with a `__crash_info` section, + /// extract the section to gather the messages annotations and the abort + /// cause. + /// + /// \param[in] target + /// The target running the crashed process. + /// + /// \return + /// A structured data array containing at each entry in each entry, the + /// module spec, its UUID, the crash messages and the abort cause. + /// \b nullptr if process has no crash information annotations. + lldb_private::StructuredData::ArraySP + ExtractCrashInfoAnnotations(lldb_private::Target &target); + void ReadLibdispatchOffsetsAddress(lldb_private::Process *process); void ReadLibdispatchOffsets(lldb_private::Process *process); diff --git a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp --- a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp +++ b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp @@ -19,6 +19,7 @@ #include "lldb/Core/Debugger.h" #include "lldb/Core/Module.h" #include "lldb/Core/ModuleSpec.h" +#include "lldb/Core/Section.h" #include "lldb/Host/Host.h" #include "lldb/Host/HostInfo.h" #include "lldb/Host/XML.h" @@ -1501,6 +1502,129 @@ return std::make_tuple(version, build); } +llvm::Expected +PlatformDarwin::FetchExtendedCrashInformation(lldb_private::Target &target) { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + StructuredData::ArraySP annotations = ExtractCrashInfoAnnotations(target); + + if (!annotations || !annotations->GetSize()) { + LLDB_LOG(log, "Couldn't extract crash information annotations"); + return nullptr; + } + + StructuredData::DictionarySP extended_crash_info = + std::make_shared(); + + extended_crash_info->AddItem("crash-info annotations", annotations); + + return extended_crash_info; +} + +StructuredData::ArraySP +PlatformDarwin::ExtractCrashInfoAnnotations(Target &target) { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + ConstString section_name("__crash_info"); + ProcessSP process_sp = target.GetProcessSP(); + StructuredData::ArraySP array_sp = std::make_shared(); + + for (ModuleSP module : target.GetImages().Modules()) { + SectionList *sections = module->GetSectionList(); + + std::string module_name = module->GetSpecificationDescription(); + + // The DYDL module is skipped since it's always loaded when running the + // binary. + if (module_name == "/usr/lib/dyld") + continue; + + if (!sections) { + LLDB_LOG(log, "Module {0} doesn't have any section!", module_name); + continue; + } + + 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(&target); + + if (load_addr == LLDB_INVALID_ADDRESS) { + LLDB_LOG(log, "Module {0} has an invalid '{1}' section load address: {2}", + module_name, section_name, load_addr); + continue; + } + + Status error; + CrashInfoAnnotations annotations; + size_t expected_size = sizeof(CrashInfoAnnotations); + size_t bytes_read = process_sp->ReadMemoryFromInferior( + load_addr, &annotations, expected_size, error); + + if (expected_size != bytes_read || error.Fail()) { + LLDB_LOG(log, "Failed to read {0} section from memory in module {1}: {2}", + section_name, module_name, error); + continue; + } + + // initial support added for version 5 + if (annotations.version < 5) { + LLDB_LOG(log, + "Annotation version lower than 5 unsupported! Module {0} has " + "version {1} instead.", + module_name, annotations.version); + continue; + } + + if (!annotations.message) { + LLDB_LOG(log, "No message available for module {0}.", module_name); + continue; + } + + std::string message; + bytes_read = + process_sp->ReadCStringFromMemory(annotations.message, message, error); + + if (message.empty() || bytes_read != message.size() || error.Fail()) { + LLDB_LOG(log, "Failed to read the message from memory in module {0}: {1}", + module_name, error); + continue; + } + + // Remove trailing newline from message + if (message.back() == '\n') + message.pop_back(); + + if (!annotations.message2) + LLDB_LOG(log, "No message2 available for module {0}.", module_name); + + std::string message2; + bytes_read = process_sp->ReadCStringFromMemory(annotations.message2, + message2, error); + + if (!message2.empty() && bytes_read == message2.size() && error.Success()) + if (message2.back() == '\n') + message2.pop_back(); + + 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", message); + entry_sp->AddStringItem("message2", message2); + entry_sp->AddIntegerItem("abort-cause", annotations.abort_cause); + + array_sp->AddItem(entry_sp); + } + + return array_sp; +} + void PlatformDarwin::AddClangModuleCompilationOptionsForSDKType( Target *target, std::vector &options, SDKType sdk_type) { const std::vector apple_arguments = { diff --git a/lldb/test/API/functionalities/process_crash_info/Makefile b/lldb/test/API/functionalities/process_crash_info/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/process_crash_info/Makefile @@ -0,0 +1,4 @@ +C_SOURCES := main.c + +include Makefile.rules + diff --git a/lldb/test/API/functionalities/process_crash_info/TestProcessCrashInfo.py b/lldb/test/API/functionalities/process_crash_info/TestProcessCrashInfo.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/process_crash_info/TestProcessCrashInfo.py @@ -0,0 +1,97 @@ +""" +Test lldb process crash info. +""" + +import os + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + +class PlatformProcessCrashInfoTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + def setUp(self): + TestBase.setUp(self) + self.runCmd("settings set auto-confirm true") + self.source = "main.c" + self.line = 3 + + def tearDown(self): + self.runCmd("settings clear auto-confirm") + TestBase.tearDown(self) + + @skipUnlessDarwin + def test_cli(self): + """Test that `process status --verbose` fetches the extended crash + information dictionnary 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 status --verbose', + patterns=["\"message\".*pointer being freed was not allocated"]) + + + @skipUnlessDarwin + def test_api(self): + """Test that lldb can fetch a crashed process' extended crash information + dictionnary from the api properly.""" + self.build() + target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + self.assertTrue(target, VALID_TARGET) + + target.LaunchSimple(None, None, os.getcwd()) + + stream = lldb.SBStream() + self.assertTrue(stream) + + crash_info = target.GetExtendedCrashInformation() + + error = crash_info.GetAsJSON(stream) + + self.assertTrue(error.Success()) + + self.assertTrue(crash_info.IsValid()) + + self.assertIn("pointer being freed was not allocated", stream.GetData()) + + @skipUnlessDarwin + def test_before_launch(self): + """Test that lldb doesn't fetch the extended crash information + dictionnary from if the process wasn't launched yet.""" + self.build() + target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + self.assertTrue(target, VALID_TARGET) + + stream = lldb.SBStream() + self.assertTrue(stream) + + crash_info = target.GetExtendedCrashInformation() + + error = crash_info.GetAsJSON(stream) + self.assertFalse(error.Success()) + self.assertIn("No structured data.", error.GetCString()) + + @skipUnlessDarwin + def test_on_sane_process(self): + """Test that lldb doesn't fetch the extended crash information + dictionnary from a 'sane' stopped process.""" + self.build() + target, _, _, _ = lldbutil.run_to_line_breakpoint(self, lldb.SBFileSpec(self.source), + self.line) + + stream = lldb.SBStream() + self.assertTrue(stream) + + crash_info = target.GetExtendedCrashInformation() + + error = crash_info.GetAsJSON(stream) + self.assertFalse(error.Success()) + self.assertIn("No structured data.", error.GetCString()) diff --git a/lldb/test/API/functionalities/process_crash_info/main.c b/lldb/test/API/functionalities/process_crash_info/main.c new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/process_crash_info/main.c @@ -0,0 +1,7 @@ +#include +int main() { + int *var = malloc(sizeof(int)); + free(var); + free(var); + return 0; +}