[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:
parent
0a6fad754e
commit
0a1d80d56e
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
52
lldb/test/API/functionalities/gdb_remote_client/TestFork.py
Normal file
52
lldb/test/API/functionalities/gdb_remote_client/TestFork.py
Normal 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")
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue