Skip to content

Commit 43df54c

Browse files
committed
[swift-inspect] Support forking corpses and self-inspection of the swift-inspect process on Darwin.
Support inspecting the swift-inspect process itself. `pidFromHint` skips the process it's in, so we manually check the requested name against `argv[0]` as a special case. Add a `--fork-corpse` flag which uses `task_generate_corpse` on the target task before inspecting it. This allows the target to keep running while we inspect it, and also works around a bug when self-inspecting.
1 parent 8b51941 commit 43df54c

File tree

10 files changed

+61
-17
lines changed

10 files changed

+61
-17
lines changed

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

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,24 @@ internal final class DarwinRemoteProcess: RemoteProcess {
113113
}
114114
}
115115

116-
init?(processId: ProcessIdentifier) {
116+
init?(processId: ProcessIdentifier, forkCorpse: Bool) {
117117
var task: task_t = task_t()
118-
let result = task_for_pid(mach_task_self_, processId, &task)
119-
guard result == KERN_SUCCESS else {
120-
print("unable to get task for pid \(processId): \(String(cString: mach_error_string(result))) (0x\(String(result, radix: 16)))")
118+
let taskResult = task_for_pid(mach_task_self_, processId, &task)
119+
guard taskResult == KERN_SUCCESS else {
120+
print("unable to get task for pid \(processId): \(String(cString: mach_error_string(taskResult))) \(hex: taskResult)")
121121
return nil
122122
}
123+
124+
if forkCorpse {
125+
var corpse = task_t()
126+
let corpseResult = task_generate_corpse(task, &corpse)
127+
if corpseResult == KERN_SUCCESS {
128+
task = corpse
129+
} else {
130+
print("unable to fork corpse for pid \(processId): \(String(cString: mach_error_string(corpseResult))) \(hex: corpseResult)")
131+
}
132+
}
133+
123134
self.task = task
124135

125136
self.symbolicator = CSSymbolicatorCreateWithTask(self.task)

tools/swift-inspect/Sources/swift-inspect/Operations/DumpArray.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ internal struct DumpArrays: ParsableCommand {
2121
var options: UniversalOptions
2222

2323
func run() throws {
24-
try inspect(process: options.nameOrPid) { process in
24+
try inspect(options: options) { process in
2525
print("Address", "Size", "Count", "Is Class", separator: "\t")
2626
process.iterateHeap { (allocation, size) in
2727
let metadata: UInt =

tools/swift-inspect/Sources/swift-inspect/Operations/DumpCacheNodes.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ internal struct DumpCacheNodes: ParsableCommand {
2121
var options: UniversalOptions
2222

2323
func run() throws {
24-
try inspect(process: options.nameOrPid) { process in
24+
try inspect(options: options) { process in
2525
print("Address", "Tag", "Tag Name", "Size", "Left", "Right", separator: "\t")
2626
try process.context.allocations.forEach {
2727
var node: swift_metadata_cache_node_t = swift_metadata_cache_node_t()

tools/swift-inspect/Sources/swift-inspect/Operations/DumpConcurrency.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ struct DumpConcurrency: ParsableCommand {
2323
var options: UniversalOptions
2424

2525
func run() throws {
26-
try inspect(process: options.nameOrPid) { process in
26+
try inspect(options: options) { process in
2727
let dumper = ConcurrencyDumper(context: process.context,
2828
process: process as! DarwinRemoteProcess)
2929
dumper.dumpTasks()

tools/swift-inspect/Sources/swift-inspect/Operations/DumpConformanceCache.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ internal struct DumpConformanceCache: ParsableCommand {
2020
var options: UniversalOptions
2121

2222
func run() throws {
23-
try inspect(process: options.nameOrPid) { process in
23+
try inspect(options: options) { process in
2424
try process.context.iterateConformanceCache { type, proto in
2525
let type: String = process.context.name(type: type) ?? "<unknown>"
2626
let conformance: String = process.context.name(protocol: proto) ?? "<unknown>"

tools/swift-inspect/Sources/swift-inspect/Operations/DumpGenericMetadata.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ internal struct DumpGenericMetadata: ParsableCommand {
3434
var backtraceOptions: BacktraceOptions
3535

3636
func run() throws {
37-
try inspect(process: options.nameOrPid) { process in
37+
try inspect(options: options) { process in
3838
let allocations: [swift_metadata_allocation_t] =
3939
try process.context.allocations.sorted()
4040

tools/swift-inspect/Sources/swift-inspect/Operations/DumpRawMetadata.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ internal struct DumpRawMetadata: ParsableCommand {
2424
var backtraceOptions: BacktraceOptions
2525

2626
func run() throws {
27-
try inspect(process: options.nameOrPid) { process in
27+
try inspect(options: options) { process in
2828
let stacks: [swift_reflection_ptr_t:[swift_reflection_ptr_t]]? =
2929
backtraceOptions.style == nil
3030
? nil

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,32 @@ import Darwin
1616
internal typealias ProcessIdentifier = DarwinRemoteProcess.ProcessIdentifier
1717

1818
internal func process(matching: String) -> ProcessIdentifier? {
19-
return pidFromHint(matching)
19+
if refersToSelf(matching) {
20+
return getpid()
21+
} else {
22+
return pidFromHint(matching)
23+
}
24+
}
25+
26+
private func refersToSelf(_ str: String) -> Bool {
27+
guard let myPath = CommandLine.arguments.first else {
28+
return false
29+
}
30+
31+
// If string matches the full path, success.
32+
if myPath == str {
33+
return true
34+
}
35+
36+
// If there's a slash in the string, compare with the component following the
37+
// slash.
38+
if let slashIndex = myPath.lastIndex(of: "/") {
39+
let myName = myPath[slashIndex...].dropFirst()
40+
return myName == str
41+
}
42+
43+
// No match.
44+
return false
2045
}
2146
#elseif os(Windows)
2247
import WinSDK

tools/swift-inspect/Sources/swift-inspect/RemoteProcess.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ internal protocol RemoteProcess: AnyObject {
1919
var process: ProcessHandle { get }
2020
var context: SwiftReflectionContextRef! { get }
2121

22-
init?(processId: ProcessIdentifier)
23-
2422
typealias QueryDataLayoutFunction =
2523
@convention(c) (UnsafeMutableRawPointer?, DataLayoutQueryType,
2624
UnsafeMutableRawPointer?, UnsafeMutableRawPointer?) -> CInt

tools/swift-inspect/Sources/swift-inspect/main.swift

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ import SwiftRemoteMirror
1717
internal struct UniversalOptions: ParsableArguments {
1818
@Argument(help: "The pid or partial name of the target process")
1919
var nameOrPid: String
20+
21+
#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS)
22+
@Flag(help: ArgumentHelp(
23+
"Fork a corpse of the target process",
24+
discussion: "Creates a low-level copy of the target process, allowing " +
25+
"the target to immediately resume execution before " +
26+
"swift-inspect has completed its work."))
27+
var forkCorpse: Bool = false
28+
#endif
2029
}
2130

2231
internal struct BacktraceOptions: ParsableArguments {
@@ -34,15 +43,16 @@ internal struct BacktraceOptions: ParsableArguments {
3443
}
3544

3645

37-
internal func inspect(process pattern: String,
46+
internal func inspect(options: UniversalOptions,
3847
_ body: (any RemoteProcess) throws -> Void) throws {
39-
guard let processId = process(matching: pattern) else {
40-
print("No process found matching \(pattern)")
48+
guard let processId = process(matching: options.nameOrPid) else {
49+
print("No process found matching \(options.nameOrPid)")
4150
return
4251
}
4352

4453
#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS)
45-
guard let process = DarwinRemoteProcess(processId: processId) else {
54+
guard let process = DarwinRemoteProcess(processId: processId,
55+
forkCorpse: options.forkCorpse) else {
4656
print("Failed to create inspector for process id \(processId)")
4757
return
4858
}

0 commit comments

Comments
 (0)