diff --git a/lldb/include/lldb/Host/common/NativeProcessProtocol.h b/lldb/include/lldb/Host/common/NativeProcessProtocol.h --- a/lldb/include/lldb/Host/common/NativeProcessProtocol.h +++ b/lldb/include/lldb/Host/common/NativeProcessProtocol.h @@ -250,8 +250,9 @@ auxv = (1u << 4), libraries_svr4 = (1u << 5), memory_tagging = (1u << 6), + savecore = (1u << 7), - LLVM_MARK_AS_BITMASK_ENUM(memory_tagging) + LLVM_MARK_AS_BITMASK_ENUM(savecore) }; class Factory { @@ -369,6 +370,19 @@ m_enabled_extensions = flags; } + /// Write a core dump (without crashing the program). + /// + /// \param[in] path_hint + /// Suggested core dump path (optional, can be empty). + /// + /// \return + /// Path to the core dump if successfully written, an error + /// otherwise. + virtual llvm::Expected SaveCore(llvm::StringRef path_hint) { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Not implemented"); + } + protected: struct SoftwareBreakpoint { uint32_t ref_count; diff --git a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h --- a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h +++ b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h @@ -170,6 +170,8 @@ eServerPacketType_qMemTags, // read memory tags eServerPacketType_QMemTags, // write memory tags + + eServerPacketType_qLLDBSaveCore, }; ServerPacketType GetServerPacketType() const; diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py @@ -860,6 +860,7 @@ "fork-events", "vfork-events", "memory-tagging", + "qSaveCore", ] def parse_qSupported_response(self, context): diff --git a/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.h b/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.h --- a/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.h +++ b/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.h @@ -88,6 +88,8 @@ static Status PtraceWrapper(int req, lldb::pid_t pid, void *addr = nullptr, int data = 0, int *result = nullptr); + llvm::Expected SaveCore(llvm::StringRef path_hint) override; + private: MainLoop::SignalHandleUP m_sigchld_handle; ArchSpec m_arch; diff --git a/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.cpp b/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.cpp --- a/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.cpp +++ b/lldb/source/Plugins/Process/NetBSD/NativeProcessNetBSD.cpp @@ -136,7 +136,8 @@ NativeProcessNetBSD::Extension NativeProcessNetBSD::Factory::GetSupportedExtensions() const { return Extension::multiprocess | Extension::fork | Extension::vfork | - Extension::pass_signals | Extension::auxv | Extension::libraries_svr4; + Extension::pass_signals | Extension::auxv | Extension::libraries_svr4 | + Extension::savecore; } // Public Instance Methods @@ -1073,3 +1074,27 @@ } } } + +llvm::Expected +NativeProcessNetBSD::SaveCore(llvm::StringRef path_hint) { + llvm::SmallString<128> path{path_hint}; + Status error; + + // Try with the suggested path first. + if (!path.empty()) { + error = PtraceWrapper(PT_DUMPCORE, GetID(), path.data(), path.size()); + if (!error.Fail()) + return path.str().str(); + + // If the request errored, fall back to a generic temporary file. + } + + if (std::error_code errc = + llvm::sys::fs::createTemporaryFile("lldb", "core", path)) + return llvm::createStringError(errc, "Unable to create a temporary file"); + + error = PtraceWrapper(PT_DUMPCORE, GetID(), path.data(), path.size()); + if (error.Fail()) + return error.ToError(); + return path.str().str(); +} diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h @@ -214,6 +214,8 @@ PacketResult Handle_QPassSignals(StringExtractorGDBRemote &packet); + PacketResult Handle_qSaveCore(StringExtractorGDBRemote &packet); + PacketResult Handle_g(StringExtractorGDBRemote &packet); PacketResult Handle_qMemTags(StringExtractorGDBRemote &packet); diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -226,6 +226,10 @@ quit = true; return this->Handle_k(packet); }); + + RegisterMemberFunctionHandler( + StringExtractorGDBRemote::eServerPacketType_qLLDBSaveCore, + &GDBRemoteCommunicationServerLLGS::Handle_qSaveCore); } void GDBRemoteCommunicationServerLLGS::SetLaunchInfo(const ProcessLaunchInfo &info) { @@ -3604,6 +3608,41 @@ return status.Success() ? SendOKResponse() : SendErrorResponse(1); } +GDBRemoteCommunication::PacketResult +GDBRemoteCommunicationServerLLGS::Handle_qSaveCore( + StringExtractorGDBRemote &packet) { + // Fail if we don't have a current process. + if (!m_current_process || + (m_current_process->GetID() == LLDB_INVALID_PROCESS_ID)) + return SendErrorResponse(Status("Process not running.")); + + std::string path_hint; + + StringRef packet_str{packet.GetStringRef()}; + bool cf = packet_str.consume_front("qSaveCore"); + assert(cf); + if (packet_str.consume_front(";")) { + llvm::SmallVector fields; + packet_str.split(fields, ';'); + + for (auto x : fields) { + if (x.consume_front("path-hint:")) + StringExtractor(x).GetHexByteString(path_hint); + else + return SendErrorResponse(Status("Unsupported qSaveCore option")); + } + } + + llvm::Expected ret = m_current_process->SaveCore(path_hint); + if (!ret) + return SendErrorResponse(std::move(ret.takeError())); + + StreamString response; + response.PutCString("core-path:"); + response.PutStringAsRawHex8(ret.get()); + return SendPacketNoLock(response.GetString()); +} + void GDBRemoteCommunicationServerLLGS::MaybeCloseInferiorTerminalConnection() { Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS)); @@ -3800,6 +3839,8 @@ ret.push_back("qXfer:libraries-svr4:read+"); if (bool(plugin_features & Extension::memory_tagging)) ret.push_back("memory-tagging+"); + if (bool(plugin_features & Extension::savecore)) + ret.push_back("qSaveCore+"); // check for client features m_extensions_supported = {}; diff --git a/lldb/source/Utility/StringExtractorGDBRemote.cpp b/lldb/source/Utility/StringExtractorGDBRemote.cpp --- a/lldb/source/Utility/StringExtractorGDBRemote.cpp +++ b/lldb/source/Utility/StringExtractorGDBRemote.cpp @@ -260,6 +260,8 @@ break; case 'S': + if (PACKET_STARTS_WITH("qSaveCore")) + return eServerPacketType_qLLDBSaveCore; if (PACKET_STARTS_WITH("qSpeedTest:")) return eServerPacketType_qSpeedTest; if (PACKET_MATCHES("qShlibInfoAddr")) diff --git a/lldb/test/API/tools/lldb-server/TestGdbRemoteSaveCore.py b/lldb/test/API/tools/lldb-server/TestGdbRemoteSaveCore.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-server/TestGdbRemoteSaveCore.py @@ -0,0 +1,52 @@ +import gdbremote_testcase +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + +import binascii +import os + +class TestGdbSaveCore(gdbremote_testcase.GdbRemoteTestCaseBase): + mydir = TestBase.compute_mydir(__file__) + + def coredump_test(self, core_path=None, expect_path=None): + self.build() + self.set_inferior_startup_attach() + procs = self.prep_debug_monitor_and_inferior() + self.add_qSupported_packets() + ret = self.expect_gdbremote_sequence() + self.assertIn("qSaveCore+", ret["qSupported_response"]) + self.reset_test_sequence() + + packet = "$qSaveCore" + if core_path is not None: + packet += ";path-hint:{}".format( + binascii.b2a_hex(core_path.encode()).decode()) + + self.test_sequence.add_log_lines([ + "read packet: {}#00".format(packet), + {"direction": "send", "regex": "[$]core-path:([0-9a-f]+)#.*", + "capture": {1: "path"}}, + ], True) + ret = self.expect_gdbremote_sequence() + out_path = binascii.a2b_hex(ret["path"].encode()).decode() + if expect_path is not None: + self.assertEqual(out_path, expect_path) + + target = self.dbg.CreateTarget(None) + process = target.LoadCore(out_path) + self.assertTrue(process, PROCESS_IS_VALID) + self.assertEqual(process.GetProcessID(), procs["inferior"].pid) + + @skipUnlessPlatform(oslist=["netbsd"]) + def test_netbsd_path(self): + core = lldbutil.append_to_process_working_directory(self, "core") + self.coredump_test(core, core) + + @skipUnlessPlatform(oslist=["netbsd"]) + def test_netbsd_no_path(self): + self.coredump_test() + + @skipUnlessPlatform(oslist=["netbsd"]) + def test_netbsd_bad_path(self): + self.coredump_test("/dev/null/cantwritehere")