Skip to content

Commit d9989d3

Browse files
committed
[swift-inspect] Add json output option for dump-generic-metadata
This change adds json output option to dump-generic-metadata for swift-inspect.
1 parent c019aef commit d9989d3

File tree

3 files changed

+112
-39
lines changed

3 files changed

+112
-39
lines changed

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

Lines changed: 97 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,47 @@
1212

1313
import ArgumentParser
1414
import SwiftRemoteMirror
15+
import Foundation
1516

16-
private struct Metadata {
17+
private struct Metadata: Encodable {
1718
let ptr: swift_reflection_ptr_t
1819
var allocation: swift_metadata_allocation_t?
19-
2020
let name: String
2121
let isArrayOfClass: Bool
22-
22+
var garbage: Bool = false
2323
var offset: Int? { allocation.map { Int(self.ptr - $0.ptr) } }
24+
var backtrace: String?
25+
26+
enum CodingKeys: String, CodingKey {
27+
case ptr = "address"
28+
case allocation
29+
case name
30+
case isArrayOfClass
31+
case garbage
32+
case offset
33+
case backtrace
34+
}
35+
36+
func encode(to encoder: Encoder) throws {
37+
var container = encoder.container(keyedBy: CodingKeys.self)
38+
try container.encode(ptr, forKey: .ptr)
39+
try container.encode(name, forKey: .name)
40+
if isArrayOfClass {
41+
try container.encode(isArrayOfClass, forKey: .isArrayOfClass)
42+
}
43+
if garbage {
44+
try container.encode(garbage, forKey: .garbage)
45+
}
46+
if let offset {
47+
try container.encode(offset, forKey: .offset)
48+
}
49+
if let backtrace {
50+
try container.encode(backtrace, forKey: .backtrace)
51+
}
52+
if let allocation {
53+
try container.encode(allocation, forKey: .allocation)
54+
}
55+
}
2456
}
2557

2658
internal struct DumpGenericMetadata: ParsableCommand {
@@ -41,52 +73,79 @@ internal struct DumpGenericMetadata: ParsableCommand {
4173
let allocations: [swift_metadata_allocation_t] =
4274
try process.context.allocations.sorted()
4375

44-
let generics: [Metadata] = allocations.compactMap { allocation -> Metadata? in
76+
let stacks: [swift_reflection_ptr_t:[swift_reflection_ptr_t]] =
77+
backtraceOptions.style == nil
78+
? [swift_reflection_ptr_t:[swift_reflection_ptr_t]]()
79+
: try process.context.allocationStacks
80+
81+
let generics: [Metadata] = allocations.compactMap { allocation in
4582
let pointer = swift_reflection_allocationMetadataPointer(process.context, allocation)
4683
if pointer == 0 { return nil }
84+
let allocation = allocations.last(where: { pointer >= $0.ptr && pointer < $0.ptr + UInt64($0.size) })
85+
let garbage = (allocation == nil && swift_reflection_ownsAddressStrict(process.context, UInt(pointer)) == 0)
86+
var currentBacktrace: String?
87+
if let style = backtraceOptions.style, let allocation, let stack = stacks[allocation.ptr] {
88+
currentBacktrace = backtrace(stack, style: style, process.symbolicate)
89+
}
4790

4891
return Metadata(ptr: pointer,
49-
allocation: allocations.last(where: { pointer >= $0.ptr && pointer < $0.ptr + swift_reflection_ptr_t($0.size) }),
50-
name: (process.context.name(type: pointer, mangled: genericMetadataOptions.mangled) ?? "<unknown>"),
51-
isArrayOfClass: process.context.isArrayOfClass(pointer))
92+
allocation: allocation,
93+
name: process.context.name(type: pointer, mangled: genericMetadataOptions.mangled) ?? "<unknown>",
94+
isArrayOfClass: process.context.isArrayOfClass(pointer),
95+
garbage: garbage,
96+
backtrace: currentBacktrace)
5297
}
5398

54-
let stacks: [swift_reflection_ptr_t:[swift_reflection_ptr_t]]? =
55-
backtraceOptions.style == nil
56-
? nil
57-
: try process.context.allocationStacks
58-
59-
var errorneousMetadata: [(ptr: swift_reflection_ptr_t, name: String)] = []
60-
61-
print("Address", "Allocation", "Size", "Offset", "isArrayOfClass", "Name", separator: "\t")
62-
generics.forEach {
63-
print("\(hex: $0.ptr)", terminator: "\t")
64-
if let allocation = $0.allocation, let offset = $0.offset {
65-
print("\(hex: allocation.ptr)\t\(allocation.size)\t\(offset)", terminator: "\t")
66-
} else {
67-
if (swift_reflection_ownsAddressStrict(process.context, UInt($0.ptr))) == 0 {
68-
errorneousMetadata.append((ptr: $0.ptr, name: $0.name))
69-
}
70-
print("???\t??\t???", terminator: "\t")
71-
}
72-
print($0.isArrayOfClass, terminator: "\t")
73-
print($0.name)
74-
if let style = backtraceOptions.style, let allocation = $0.allocation {
75-
if let stack = stacks?[allocation.ptr] {
76-
print(backtrace(stack, style: style, process.symbolicate))
77-
} else {
78-
print(" No stacktrace available")
79-
}
80-
}
99+
if let outputFile = genericMetadataOptions.outputJson {
100+
try dumpToJson(process: process, generics: generics,
101+
outputPath: outputFile)
102+
} else {
103+
try dumpToStdout(process: process, generics: generics)
81104
}
105+
}
106+
}
82107

83-
if errorneousMetadata.count > 0 {
84-
print("Error: The following metadata was not found in any DATA or AUTH segments, may be garbage.")
85-
errorneousMetadata.forEach {
86-
print("\(hex: $0.ptr)\t\($0.name)")
108+
private func dumpToStdout(process: any RemoteProcess, generics: [Metadata]) throws {
109+
var errorneousMetadata: [(ptr: swift_reflection_ptr_t, name: String)] = []
110+
111+
print("Address", "Allocation", "Size", "Offset", "isArrayOfClass", "Name", separator: "\t")
112+
generics.forEach {
113+
print("\(hex: $0.ptr)", terminator: "\t")
114+
if let allocation = $0.allocation, let offset = $0.offset {
115+
print("\(hex: allocation.ptr)\t\(allocation.size)\t\(offset)", terminator: "\t")
116+
} else {
117+
if $0.garbage {
118+
errorneousMetadata.append((ptr: $0.ptr, name: $0.name))
87119
}
120+
print("???\t??\t???", terminator: "\t")
121+
}
122+
print($0.isArrayOfClass, terminator: "\t")
123+
print($0.name)
124+
if let _ = backtraceOptions.style, let _ = $0.allocation {
125+
print($0.backtrace ?? " No stacktrace available")
88126
}
127+
}
128+
129+
if errorneousMetadata.count > 0 {
130+
print("Error: The following metadata was not found in any DATA or AUTH segments, may be garbage.")
131+
errorneousMetadata.forEach {
132+
print("\(hex: $0.ptr)\t\($0.name)")
133+
}
134+
}
135+
}
89136

137+
private func dumpToJson(process: any RemoteProcess,
138+
generics: [Metadata],
139+
outputPath: String) throws {
140+
struct AllMetadataEntries: Encodable {
141+
var metadata: [Metadata]
90142
}
143+
let allMetadataEntries = AllMetadataEntries(metadata: generics)
144+
let encoder = JSONEncoder()
145+
encoder.outputFormatting = .prettyPrinted
146+
let data = try encoder.encode(allMetadataEntries)
147+
try String(data: data, encoding: .utf8)!
148+
.write(toFile: outputPath, atomically: true, encoding: .utf8)
91149
}
150+
92151
}

tools/swift-inspect/Sources/swift-inspect/RemoteMirror+Extensions.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,21 @@
1212

1313
import SwiftRemoteMirror
1414

15-
extension swift_metadata_allocation_t {
15+
extension swift_metadata_allocation_t: Encodable {
1616
internal var tag: swift_metadata_allocation_tag_t { return self.Tag }
1717
internal var ptr: swift_reflection_ptr_t { return self.Ptr }
1818
internal var size: Int { return Int(self.Size) }
19+
enum CodingKeys: String, CodingKey {
20+
case tag
21+
case ptr = "address"
22+
case size
23+
}
24+
public func encode(to encoder: Encoder) throws {
25+
var container = encoder.container(keyedBy: CodingKeys.self)
26+
try container.encode(tag, forKey: .tag)
27+
try container.encode(ptr, forKey: .ptr)
28+
try container.encode(size, forKey: .size)
29+
}
1930
}
2031

2132
extension swift_metadata_allocation_t: Comparable {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ internal struct BacktraceOptions: ParsableArguments {
4545
internal struct GenericMetadataOptions: ParsableArguments {
4646
@Flag(help: "Show allocations in mangled form")
4747
var mangled: Bool = false
48+
49+
@Option(help: "Path to output JSON file")
50+
var outputJson: String? = nil
4851
}
4952

5053
internal func inspect(options: UniversalOptions,

0 commit comments

Comments
 (0)