Skip to content

Commit eabbe11

Browse files
mgornymemfrob
authored andcommitted
[lldb] [client] Implement follow-fork-mode
Implement a new target.process.follow-fork-mode setting to control LLDB's behavior on fork. If set to 'parent', the forked child is detached and parent continues being traced. If set to 'child', the parent is detached and child becomes traced instead. Differential Revision: https://reviews.llvm.org/D100503
1 parent 4bae4e1 commit eabbe11

16 files changed

+245
-16
lines changed

lldb/include/lldb/Target/Process.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ class ProcessProperties : public Properties {
9999
bool GetOSPluginReportsAllThreads() const;
100100
void SetOSPluginReportsAllThreads(bool does_report);
101101
bool GetSteppingRunsAllThreads() const;
102+
FollowForkMode GetFollowForkMode() const;
102103

103104
protected:
104105
Process *m_process; // Can be nullptr for global ProcessProperties

lldb/include/lldb/lldb-private-enumerations.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,12 @@ enum MemoryModuleLoadLevel {
172172
eMemoryModuleLoadLevelComplete, // Load sections and all symbols
173173
};
174174

175+
// Behavior on fork/vfork
176+
enum FollowForkMode {
177+
eFollowParent, // Follow parent process
178+
eFollowChild, // Follow child process
179+
};
180+
175181
// Result enums for when reading multiple lines from IOHandlers
176182
enum class LineStatus {
177183
Success, // The line that was just edited if good and should be added to the

lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp

Lines changed: 109 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5498,6 +5498,31 @@ void ProcessGDBRemote::DidForkSwitchSoftwareBreakpoints(bool enable) {
54985498
});
54995499
}
55005500

5501+
void ProcessGDBRemote::DidForkSwitchHardwareTraps(bool enable) {
5502+
if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointHardware)) {
5503+
GetBreakpointSiteList().ForEach([this, enable](BreakpointSite *bp_site) {
5504+
if (bp_site->IsEnabled() &&
5505+
bp_site->GetType() == BreakpointSite::eHardware) {
5506+
m_gdb_comm.SendGDBStoppointTypePacket(
5507+
eBreakpointHardware, enable, bp_site->GetLoadAddress(),
5508+
GetSoftwareBreakpointTrapOpcode(bp_site), GetInterruptTimeout());
5509+
}
5510+
});
5511+
}
5512+
5513+
WatchpointList &wps = GetTarget().GetWatchpointList();
5514+
size_t wp_count = wps.GetSize();
5515+
for (size_t i = 0; i < wp_count; ++i) {
5516+
WatchpointSP wp = wps.GetByIndex(i);
5517+
if (wp->IsEnabled()) {
5518+
GDBStoppointType type = GetGDBStoppointType(wp.get());
5519+
m_gdb_comm.SendGDBStoppointTypePacket(type, enable, wp->GetLoadAddress(),
5520+
wp->GetByteSize(),
5521+
GetInterruptTimeout());
5522+
}
5523+
}
5524+
}
5525+
55015526
void ProcessGDBRemote::DidFork(lldb::pid_t child_pid, lldb::tid_t child_tid) {
55025527
Log *log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet(GDBR_LOG_PROCESS));
55035528

@@ -5506,30 +5531,58 @@ void ProcessGDBRemote::DidFork(lldb::pid_t child_pid, lldb::tid_t child_tid) {
55065531
// anyway.
55075532
lldb::tid_t parent_tid = m_thread_ids.front();
55085533

5509-
if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointSoftware)) {
5510-
// Switch to the new process to clear breakpoints there.
5511-
if (!m_gdb_comm.SetCurrentThread(child_tid, child_pid)) {
5512-
LLDB_LOG(log, "ProcessGDBRemote::DidFork() unable to set pid/tid");
5513-
return;
5514-
}
5534+
lldb::pid_t follow_pid, detach_pid;
5535+
lldb::tid_t follow_tid, detach_tid;
5536+
5537+
switch (GetFollowForkMode()) {
5538+
case eFollowParent:
5539+
follow_pid = parent_pid;
5540+
follow_tid = parent_tid;
5541+
detach_pid = child_pid;
5542+
detach_tid = child_tid;
5543+
break;
5544+
case eFollowChild:
5545+
follow_pid = child_pid;
5546+
follow_tid = child_tid;
5547+
detach_pid = parent_pid;
5548+
detach_tid = parent_tid;
5549+
break;
5550+
}
55155551

5516-
// Disable all software breakpoints in the forked process.
5552+
// Switch to the process that is going to be detached.
5553+
if (!m_gdb_comm.SetCurrentThread(detach_tid, detach_pid)) {
5554+
LLDB_LOG(log, "ProcessGDBRemote::DidFork() unable to set pid/tid");
5555+
return;
5556+
}
5557+
5558+
// Disable all software breakpoints in the forked process.
5559+
if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointSoftware))
55175560
DidForkSwitchSoftwareBreakpoints(false);
55185561

