diff --git a/lldb/bindings/interface/SBPlatform.i b/lldb/bindings/interface/SBPlatform.i --- a/lldb/bindings/interface/SBPlatform.i +++ b/lldb/bindings/interface/SBPlatform.i @@ -192,6 +192,11 @@ lldb::SBUnixSignals GetUnixSignals(); + %feature("autodoc", " + Returns the platform's process extended crash information.") GetExtendedCrashInformation; + lldb::SBStructuredData + GetExtendedCrashInformation (lldb::SBTarget& target); + }; } // namespace lldb diff --git a/lldb/include/lldb/API/SBPlatform.h b/lldb/include/lldb/API/SBPlatform.h --- a/lldb/include/lldb/API/SBPlatform.h +++ b/lldb/include/lldb/API/SBPlatform.h @@ -10,6 +10,7 @@ #define LLDB_API_SBPLATFORM_H #include "lldb/API/SBDefines.h" +#include "lldb/API/SBStructuredData.h" #include @@ -152,6 +153,8 @@ SBUnixSignals GetUnixSignals() const; + SBStructuredData GetExtendedCrashInformation(SBTarget &target); + protected: friend class SBDebugger; friend class SBTarget; 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 @@ -90,6 +90,7 @@ protected: friend class SBTraceOptions; friend class SBDebugger; + friend class SBPlatform; friend class SBTarget; friend class SBThread; friend class SBThreadPlan; 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 @@ -829,6 +829,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,25 @@ 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 processes 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 an array for + /// each different crash information type. Or a nullptr if the process has + /// no crash information entry. + virtual StructuredData::DictionarySP + 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 @@ -858,6 +878,7 @@ std::vector m_trap_handlers; bool m_calculated_trap_handlers; const std::unique_ptr m_module_cache; + StructuredData::DictionarySP m_extended_crash_info; /// Ask the Platform subclass to fill in the list of trap handler names /// 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/SBPlatform.cpp b/lldb/source/API/SBPlatform.cpp --- a/lldb/source/API/SBPlatform.cpp +++ b/lldb/source/API/SBPlatform.cpp @@ -11,7 +11,9 @@ #include "lldb/API/SBError.h" #include "lldb/API/SBFileSpec.h" #include "lldb/API/SBLaunchInfo.h" +#include "lldb/API/SBTarget.h" #include "lldb/API/SBUnixSignals.h" +#include "lldb/Core/StructuredDataImpl.h" #include "lldb/Host/File.h" #include "lldb/Target/Platform.h" #include "lldb/Target/Target.h" @@ -643,6 +645,21 @@ return LLDB_RECORD_RESULT(SBUnixSignals()); } +SBStructuredData SBPlatform::GetExtendedCrashInformation(SBTarget &target) { + LLDB_RECORD_METHOD(lldb::SBStructuredData, SBPlatform, + GetExtendedCrashInformation, (lldb::SBTarget &), target); + + SBStructuredData data; + PlatformSP platform_sp(GetSP()); + if (!platform_sp) + return LLDB_RECORD_RESULT(data); + + StructuredData::ObjectSP fetched_data = + platform_sp->FetchExtendedCrashInformation(*target.GetSP().get()); + data.m_impl_up->SetObjectSP(fetched_data); + return LLDB_RECORD_RESULT(data); +} + namespace lldb_private { namespace repro { @@ -736,6 +753,8 @@ (const char *, uint32_t)); LLDB_REGISTER_METHOD_CONST(lldb::SBUnixSignals, SBPlatform, GetUnixSignals, ()); + LLDB_REGISTER_METHOD(lldb::SBStructuredData, SBPlatform, + GetExtendedCrashInformation, (lldb::SBTarget &)); } } diff --git a/lldb/source/Commands/CommandObjectPlatform.cpp b/lldb/source/Commands/CommandObjectPlatform.cpp --- a/lldb/source/Commands/CommandObjectPlatform.cpp +++ b/lldb/source/Commands/CommandObjectPlatform.cpp @@ -1517,14 +1517,64 @@ CommandOptions m_options; }; +// CommandObjectPlatformProcessCrashInfo +#pragma mark CommandObjectPlatformProcessCrashInfo + +class CommandObjectPlatformProcessCrashInfo : public CommandObjectParsed { +public: + CommandObjectPlatformProcessCrashInfo(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "platform process crash-info", + "Fetch the platform's process crash information", + "process crash-info", + eCommandRequiresProcess | + eCommandTryTargetAPILock) {} + + ~CommandObjectPlatformProcessCrashInfo() override = default; + + bool DoExecute(Args &command, CommandReturnObject &result) override { + Stream &strm = result.GetOutputStream(); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + + PlatformSP platform_sp( + GetDebugger().GetPlatformList().GetSelectedPlatform()); + if (!platform_sp) { + result.AppendError("Couldn'retrieve the selected platform"); + return result.Succeeded(); + } + + if (command.GetArgumentCount()) { + result.AppendError("`process crash-info` takes no arguments"); + return result.Succeeded(); + } + + // No need to check "process" for validity as eCommandRequiresProcess + // ensures it is valid + Target &target = GetSelectedTarget(); + ProcessSP process_sp = target.GetProcessSP(); + + StructuredData::ObjectSP crash_info_sp = + platform_sp->FetchExtendedCrashInformation(target); + + if (!crash_info_sp) { + result.AppendError("Couldn't fetch the crash information"); + return result.Succeeded(); + } + + crash_info_sp->Dump(strm); + + return result.Succeeded(); + } +}; + class CommandObjectPlatformProcess : public CommandObjectMultiword { public: // Constructors and Destructors CommandObjectPlatformProcess(CommandInterpreter &interpreter) - : CommandObjectMultiword(interpreter, "platform process", - "Commands to query, launch and attach to " - "processes on the current platform.", - "platform process [attach|launch|list] ...") { + : CommandObjectMultiword( + interpreter, "platform process", + "Commands to query, launch and attach to " + "processes on the current platform.", + "platform process [attach|launch|list|crash-info] ...") { LoadSubCommand( "attach", CommandObjectSP(new CommandObjectPlatformProcessAttach(interpreter))); @@ -1535,6 +1585,9 @@ interpreter))); LoadSubCommand("list", CommandObjectSP(new CommandObjectPlatformProcessList( interpreter))); + LoadSubCommand("crash-info", + CommandObjectSP( + new CommandObjectPlatformProcessCrashInfo(interpreter))); } ~CommandObjectPlatformProcess() override = default; 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,44 @@ iPhoneOS, }; + lldb_private::StructuredData::DictionarySP + FetchExtendedCrashInformation(lldb_private::Target &target) override; + protected: + // This struct should only be available in macOS but could be used on other + // platforms. #ifdef HAVE_CRASHREPORTERCLIENT_H #include + // #endif + 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. + /// \param[in] log + /// The log pointer initialized with the right logging channel + /// + /// \return + /// A structured data array containing at each entry in each entry, the + /// module spec, its UUID, the crash messages and the abort cause. Or a + /// nullptr if the process has no crash information entry. + lldb_private::StructuredData::ArraySP + ExtractCrashInfoAnnotations(lldb_private::Target &target, + lldb_private::Log *log); + 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,125 @@ return std::make_tuple(version, build); } +StructuredData::DictionarySP +PlatformDarwin::FetchExtendedCrashInformation(lldb_private::Target &target) { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS)); + + StructuredData::ArraySP annotations = + ExtractCrashInfoAnnotations(target, log); + + if (!annotations) { + LLDB_LOG(log, "Couldn't extract crash information annotations"); + } + + if (!m_extended_crash_info) + m_extended_crash_info = std::make_shared(); + + if (annotations) + m_extended_crash_info->AddItem("crash-info annotations", annotations); + + return m_extended_crash_info; +} + +StructuredData::ArraySP +PlatformDarwin::ExtractCrashInfoAnnotations(Target &target, Log *log) { + 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(); + + 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(&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/source/Target/Platform.cpp b/lldb/source/Target/Platform.cpp --- a/lldb/source/Target/Platform.cpp +++ b/lldb/source/Target/Platform.cpp @@ -384,7 +384,8 @@ m_rsync_opts(), m_rsync_prefix(), m_supports_ssh(false), m_ssh_opts(), m_ignores_remote_hostname(false), m_trap_handlers(), m_calculated_trap_handlers(false), - m_module_cache(std::make_unique()) { + m_module_cache(std::make_unique()), + m_extended_crash_info(nullptr) { Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_OBJECT)); LLDB_LOGF(log, "%p Platform::Platform()", static_cast(this)); } diff --git a/lldb/test/API/commands/platform/process/crash-info/Makefile b/lldb/test/API/commands/platform/process/crash-info/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/platform/process/crash-info/Makefile @@ -0,0 +1,4 @@ +C_SOURCES := main.c + +include Makefile.rules + diff --git a/lldb/test/API/commands/platform/process/crash-info/TestPlatformProcessCrashInfo.py b/lldb/test/API/commands/platform/process/crash-info/TestPlatformProcessCrashInfo.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/platform/process/crash-info/TestPlatformProcessCrashInfo.py @@ -0,0 +1,63 @@ +""" +Test lldb platform 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") + + def tearDown(self): + self.runCmd("settings clear auto-confirm") + TestBase.tearDown(self) + + @skipUnlessDarwin + def test_cli(self): + """Test that platform process crash-info fetches the extended crash + information array 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('platform process crash-info', + patterns=["\"message\".*pointer being freed was not allocated"]) + + + @skipUnlessDarwin + def test_api(self): + """Test that platform process crash-info fetches the extended crash + information array 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()) + + platform = target.GetPlatform() + + stream = lldb.SBStream() + self.assertTrue(stream) + + crash_info = platform.GetExtendedCrashInformation(target) + + error = crash_info.GetAsJSON(stream) + + self.assertTrue(error.Success()) + + self.assertTrue(crash_info.IsValid()) + + self.assertIn("pointer being freed was not allocated", stream.GetData()) diff --git a/lldb/test/API/commands/platform/process/crash-info/main.c b/lldb/test/API/commands/platform/process/crash-info/main.c new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/platform/process/crash-info/main.c @@ -0,0 +1,7 @@ +#include +int main() { + int *var = malloc(sizeof(int)); + free(var); + free(var); + return 0; +}