[lldb] [gdb-remote client] Support minimal fork/vfork handling

Add a support for handling fork/vfork stops in LLGS client.  At this
point, it only sends a detach packet for the newly forked child
(and implicitly resumes the parent).

Differential Revision: https://reviews.llvm.org/D100206
This commit is contained in:
Michał Górny 2021-04-09 16:18:50 +02:00
parent 0a6fad754e
commit 0a1d80d56e
9 changed files with 280 additions and 12 deletions

View file

@ -981,6 +981,15 @@ public:
/// anything after a process exec's itself.
virtual void DoDidExec() {}
/// Called after a reported fork.
virtual void DidFork(lldb::pid_t child_pid, lldb::tid_t child_tid) {}
/// Called after a reported vfork.
virtual void DidVFork(lldb::pid_t child_pid, lldb::tid_t child_tid) {}
/// Called after reported vfork completion.
virtual void DidVForkDone() {}
/// Called before launching to a process.
///
/// Allow Process plug-ins to execute some code before launching a process.

View file

@ -132,6 +132,16 @@ public:
static lldb::StopInfoSP
CreateStopReasonProcessorTrace(Thread &thread, const char *description);
static lldb::StopInfoSP CreateStopReasonFork(Thread &thread,
lldb::pid_t child_pid,
lldb::tid_t child_tid);
static lldb::StopInfoSP CreateStopReasonVFork(Thread &thread,
lldb::pid_t child_pid,
lldb::tid_t child_tid);
static lldb::StopInfoSP CreateStopReasonVForkDone(Thread &thread);
static lldb::ValueObjectSP
GetReturnValueObject(lldb::StopInfoSP &stop_info_sp);

View file

