Skip to content

Commit 9671d6b

Browse files
committed
[swift-inspect] Fix corpse leaks when target doesn't have libswiftCore loaded.
In DarwinRemoteProcess's early return when libswiftCore.dylib isn't loaded in the process, we hadn't initialized all stored properties yet, which means our deinit didn't run. This leaked the task. This is especially bad when forking a corpse, as the system has a very limited number of corpses available. Add a Cleanup helper to manage resources that need explicit cleanup. This is a noncopyable struct which takes a cleanup function and calls it whenever a new value is set or when the struct is destroyed. Use this for the DarwinRemoteProcess properties that need explicit cleanup. This allows us to remove DarwinRemoteProcess's deinit and always run these cleanups regardless of how we exit the initializer. While we're here, fix the truncations of buffer in getAllProcesses. rdar://151170155
1 parent 6667471 commit 9671d6b

File tree

3 files changed

+89
-37
lines changed

3 files changed

+89
-37
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
/// A helper struct that manages the cleanup of some external resource.
14+
/// The value is stored as an Optional, but presented as a non-optional.
15+
/// Accessing the value before one has been set is a runtime error. The cleanup
16+
/// function is called on the value if one has been set, when setting a new
17+
/// value or when the struct is destroyed.
18+
struct Cleanup<T>: ~Copyable {
19+
/// The underlying Optional storage for the value.
20+
private var _value: T?
21+
22+
/// The wrapped value. A value must be set before any value is read here.
23+
var value: T {
24+
get {
25+
_value!
26+
}
27+
set {
28+
if let _value {
29+
cleanup(_value)
30+
}
31+
_value = newValue
32+
}
33+
}
34+
35+
/// The function used to clean up the resource held by this struct.
36+
let cleanup: (T) -> Void
37+
38+
init(cleanup: @escaping (T) -> Void) {
39+
self._value = nil
40+
self.cleanup = cleanup
41+
}
42+
43+
deinit {
44+
if let _value {
45+
cleanup(_value)
46+
}
47+
}
48+
}