5519-
// Reset gdb-remote to the original process.
5520-
if (!m_gdb_comm.SetCurrentThread(parent_tid, parent_pid)) {
5521-
LLDB_LOG(log, "ProcessGDBRemote::DidFork() unable to reset pid/tid");
5522-
return;
5523-
}
5562+
// Remove hardware breakpoints / watchpoints from parent process if we're
5563+
// following child.
5564+
if (GetFollowForkMode() == eFollowChild)
5565+
DidForkSwitchHardwareTraps(false);
5566+
5567+
// Switch to the process that is going to be followed
5568+
if (!m_gdb_comm.SetCurrentThread(follow_tid, follow_pid) ||
5569+
!m_gdb_comm.SetCurrentThreadForRun(follow_tid, follow_pid)) {
5570+
LLDB_LOG(log, "ProcessGDBRemote::DidFork() unable to reset pid/tid");
5571+
return;
55245572
}
55255573

5526-
LLDB_LOG(log, "Detaching forked child {0}", child_pid);
5527-
Status error = m_gdb_comm.Detach(false, child_pid);
5574+
LLDB_LOG(log, "Detaching process {0}", detach_pid);
5575+
Status error = m_gdb_comm.Detach(false, detach_pid);
55285576
if (error.Fail()) {
55295577
LLDB_LOG(log, "ProcessGDBRemote::DidFork() detach packet send failed: {0}",
55305578
error.AsCString() ? error.AsCString() : "<unknown error>");
55315579
return;
55325580
}
5581+
5582+
// Hardware breakpoints/watchpoints are not inherited implicitly,
5583+
// so we need to readd them if we're following child.
5584+
if (GetFollowForkMode() == eFollowChild)
5585+
DidForkSwitchHardwareTraps(true);
55335586
}
55345587

55355588
void ProcessGDBRemote::DidVFork(lldb::pid_t child_pid, lldb::tid_t child_tid) {
@@ -5542,8 +5595,40 @@ void ProcessGDBRemote::DidVFork(lldb::pid_t child_pid, lldb::tid_t child_tid) {
55425595
if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointSoftware))
55435596
DidForkSwitchSoftwareBreakpoints(false);
55445597

5545-
LLDB_LOG(log, "Detaching forked child {0}", child_pid);
5546-
Status error = m_gdb_comm.Detach(false, child_pid);
5598+
lldb::pid_t detach_pid;
5599+
lldb::tid_t detach_tid;
5600+
5601+
switch (GetFollowForkMode()) {
5602+
case eFollowParent:
5603+
detach_pid = child_pid;
5604+
detach_tid = child_tid;
5605+
break;
5606+
case eFollowChild:
5607+
detach_pid = m_gdb_comm.GetCurrentProcessID();
5608+
// Any valid TID will suffice, thread-relevant actions will set a proper TID
5609+
// anyway.
5610+
detach_tid = m_thread_ids.front();
5611+
5612+
// Switch to the parent process before detaching it.
5613+
if (!m_gdb_comm.SetCurrentThread(detach_tid, detach_pid)) {
5614+
LLDB_LOG(log, "ProcessGDBRemote::DidFork() unable to set pid/tid");
5615+
return;
5616+
}
5617+
5618+
// Remove hardware breakpoints / watchpoints from the parent process.
5619+
DidForkSwitchHardwareTraps(false);
5620+
5621+
// Switch to the child process.
5622+
if (!m_gdb_comm.SetCurrentThread(child_tid, child_pid) ||
5623+
!m_gdb_comm.SetCurrentThreadForRun(child_tid, child_pid)) {
5624+
LLDB_LOG(log, "ProcessGDBRemote::DidFork() unable to reset pid/tid");
5625+
return;
5626+
}
5627+
break;
5628+
}
5629+
5630+
LLDB_LOG(log, "Detaching process {0}", detach_pid);
5631+
Status error = m_gdb_comm.Detach(false, detach_pid);
55475632
if (error.Fail()) {
55485633
LLDB_LOG(log,
55495634
"ProcessGDBRemote::DidFork() detach packet send failed: {0}",
@@ -5560,3 +5645,11 @@ void ProcessGDBRemote::DidVForkDone() {
55605645
if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointSoftware))
55615646
DidForkSwitchSoftwareBreakpoints(true);
55625647
}
5648+
5649+
void ProcessGDBRemote::DidExec() {
5650+
// If we are following children, vfork is finished by exec (rather than
5651+
// vforkdone that is submitted for parent).
5652+
if (GetFollowForkMode() == eFollowChild)
5653+
m_vfork_in_progress = false;
5654+
Process::DidExec();
5655+
}

lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ class ProcessGDBRemote : public Process,
233233
void DidFork(lldb::pid_t child_pid, lldb::tid_t child_tid) override;
234234
void DidVFork(lldb::pid_t child_pid, lldb::tid_t child_tid) override;
235235
void DidVForkDone() override;
236+
void DidExec() override;
236237