@ -318,7 +318,8 @@ void GDBRemoteCommunicationClient::GetRemoteQSupported() {
// build the qSupported packet
std::vector<std::string> features = {"xmlRegisters=i386,arm,mips,arc",
"multiprocess+"};
"multiprocess+", "fork-events+",
"vfork-events+"};
StreamString packet;
packet.PutCString("qSupported");
for (uint32_t i = 0; i < features.size(); ++i) {
@ -1457,9 +1458,12 @@ bool GDBRemoteCommunicationClient::DeallocateMemory(addr_t addr) {
return false;
}
Status GDBRemoteCommunicationClient::Detach(bool keep_stopped) {
Status GDBRemoteCommunicationClient::Detach(bool keep_stopped,
lldb::pid_t pid) {
Status error;
lldb_private::StreamString packet;
packet.PutChar('D');
if (keep_stopped) {
if (m_supports_detach_stay_stopped == eLazyBoolCalculate) {
char packet[64];
@ -1481,17 +1485,25 @@ Status GDBRemoteCommunicationClient::Detach(bool keep_stopped) {
error.SetErrorString("Stays stopped not supported by this target.");
return error;
} else {
StringExtractorGDBRemote response;
PacketResult packet_result = SendPacketAndWaitForResponse("D1", response);
if (packet_result != PacketResult::Success)
error.SetErrorString("Sending extended disconnect packet failed.");
packet.PutChar('1');
}
} else {
StringExtractorGDBRemote response;
PacketResult packet_result = SendPacketAndWaitForResponse("D", response);
if (packet_result != PacketResult::Success)
error.SetErrorString("Sending disconnect packet failed.");
}
if (pid != LLDB_INVALID_PROCESS_ID) {
if (!m_supports_multiprocess) {
error.SetErrorString(
"Multiprocess extension not supported by the server.");
return error;
}
packet.PutChar(';');
packet.PutHex64(pid);
}
StringExtractorGDBRemote response;
PacketResult packet_result =
SendPacketAndWaitForResponse(packet.GetString(), response);
if (packet_result != PacketResult::Success)
error.SetErrorString("Sending isconnect packet failed.");
return error;
}

View file

@ -235,7 +235,7 @@ public:
bool DeallocateMemory(lldb::addr_t addr);
Status Detach(bool keep_stopped);
Status Detach(bool keep_stopped, lldb::pid_t pid = LLDB_INVALID_PROCESS_ID);
Status GetMemoryRegionInfo(lldb::addr_t addr, MemoryRegionInfo &range_info);

View file

@ -1906,6 +1906,28 @@ ThreadSP ProcessGDBRemote::SetThreadStopInfo(
} else if (reason == "processor trace") {
thread_sp->SetStopInfo(StopInfo::CreateStopReasonProcessorTrace(
*thread_sp, description.c_str()));
} else if (reason == "fork") {
StringExtractor desc_extractor(description.c_str());
lldb::pid_t child_pid = desc_extractor.GetU64(
LLDB_INVALID_PROCESS_ID);
lldb::tid_t child_tid = desc_extractor.GetU64(
LLDB_INVALID_THREAD_ID);
thread_sp->SetStopInfo(StopInfo::CreateStopReasonFork(
*thread_sp, child_pid, child_tid));
handled = true;
} else if (reason == "vfork") {
StringExtractor desc_extractor(description.c_str());
lldb::pid_t child_pid = desc_extractor.GetU64(
LLDB_INVALID_PROCESS_ID);
lldb::tid_t child_tid = desc_extractor.GetU64(
LLDB_INVALID_THREAD_ID);
thread_sp->SetStopInfo(StopInfo::CreateStopReasonVFork(
*thread_sp, child_pid, child_tid));
handled = true;
} else if (reason == "vforkdone") {
thread_sp->SetStopInfo(
StopInfo::CreateStopReasonVForkDone(*thread_sp));
handled = true;
}
} else if (!signo) {
addr_t pc = thread_sp->GetRegisterContext()->GetPC();
@ -2312,6 +2334,21 @@ StateType ProcessGDBRemote::SetThreadStopInfo(StringExtractor &stop_packet) {
ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS));
LLDB_LOG_ERROR(log, std::move(error), "Failed to load modules: {0}");
}
} else if (key.compare("fork") == 0 || key.compare("vfork") == 0) {
// fork includes child pid/tid in thread-id format
StringExtractorGDBRemote thread_id{value};
auto pid_tid = thread_id.GetPidTid(LLDB_INVALID_PROCESS_ID);
if (!pid_tid) {
Log *log(
ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS));
LLDB_LOG(log, "Invalid PID/TID to fork: {0}", value);
pid_tid = {{LLDB_INVALID_PROCESS_ID, LLDB_INVALID_THREAD_ID}};
}
reason = key.str();
StreamString ostr;
ostr.Printf("%" PRIu64 " %" PRIu64, pid_tid->first, pid_tid->second);
description = std::string(ostr.GetString());
} else if (key.size() == 2 && ::isxdigit(key[0]) && ::isxdigit(key[1])) {
uint32_t reg = UINT32_MAX;
if (!key.getAsInteger(16, reg))
@ -5447,3 +5484,29 @@ CommandObject *ProcessGDBRemote::GetPluginCommandObject() {
GetTarget().GetDebugger().GetCommandInterpreter());
return m_command_sp.get();
}
void ProcessGDBRemote::DidFork(lldb::pid_t child_pid, lldb::tid_t child_tid) {
Log *log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS));
LLDB_LOG(log, "Detaching forked child {0}", child_pid);
Status error = m_gdb_comm.Detach(false, child_pid);
if (error.Fail()) {
LLDB_LOG(log,
"ProcessGDBRemote::DidFork() detach packet send failed: {0}",
error.AsCString() ? error.AsCString() : "<unknown error>");
return;
}
}
void ProcessGDBRemote::DidVFork(lldb::pid_t child_pid, lldb::tid_t child_tid) {
Log *log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS));
LLDB_LOG(log, "Detaching forked child {0}", child_pid);
Status error = m_gdb_comm.Detach(false, child_pid);
if (error.Fail()) {
LLDB_LOG(log,
"ProcessGDBRemote::DidFork() detach packet send failed: {0}",
error.AsCString() ? error.AsCString() : "<unknown error>");
return;
}
}

View file

@ -230,6 +230,9 @@ public:
std::string HarmonizeThreadIdsForProfileData(
StringExtractorGDBRemote &inputStringExtractor);
void DidFork(lldb::pid_t child_pid, lldb::tid_t child_tid) override;
void DidVFork(lldb::pid_t child_pid, lldb::tid_t child_tid) override;
protected:
friend class ThreadGDBRemote;
friend class GDBRemoteCommunicationClient;

View file

