Skip to content

Commit 06b34eb

Browse files
committed
Search for dSYM files using DebugSymbols settings
1 parent 0d0af2d commit 06b34eb

File tree

3 files changed

+183
-2
lines changed

3 files changed

+183
-2
lines changed

MacSymbolicator.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
B2CA03D72B9CC15700AE3DFA /* FullDiskAccess in Frameworks */ = {isa = PBXBuildFile; productRef = B2CA03D62B9CC15700AE3DFA /* FullDiskAccess */; };
6262
B2EC0F4126450D0B00E5473C /* DSYMTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2EC0F4026450D0B00E5473C /* DSYMTests.swift */; };
6363
B2F6E9AF2B9E962500F2AEE2 /* FullDiskAccess in Frameworks */ = {isa = PBXBuildFile; productRef = B2F6E9AE2B9E962500F2AEE2 /* FullDiskAccess */; };
64+
B518E9CF2EA6D21900EB1AB2 /* SymbolStoreSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = B518E9CE2EA6D20B00EB1AB2 /* SymbolStoreSearch.swift */; };
6465
/* End PBXBuildFile section */
6566

6667
/* Begin PBXContainerItemProxy section */
@@ -148,6 +149,7 @@
148149
B2C2292C1EF223C50015AB33 /* MacSymbolicator.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MacSymbolicator.entitlements; sourceTree = "<group>"; };
149150
B2EC0F4026450D0B00E5473C /* DSYMTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DSYMTests.swift; sourceTree = "<group>"; };
150151
B2F64E012B9339F700D1410D /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = "<group>"; };
152+
B518E9CE2EA6D20B00EB1AB2 /* SymbolStoreSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymbolStoreSearch.swift; sourceTree = "<group>"; };
151153
/* End PBXFileReference section */
152154

