Skip to content

Commit 5488f2f

Browse files
committed
PR Feedback: improve PTrace implementation
1 parent 48d6237 commit 5488f2f

File tree

3 files changed

+82
-85
lines changed

3 files changed

+82
-85
lines changed

tools/swift-inspect/Sources/SwiftInspectLinux/PTrace.swift

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,31 @@ import LinuxSystemHeaders
1515

1616
public class PTrace {
1717
enum PTraceError: Error {
18-
case ptraceFailure(_ command: Int32, pid: pid_t, errno: Int32 = get_errno())
19-
case waitFailure(pid: pid_t, errno: Int32 = get_errno())
20-
case unexpectedWaitStatus(pid: pid_t, status: Int32, sigInfo: siginfo_t? = nil)
18+
case operationFailure(_ command: CInt, pid: pid_t, errno: CInt = get_errno())
19+
case waitFailure(pid: pid_t, errno: CInt = get_errno())
20+
case unexpectedWaitStatus(pid: pid_t, status: CInt, sigInfo: siginfo_t? = nil)
2121
}
2222

2323
let pid: pid_t
2424

25+
// Provides scoped access to a PTrace object.
26+
public static func withAttachedProcess(pid: pid_t, _ closure: (PTrace) throws -> Void) throws {
27+
let ptrace = try PTrace(pid);
28+
try closure(ptrace)
29+
}
30+
2531
// Initializing a PTrace instance attaches to the target process, waits for
2632
// it to stop, and leaves it in a stopped state. The caller may resume the
2733
// process by calling cont().
28-
public init(process pid: pid_t) throws {
34+
init(_ pid: pid_t) throws {
2935
guard ptrace_attach(pid) != -1 else {
30-
throw PTraceError.ptraceFailure(PTRACE_ATTACH, pid: pid)
36+
throw PTraceError.operationFailure(PTRACE_ATTACH, pid: pid)
3137
}
3238

3339
while true {
34-
var status: Int32 = 0
40+
var status: CInt = 0
3541
let result = waitpid(pid, &status, 0)
36-
guard result != -1 else {
42+
if result == -1 {
3743
if get_errno() == EINTR { continue }
3844
throw PTraceError.waitFailure(pid: pid)
3945
}
@@ -49,38 +55,38 @@ public class PTrace {
4955
deinit { _ = ptrace_detach(self.pid) }
5056

5157
public func cont() throws {
52-
guard ptrace_cont(self.pid) != -1 else {
53-
throw PTraceError.ptraceFailure(PTRACE_CONT, pid: self.pid)
58+
if ptrace_cont(self.pid) == -1 {
59+
throw PTraceError.operationFailure(PTRACE_CONT, pid: self.pid)
5460
}
5561
}
5662

5763
public func interrupt() throws {
58-
guard ptrace_interrupt(self.pid) != -1 else {
59-
throw PTraceError.ptraceFailure(PTRACE_INTERRUPT, pid: self.pid)
64+
if ptrace_interrupt(self.pid) == -1 {
65+
throw PTraceError.operationFailure(PTRACE_INTERRUPT, pid: self.pid)
6066
}
6167
}
6268

6369
public func getSigInfo() throws -> siginfo_t {
6470
var sigInfo = siginfo_t()
65-
guard ptrace_getsiginfo(self.pid, &sigInfo) != -1 else {
66-
throw PTraceError.ptraceFailure(PTRACE_GETSIGINFO, pid: self.pid)
71+
if ptrace_getsiginfo(self.pid, &sigInfo) == -1 {
72+
throw PTraceError.operationFailure(PTRACE_GETSIGINFO, pid: self.pid)
6773
}
6874

6975
return sigInfo
7076
}
7177

7278
public func pokeData(addr: UInt64, value: UInt64) throws {
73-
guard ptrace_pokedata(self.pid, UInt(addr), UInt(value)) != -1 else {
74-
throw PTraceError.ptraceFailure(PTRACE_POKEDATA, pid: self.pid)
79+
if ptrace_pokedata(self.pid, UInt(addr), UInt(value)) == -1 {
80+
throw PTraceError.operationFailure(PTRACE_POKEDATA, pid: self.pid)
7581
}
7682
}
7783

7884
public func getRegSet() throws -> RegisterSet {
7985
var regSet = RegisterSet()
8086
try withUnsafeMutableBytes(of: &regSet) {
8187
var vec = iovec(iov_base: $0.baseAddress!, iov_len: MemoryLayout<RegisterSet>.size)
82-
guard ptrace_getregset(self.pid, NT_PRSTATUS, &vec) != -1 else {
83-
throw PTraceError.ptraceFailure(PTRACE_GETREGSET, pid: self.pid)
88+
if ptrace_getregset(self.pid, NT_PRSTATUS, &vec) == -1 {
89+
throw PTraceError.operationFailure(PTRACE_GETREGSET, pid: self.pid)
8490
}
8591
}
8692

@@ -91,21 +97,19 @@ public class PTrace {
9197
var regSetCopy = regSet
9298
try withUnsafeMutableBytes(of: &regSetCopy) {
9399
var vec = iovec(iov_base: $0.baseAddress!, iov_len: MemoryLayout<RegisterSet>.size)
94-
guard ptrace_setregset(self.pid, NT_PRSTATUS, &vec) != -1 else {
95-
throw PTraceError.ptraceFailure(PTRACE_SETREGSET, pid: self.pid)
100+
if ptrace_setregset(self.pid, NT_PRSTATUS, &vec) == -1 {
101+
throw PTraceError.operationFailure(PTRACE_SETREGSET, pid: self.pid)
96102
}
97103
}
98104
}
99105

100-
// Call the function at the specified address in the attached process. Caller
101-
// may pass up to six 8-byte arguments. The optional callback is invoked when
102-
// the process is stopped with a SIGTRAP signal. In this case, the caller is
103-
// responsible for taking action on the signal.
104-
public func callRemoteFunction(
105-
at address: UInt64, with args: [UInt64] = [], onTrap callback: (() throws -> Void)? = nil
106+
// Call the function at the specified address in the attached process with the
107+
// provided argument array. The optional callback is invoked when the process
108+
// is stopped with a SIGTRAP signal. In this case, the caller is responsible
109+
// for taking action on the signal.
110+
public func jump(
111+
to address: UInt64, with args: [UInt64] = [], _ callback: (() throws -> Void)? = nil
106112
) throws -> UInt64 {
107-
precondition(args.count <= 6, "callRemoteFunction supports max of 6 arguments")
108-
109113
let origRegs = try self.getRegSet()
110114
defer { try? self.setRegSet(regSet: origRegs) }
111115

@@ -119,7 +123,7 @@ public class PTrace {
119123
try self.setRegSet(regSet: newRegs)
120124
try self.cont()
121125

122-
var status: Int32 = 0
126+
var status: CInt = 0
123127
while true {
124128
let result = waitpid(self.pid, &status, 0)
125129
guard result != -1 else {

tools/swift-inspect/Sources/SwiftInspectLinux/RegisterSet.swift

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,20 @@ public typealias RegisterSet = user_pt_regs
1919
extension RegisterSet {
2020
public static var trapInstructionSize: UInt { return 4 } // brk #0x0
2121

22-
public func setupCall(
23-
_ ptrace: PTrace, to funcAddr: UInt64, with args: [UInt64], returnTo returnAddr: UInt64
24-
) throws -> RegisterSet {
25-
precondition(args.count <= 6)
22+
public func setupCall(_ ptrace: PTrace, to funcAddr: UInt64, with args: [UInt64], returnTo returnAddr: UInt64) throws -> RegisterSet {
23+
// The first 8 arguments are passed in regsters. Any additional arguments
24+
// must be pushed on the stack, which is not implemented.
25+
precondition(args.count <= 8)
26+
2627
var registers = self
2728
registers.regs.0 = args.count > 0 ? args[0] : 0
2829
registers.regs.1 = args.count > 1 ? args[1] : 0
2930
registers.regs.2 = args.count > 2 ? args[2] : 0
3031
registers.regs.3 = args.count > 3 ? args[3] : 0
3132
registers.regs.4 = args.count > 4 ? args[4] : 0
3233
registers.regs.5 = args.count > 5 ? args[5] : 0
34+
registers.regs.6 = args.count > 6 ? args[6] : 0
35+
registers.regs.7 = args.count > 7 ? args[7] : 0
3336
registers.pc = funcAddr
3437
registers.regs.30 = returnAddr // link register (x30)
3538
return registers
@@ -50,10 +53,11 @@ public typealias RegisterSet = pt_regs
5053
extension RegisterSet {
5154
public static var trapInstructionSize: UInt { return 1 } // int3
5255

53-
public func setupCall(
54-
_ ptrace: PTrace, to funcAddr: UInt64, with args: [UInt64], returnTo returnAddr: UInt64
55-
) throws -> RegisterSet {
56+
public func setupCall(_ ptrace: PTrace, to funcAddr: UInt64, with args: [UInt64], returnTo returnAddr: UInt64) throws -> RegisterSet {
57+
// The first 6 arguments are passed in registers. Any additional arguments
58+
// must be pushed on the stack, which is not implemented.
5659
precondition(args.count <= 6)
60+
5761
var registers = self
5862
registers.rdi = UInt(args.count > 0 ? args[0] : 0)
5963
registers.rsi = UInt(args.count > 1 ? args[1] : 0)

tools/swift-inspect/Sources/swift-inspect/AndroidRemoteProcess.swift

Lines changed: 39 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,6 @@ internal final class AndroidRemoteProcess: LinuxRemoteProcess {
4949
}
5050
}
5151

52-
let ptrace: SwiftInspectLinux.PTrace
53-
5452
// We call mmap/munmap in the remote process to alloc/free memory for our own
5553
// use without impacting existing allocations in the remote process.
5654
lazy var mmapSymbol: RemoteSymbol = RemoteSymbol("mmap", self.symbolCache)
@@ -63,16 +61,6 @@ internal final class AndroidRemoteProcess: LinuxRemoteProcess {
6361
lazy var mallocEnableSymbol: RemoteSymbol = RemoteSymbol("malloc_enable", self.symbolCache)
6462
lazy var mallocIterateSymbol: RemoteSymbol = RemoteSymbol("malloc_iterate", self.symbolCache)
6563

66-
override init?(processId: ProcessIdentifier) {
67-
do {
68-
self.ptrace = try SwiftInspectLinux.PTrace(process: processId)
69-
} catch {
70-
print("failed initialization: \(error)")
71-
return nil
72-
}
73-
super.init(processId: processId)
74-
}
75-
7664
// Linux and Android have no supported method to enumerate allocations in the
7765
// heap of a remote process. Android does, however, support the malloc_iterate
7866
// API, which enumerates allocations in the current process. We leverage this
@@ -81,26 +69,27 @@ internal final class AndroidRemoteProcess: LinuxRemoteProcess {
8169
override internal func iterateHeap(_ body: (swift_addr_t, UInt64) -> Void) {
8270
var regionCount = 0
8371
var allocCount = 0
84-
for entry in self.memoryMap.entries {
85-
// Limiting malloc_iterate calls to only memory regions that are known
86-
// to contain heap allocations is not strictly necessary but it does
87-
// significantly improve the speed of heap iteration.
88-
guard entry.isHeapRegion() else { continue }
89-
90-
// collect all of the allocations in this heap region
91-
let allocations: [(base: swift_addr_t, len: UInt64)]
92-
do {
93-
allocations = try self.iterateHeapRegion(region: entry)
94-
regionCount += 1
95-
} catch {
96-
print("failed iterating remote heap: \(error)")
97-
return
72+
do {
73+
try PTrace.withAttachedProcess(pid: self.processIdentifier) { ptrace in
74+
for entry in self.memoryMap.entries {
75+
// Limiting malloc_iterate calls to only memory regions that are known
76+
// to contain heap allocations is not strictly necessary but it does
77+
// significantly improve the speed of heap iteration.
78+
guard entry.isHeapRegion() else { continue }
79+
80+
// collect all of the allocations in this heap region
81+
let allocations: [(base: swift_addr_t, len: UInt64)]
82+
allocations = try self.iterateHeapRegion(ptrace, region: entry)
83+
regionCount += 1
84+
allocCount += allocations.count
85+
86+
// process all of the collected allocations
87+
for alloc in allocations { body(alloc.base, alloc.len) }
88+
}
9889
}
99-
100-
allocCount += allocations.count
101-
102-
// process all of the collected allocations
103-
for alloc in allocations { body(alloc.base, alloc.len) }
90+
} catch {
91+
print("failed iterating remote heap: \(error)")
92+
return
10493
}
10594

10695
if regionCount == 0 {
@@ -116,16 +105,16 @@ internal final class AndroidRemoteProcess: LinuxRemoteProcess {
116105

117106
// Iterate a single heap region in the remote process and return an array
118107
// of (base, len) pairs describing each heap allocation in the region.
119-
internal func iterateHeapRegion(region: MemoryMap.Entry) throws -> [(
108+
internal func iterateHeapRegion(_ ptrace: PTrace, region: MemoryMap.Entry) throws -> [(
120109
base: swift_addr_t, len: UInt64
121110
)] {
122111
// Allocate a page-sized buffer in the remote process that malloc_iterate
123112
// will populaate with metadata describing each heap entry it enumerates.
124113
let dataLen = sysconf(Int32(_SC_PAGESIZE))
125114
let remoteDataAddr = try self.mmapRemote(
126-
len: dataLen, prot: PROT_READ | PROT_WRITE, flags: MAP_ANON | MAP_PRIVATE)
115+
ptrace, len: dataLen, prot: PROT_READ | PROT_WRITE, flags: MAP_ANON | MAP_PRIVATE)
127116
defer {
128-
_ = try? self.munmapRemote(addr: remoteDataAddr, len: dataLen)
117+
_ = try? self.munmapRemote(ptrace, addr: remoteDataAddr, len: dataLen)
129118
}
130119

131120
// Allocate and inialize a local buffer that will be used to copy metadata
@@ -142,9 +131,9 @@ internal final class AndroidRemoteProcess: LinuxRemoteProcess {
142131
// executed in the remote process.
143132
let codeLen = heap_iterate_callback_len()
144133
let remoteCodeAddr = try mmapRemote(
145-
len: codeLen, prot: PROT_READ | PROT_WRITE | PROT_EXEC, flags: MAP_ANON | MAP_PRIVATE)
134+
ptrace, len: codeLen, prot: PROT_READ | PROT_WRITE | PROT_EXEC, flags: MAP_ANON | MAP_PRIVATE)
146135
defer {
147-
_ = try? self.munmapRemote(addr: remoteCodeAddr, len: codeLen)
136+
_ = try? self.munmapRemote(ptrace, addr: remoteCodeAddr, len: codeLen)
148137
}
149138

150139
// Copy the malloc_iterate callback implementation to the remote process.
@@ -158,9 +147,9 @@ internal final class AndroidRemoteProcess: LinuxRemoteProcess {
158147

159148
// Disable malloc/free while enumerating the region to get a consistent
160149
// snapshot of existing allocations.
161-
try self.mallocDisableRemote()
150+
try self.mallocDisableRemote(ptrace)
162151
defer {
163-
_ = try? self.mallocEnableRemote()
152+
_ = try? self.mallocEnableRemote(ptrace)
164153
}
165154

166155
// Collects (base, len) pairs describing each heap allocation in the remote
@@ -169,7 +158,7 @@ internal final class AndroidRemoteProcess: LinuxRemoteProcess {
169158

170159
let regionLen = region.endAddr - region.startAddr
171160
let args = [region.startAddr, regionLen, remoteCodeAddr, remoteDataAddr]
172-
_ = try self.ptrace.callRemoteFunction(at: mallocIterateAddr, with: args) {
161+
_ = try ptrace.jump(to: mallocIterateAddr, with: args) {
173162
// This callback is invoked when a SIGTRAP is encountered in the remote
174163
// process. In this context, this signal indicates there is no more room
175164
// in the allocated metadata region (see AndroidCLib/heap.c).
@@ -183,11 +172,11 @@ internal final class AndroidRemoteProcess: LinuxRemoteProcess {
183172
}
184173
try self.process.writeMem(remoteAddr: remoteDataAddr, localAddr: buffer, len: UInt(dataLen))
185174

186-
var regs = try self.ptrace.getRegSet()
175+
var regs = try ptrace.getRegSet()
187176
regs.step(RegisterSet.trapInstructionSize)
188177

189-
try self.ptrace.setRegSet(regSet: regs)
190-
try self.ptrace.cont()
178+
try ptrace.setRegSet(regSet: regs)
179+
try ptrace.cont()
191180
}
192181

193182
try self.process.readMem(remoteAddr: remoteDataAddr, localAddr: buffer, len: UInt(dataLen))
@@ -219,37 +208,37 @@ internal final class AndroidRemoteProcess: LinuxRemoteProcess {
219208
}
220209

221210
// call mmap in the remote process with the provided arguments
222-
internal func mmapRemote(len: Int, prot: Int32, flags: Int32) throws -> UInt64 {
211+
internal func mmapRemote(_ ptrace: PTrace, len: Int, prot: Int32, flags: Int32) throws -> UInt64 {
223212
guard let sym = self.mmapSymbol.addr else {
224213
throw RemoteProcessError.missingSymbol(self.mmapSymbol.name)
225214
}
226215
let args = [0, UInt64(len), UInt64(prot), UInt64(flags)]
227-
return try self.ptrace.callRemoteFunction(at: sym, with: args)
216+
return try ptrace.jump(to: sym, with: args)
228217
}
229218

230219
// call munmap in the remote process with the provdied arguments
231-
internal func munmapRemote(addr: UInt64, len: Int) throws -> UInt64 {
220+
internal func munmapRemote(_ ptrace: PTrace, addr: UInt64, len: Int) throws -> UInt64 {
232221
guard let sym = self.munmapSymbol.addr else {
233222
throw RemoteProcessError.missingSymbol(self.munmapSymbol.name)
234223
}
235224
let args: [UInt64] = [addr, UInt64(len)]
236-
return try self.ptrace.callRemoteFunction(at: sym, with: args)
225+
return try ptrace.jump(to: sym, with: args)
237226
}
238227

239228
// call malloc_disable in the remote process
240-
internal func mallocDisableRemote() throws {
229+
internal func mallocDisableRemote(_ ptrace: PTrace) throws {
241230
guard let sym = self.mallocDisableSymbol.addr else {
242231
throw RemoteProcessError.missingSymbol(self.mallocDisableSymbol.name)
243232
}
244-
_ = try self.ptrace.callRemoteFunction(at: sym)
233+
_ = try ptrace.jump(to: sym)
245234
}
246235

247236
// call malloc_enable in the remote process
248-
internal func mallocEnableRemote() throws {
237+
internal func mallocEnableRemote(_ ptrace: PTrace) throws {
249238
guard let sym = self.mallocEnableSymbol.addr else {
250239
throw RemoteProcessError.missingSymbol(self.mallocEnableSymbol.name)
251240
}
252-
_ = try self.ptrace.callRemoteFunction(at: sym)
241+
_ = try ptrace.jump(to: sym)
253242
}
254243
}
255244

0 commit comments

Comments
 (0)