tools/swift-inspect/Sources/swift-inspect/DarwinRemoteProcess.swift

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,25 @@ internal final class DarwinRemoteProcess: RemoteProcess {
1919
public typealias ProcessIdentifier = pid_t
2020
public typealias ProcessHandle = task_t
2121

22-
private var task: task_t
22+
private var task = Cleanup<task_t> {
23+
// task_stop_peeking does nothing if we didn't task_start_peeking first, so
24+
// we can call it unconditionally.
25+
task_stop_peeking($0)
26+
mach_port_deallocate(mach_task_self_, $0)
27+
}
28+
2329
internal var processIdentifier: ProcessIdentifier
2430
internal lazy var processName = getProcessName(processId: processIdentifier) ?? "<unknown process>"
2531

26-
public var process: ProcessHandle { task }
27-
public private(set) var context: SwiftReflectionContextRef!
28-
private var symbolicator: CSSymbolicatorRef
32+
public var process: ProcessHandle { task.value }
33+
private var _context = Cleanup<SwiftReflectionContextRef> {
34+
swift_reflection_destroyReflectionContext($0)
35+
}
36+
public var context: SwiftReflectionContextRef! { _context.value }
37+
38+
private var symbolicator = Cleanup<CSSymbolicatorRef> {
39+
CSRelease($0)
40+
}
2941

3042
private var swiftCore: CSTypeRef
3143
private let swiftConcurrency: CSTypeRef
@@ -72,7 +84,7 @@ internal final class DarwinRemoteProcess: RemoteProcess {
7284
}
7385

7486
func read(address: swift_addr_t, size: Int) -> UnsafeRawPointer? {
75-
return task_peek(task, address, mach_vm_size_t(size))
87+
return task_peek(task.value, address, mach_vm_size_t(size))
7688
}
7789

7890
func getAddr(symbolName: String) -> swift_addr_t {
@@ -98,7 +110,7 @@ internal final class DarwinRemoteProcess: RemoteProcess {
98110
static var GetStringLength: GetStringLengthFunction {
99111
return { (context, address) in
100112
let process: DarwinRemoteProcess = DarwinRemoteProcess.fromOpaque(context!)
101-
if let str = task_peek_string(process.task, address) {
113+
if let str = task_peek_string(process.task.value, address) {
102114
return UInt64(strlen(str))
103115
}
104116
return 0
@@ -119,18 +131,19 @@ internal final class DarwinRemoteProcess: RemoteProcess {
119131

120132
init?(processId: ProcessIdentifier, forkCorpse: Bool) {
121133
processIdentifier = processId
122-
var task: task_t = task_t()
123-
let taskResult = task_for_pid(mach_task_self_, processId, &task)
134+
var processTask: task_t = task_t()
135+
let taskResult = task_for_pid(mach_task_self_, processId, &processTask)
124136
guard taskResult == KERN_SUCCESS else {
125137
print("unable to get task for pid \(processId): \(String(cString: mach_error_string(taskResult))) \(hex: taskResult)",
126138
to: &Std.err)
127139
return nil
128140
}
141+
self.task.value = processTask
129142

130143
// Consult with VMUProcInfo to determine if we should force forkCorpse.
131144
let forceForkCorpse: Bool
132145
if let procInfoClass = getVMUProcInfoClass() {
133-
let procInfo = procInfoClass.init(task: task)
146+
let procInfo = procInfoClass.init(task: self.task.value)
134147
forceForkCorpse = procInfo.shouldAnalyzeWithCorpse
135148
} else {
136149
// Default to not forcing forkCorpse.
@@ -141,11 +154,9 @@ internal final class DarwinRemoteProcess: RemoteProcess {
141154
var corpse = task_t()
142155
let maxRetry = 6
143156
for retry in 0..<maxRetry {
144-
let corpseResult = task_generate_corpse(task, &corpse)
157+
let corpseResult = task_generate_corpse(task.value, &corpse)
145158
if corpseResult == KERN_SUCCESS {
146-
task_stop_peeking(task)
147-
mach_port_deallocate(mach_task_self_, task)
148-
task = corpse
159+
task.value = corpse
149160
break
150161
}
151162
if corpseResult != KERN_RESOURCE_SHORTAGE || retry == maxRetry {
@@ -157,20 +168,19 @@ internal final class DarwinRemoteProcess: RemoteProcess {
157168
}
158169
}
159170

160-
self.task = task
171+
self.symbolicator.value = CSSymbolicatorCreateWithTask(self.task.value)
172+
173+
self.swiftCore = CSSymbolicatorGetSymbolOwnerWithNameAtTime(
174+
self.symbolicator.value, "libswiftCore.dylib", kCSNow)
175+
self.swiftConcurrency = CSSymbolicatorGetSymbolOwnerWithNameAtTime(
176+
self.symbolicator.value, "libswift_Concurrency.dylib", kCSNow)
161177

162-
self.symbolicator = CSSymbolicatorCreateWithTask(self.task)
163-
self.swiftCore =
164-
CSSymbolicatorGetSymbolOwnerWithNameAtTime(self.symbolicator,
165-
"libswiftCore.dylib", kCSNow)
166178
if CSIsNull(self.swiftCore) {
167179
print("pid \(processId) does not have libswiftCore.dylib loaded")
168180
return nil
169181
}
170182

171-
self.swiftConcurrency = CSSymbolicatorGetSymbolOwnerWithNameAtTime(
172-
symbolicator, "libswift_Concurrency.dylib", kCSNow)
173-
_ = task_start_peeking(self.task)
183+
_ = task_start_peeking(self.task.value)
174184

175185
guard let context =
176186
swift_reflection_createReflectionContextWithDataLayout(self.toOpaqueRef(),
@@ -181,23 +191,17 @@ internal final class DarwinRemoteProcess: RemoteProcess {
181191
Self.GetSymbolAddress) else {
182192
return nil
183193
}
184-
self.context = context
194+
self._context.value = context
185195

186-
_ = CSSymbolicatorForeachSymbolOwnerAtTime(self.symbolicator, kCSNow, { owner in
196+
_ = CSSymbolicatorForeachSymbolOwnerAtTime(self.symbolicator.value, kCSNow, { owner in
187197
let address = CSSymbolOwnerGetBaseAddress(owner)
188198
_ = swift_reflection_addImage(self.context, address)
189199
})
190200
}
191201

192-
deinit {
193-
task_stop_peeking(self.task)
194-
CSRelease(self.symbolicator)
195-
mach_port_deallocate(mach_task_self_, self.task)
196-
}
197-
198202
func symbolicate(_ address: swift_addr_t) -> (module: String?, symbol: String?) {
199203
let symbol =
200-
CSSymbolicatorGetSymbolWithAddressAtTime(self.symbolicator, address, kCSNow)
204+
CSSymbolicatorGetSymbolWithAddressAtTime(self.symbolicator.value, address, kCSNow)
201205

202206
let module = CSSymbolGetSymbolOwner(symbol)
203207
return (CSSymbolOwnerGetName(module), CSSymbolGetName(symbol))
@@ -206,7 +210,7 @@ internal final class DarwinRemoteProcess: RemoteProcess {
206210
internal func iterateHeap(_ body: (swift_addr_t, UInt64) -> Void) {
207211
withoutActuallyEscaping(body) {
208212
withUnsafePointer(to: $0) {
209-
task_enumerate_malloc_blocks(self.task,
213+
task_enumerate_malloc_blocks(self.task.value,
210214
UnsafeMutableRawPointer(mutating: $0),
211215
CUnsignedInt(MALLOC_PTR_IN_USE_RANGE_TYPE),
212216
{ (task, context, type, ranges, count) in
@@ -264,14 +268,14 @@ extension DarwinRemoteProcess {
264268
}
265269

266270
private func getThreadInfos() -> [ThreadInfo] {
267-
guard let threads = PortList(task: self.task) else {
271+
guard let threads = PortList(task: self.task.value) else {
268272
return []
269273
}
270-
return threads.compactMap {
271-
guard let info = getThreadInfo(thread: $0) else {
274+
return threads.compactMap { t -> ThreadInfo? in
275+
guard let info = getThreadInfo(thread: t) else {
272276
return nil
273277
}
274-
guard let kernelObj = getKernelObject(task: mach_task_self_, port: $0) else {
278+
guard let kernelObj = getKernelObject(task: mach_task_self_, port: t) else {
275279
return nil
276280
}
277281
return ThreadInfo(threadID: info.thread_id,
@@ -328,7 +332,7 @@ extension DarwinRemoteProcess {
328332
}
329333

330334
internal func getThreadID(remotePort: thread_t) -> UInt64? {
331-
guard let remoteThreadObj = getKernelObject(task: self.task, port: remotePort) else {
335+
guard let remoteThreadObj = getKernelObject(task: self.task.value, port: remotePort) else {
332336
return nil
333337
}
334338
return threadInfos.first{ $0.kernelObject == remoteThreadObj }?.threadID

tools/swift-inspect/Sources/swift-inspect/Process.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ internal func getAllProcesses(options: UniversalOptions) -> [ProcessIdentifier]?
8080
}
8181
let newCount = bufferSize / kinfo_stride
8282
if count > newCount {
83-
buffer.dropLast(count - newCount)
83+
buffer.removeLast(count - newCount)
8484
}
8585
let sorted = buffer.sorted { first, second in
8686
first.kp_proc.p_pid > second.kp_proc.p_pid

0 commit comments

Comments
 (0)