@ -1154,6 +1154,103 @@ protected:
bool m_performed_action;
};
// StopInfoFork
class StopInfoFork : public StopInfo {
public:
StopInfoFork(Thread &thread, lldb::pid_t child_pid, lldb::tid_t child_tid)
: StopInfo(thread, child_pid), m_performed_action(false),
m_child_pid(child_pid), m_child_tid(child_tid) {}
~StopInfoFork() override = default;
bool ShouldStop(Event *event_ptr) override { return false; }
StopReason GetStopReason() const override { return eStopReasonFork; }
const char *GetDescription() override { return "fork"; }
protected:
void PerformAction(Event *event_ptr) override {
// Only perform the action once
if (m_performed_action)
return;
m_performed_action = true;
ThreadSP thread_sp(m_thread_wp.lock());
if (thread_sp)
thread_sp->GetProcess()->DidFork(m_child_pid, m_child_tid);
}
bool m_performed_action;
private:
lldb::pid_t m_child_pid;
lldb::tid_t m_child_tid;
};
// StopInfoVFork
class StopInfoVFork : public StopInfo {
public:
StopInfoVFork(Thread &thread, lldb::pid_t child_pid, lldb::tid_t child_tid)
: StopInfo(thread, child_pid), m_performed_action(false),
m_child_pid(child_pid), m_child_tid(child_tid) {}
~StopInfoVFork() override = default;
bool ShouldStop(Event *event_ptr) override { return false; }
StopReason GetStopReason() const override { return eStopReasonVFork; }
const char *GetDescription() override { return "vfork"; }
protected:
void PerformAction(Event *event_ptr) override {
// Only perform the action once
if (m_performed_action)
return;
m_performed_action = true;
ThreadSP thread_sp(m_thread_wp.lock());
if (thread_sp)
thread_sp->GetProcess()->DidVFork(m_child_pid, m_child_tid);
}
bool m_performed_action;
private:
lldb::pid_t m_child_pid;
lldb::tid_t m_child_tid;
};
// StopInfoVForkDone
class StopInfoVForkDone : public StopInfo {
public:
StopInfoVForkDone(Thread &thread)
: StopInfo(thread, 0), m_performed_action(false) {}
~StopInfoVForkDone() override = default;
bool ShouldStop(Event *event_ptr) override { return false; }
StopReason GetStopReason() const override { return eStopReasonVForkDone; }
const char *GetDescription() override { return "vforkdone"; }
protected:
void PerformAction(Event *event_ptr) override {
// Only perform the action once
if (m_performed_action)
return;
m_performed_action = true;
ThreadSP thread_sp(m_thread_wp.lock());
if (thread_sp)
thread_sp->GetProcess()->DidVForkDone();
}
bool m_performed_action;
};
} // namespace lldb_private
StopInfoSP StopInfo::CreateStopReasonWithBreakpointSiteID(Thread &thread,
@ -1203,6 +1300,23 @@ StopInfoSP StopInfo::CreateStopReasonWithExec(Thread &thread) {
return StopInfoSP(new StopInfoExec(thread));
}
StopInfoSP StopInfo::CreateStopReasonFork(Thread &thread,
lldb::pid_t child_pid,
lldb::tid_t child_tid) {
return StopInfoSP(new StopInfoFork(thread, child_pid, child_tid));
}
StopInfoSP StopInfo::CreateStopReasonVFork(Thread &thread,
lldb::pid_t child_pid,
lldb::tid_t child_tid) {
return StopInfoSP(new StopInfoVFork(thread, child_pid, child_tid));
}
StopInfoSP StopInfo::CreateStopReasonVForkDone(Thread &thread) {
return StopInfoSP(new StopInfoVForkDone(thread));
}
ValueObjectSP StopInfo::GetReturnValueObject(StopInfoSP &stop_info_sp) {
if (stop_info_sp &&
stop_info_sp->GetStopReason() == eStopReasonPlanComplete) {

View file

@ -0,0 +1,52 @@
from __future__ import print_function
import lldb
import unittest
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *
from gdbclientutils import *
class TestMultiprocess(GDBRemoteTestBase):
def base_test(self, variant):
class MyResponder(MockGDBServerResponder):
def __init__(self):
super().__init__()
self.first = True
self.detached = None
self.property = "{}-events+".format(variant)
def qSupported(self, client_supported):
assert "multiprocess+" in client_supported
assert self.property in client_supported
return "{};multiprocess+;{}".format(
super().qSupported(client_supported), self.property)
def qfThreadInfo(self):
return "mp400.10200"
def cont(self):
if self.first:
self.first = False
return ("T0fthread:p400.10200;reason:{0};{0}:p401.10400;"
.format(variant))
return "W00"
def D(self, packet):
self.detached = packet
return "OK"
self.server.responder = MyResponder()
target = self.dbg.CreateTarget('')
if self.TraceOn():
self.runCmd("log enable gdb-remote packets")
self.addTearDownHook(
lambda: self.runCmd("log disable gdb-remote packets"))
process = self.connect(target)
process.Continue()
self.assertRegex(self.server.responder.detached, r"D;0*401")
def test_fork(self):
self.base_test("fork")
def test_vfork(self):
self.base_test("vfork")

View file

@ -109,6 +109,8 @@ class MockGDBServerResponder:
return self.vCont(packet)
if packet[0] == "A":
return self.A(packet)
if packet[0] == "D":
return self.D(packet)
if packet[0] == "g":
return self.readRegisters()
if packet[0] == "G":
@ -216,6 +218,9 @@ class MockGDBServerResponder:
def A(self, packet):
return ""
def D(self, packet):
return "OK"
def readRegisters(self):
return "00000000" * self.registerCount