Skip to content

Commit 513f5fb

Browse files
committed
[swift-inspect] Add a mode to search for and dump generic metadata without metadata iteration enabled.
We scan the target's initial allocation pool, and all 16kB heap allocations. We check each pointer-aligned offset within those areas, and try to read it as Swift metadata and get a name from it. If that fails, quietly move on. It's very unlikely for some random memory to look enough like Swift metadata for this to produce a name, so this works very well to print the generic metadata instantiated in the remote process without requiring `SWIFT_DEBUG_ENABLE_METADATA_ALLOCATION_ITERATION`. rdar://161120936
1 parent a5c6156 commit 513f5fb

File tree

13 files changed

+148
-31
lines changed

13 files changed

+148
-31
lines changed

include/swift/Remote/MetadataReader.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3215,6 +3215,23 @@ class MetadataReader {
32153215
if (!descriptor)
32163216
return BuiltType();
32173217

3218+
// Make sure the kinds match, to catch bad data from not reading an actual
3219+
// metadata.
3220+
auto metadataKind = metadata.getLocalBuffer()->getKind();
3221+
auto descriptorKind = descriptor.getLocalBuffer()->getKind();
3222+
if (metadataKind == MetadataKind::Class &&
3223+
descriptorKind != ContextDescriptorKind::Class)
3224+
return BuiltType();
3225+
if (metadataKind == MetadataKind::Struct &&
3226+
descriptorKind != ContextDescriptorKind::Struct)
3227+
return BuiltType();
3228+
if (metadataKind == MetadataKind::Enum &&
3229+
descriptorKind != ContextDescriptorKind::Enum)
3230+
return BuiltType();
3231+
if (metadataKind == MetadataKind::Optional &&
3232+
descriptorKind != ContextDescriptorKind::Enum)
3233+
return BuiltType();
3234+
32183235
// From that, attempt to resolve a nominal type.
32193236
BuiltTypeDecl typeDecl = buildNominalTypeDecl(descriptor);
32203237
if (!typeDecl)

include/swift/Runtime/Debug.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,14 @@ bool _swift_debug_metadataAllocationIterationEnabled;
266266
SWIFT_RUNTIME_STDLIB_SPI
267267
const void * const _swift_debug_allocationPoolPointer;
268268

269+
SWIFT_RUNTIME_STDLIB_SPI
270+
const size_t _swift_debug_allocationPoolSize;
271+
272+
// The size of the pages the metadata allocator allocates on the heap. May be
273+
// used to filter out possible metadata pages when examining the heap.
274+
SWIFT_RUNTIME_STDLIB_SPI
275+
const size_t _swift_debug_metadataAllocatorPageSize;
276+
269277
SWIFT_RUNTIME_STDLIB_SPI
270278
std::atomic<const void *> _swift_debug_metadataAllocationBacktraceList;
271279

stdlib/public/RemoteInspection/TypeLowering.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2324,8 +2324,9 @@ class LowerType
23242324
if (auto N = dyn_cast<NominalTypeRef>(TR)) {
23252325
Demangler Dem;
23262326
auto Node = N->getDemangling(Dem);
2327-
if (Node->getKind() == Node::Kind::Type && Node->getNumChildren() == 1) {
2328-
auto Alias = Node->getChild(0);
2327+
if (Node && Node->getKind() == Node::Kind::Type &&
2328+
Node->getNumChildren() == 1) {
2329+
auto Alias = Node->getChild(0);
23292330
if (Alias->getKind() == Node::Kind::TypeAlias && Alias->getNumChildren() == 2) {
23302331
auto Module = Alias->getChild(0);
23312332
auto Name = Alias->getChild(1);

stdlib/public/runtime/Metadata.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7903,6 +7903,8 @@ std::tuple<const void *, size_t> MetadataAllocator::InitialPoolLocation() {
79037903

79047904
bool swift::_swift_debug_metadataAllocationIterationEnabled = false;
79057905
const void * const swift::_swift_debug_allocationPoolPointer = &AllocationPool;
7906+
const size_t swift::_swift_debug_allocationPoolSize = InitialPoolSize;
7907+
const size_t swift::_swift_debug_metadataAllocatorPageSize = PoolRange::PageSize;
79067908
std::atomic<const void *> swift::_swift_debug_metadataAllocationBacktraceList;
79077909

79087910
static void recordBacktrace(void *allocation) {

test/abi/macOS/arm64/stdlib.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,3 +1139,7 @@ Added: _$ss7UnicodeO27_RandomAccessWordRecognizerVN
11391139
// Obsolete/broken SPIs removed in 6.3
11401140
Removed: _$sSS17_nearestWordIndex9atOrBelowSS0C0VAD_tF
11411141
Removed: _$sSS10_wordIndex6beforeSS0B0VAD_tF
1142+
1143+
// Internal info exposed for swift-inspect.
1144+
Added: __swift_debug_allocationPoolSize
1145+
Added: __swift_debug_metadataAllocatorPageSize

test/abi/macOS/x86_64/stdlib.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,3 +1139,7 @@ Added: _$ss7UnicodeO27_RandomAccessWordRecognizerVN
11391139
// Obsolete/broken SPIs removed in 6.3
11401140
Removed: _$sSS17_nearestWordIndex9atOrBelowSS0C0VAD_tF
11411141
Removed: _$sSS10_wordIndex6beforeSS0B0VAD_tF
1142+
1143+
// Internal info exposed for swift-inspect.
1144+
Added: __swift_debug_allocationPoolSize
1145+
Added: __swift_debug_metadataAllocatorPageSize

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

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,30 @@ internal final class DarwinRemoteProcess: RemoteProcess {
8787
return task_peek(task.value, address, mach_vm_size_t(size))
8888
}
8989

90-
func getAddr(symbolName: String) -> swift_addr_t {
90+
func getAddr(symbolName: String) -> swift_addr_t? {
9191
// FIXME: use `__USER_LABEL_PREFIX__` instead of the hardcoded `_`.
9292
let fullName = "_\(symbolName)"
9393
var symbol = CSSymbolOwnerGetSymbolWithMangledName(swiftCore, fullName)
9494
if CSIsNull(symbol) {
9595
symbol = CSSymbolOwnerGetSymbolWithMangledName(swiftConcurrency, fullName)
9696
}
97+
if CSIsNull(symbol) {
98+
return nil
99+
}
97100
let range = CSSymbolGetRange(symbol)
98101
return swift_addr_t(range.location)
99102
}
100103

104+
private func readGlobalVariable<T>(named name: String) -> T? {
105+
guard let globalPointer = getAddr(symbolName: name) else {
106+
return nil
107+
}
108+
guard let readPointer = read(address: globalPointer, size: MemoryLayout<T>.size) else {
109+
return nil
110+
}
111+
return readPointer.load(as: T.self)
112+
}
113+
101114
static var Free: FreeFunction? { return nil }
102115

103116
static var ReadBytes: ReadBytesFunction {
@@ -125,7 +138,7 @@ internal final class DarwinRemoteProcess: RemoteProcess {
125138
let buffer = UnsafeBufferPointer(start: $0, count: Int(length))
126139
return String(decoding: buffer, as: UTF8.self)
127140
}
128-
return process.getAddr(symbolName: name)
141+
return process.getAddr(symbolName: name) ?? 0
129142
}
130143
}
131144

@@ -224,6 +237,21 @@ internal final class DarwinRemoteProcess: RemoteProcess {
224237
}
225238
}
226239
}
240+
241+
internal func iteratePotentialMetadataPages(_ body: (swift_addr_t, UInt64) -> Void) {
242+
if let initialPoolPointer: UInt = readGlobalVariable(named: "_swift_debug_allocationPoolPointer"),
243+
let initialPoolSize: UInt = readGlobalVariable(named: "_swift_debug_allocationPoolSize") {
244+
body(swift_reflection_ptr_t(initialPoolPointer), UInt64(initialPoolSize));
245+
}
246+
247+
if let pageSize: UInt = readGlobalVariable(named: "_swift_debug_metadataAllocatorPageSize") {
248+
iterateHeap { address, size in
249+
if size == pageSize {
250+
body(address, size)
251+
}
252+
}
253+
}
254+
}
227255
}
228256

229257
extension DarwinRemoteProcess {

tools/swift-inspect/Sources/swift-inspect/LinuxRemoteProcess.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,5 +154,9 @@
154154
internal func iterateHeap(_ body: (swift_addr_t, UInt64) -> Void) {
155155
fatalError("heap iteration is not supported on Linux")
156156
}
157+
158+
internal func iteratePotentialMetadataPages(_ body: (swift_addr_t, UInt64) -> Void) {
159+
fatalError("metadata page iteration is not supported on Linux")
160+
}
157161
}
158162
#endif // os(Linux)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ fileprivate class ConcurrencyDumper {
9292
self.process = process
9393

9494
func getMetadata(symbolName: String) -> swift_reflection_ptr_t? {
95-
let addr = process.getAddr(symbolName: symbolName)
96-
if let ptr = process.read(address: addr, size: MemoryLayout<UInt>.size) {
95+
if let addr = process.getAddr(symbolName: symbolName),
96+
let ptr = process.read(address: addr, size: MemoryLayout<UInt>.size) {
9797
return swift_reflection_ptr_t(ptr.load(as: UInt.self))
9898
}
9999
return nil

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

Lines changed: 57 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -116,36 +116,17 @@ internal struct DumpGenericMetadata: ParsableCommand {
116116
@Flag(help: "Show allocations in mangled form")
117117
var mangled: Bool = false
118118

119+
@Flag(help: "Scan for data that looks like Swift type metadata.")
120+
var scanSearch: Bool = false
121+
119122
func run() throws {
120123
disableStdErrBuffer()
121124
var metadataSummary = [String: MetadataSummary]()
122125
var allProcesses = [ProcessMetadata]()
123126
try inspect(options: options) { process in
124-
let allocations: [swift_metadata_allocation_t] =
125-
try process.context.allocations.sorted()
126-
127-
let stacks: [swift_reflection_ptr_t:[swift_reflection_ptr_t]] =
128-
backtraceOptions.style == nil
129-
? [swift_reflection_ptr_t:[swift_reflection_ptr_t]]()
130-
: try process.context.allocationStacks
131-
132-
let generics: [Metadata] = allocations.compactMap { allocation -> Metadata? in
133-
let pointer = swift_reflection_allocationMetadataPointer(process.context, allocation)
134-
if pointer == 0 { return nil }
135-
let allocation = allocations.last(where: { pointer >= $0.ptr && pointer < $0.ptr + swift_reflection_ptr_t($0.size) })
136-
let garbage = (allocation == nil && swift_reflection_ownsAddressStrict(process.context, UInt(pointer)) == 0)
137-
var currentBacktrace: String?
138-
if let style = backtraceOptions.style, let allocation, let stack = stacks[allocation.ptr] {
139-
currentBacktrace = backtrace(stack, style: style, process.symbolicate)
140-
}
141-
142-
return Metadata(ptr: pointer,
143-
allocation: allocation,
144-
name: process.context.name(type: pointer, mangled: mangled) ?? "<unknown>",
145-
isArrayOfClass: process.context.isArrayOfClass(pointer),
146-
garbage: garbage,
147-
backtrace: currentBacktrace)
148-
} // generics
127+
let generics = scanSearch
128+
? try metadataFromScanning(process: process)
129+
: try metadataFromAllocations(process: process)
149130

150131
// Update summary
151132
generics.forEach { metadata in
@@ -182,6 +163,57 @@ internal struct DumpGenericMetadata: ParsableCommand {
182163
}
183164
}
184165

166+
private func metadataFromAllocations(process: any RemoteProcess) throws -> [Metadata] {
167+
let allocations: [swift_metadata_allocation_t] =
168+
try process.context.allocations.sorted()
169+
170+
let stacks: [swift_reflection_ptr_t:[swift_reflection_ptr_t]] =
171+
backtraceOptions.style == nil
172+
? [swift_reflection_ptr_t:[swift_reflection_ptr_t]]()
173+
: try process.context.allocationStacks
174+
175+
return allocations.compactMap { allocation -> Metadata? in
176+
let pointer = swift_reflection_allocationMetadataPointer(process.context, allocation)
177+
if pointer == 0 { return nil }
178+
let allocation = allocations.last(where: { pointer >= $0.ptr && pointer < $0.ptr + swift_reflection_ptr_t($0.size) })
179+
let garbage = (allocation == nil && swift_reflection_ownsAddressStrict(process.context, UInt(pointer)) == 0)
180+
var currentBacktrace: String?
181+
if let style = backtraceOptions.style, let allocation, let stack = stacks[allocation.ptr] {
182+
currentBacktrace = backtrace(stack, style: style, process.symbolicate)
183+
}
184+
185+
return Metadata(ptr: pointer,
186+
allocation: allocation,
187+
name: process.context.name(type: pointer, mangled: mangled) ?? "<unknown>",
188+
isArrayOfClass: process.context.isArrayOfClass(pointer),
189+
garbage: garbage,
190+
backtrace: currentBacktrace)
191+
}
192+
}
193+
194+
private func metadataFromScanning(process: any RemoteProcess) throws -> [Metadata] {
195+
var metadata: [Metadata] = []
196+
197+
func scanMemory(address: swift_reflection_ptr_t, size: UInt64) {
198+
for candidate in stride(from: address, to: address + swift_reflection_ptr_t(size), by: MemoryLayout<UInt>.size) {
199+
guard let name = process.context.name(type: candidate, mangled: mangled) else {
200+
continue
201+
}
202+
let m = Metadata(ptr: candidate,
203+
allocation: nil,
204+
name: name,
205+
isArrayOfClass: process.context.isArrayOfClass(candidate),
206+
garbage: false,
207+
backtrace: nil)
208+
metadata.append(m)
209+
}
210+
}
211+
212+
process.iteratePotentialMetadataPages(scanMemory)
213+
214+
return metadata
215+
}
216+
185217
private func dumpText(process: any RemoteProcess, generics: [Metadata]) throws {
186218
var erroneousMetadata: [(ptr: swift_reflection_ptr_t, name: String)] = []
187219
var output = try Output(metadataOptions.outputFile)

0 commit comments

Comments
 (0)