237238
protected:
238239
friend class ThreadGDBRemote;
@@ -468,6 +469,7 @@ class ProcessGDBRemote : public Process,
468469

469470
// fork helpers
470471
void DidForkSwitchSoftwareBreakpoints(bool enable);
472+
void DidForkSwitchHardwareTraps(bool enable);
471473
};
472474

473475
} // namespace process_gdb_remote

lldb/source/Target/Process.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,19 @@ class ProcessOptionValueProperties
110110
}
111111
};
112112

113+
static constexpr OptionEnumValueElement g_follow_fork_mode_values[] = {
114+
{
115+
eFollowParent,
116+
"parent",
117+
"Continue tracing the parent process and detach the child.",
118+
},
119+
{
120+
eFollowChild,
121+
"child",
122+
"Trace the child process and detach the parent.",
123+
},
124+
};
125+
113126
#define LLDB_PROPERTIES_process
114127
#include "TargetProperties.inc"
115128

@@ -334,6 +347,12 @@ void ProcessProperties::SetOSPluginReportsAllThreads(bool does_report) {
334347
nullptr, ePropertyOSPluginReportsAllThreads, does_report);
335348
}
336349

350+
FollowForkMode ProcessProperties::GetFollowForkMode() const {
351+
const uint32_t idx = ePropertyFollowForkMode;
352+
return (FollowForkMode)m_collection_sp->GetPropertyAtIndexAsEnumeration(
353+
nullptr, idx, g_process_properties[idx].default_uint_value);
354+
}
355+
337356
ProcessSP Process::FindPlugin(lldb::TargetSP target_sp,
338357
llvm::StringRef plugin_name,
339358
ListenerSP listener_sp,

lldb/source/Target/TargetProperties.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,10 @@ let Definition = "process" in {
239239
def VirtualAddressableBits: Property<"virtual-addressable-bits", "UInt64">,
240240
DefaultUnsignedValue<0>,
241241
Desc<"The number of bits used for addressing. If the value is 39, then bits 0..38 are used for addressing. The default value of 0 means unspecified.">;
242+
def FollowForkMode: Property<"follow-fork-mode", "Enum">,
243+
DefaultEnumValue<"eFollowParent">,
244+
EnumValues<"OptionEnumValues(g_follow_fork_mode_values)">,
245+
Desc<"Debugger's behavior upon fork or vfork.">;
242246
}
243247

244248
let Definition = "platform" in {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# REQUIRES: native && system-linux
2+
# clone() tests fails on arm64 Linux, PR #49899
3+
# UNSUPPORTED: system-linux && target-aarch64
4+
# RUN: %clangxx_host %p/Inputs/fork.cpp -DTEST_CLONE -o %t
5+
# RUN: %lldb -b -s %s %t | FileCheck %s
6+
settings set target.process.follow-fork-mode child
7+
b child_func
8+
b parent_func
9+
process launch
10+
# CHECK: stop reason = breakpoint
11+
# CHECK-NEXT: child_func
12+
continue
13+
# CHECK: child exited: 0
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# REQUIRES: native && system-linux && dbregs-set
2+
# clone() tests fails on arm64 Linux, PR #49899
3+
# UNSUPPORTED: system-linux && target-aarch64
4+
# RUN: %clangxx_host -g %p/Inputs/fork.cpp -DTEST_CLONE -o %t
5+
# RUN: %lldb -b -s %s %t | FileCheck %s
6+
settings set target.process.follow-fork-mode child
7+
process launch -s
8+
watchpoint set variable -w write g_val
9+
# CHECK: Watchpoint created:
10+
continue
11+
# CHECK: stop reason = watchpoint
12+
continue
13+
# CHECK: stop reason = watchpoint
14+
continue
15+
# CHECK: child exited: 0
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# REQUIRES: native && system-linux
2+
# clone() tests fails on arm64 Linux, PR #49899
3+
# UNSUPPORTED: system-linux && target-aarch64
4+
# RUN: %clangxx_host %p/Inputs/fork.cpp -DTEST_CLONE -o %t
5+
# RUN: %lldb -b -s %s %t | FileCheck %s
6+
settings set target.process.follow-fork-mode child
7+
b parent_func
8+
process launch
9+
# CHECK: function run in parent
10+
# CHECK: child exited: 0
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# REQUIRES: native
2+
# UNSUPPORTED: system-windows
3+
# RUN: %clangxx_host %p/Inputs/fork.cpp -DTEST_FORK=fork -o %t
4+
# RUN: %lldb -b -s %s %t | FileCheck %s
5+
settings set target.process.follow-fork-mode child
6+
b child_func
7+
b parent_func
8+
process launch
9+
# CHECK: stop reason = breakpoint
10+
# CHECK-NEXT: child_func
11+
continue
12+
# CHECK: child exited: 0

0 commit comments

Comments
 (0)