153155
/* Begin PBXFrameworksBuildPhase section */
@@ -228,6 +230,7 @@
228230
B289E0A023F5F72A0019C28B /* DSYM Search */ = {
229231
isa = PBXGroup;
230232
children = (
233+
B518E9CE2EA6D20B00EB1AB2 /* SymbolStoreSearch.swift */,
231234
B289E0A123F5F7350019C28B /* DSYMSearch.swift */,
232235
B289E0A323F5F77C0019C28B /* SpotlightSearch.swift */,
233236
B21CA7E71EFCB61500AD9B75 /* FileSearch.swift */,
@@ -529,6 +532,7 @@
529532
B2B74D8F1EF8E9CC000BBFD6 /* StringExtensions.swift in Sources */,
530533
B2B74D8D1EF8E925000BBFD6 /* DSYMFile.swift in Sources */,
531534
B2B74D8B1EF8BF70000BBFD6 /* MainController.swift in Sources */,
535+
B518E9CF2EA6D21900EB1AB2 /* SymbolStoreSearch.swift in Sources */,
532536
B295FFB3232DDCE8002791BF /* TextWindowController.swift in Sources */,
533537
B23646872618178400AB9486 /* BinaryImage.swift in Sources */,
534538
B24BB98628AFB42600610CF0 /* ReportProcess.swift in Sources */,

MacSymbolicator/DSYM Search/DSYMSearch.swift

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,29 @@ class DSYMSearch {
107107
processingResult = processSearchResults(
108108
recursiveFileSearchResults,
109109
expectedUUIDs: expectedUUIDs,
110-
finished: true,
110+
finished: false,
111111
logHandler: logMessage,
112112
callback: callback
113113
)
114114
missingUUIDs = processingResult.missingUUIDs
115-
logMessage("Missing UUIDs: \(missingUUIDs)")
115+
116+
// Try downloading them using the user's symbol search
117+
guard !missingUUIDs.isEmpty else { return }
118+
119+
SymbolStoreSearch().search(forUUIDs: missingUUIDs, logHandler: logMessage) { results, finished in
120+
if let results {
121+
processingResult = processSearchResults(
122+
results,
123+
expectedUUIDs: expectedUUIDs,
124+
finished: finished,
125+
logHandler: logMessage,
126+
callback: callback
127+
)
128+
} else {
129+
logMessage("Symbol server query failure.")
130+
processingResult = ProcessingResult(missingUUIDs: expectedUUIDs)
131+
}
132+
}
116133
}
117134
}
118135
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
//
2+
// SymbolStoreSearch.swift
3+
// MacSymbolicator
4+
//
5+
6+
import Foundation
7+
8+
class SymbolStoreSearch {
9+
10+
typealias CompletionHandler = ([SearchResult]?, Bool) -> Void
11+
12+
private static let guidRegex = #"(....)(....)-(....)-(....)-(....)-(............)"#
13+
14+
func search(
15+
forUUIDs uuids: Set<String>,
16+
logHandler logMessage: @escaping LogHandler,
17+
completion: @escaping CompletionHandler
18+
) {
19+
DispatchQueue.global().async {
20+
let missingUUIDs = self.mappedPathsSearch(forUUIDs: uuids, logHandler: logMessage, completion: completion)
21+
self.shellCommandSearch(forUUIDs: missingUUIDs, logHandler: logMessage, completion: completion)
22+
}
23+
}
24+
25+
private static func mappedPathList(suiteName: String?) -> [String] {
26+
guard let defaults = UserDefaults(suiteName: suiteName) else { return [] }
27+
28+
// This can be either a string or array of strings
29+
guard let mappedPaths = defaults.stringArray(forKey: "DBGFileMappedPaths") else {
30+
guard let mappedPathString = defaults.string(forKey: "DBGFileMappedPaths") else { return [] }
31+
return [mappedPathString]
32+
}
33+
34+
return mappedPaths
35+
}
36+
37+
private static func shellCommandList(suiteName: String?) -> [String] {
38+
guard let defaults = UserDefaults(suiteName: suiteName) else { return [] }
39+
40+
// This can be either a string or array of strings
41+
guard let mappedPaths = defaults.stringArray(forKey: "DBGShellCommands") else {
42+
guard let mappedPathString = defaults.string(forKey: "DBGShellCommands") else { return [] }
43+
return [mappedPathString]
44+
}
45+
46+
return mappedPaths
47+
}
48+
49+
private static func pathUUIDs(path: String) -> [String]? {
50+
let command = "dwarfdump --uuid \"\(path)\""
51+
let commandResult = command.run()
52+
53+
if let errorOutput = commandResult.error?.trimmed, !errorOutput.isEmpty {
54+
// dwarfdump --uuid on /Users/x/Library/Developer/Xcode/Archives seems to output the dsym identifier
55+
// correctly followed by an stderr message about not being able to open macho file due to
56+
// "Too many levels of symbolic links". Seems safe to ignore.
57+
if !errorOutput.contains("Too many levels of symbolic links") {
58+
return nil
59+
}
60+
}
61+
62+
guard let dwarfDumpOutput = commandResult.output?.trimmed else { return nil }
63+
64+
let foundUUIDs = dwarfDumpOutput.scan(pattern: #"UUID: (.*) \("#).flatMap({ $0 })
65+
return foundUUIDs
66+
}
67+
68+
private func mappedPathsSearch(
69+
forUUIDs uuids: Set<String>,
70+
logHandler logMessage: @escaping LogHandler,
71+
completion: @escaping CompletionHandler
72+
) -> Set<String> {
73+
let mappedPaths = SymbolStoreSearch.mappedPathList(suiteName: "com.apple.DebugSymbols") + SymbolStoreSearch.mappedPathList(suiteName: nil)
74+
75+
var results: [SearchResult] = []
76+
77+
for uuid in uuids {
78+
// Need to convert UUID from AAAABBBB-CCCC-DDDD-EEEE-FFFFFFFFFFFF
79+
// into AAAA/BBBB/CCCC/DDDD/EEEE/FFFFFFFFFFFF
80+
let subPath = uuid.scan(pattern: SymbolStoreSearch.guidRegex)[0]
81+
82+
for mappedPath in mappedPaths {
83+
// There's gotta be a better way to do this
84+
var path = URL(fileURLWithPath: mappedPath)
85+
for sub in subPath {
86+
path.appendPathComponent(sub)
87+
}
88+
89+
// If the file exists and is a dwarf file matching, accept it
90+
if FileManager().fileExists(atPath: path.path) {
91+
if let pathUUIDs = SymbolStoreSearch.pathUUIDs(path: path.path) {
92+
if pathUUIDs.contains(uuid) {
93+
results.append(SearchResult(path: path.path, matchedUUID: uuid))
94+
}
95+
}
96+
}
97+
}
98+
}
99+
100+
completion(results, false)
101+
102+
return uuids.subtracting(results.map({ result in result.matchedUUID }))
103+
}
104+
105+
private func shellCommandSearch(
106+
forUUIDs uuids: Set<String>,
107+
logHandler logMessage: @escaping LogHandler,
108+
completion: @escaping CompletionHandler
109+
) {
110+
// Search through mapped paths
111+
let shellCommands = SymbolStoreSearch.shellCommandList(suiteName: "com.apple.DebugSymbols") + SymbolStoreSearch.shellCommandList(suiteName: nil)
112+
113+
var results: [SearchResult] = []
114+
115+
// TODO: parallelize
116+
for uuid in uuids {
117+
for command in shellCommands {
118+
// $(command uuid) returns us an XML document with the path to the dYSM or with an error
119+
/*
120+
<?xml version="1.0" encoding="UTF-8"?>
121+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
122+
<plist version="1.0">
123+
<dict>
124+
<key>4E793E15-4672-3387-9EA0-C1701F5C59CA</key>
125+
<dict>
126+
<key>DBGDSYMPath</key>
127+
<string>/Users/x/Library/SymbolCache/dsyms/4E79/3E15/4672/3387/9EA0/C1701F5C59CA</string>
128+
</dict>
129+
</dict>
130+
</plist>
131+
*/
132+
133+
// Try to parse output
134+
logMessage("Run script: \(command) \(uuid)")
135+
guard let scriptOutput = "\(command) \(uuid)".run().output else { continue }
136+
137+
logMessage("==> \(scriptOutput)")
138+
139+
guard let data = scriptOutput.data(using: .utf8) else { continue }
140+
guard let plist = try? PropertyListSerialization.propertyList(from: data, format: nil) as? [String: Any] else { continue }
141+
142+
// If the plist has our guid as a key then see what results it has
143+
guard let value = plist[uuid] as? [String: Any] else { continue }
144+
guard let dsymPath = value["DBGDSYMPath"] as? String else { continue }
145+
146+
// If the file exists and is a dwarf file matching, accept it
147+
if FileManager().fileExists(atPath: dsymPath) {
148+
if let pathUUIDs = SymbolStoreSearch.pathUUIDs(path: dsymPath) {
149+
if pathUUIDs.contains(uuid) {
150+
results.append(SearchResult(path: dsymPath, matchedUUID: uuid))
151+
// Run the completion handler after each search so we can see results as they arrive
152+
completion(results, false)
153+
}
154+
}
155+
}
156+
}
157+
}
158+
completion(results, true)
159+
}
160+
}

0 commit comments

Comments
 (0)