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 @@ -687,6 +687,18 @@ "Not implemented"); } + /// Save core dump into the specified file. + /// + /// \param[in] outfile + /// Path to store core dump in. + /// + /// \return + /// true if saved successfully, false if saving the core dump + /// is not supported by the plugin, error otherwise. + virtual llvm::Expected SaveCore(llvm::StringRef outfile) { + return false; + } + protected: virtual JITLoaderList &GetJITLoaders(); diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp --- a/lldb/source/Core/PluginManager.cpp +++ b/lldb/source/Core/PluginManager.cpp @@ -12,6 +12,7 @@ #include "lldb/Host/FileSystem.h" #include "lldb/Host/HostInfo.h" #include "lldb/Interpreter/OptionValueProperties.h" +#include "lldb/Target/Process.h" #include "lldb/Utility/ConstString.h" #include "lldb/Utility/FileSpec.h" #include "lldb/Utility/Status.h" @@ -687,6 +688,16 @@ const FileSpec &outfile, lldb::SaveCoreStyle &core_style, const ConstString plugin_name) { + if (!plugin_name) { + // Try saving core directly from the process plugin first. + llvm::Expected ret = process_sp->SaveCore(outfile.GetPath()); + if (!ret) + return Status(std::move(ret.takeError())); + if (ret.get()) + return Status(); + } + + // Fall back to object plugins. Status error; auto &instances = GetObjectFileInstances().GetInstances(); for (auto &instance : instances) { diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h @@ -547,6 +547,8 @@ SendTraceGetBinaryData(const TraceGetBinaryDataRequest &request, std::chrono::seconds interrupt_timeout); + bool GetSaveCoreSupported() const; + protected: LazyBool m_supports_not_sending_acks = eLazyBoolCalculate; LazyBool m_supports_thread_suffix = eLazyBoolCalculate; @@ -585,6 +587,7 @@ LazyBool m_supports_error_string_reply = eLazyBoolCalculate; LazyBool m_supports_multiprocess = eLazyBoolCalculate; LazyBool m_supports_memory_tagging = eLazyBoolCalculate; + LazyBool m_supports_qSaveCore = eLazyBoolCalculate; bool m_supports_qProcessInfoPID : 1, m_supports_qfProcessInfo : 1, m_supports_qUserName : 1, m_supports_qGroupName : 1, diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp @@ -257,6 +257,7 @@ m_attach_or_wait_reply = eLazyBoolCalculate; m_avoid_g_packets = eLazyBoolCalculate; m_supports_multiprocess = eLazyBoolCalculate; + m_supports_qSaveCore = eLazyBoolCalculate; m_supports_qXfer_auxv_read = eLazyBoolCalculate; m_supports_qXfer_libraries_read = eLazyBoolCalculate; m_supports_qXfer_libraries_svr4_read = eLazyBoolCalculate; @@ -312,6 +313,7 @@ m_supports_qEcho = eLazyBoolNo; m_supports_QPassSignals = eLazyBoolNo; m_supports_memory_tagging = eLazyBoolNo; + m_supports_qSaveCore = eLazyBoolNo; m_max_packet_size = UINT64_MAX; // It's supposed to always be there, but if // not, we assume no limit @@ -359,6 +361,8 @@ m_supports_multiprocess = eLazyBoolYes; else if (x == "memory-tagging+") m_supports_memory_tagging = eLazyBoolYes; + else if (x == "qSaveCore+") + m_supports_qSaveCore = eLazyBoolYes; // Look for a list of compressions in the features list e.g. // qXfer:features:read+;PacketSize=20000;qEcho+;SupportedCompressions=zlib- // deflate,lzma @@ -501,6 +505,10 @@ return eLazyBoolNo; } +bool GDBRemoteCommunicationClient::GetSaveCoreSupported() const { + return m_supports_qSaveCore == eLazyBoolYes; +} + StructuredData::ObjectSP GDBRemoteCommunicationClient::GetThreadsInfo() { // Get information on all threads at one using the "jThreadsInfo" packet StructuredData::ObjectSP object_sp; diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h @@ -235,6 +235,8 @@ void DidVForkDone() override; void DidExec() override; + llvm::Expected SaveCore(llvm::StringRef outfile) override; + protected: friend class ThreadGDBRemote; friend class GDBRemoteCommunicationClient; diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -5147,6 +5147,58 @@ BuildDynamicRegisterInfo(true); } +llvm::Expected ProcessGDBRemote::SaveCore(llvm::StringRef outfile) { + if (!m_gdb_comm.GetSaveCoreSupported()) + return false; + + StreamString packet; + packet.PutCString("qSaveCore;path-hint:"); + packet.PutStringAsRawHex8(outfile); + + StringExtractorGDBRemote response; + if (m_gdb_comm.SendPacketAndWaitForResponse(packet.GetString(), response) == + GDBRemoteCommunication::PacketResult::Success) { + // TODO: grab error message from the packet? StringExtractor seems to + // be missing a method for that + if (response.IsErrorResponse()) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + llvm::formatv("qSaveCore returned an error")); + + std::string path; + + // process the response + llvm::SmallVector reply_data; + response.GetStringRef().split(reply_data, ';'); + for (auto x : reply_data) { + if (x.consume_front("core-path:")) + StringExtractor(x).GetHexByteString(path); + } + + // verify that we've gotten what we need + if (path.empty()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "qSaveCore returned no core path"); + + // now transfer the core file + FileSpec remote_core{llvm::StringRef(path)}; + Platform &platform = *GetTarget().GetPlatform(); + Status error = platform.GetFile(remote_core, FileSpec(outfile)); + + if (platform.IsRemote()) { + // NB: we unlink the file on error too + platform.Unlink(remote_core); + if (error.Fail()) + return error.ToError(); + } + + return true; + } + + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Unable to send qSaveCore"); +} + static const char *const s_async_json_packet_prefix = "JSON-async:"; static StructuredData::ObjectSP diff --git a/lldb/test/API/functionalities/process_save_core/TestProcessSaveCore.py b/lldb/test/API/functionalities/process_save_core/TestProcessSaveCore.py --- a/lldb/test/API/functionalities/process_save_core/TestProcessSaveCore.py +++ b/lldb/test/API/functionalities/process_save_core/TestProcessSaveCore.py @@ -63,3 +63,31 @@ self.assertTrue(self.dbg.DeleteTarget(target)) if (os.path.isfile(core)): os.unlink(core) + + @skipUnlessPlatform(["netbsd"]) + def test_save_core_via_process_plugin(self): + self.build() + exe = self.getBuildArtifact("a.out") + core = self.getBuildArtifact("a.out.core") + try: + target = self.dbg.CreateTarget(exe) + breakpoint = target.BreakpointCreateByName("bar") + process = target.LaunchSimple( + None, None, self.get_process_working_directory()) + self.assertEqual(process.GetState(), lldb.eStateStopped) + self.assertTrue(process.SaveCore(core)) + self.assertTrue(os.path.isfile(core)) + self.assertTrue(process.Kill().Success()) + pid = process.GetProcessID() + + target = self.dbg.CreateTarget(None) + process = target.LoadCore(core) + self.assertTrue(process, PROCESS_IS_VALID) + self.assertEqual(process.GetProcessID(), pid) + + finally: + self.assertTrue(self.dbg.DeleteTarget(target)) + try: + os.unlink(core) + except OSError: + pass