[lldb] [llgs server] Support creating core dumps on NetBSD

Add a new SaveCore() process method that can be used to request a core
dump.  This is currently implemented on NetBSD via the PT_DUMPCORE
ptrace(2) request, and enabled via 'savecore' extension.

Protocol-wise, a new qSaveCore packet is introduced.  It accepts zero
or more semicolon-separated key:value options, invokes the core dump
and returns a key:value response.  Currently the only option supported
is "path-hint", and the return value contains the "path" actually used.
The support for the feature is exposed via qSaveCore qSupported feature.

Differential Revision: https://reviews.llvm.org/D101285
This commit is contained in:
Michał Górny 2021-04-26 13:47:02 +02:00
parent fae0dfa642
commit 37cbd817d3
9 changed files with 143 additions and 2 deletions

View File

@ -250,8 +250,9 @@ public:
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 @@ public:
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<std::string> SaveCore(llvm::StringRef path_hint) {
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"Not implemented");
}
protected:
struct SoftwareBreakpoint {
uint32_t ref_count;

View File

@ -170,6 +170,8 @@ public:
eServerPacketType_qMemTags, // read memory tags
eServerPacketType_QMemTags, // write memory tags
eServerPacketType_qLLDBSaveCore,
};
ServerPacketType GetServerPacketType() const;

View File

@ -860,6 +860,7 @@ class GdbRemoteTestCaseBase(Base):
"fork-events",
"vfork-events",
"memory-tagging",
"qSaveCore",
]
def parse_qSupported_response(self, context):

View File

@ -136,7 +136,8 @@ NativeProcessNetBSD::Factory::Attach(
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 @@ void NativeProcessNetBSD::MonitorClone(::pid_t child_pid, bool is_vfork,
}
}
}
llvm::Expected<std::string>
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();
}

View File

@ -88,6 +88,8 @@ public:
static Status PtraceWrapper(int req, lldb::pid_t pid, void *addr = nullptr,
int data = 0, int *result = nullptr);
llvm::Expected<std::string> SaveCore(llvm::StringRef path_hint) override;
private:
MainLoop::SignalHandleUP m_sigchld_handle;
ArchSpec m_arch;

View File

@ -226,6 +226,10 @@ void GDBRemoteCommunicationServerLLGS::RegisterPacketHandlers() {
quit = true;
return this->Handle_k(packet);
});
RegisterMemberFunctionHandler(
StringExtractorGDBRemote::eServerPacketType_qLLDBSaveCore,
&GDBRemoteCommunicationServerLLGS::Handle_qSaveCore);
}
void GDBRemoteCommunicationServerLLGS::SetLaunchInfo(const ProcessLaunchInfo &info) {
@ -3604,6 +3608,41 @@ GDBRemoteCommunicationServerLLGS::Handle_QMemTags(
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<llvm::StringRef, 2> 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<std::string> 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 @@ std::vector<std::string> GDBRemoteCommunicationServerLLGS::HandleFeatures(
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 = {};

View File

@ -214,6 +214,8 @@ protected:
PacketResult Handle_QPassSignals(StringExtractorGDBRemote &packet);
PacketResult Handle_qSaveCore(StringExtractorGDBRemote &packet);
PacketResult Handle_g(StringExtractorGDBRemote &packet);
PacketResult Handle_qMemTags(StringExtractorGDBRemote &packet);

View File

@ -260,6 +260,8 @@ StringExtractorGDBRemote::GetServerPacketType() const {
break;
case 'S':
if (PACKET_STARTS_WITH("qSaveCore"))
return eServerPacketType_qLLDBSaveCore;
if (PACKET_STARTS_WITH("qSpeedTest:"))
return eServerPacketType_qSpeedTest;
if (PACKET_MATCHES("qShlibInfoAddr"))

View File

@ -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")