Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions MacSymbolicator.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
B2CA03D72B9CC15700AE3DFA /* FullDiskAccess in Frameworks */ = {isa = PBXBuildFile; productRef = B2CA03D62B9CC15700AE3DFA /* FullDiskAccess */; };
B2EC0F4126450D0B00E5473C /* DSYMTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2EC0F4026450D0B00E5473C /* DSYMTests.swift */; };
B2F6E9AF2B9E962500F2AEE2 /* FullDiskAccess in Frameworks */ = {isa = PBXBuildFile; productRef = B2F6E9AE2B9E962500F2AEE2 /* FullDiskAccess */; };
B518E9CF2EA6D21900EB1AB2 /* SymbolStoreSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = B518E9CE2EA6D20B00EB1AB2 /* SymbolStoreSearch.swift */; };
/* End PBXBuildFile section */

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

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -228,6 +230,7 @@
B289E0A023F5F72A0019C28B /* DSYM Search */ = {
isa = PBXGroup;
children = (
B518E9CE2EA6D20B00EB1AB2 /* SymbolStoreSearch.swift */,
B289E0A123F5F7350019C28B /* DSYMSearch.swift */,
B289E0A323F5F77C0019C28B /* SpotlightSearch.swift */,
B21CA7E71EFCB61500AD9B75 /* FileSearch.swift */,
Expand Down Expand Up @@ -529,6 +532,7 @@
B2B74D8F1EF8E9CC000BBFD6 /* StringExtensions.swift in Sources */,
B2B74D8D1EF8E925000BBFD6 /* DSYMFile.swift in Sources */,
B2B74D8B1EF8BF70000BBFD6 /* MainController.swift in Sources */,
B518E9CF2EA6D21900EB1AB2 /* SymbolStoreSearch.swift in Sources */,
B295FFB3232DDCE8002791BF /* TextWindowController.swift in Sources */,
B23646872618178400AB9486 /* BinaryImage.swift in Sources */,
B24BB98628AFB42600610CF0 /* ReportProcess.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion MacSymbolicator/Controllers/InputCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class InputCoordinator {
DispatchQueue.main.async {
results?.forEach { dsymResult in
let dsymURL = URL(fileURLWithPath: dsymResult.path)
self?.dsymFilesDropZone.acceptFile(url: dsymURL)
self?.dsymFilesDropZone.acceptFile(url: dsymURL, validate: false)
}

if finished {
Expand Down
21 changes: 19 additions & 2 deletions MacSymbolicator/DSYM Search/DSYMSearch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,29 @@ class DSYMSearch {
processingResult = processSearchResults(
recursiveFileSearchResults,
expectedUUIDs: expectedUUIDs,
finished: true,
finished: false,
logHandler: logMessage,
callback: callback
)
missingUUIDs = processingResult.missingUUIDs
logMessage("Missing UUIDs: \(missingUUIDs)")

// Try downloading them using the user's symbol search
guard !missingUUIDs.isEmpty else { return }

SymbolStoreSearch().search(forUUIDs: missingUUIDs, logHandler: logMessage) { results, finished in
if let results {
processingResult = processSearchResults(
results,
expectedUUIDs: expectedUUIDs,
finished: finished,
logHandler: logMessage,
callback: callback
)
} else {
logMessage("Symbol server query failure.")
processingResult = ProcessingResult(missingUUIDs: expectedUUIDs)
}
}
}
}
}
Expand Down
160 changes: 160 additions & 0 deletions MacSymbolicator/DSYM Search/SymbolStoreSearch.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
//
// SymbolStoreSearch.swift
// MacSymbolicator
//

import Foundation

class SymbolStoreSearch {

typealias CompletionHandler = ([SearchResult]?, Bool) -> Void

private static let guidRegex = #"(....)(....)-(....)-(....)-(....)-(............)"#

func search(
forUUIDs uuids: Set<String>,
logHandler logMessage: @escaping LogHandler,
completion: @escaping CompletionHandler
) {
DispatchQueue.global().async {
let missingUUIDs = self.mappedPathsSearch(forUUIDs: uuids, logHandler: logMessage, completion: completion)
self.shellCommandSearch(forUUIDs: missingUUIDs, logHandler: logMessage, completion: completion)
}
}

private static func mappedPathList(suiteName: String?) -> [String] {
guard let defaults = UserDefaults(suiteName: suiteName) else { return [] }

// This can be either a string or array of strings
guard let mappedPaths = defaults.stringArray(forKey: "DBGFileMappedPaths") else {
guard let mappedPathString = defaults.string(forKey: "DBGFileMappedPaths") else { return [] }
return [mappedPathString]
}

return mappedPaths
}

private static func shellCommandList(suiteName: String?) -> [String] {
guard let defaults = UserDefaults(suiteName: suiteName) else { return [] }

// This can be either a string or array of strings
guard let mappedPaths = defaults.stringArray(forKey: "DBGShellCommands") else {
guard let mappedPathString = defaults.string(forKey: "DBGShellCommands") else { return [] }
return [mappedPathString]
}

return mappedPaths
}

private static func pathUUIDs(path: String) -> [String]? {
let command = "dwarfdump --uuid \"\(path)\""
let commandResult = command.run()

if let errorOutput = commandResult.error?.trimmed, !errorOutput.isEmpty {
// dwarfdump --uuid on /Users/x/Library/Developer/Xcode/Archives seems to output the dsym identifier
// correctly followed by an stderr message about not being able to open macho file due to
// "Too many levels of symbolic links". Seems safe to ignore.
if !errorOutput.contains("Too many levels of symbolic links") {
return nil
}
}

guard let dwarfDumpOutput = commandResult.output?.trimmed else { return nil }

let foundUUIDs = dwarfDumpOutput.scan(pattern: #"UUID: (.*) \("#).flatMap({ $0 })
return foundUUIDs
}

private func mappedPathsSearch(
forUUIDs uuids: Set<String>,
logHandler logMessage: @escaping LogHandler,
completion: @escaping CompletionHandler
) -> Set<String> {
let mappedPaths = SymbolStoreSearch.mappedPathList(suiteName: "com.apple.DebugSymbols") + SymbolStoreSearch.mappedPathList(suiteName: nil)

var results: [SearchResult] = []

for uuid in uuids {
// Need to convert UUID from AAAABBBB-CCCC-DDDD-EEEE-FFFFFFFFFFFF
// into AAAA/BBBB/CCCC/DDDD/EEEE/FFFFFFFFFFFF
let subPath = uuid.scan(pattern: SymbolStoreSearch.guidRegex)[0]

for mappedPath in mappedPaths {
// There's gotta be a better way to do this
var path = URL(fileURLWithPath: mappedPath)
for sub in subPath {
path.appendPathComponent(sub)
}

// If the file exists and is a dwarf file matching, accept it
if FileManager().fileExists(atPath: path.path) {
if let pathUUIDs = SymbolStoreSearch.pathUUIDs(path: path.path) {
if pathUUIDs.contains(uuid) {
results.append(SearchResult(path: path.path, matchedUUID: uuid))
}
}
}
}
}

completion(results, false)

return uuids.subtracting(results.map({ result in result.matchedUUID }))
}

private func shellCommandSearch(
forUUIDs uuids: Set<String>,
logHandler logMessage: @escaping LogHandler,
completion: @escaping CompletionHandler
) {
// Search through mapped paths
let shellCommands = SymbolStoreSearch.shellCommandList(suiteName: "com.apple.DebugSymbols") + SymbolStoreSearch.shellCommandList(suiteName: nil)

var results: [SearchResult] = []

// TODO: parallelize
for uuid in uuids {
for command in shellCommands {
// $(command uuid) returns us an XML document with the path to the dYSM or with an error
/*
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>4E793E15-4672-3387-9EA0-C1701F5C59CA</key>
<dict>
<key>DBGDSYMPath</key>
<string>/Users/x/Library/SymbolCache/dsyms/4E79/3E15/4672/3387/9EA0/C1701F5C59CA</string>
</dict>
</dict>
</plist>
*/

// Try to parse output
logMessage("Run script: \(command) \(uuid)")
guard let scriptOutput = "\(command) \(uuid)".run().output else { continue }

logMessage("==> \(scriptOutput)")

guard let data = scriptOutput.data(using: .utf8) else { continue }
guard let plist = try? PropertyListSerialization.propertyList(from: data, format: nil) as? [String: Any] else { continue }

// If the plist has our guid as a key then see what results it has
guard let value = plist[uuid] as? [String: Any] else { continue }
guard let dsymPath = value["DBGDSYMPath"] as? String else { continue }

// If the file exists and is a dwarf file matching, accept it
if FileManager().fileExists(atPath: dsymPath) {
if let pathUUIDs = SymbolStoreSearch.pathUUIDs(path: dsymPath) {
if pathUUIDs.contains(uuid) {
results.append(SearchResult(path: dsymPath, matchedUUID: uuid))
// Run the completion handler after each search so we can see results as they arrive
completion(results, false)
}
}
}
}
}
completion(results, true)
}
}
6 changes: 4 additions & 2 deletions MacSymbolicator/Views/DropZone.swift
Original file line number Diff line number Diff line change
Expand Up @@ -402,8 +402,10 @@ class DropZone: NSView {
}

@discardableResult
func acceptFile(url fileURL: URL) -> Bool {
guard validFileURL(fileURL) else { return false }
func acceptFile(url fileURL: URL, validate: Bool = true) -> Bool {
if validate {
guard validFileURL(fileURL) else { return false }
}

let acceptedFileURLs = delegate?.receivedFiles(dropZone: self, fileURLs: [fileURL]) ?? [fileURL]

Expand Down