├── images └── crashreports.gif ├── CrashReports.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── project.pbxproj ├── README.md ├── LICENSE ├── CrashReports ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── AppDelegate.swift ├── Document.swift └── Base.lproj │ ├── Document.xib │ └── MainMenu.xib ├── .gitignore └── symbolicatecrash.pl /images/crashreports.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nst/CrashReports/master/images/crashreports.gif -------------------------------------------------------------------------------- /CrashReports.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CrashReports 2 | Symbolicate iOS Crash Reports 3 | 4 | Download [CrashReports 0.1](http://seriot.ch/crashreports/CrashReports_0_1.app.zip) 5 | 6 | ![CrashReports](images/crashreports.gif "Symbolicate iOS Crash Reports") 7 | 8 | CrashReports is a Cocoa wrapper over Apple's `symbolicatecrash.pl`. 9 | 10 | It can convert addresses into symbols in iOS crash reports, such as the ones generated by [PLCrashReporter](https://www.plcrashreporter.org/). 11 | 12 | CrashReports assumes that the `.xcarchive` is indexed by Spotlight. 13 | 14 | Also, `otool`, `atos`, `lipo` and `size` should be in their default location. 15 | 16 | If not, edit `symbolicatecrash.pl`: 17 | 18 | $otool = "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool"; 19 | $atos = "/Applications/Xcode.app/Contents/Developer/usr/bin/atos"; 20 | $lipo = "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/lipo"; 21 | $size = "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/size"; 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Nicolas Seriot 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CrashReports/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/screenshots 64 | -------------------------------------------------------------------------------- /CrashReports/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDocumentTypes 8 | 9 | 10 | CFBundleTypeExtensions 11 | 12 | crash 13 | 14 | CFBundleTypeIconFile 15 | 16 | CFBundleTypeName 17 | DocumentType 18 | CFBundleTypeOSTypes 19 | 20 | ???? 21 | 22 | CFBundleTypeRole 23 | Viewer 24 | NSDocumentClass 25 | $(PRODUCT_MODULE_NAME).Document 26 | 27 | 28 | CFBundleExecutable 29 | $(EXECUTABLE_NAME) 30 | CFBundleIconFile 31 | 32 | CFBundleIdentifier 33 | $(PRODUCT_BUNDLE_IDENTIFIER) 34 | CFBundleInfoDictionaryVersion 35 | 6.0 36 | CFBundleName 37 | $(PRODUCT_NAME) 38 | CFBundlePackageType 39 | APPL 40 | CFBundleShortVersionString 41 | 0.1 42 | CFBundleSignature 43 | ???? 44 | CFBundleVersion 45 | 1 46 | LSMinimumSystemVersion 47 | $(MACOSX_DEPLOYMENT_TARGET) 48 | NSHumanReadableCopyright 49 | Copyright © 2015 seriot.ch. All rights reserved. 50 | NSMainNibFile 51 | MainMenu 52 | NSPrincipalClass 53 | NSApplication 54 | NSAppTransportSecurity 55 | 56 | NSExceptionDomains 57 | 58 | seriot.ch 59 | 60 | NSIncludesSubdomains 61 | 62 | NSTemporaryExceptionAllowsInsecureHTTPLoads 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /CrashReports/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CrashReports 4 | // 5 | // Created by Nicolas Seriot on 18/11/15. 6 | // Copyright © 2015 seriot.ch. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | fileprivate func < (lhs: T?, rhs: T?) -> Bool { 11 | switch (lhs, rhs) { 12 | case let (l?, r?): 13 | return l < r 14 | case (nil, _?): 15 | return true 16 | default: 17 | return false 18 | } 19 | } 20 | 21 | 22 | @NSApplicationMain 23 | class AppDelegate: NSObject, NSApplicationDelegate { 24 | 25 | func applicationDidFinishLaunching(_ aNotification: Notification) { 26 | // Insert code here to initialize your application 27 | 28 | checkForUpdates() 29 | } 30 | 31 | func applicationWillTerminate(_ aNotification: Notification) { 32 | // Insert code here to tear down your application 33 | } 34 | 35 | func checkForUpdates() { 36 | 37 | let url = URL(string:"http://www.seriot.ch/crashreports/crashreports.json") 38 | 39 | URLSession.shared.dataTask(with: url!, completionHandler: { (optionalData, response, error) -> Void in 40 | 41 | DispatchQueue.main.async(execute: { 42 | 43 | guard let data = optionalData, 44 | let optionalDict = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String:AnyObject], 45 | let d = optionalDict, 46 | let latestVersionString = d["latest_version_string"] as? String, 47 | let latestVersionURL = d["latest_version_url"] as? String 48 | else { 49 | return 50 | } 51 | 52 | print("-- latestVersionString: \(latestVersionString)") 53 | print("-- latestVersionURL: \(latestVersionURL)") 54 | 55 | let currentVersionString = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String 56 | 57 | let needsUpdate = currentVersionString < latestVersionString 58 | 59 | print("-- needsUpdate: \(needsUpdate)") 60 | if needsUpdate == false { return } 61 | 62 | let alert = NSAlert() 63 | alert.messageText = "CrashReports \(latestVersionString) is Available" 64 | alert.informativeText = "Please download it and replace the current version."; 65 | alert.addButton(withTitle: "Download") 66 | alert.addButton(withTitle: "Cancel") 67 | alert.alertStyle = .critical 68 | 69 | let modalResponse = alert.runModal() 70 | 71 | if modalResponse == NSAlertFirstButtonReturn { 72 | if let downloadURL = URL(string:latestVersionURL) { 73 | NSWorkspace.shared().open(downloadURL) 74 | } 75 | } 76 | 77 | }) 78 | }) .resume() 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /CrashReports/Document.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Document.swift 3 | // CrashReports 4 | // 5 | // Created by Nicolas Seriot on 18/11/15. 6 | // Copyright © 2015 seriot.ch. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class Document: NSDocument { 12 | 13 | @IBOutlet var textView: NSTextView! 14 | var crashReportPath: String? 15 | var crashReportInterpreted: String? 16 | var font: NSFont 17 | 18 | override init() { 19 | self.font = NSFont.userFixedPitchFont(ofSize: 11)! 20 | super.init() 21 | } 22 | 23 | override func windowControllerDidLoadNib(_ aController: NSWindowController) { 24 | super.windowControllerDidLoadNib(aController) 25 | // Add any code here that needs to be executed once the windowController has loaded the document's window. 26 | 27 | guard let scriptPath = Bundle.main.path(forResource: "symbolicatecrash", ofType: "pl") else { return } 28 | guard let crashReportPath = self.crashReportPath else { return } 29 | 30 | self.textView.isSelectable = true 31 | self.textView.isEditable = false 32 | 33 | self.textView.font = self.font 34 | self.textView.textColor = NSColor.disabledControlTextColor 35 | 36 | do { 37 | self.textView.string = try NSString(contentsOfFile:crashReportPath, encoding:String.Encoding.utf8.rawValue) as String 38 | } catch let error as NSError { 39 | let alert = NSAlert(error:error) 40 | alert.runModal() 41 | return 42 | } 43 | 44 | var taskHasReceivedData = false 45 | 46 | let task = Process() 47 | task.launchPath = "/usr/bin/perl" 48 | task.arguments = [scriptPath, crashReportPath] 49 | task.standardOutput = Pipe() 50 | // task.standardError = task.standardOutput 51 | 52 | let readabilityHandler: (FileHandle!) -> Void = { file in 53 | 54 | let data = file.availableData 55 | var mas = NSMutableAttributedString(string: "") 56 | 57 | if let s = NSString(data: data, encoding: String.Encoding.utf8.rawValue) { 58 | mas = NSMutableAttributedString(string: s as String) 59 | } 60 | 61 | mas.addAttribute(NSFontAttributeName, value: self.font, range: NSMakeRange(0, mas.length)) 62 | 63 | DispatchQueue.main.async { [unowned self] in 64 | if(taskHasReceivedData == false) { 65 | self.textView.string = "" 66 | taskHasReceivedData = true 67 | } 68 | 69 | self.textView.textColor = NSColor.controlTextColor 70 | self.textView.textStorage?.append(mas) 71 | } 72 | } 73 | 74 | let terminationHandler: (Process!) -> Void = { task in 75 | 76 | DispatchQueue.main.async { 77 | guard let output = task.standardOutput as? Pipe else { return } 78 | output.fileHandleForReading.readabilityHandler = nil 79 | Swift.print("-- task terminated") 80 | } 81 | } 82 | 83 | guard let output = task.standardOutput as? Pipe else { return } 84 | output.fileHandleForReading.readabilityHandler = readabilityHandler 85 | task.terminationHandler = terminationHandler 86 | 87 | task.launch() 88 | } 89 | 90 | override class func autosavesInPlace() -> Bool { 91 | return true 92 | } 93 | 94 | override var windowNibName: String? { 95 | // Returns the nib file name of the document 96 | // If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this property and override -makeWindowControllers instead. 97 | return "Document" 98 | } 99 | 100 | override func read(from url: URL, ofType typeName: String) throws { 101 | self.crashReportPath = url.path 102 | } 103 | } 104 | 105 | -------------------------------------------------------------------------------- /CrashReports/Base.lproj/Document.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /CrashReports.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 030E479B1BFCADA5000E7355 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030E479A1BFCADA5000E7355 /* AppDelegate.swift */; }; 11 | 030E479D1BFCADA5000E7355 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030E479C1BFCADA5000E7355 /* Document.swift */; }; 12 | 030E47A01BFCADA5000E7355 /* Document.xib in Resources */ = {isa = PBXBuildFile; fileRef = 030E479E1BFCADA5000E7355 /* Document.xib */; }; 13 | 030E47A21BFCADA5000E7355 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 030E47A11BFCADA5000E7355 /* Assets.xcassets */; }; 14 | 030E47A51BFCADA5000E7355 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 030E47A31BFCADA5000E7355 /* MainMenu.xib */; }; 15 | 030E47AD1BFCADB2000E7355 /* symbolicatecrash.pl in Resources */ = {isa = PBXBuildFile; fileRef = 030E47AC1BFCADB2000E7355 /* symbolicatecrash.pl */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 030E47971BFCADA5000E7355 /* CrashReports.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CrashReports.app; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | 030E479A1BFCADA5000E7355 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 21 | 030E479C1BFCADA5000E7355 /* Document.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Document.swift; sourceTree = ""; }; 22 | 030E479F1BFCADA5000E7355 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/Document.xib; sourceTree = ""; }; 23 | 030E47A11BFCADA5000E7355 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 24 | 030E47A41BFCADA5000E7355 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 25 | 030E47A61BFCADA5000E7355 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 26 | 030E47AC1BFCADB2000E7355 /* symbolicatecrash.pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.perl; path = symbolicatecrash.pl; sourceTree = ""; }; 27 | /* End PBXFileReference section */ 28 | 29 | /* Begin PBXFrameworksBuildPhase section */ 30 | 030E47941BFCADA5000E7355 /* Frameworks */ = { 31 | isa = PBXFrameworksBuildPhase; 32 | buildActionMask = 2147483647; 33 | files = ( 34 | ); 35 | runOnlyForDeploymentPostprocessing = 0; 36 | }; 37 | /* End PBXFrameworksBuildPhase section */ 38 | 39 | /* Begin PBXGroup section */ 40 | 030E478E1BFCADA5000E7355 = { 41 | isa = PBXGroup; 42 | children = ( 43 | 030E47AC1BFCADB2000E7355 /* symbolicatecrash.pl */, 44 | 030E47991BFCADA5000E7355 /* CrashReports */, 45 | 030E47981BFCADA5000E7355 /* Products */, 46 | ); 47 | sourceTree = ""; 48 | }; 49 | 030E47981BFCADA5000E7355 /* Products */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | 030E47971BFCADA5000E7355 /* CrashReports.app */, 53 | ); 54 | name = Products; 55 | sourceTree = ""; 56 | }; 57 | 030E47991BFCADA5000E7355 /* CrashReports */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | 030E479A1BFCADA5000E7355 /* AppDelegate.swift */, 61 | 030E479C1BFCADA5000E7355 /* Document.swift */, 62 | 030E479E1BFCADA5000E7355 /* Document.xib */, 63 | 030E47A11BFCADA5000E7355 /* Assets.xcassets */, 64 | 030E47A31BFCADA5000E7355 /* MainMenu.xib */, 65 | 030E47A61BFCADA5000E7355 /* Info.plist */, 66 | ); 67 | path = CrashReports; 68 | sourceTree = ""; 69 | }; 70 | /* End PBXGroup section */ 71 | 72 | /* Begin PBXNativeTarget section */ 73 | 030E47961BFCADA5000E7355 /* CrashReports */ = { 74 | isa = PBXNativeTarget; 75 | buildConfigurationList = 030E47A91BFCADA5000E7355 /* Build configuration list for PBXNativeTarget "CrashReports" */; 76 | buildPhases = ( 77 | 030E47931BFCADA5000E7355 /* Sources */, 78 | 030E47941BFCADA5000E7355 /* Frameworks */, 79 | 030E47951BFCADA5000E7355 /* Resources */, 80 | ); 81 | buildRules = ( 82 | ); 83 | dependencies = ( 84 | ); 85 | name = CrashReports; 86 | productName = CrashReports; 87 | productReference = 030E47971BFCADA5000E7355 /* CrashReports.app */; 88 | productType = "com.apple.product-type.application"; 89 | }; 90 | /* End PBXNativeTarget section */ 91 | 92 | /* Begin PBXProject section */ 93 | 030E478F1BFCADA5000E7355 /* Project object */ = { 94 | isa = PBXProject; 95 | attributes = { 96 | LastSwiftUpdateCheck = 0710; 97 | LastUpgradeCheck = 0710; 98 | ORGANIZATIONNAME = seriot.ch; 99 | TargetAttributes = { 100 | 030E47961BFCADA5000E7355 = { 101 | CreatedOnToolsVersion = 7.1; 102 | LastSwiftMigration = 0800; 103 | }; 104 | }; 105 | }; 106 | buildConfigurationList = 030E47921BFCADA5000E7355 /* Build configuration list for PBXProject "CrashReports" */; 107 | compatibilityVersion = "Xcode 3.2"; 108 | developmentRegion = English; 109 | hasScannedForEncodings = 0; 110 | knownRegions = ( 111 | en, 112 | Base, 113 | ); 114 | mainGroup = 030E478E1BFCADA5000E7355; 115 | productRefGroup = 030E47981BFCADA5000E7355 /* Products */; 116 | projectDirPath = ""; 117 | projectRoot = ""; 118 | targets = ( 119 | 030E47961BFCADA5000E7355 /* CrashReports */, 120 | ); 121 | }; 122 | /* End PBXProject section */ 123 | 124 | /* Begin PBXResourcesBuildPhase section */ 125 | 030E47951BFCADA5000E7355 /* Resources */ = { 126 | isa = PBXResourcesBuildPhase; 127 | buildActionMask = 2147483647; 128 | files = ( 129 | 030E47A21BFCADA5000E7355 /* Assets.xcassets in Resources */, 130 | 030E47A01BFCADA5000E7355 /* Document.xib in Resources */, 131 | 030E47AD1BFCADB2000E7355 /* symbolicatecrash.pl in Resources */, 132 | 030E47A51BFCADA5000E7355 /* MainMenu.xib in Resources */, 133 | ); 134 | runOnlyForDeploymentPostprocessing = 0; 135 | }; 136 | /* End PBXResourcesBuildPhase section */ 137 | 138 | /* Begin PBXSourcesBuildPhase section */ 139 | 030E47931BFCADA5000E7355 /* Sources */ = { 140 | isa = PBXSourcesBuildPhase; 141 | buildActionMask = 2147483647; 142 | files = ( 143 | 030E479D1BFCADA5000E7355 /* Document.swift in Sources */, 144 | 030E479B1BFCADA5000E7355 /* AppDelegate.swift in Sources */, 145 | ); 146 | runOnlyForDeploymentPostprocessing = 0; 147 | }; 148 | /* End PBXSourcesBuildPhase section */ 149 | 150 | /* Begin PBXVariantGroup section */ 151 | 030E479E1BFCADA5000E7355 /* Document.xib */ = { 152 | isa = PBXVariantGroup; 153 | children = ( 154 | 030E479F1BFCADA5000E7355 /* Base */, 155 | ); 156 | name = Document.xib; 157 | sourceTree = ""; 158 | }; 159 | 030E47A31BFCADA5000E7355 /* MainMenu.xib */ = { 160 | isa = PBXVariantGroup; 161 | children = ( 162 | 030E47A41BFCADA5000E7355 /* Base */, 163 | ); 164 | name = MainMenu.xib; 165 | sourceTree = ""; 166 | }; 167 | /* End PBXVariantGroup section */ 168 | 169 | /* Begin XCBuildConfiguration section */ 170 | 030E47A71BFCADA5000E7355 /* Debug */ = { 171 | isa = XCBuildConfiguration; 172 | buildSettings = { 173 | ALWAYS_SEARCH_USER_PATHS = NO; 174 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 175 | CLANG_CXX_LIBRARY = "libc++"; 176 | CLANG_ENABLE_MODULES = YES; 177 | CLANG_ENABLE_OBJC_ARC = YES; 178 | CLANG_WARN_BOOL_CONVERSION = YES; 179 | CLANG_WARN_CONSTANT_CONVERSION = YES; 180 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 181 | CLANG_WARN_EMPTY_BODY = YES; 182 | CLANG_WARN_ENUM_CONVERSION = YES; 183 | CLANG_WARN_INT_CONVERSION = YES; 184 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 185 | CLANG_WARN_UNREACHABLE_CODE = YES; 186 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 187 | CODE_SIGN_IDENTITY = "-"; 188 | COPY_PHASE_STRIP = NO; 189 | DEBUG_INFORMATION_FORMAT = dwarf; 190 | ENABLE_STRICT_OBJC_MSGSEND = YES; 191 | ENABLE_TESTABILITY = YES; 192 | GCC_C_LANGUAGE_STANDARD = gnu99; 193 | GCC_DYNAMIC_NO_PIC = NO; 194 | GCC_NO_COMMON_BLOCKS = YES; 195 | GCC_OPTIMIZATION_LEVEL = 0; 196 | GCC_PREPROCESSOR_DEFINITIONS = ( 197 | "DEBUG=1", 198 | "$(inherited)", 199 | ); 200 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 201 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 202 | GCC_WARN_UNDECLARED_SELECTOR = YES; 203 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 204 | GCC_WARN_UNUSED_FUNCTION = YES; 205 | GCC_WARN_UNUSED_VARIABLE = YES; 206 | MACOSX_DEPLOYMENT_TARGET = 10.11; 207 | MTL_ENABLE_DEBUG_INFO = YES; 208 | ONLY_ACTIVE_ARCH = YES; 209 | SDKROOT = macosx; 210 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 211 | }; 212 | name = Debug; 213 | }; 214 | 030E47A81BFCADA5000E7355 /* Release */ = { 215 | isa = XCBuildConfiguration; 216 | buildSettings = { 217 | ALWAYS_SEARCH_USER_PATHS = NO; 218 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 219 | CLANG_CXX_LIBRARY = "libc++"; 220 | CLANG_ENABLE_MODULES = YES; 221 | CLANG_ENABLE_OBJC_ARC = YES; 222 | CLANG_WARN_BOOL_CONVERSION = YES; 223 | CLANG_WARN_CONSTANT_CONVERSION = YES; 224 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 225 | CLANG_WARN_EMPTY_BODY = YES; 226 | CLANG_WARN_ENUM_CONVERSION = YES; 227 | CLANG_WARN_INT_CONVERSION = YES; 228 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 229 | CLANG_WARN_UNREACHABLE_CODE = YES; 230 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 231 | CODE_SIGN_IDENTITY = "-"; 232 | COPY_PHASE_STRIP = NO; 233 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 234 | ENABLE_NS_ASSERTIONS = NO; 235 | ENABLE_STRICT_OBJC_MSGSEND = YES; 236 | GCC_C_LANGUAGE_STANDARD = gnu99; 237 | GCC_NO_COMMON_BLOCKS = YES; 238 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 239 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 240 | GCC_WARN_UNDECLARED_SELECTOR = YES; 241 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 242 | GCC_WARN_UNUSED_FUNCTION = YES; 243 | GCC_WARN_UNUSED_VARIABLE = YES; 244 | MACOSX_DEPLOYMENT_TARGET = 10.11; 245 | MTL_ENABLE_DEBUG_INFO = NO; 246 | SDKROOT = macosx; 247 | }; 248 | name = Release; 249 | }; 250 | 030E47AA1BFCADA5000E7355 /* Debug */ = { 251 | isa = XCBuildConfiguration; 252 | buildSettings = { 253 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 254 | COMBINE_HIDPI_IMAGES = YES; 255 | INFOPLIST_FILE = CrashReports/Info.plist; 256 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 257 | PRODUCT_BUNDLE_IDENTIFIER = ch.seriot.CrashReports; 258 | PRODUCT_NAME = "$(TARGET_NAME)"; 259 | SWIFT_VERSION = 3.0; 260 | }; 261 | name = Debug; 262 | }; 263 | 030E47AB1BFCADA5000E7355 /* Release */ = { 264 | isa = XCBuildConfiguration; 265 | buildSettings = { 266 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 267 | COMBINE_HIDPI_IMAGES = YES; 268 | INFOPLIST_FILE = CrashReports/Info.plist; 269 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 270 | PRODUCT_BUNDLE_IDENTIFIER = ch.seriot.CrashReports; 271 | PRODUCT_NAME = "$(TARGET_NAME)"; 272 | SWIFT_VERSION = 3.0; 273 | }; 274 | name = Release; 275 | }; 276 | /* End XCBuildConfiguration section */ 277 | 278 | /* Begin XCConfigurationList section */ 279 | 030E47921BFCADA5000E7355 /* Build configuration list for PBXProject "CrashReports" */ = { 280 | isa = XCConfigurationList; 281 | buildConfigurations = ( 282 | 030E47A71BFCADA5000E7355 /* Debug */, 283 | 030E47A81BFCADA5000E7355 /* Release */, 284 | ); 285 | defaultConfigurationIsVisible = 0; 286 | defaultConfigurationName = Release; 287 | }; 288 | 030E47A91BFCADA5000E7355 /* Build configuration list for PBXNativeTarget "CrashReports" */ = { 289 | isa = XCConfigurationList; 290 | buildConfigurations = ( 291 | 030E47AA1BFCADA5000E7355 /* Debug */, 292 | 030E47AB1BFCADA5000E7355 /* Release */, 293 | ); 294 | defaultConfigurationIsVisible = 0; 295 | defaultConfigurationName = Release; 296 | }; 297 | /* End XCConfigurationList section */ 298 | }; 299 | rootObject = 030E478F1BFCADA5000E7355 /* Project object */; 300 | } 301 | -------------------------------------------------------------------------------- /symbolicatecrash.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | # 3 | # This script parses a crashdump file and attempts to resolve addresses into function names. 4 | # 5 | # It finds symbol-rich binaries by: 6 | # a) searching in Spotlight to find .dSYM files by UUID, then finding the executable from there. 7 | # That finds the symbols for binaries that a developer has built with "DWARF with dSYM File". 8 | # b) searching in various SDK directories. 9 | # 10 | # Copyright (c) 2008-2011 Apple Inc. All Rights Reserved. 11 | # 12 | # 13 | 14 | use strict; 15 | use warnings; 16 | use Getopt::Std; 17 | use Cwd qw(realpath); 18 | use List::MoreUtils qw(uniq); 19 | use File::Basename qw(basename); 20 | use File::Glob ':glob'; 21 | use Env qw(DEVELOPER_DIR); 22 | use Config; 23 | no warnings "portable"; 24 | 25 | require bigint; 26 | if($Config{ivsize} < 8) { 27 | bigint->import(qw(hex)); 28 | } 29 | 30 | ############################# 31 | 32 | # Forward definitons 33 | sub usage(); 34 | 35 | ############################# 36 | 37 | # read and parse command line 38 | my %opt; 39 | $Getopt::Std::STANDARD_HELP_VERSION = 1; 40 | 41 | getopts('hvo:',\%opt); 42 | 43 | usage() if $opt{'h'}; 44 | 45 | ############################# 46 | 47 | # have this thing to de-HTMLize Leopard-era plists 48 | my %entity2char = ( 49 | # Some normal chars that have special meaning in SGML context 50 | amp => '&', # ampersand 51 | 'gt' => '>', # greater than 52 | 'lt' => '<', # less than 53 | quot => '"', # double quote 54 | apos => "'", # single quote 55 | ); 56 | 57 | ############################# 58 | 59 | if(!defined($DEVELOPER_DIR)) { 60 | # die "Error: \"DEVELOPER_DIR\" is not defined"; 61 | $DEVELOPER_DIR=`xcode-select -p`; 62 | chomp $DEVELOPER_DIR; 63 | 64 | } 65 | 66 | # We will find these tools once we can guess the right SDK 67 | my $otool = undef; 68 | my $atos = undef; 69 | my $lipo = undef; 70 | my $size = undef; 71 | 72 | 73 | ############################# 74 | # run the script 75 | 76 | symbolicate_log(@ARGV); 77 | 78 | exit 0; 79 | 80 | ############################# 81 | 82 | # begin subroutines 83 | 84 | sub HELP_MESSAGE() { 85 | usage(); 86 | } 87 | 88 | sub usage() { 89 | print STDERR <] LOGFILE [SYMBOL_PATH ...] 92 | 93 | Symbolicates a crashdump LOGFILE which may be "-" to refer to stdin. By default, 94 | all heuristics will be employed in an attempt to symbolicate all addresses. 95 | Additional symbol files can be found under specified directories. 96 | 97 | Options: 98 | 99 | -o If specified, the symbolicated log will be written to OUTPUT_FILE (defaults to stdout) 100 | -h Display this message 101 | -v Verbose 102 | EOF 103 | exit 1; 104 | } 105 | 106 | ############## 107 | 108 | sub getToolPath { 109 | my ($toolName, $sdkGuess) = @_; 110 | 111 | if (!defined($sdkGuess)) { 112 | $sdkGuess = "macosx"; 113 | } 114 | 115 | my $toolPath = `'$DEVELOPER_DIR/usr/bin/xcrun' -sdk $sdkGuess -find $toolName`; 116 | if (!defined($toolPath) || $? != 0) { 117 | if ($sdkGuess eq "macosx") { 118 | die "Error: can't find tool named '$toolName' in the $sdkGuess SDK or any fallback SDKs"; 119 | } elsif ($sdkGuess eq "iphoneos") { 120 | print STDERR "## Warning: can't find tool named '$toolName' in iOS SDK, falling back to searching the Mac OS X SDK\n"; 121 | return getToolPath($toolName, "macosx"); 122 | } else { 123 | print STDERR "## Warning: can't find tool named '$toolName' in the $sdkGuess SDK, falling back to searching the iOS SDK\n"; 124 | return getToolPath($toolName, "iphoneos"); 125 | } 126 | } 127 | 128 | chomp $toolPath; 129 | print STDERR "$toolName path is '$toolPath'\n" if $opt{v}; 130 | 131 | return $toolPath; 132 | } 133 | 134 | ############## 135 | 136 | sub getSymbolDirPaths { 137 | my ($hwModel, $osVersion, $osBuild) = @_; 138 | 139 | print STDERR "(\$hwModel, \$osVersion, \$osBuild) = ($hwModel, $osVersion, $osBuild)\n" if $opt{v}; 140 | 141 | my $versionPattern = "{$hwModel $osVersion ($osBuild),$osVersion ($osBuild),$osVersion,$osBuild}"; 142 | #my $versionPattern = '*'; 143 | print STDERR "\$versionPattern = $versionPattern\n" if $opt{v}; 144 | 145 | my @result = grep { -e && -d } bsd_glob('{/System,,~}/Library/Developer/Xcode/*DeviceSupport/'.$versionPattern.'/Symbols*', GLOB_BRACE | GLOB_TILDE); 146 | 147 | foreach my $foundPath (`mdfind "kMDItemCFBundleIdentifier == 'com.apple.dt.Xcode' || kMDItemCFBundleIdentifier == 'com.apple.Xcode'"`) { 148 | chomp $foundPath; 149 | my @pathResults = grep { -e && -d && !/Simulator/ } bsd_glob($foundPath.'/Contents/Developer/Platforms/*.platform/DeviceSupport/'.$versionPattern.'/Symbols*/'); 150 | push(@result, @pathResults); 151 | } 152 | 153 | print STDERR "Symbol directory paths: @result\n" if $opt{v}; 154 | return @result; 155 | } 156 | 157 | sub getSymbolPathFor_searchpaths { 158 | my ($bin,$path,$build,@extra_search_paths) = @_; 159 | my @result; 160 | for my $item (@extra_search_paths) 161 | { 162 | my $glob = "$item"."{$bin,*/$bin,$path}*"; 163 | #print STDERR "\nSearching pattern: [$glob]..." if $opt{v}; 164 | push(@result, grep { -e && (! -d) } bsd_glob ($glob, GLOB_BRACE)); 165 | } 166 | 167 | print STDERR "\nSearching [@result]..." if $opt{v}; 168 | return @result; 169 | } 170 | 171 | sub getSymbolPathFor_uuid{ 172 | my ($uuid, $uuidsPath) = @_; 173 | $uuid or return undef; 174 | $uuid =~ /(.{4})(.{4})(.{4})(.{4})(.{4})(.{4})(.{8})/; 175 | return Cwd::realpath("$uuidsPath/$1/$2/$3/$4/$5/$6/$7"); 176 | } 177 | 178 | # Look up a dsym file by UUID in Spotlight, then find the executable from the dsym. 179 | sub getSymbolPathFor_dsymUuid{ 180 | my ($uuid,$arch) = @_; 181 | $uuid or return undef; 182 | 183 | # Convert a uuid from the crash log, like "c42a118d722d2625f2357463535854fd", 184 | # to canonical format like "C42A118D-722D-2625-F235-7463535854FD". 185 | my $myuuid = uc($uuid); # uuid's in Spotlight database are all uppercase 186 | $myuuid =~ /(.{8})(.{4})(.{4})(.{4})(.{12})/; 187 | $myuuid = "$1-$2-$3-$4-$5"; 188 | 189 | # Do the search in Spotlight. 190 | my $cmd = "mdfind \"com_apple_xcode_dsym_uuids == $myuuid\""; 191 | print STDERR "Running $cmd\n" if $opt{v}; 192 | 193 | my @dsym_paths = (); 194 | my @archive_paths = (); 195 | 196 | foreach my $dsymdir (split(/\n/, `$cmd`)) { 197 | $cmd = "mdls -name com_apple_xcode_dsym_paths ".quotemeta($dsymdir); 198 | print STDERR "Running $cmd\n" if $opt{v}; 199 | 200 | my $com_apple_xcode_dsym_paths = `$cmd`; 201 | $com_apple_xcode_dsym_paths =~ s/^com_apple_xcode_dsym_paths\ \= \(\n//; 202 | $com_apple_xcode_dsym_paths =~ s/\n\)//; 203 | 204 | my @subpaths = split(/,\n/, $com_apple_xcode_dsym_paths); 205 | map(s/^[[:space:]]*\"//, @subpaths); 206 | map(s/\"[[:space:]]*$//, @subpaths); 207 | 208 | push(@dsym_paths, map($dsymdir."/".$_, @subpaths)); 209 | 210 | if($dsymdir =~ m/\.xcarchive$/) { 211 | push(@archive_paths, $dsymdir); 212 | } 213 | } 214 | 215 | @dsym_paths = uniq(@dsym_paths); 216 | 217 | my @exec_names = map(basename($_), @dsym_paths); 218 | @exec_names = uniq(@exec_names); 219 | 220 | print STDERR "\@dsym_paths = ( @dsym_paths )\n" if $opt{v}; 221 | print STDERR "\@exec_names = ( @exec_names )\n" if $opt{v}; 222 | 223 | my @app_bundles_next_to_dsyms; 224 | foreach my $dsymdir (@dsym_paths) { 225 | my ($dsympath) = $dsymdir =~ /(^.*)\.dSYM/i; 226 | push(@app_bundles_next_to_dsyms, $dsympath . '.app'); 227 | } 228 | 229 | my @exec_paths = (); 230 | foreach my $exec_name (@exec_names) { 231 | #We need to find all of the apps with the given name (both in- and outside of any archive) 232 | #First, use spotlight to find un-archived apps: 233 | my $cmd = "mdfind \"kMDItemContentType == com.apple.application-bundle && (kMDItemAlternateNames == '$exec_name.app' || kMDItemDisplayName == '$exec_name' || kMDItemDisplayName == '$exec_name.app')\""; 234 | print STDERR "Running $cmd\n" if $opt{v}; 235 | 236 | my @app_bundles = (@app_bundles_next_to_dsyms, split(/\n/, `$cmd`)); 237 | foreach my $app_bundle (@app_bundles) { 238 | if( -f "$app_bundle/$exec_name") { 239 | push(@exec_paths, "$app_bundle/$exec_name"); 240 | } 241 | } 242 | 243 | #Find any naked executables 244 | $cmd = "mdfind \"kMDItemContentType == public.unix-executable && kMDItemDisplayName == '$exec_name'\""; 245 | print STDERR "Running $cmd\n" if $opt{v}; 246 | 247 | foreach my $exec_file (split(/\n/, `$cmd`)) { 248 | if( -f "$exec_file") { 249 | push(@exec_paths, "$exec_file"); 250 | } 251 | } 252 | 253 | #Next, try to find paths within any archives 254 | foreach my $archive_path (@archive_paths) { 255 | my $cmd = "find \"$archive_path/Products\" -name \"$exec_name.app\""; 256 | print STDERR "Running $cmd\n" if $opt{v}; 257 | 258 | foreach my $app_bundle (split(/\n/, `$cmd`)) { 259 | if( -f "$app_bundle/$exec_name") { 260 | push(@exec_paths, "$app_bundle/$exec_name"); 261 | } 262 | } 263 | } 264 | } 265 | 266 | if ( @exec_paths >= 1 ) { 267 | foreach my $exec (@exec_paths) { 268 | if ( !matchesUUID($exec, $uuid, $arch) ) { 269 | print STDERR "UUID of executable is: $uuid\n" if $opt{v}; 270 | print STDERR "Executable name: $exec\n\n" if $opt{v}; 271 | print STDERR "UUID doesn't match dsym for executable $exec\n" if $opt{v}; 272 | } else { 273 | print STDERR "Found executable $exec\n" if $opt{v}; 274 | return $exec; 275 | } 276 | } 277 | } 278 | 279 | print STDERR "Did not find executable for dsym\n" if $opt{v}; 280 | return undef; 281 | } 282 | 283 | ######### 284 | 285 | sub matchesUUID { 286 | my ($path, $uuid, $arch) = @_; 287 | 288 | if ( ! -f $path ) { 289 | print STDERR "## $path doesn't exist " if $opt{v}; 290 | return 0; 291 | } 292 | 293 | my $cmd = "$lipo -info '$path'"; 294 | print STDERR "Running $cmd\n" if $opt{v}; 295 | 296 | my $lipo_result = `$cmd`; 297 | if( index($lipo_result, $arch) < 0) { 298 | print STDERR "## $path doesn't contain $arch slice\n" if $opt{v}; 299 | return 0; 300 | } 301 | 302 | $cmd = "$otool -arch $arch -l '$path'"; 303 | 304 | print STDERR "Running $cmd\n" if $opt{v}; 305 | 306 | my $TEST_uuid = `$cmd`; 307 | 308 | if ( $TEST_uuid =~ /uuid ((0x[0-9A-Fa-f]{2}\s+?){16})/ || $TEST_uuid =~ /uuid ([^\s]+)\s/ ) { 309 | my $test = $1; 310 | 311 | if ( $test =~ /^0x/ ) { 312 | # old style 0xnn 0xnn 0xnn ... on two lines 313 | $test = join("", split /\s*0x/, $test); 314 | 315 | $test =~ s/0x//g; ## remove 0x 316 | $test =~ s/\s//g; ## remove spaces 317 | } else { 318 | # new style XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX 319 | $test =~ s/-//g; ## remove - 320 | $test = lc($test); 321 | } 322 | 323 | if ( $test eq $uuid ) { 324 | ## See that it isn't stripped. Even fully stripped apps have one symbol, so ensure that there is more than one. 325 | my ($nlocalsym) = $TEST_uuid =~ /nlocalsym\s+([0-9A-Fa-f]+)/; 326 | my ($nextdefsym) = $TEST_uuid =~ /nextdefsym\s+([0-9A-Fa-f]+)/; 327 | my $totalsym = $nextdefsym + $nlocalsym; 328 | print STDERR "\nNumber of symbols in $path: $nextdefsym + $nlocalsym = $totalsym\n" if $opt{v}; 329 | return 1 if ( $totalsym > 1 ); 330 | 331 | print STDERR "## $path appears to be stripped, skipping.\n" if $opt{v}; 332 | } else { 333 | print STDERR "Given UUID $uuid for '$path' is really UUID $test\n" if $opt{v}; 334 | } 335 | } else { 336 | print STDERR "Can't understand the output from otool ($TEST_uuid -> $cmd)\n"; 337 | return 0; 338 | } 339 | 340 | return 0; 341 | } 342 | 343 | 344 | sub getSymbolPathFor { 345 | my ($path,$build,$uuid,$arch,@extra_search_paths) = @_; 346 | 347 | # derive a few more parameters... 348 | my $bin = ($path =~ /^.*?([^\/]+)$/)[0]; # basename 349 | 350 | # This setting can be tailored for a specific environment. If it's not present, oh well... 351 | my $uuidsPath = "/Volumes/Build/UUIDToSymbolMap"; 352 | if ( ! -d $uuidsPath ) { 353 | #print STDERR "No '$uuidsPath' path visible." if $opt{v}; 354 | } 355 | 356 | # First try the simplest route, looking for a UUID match. 357 | my $out_path; 358 | $out_path = getSymbolPathFor_uuid($uuid, $uuidsPath); 359 | undef $out_path if ( defined($out_path) && !length($out_path) ); 360 | 361 | print STDERR "--[$out_path] " if defined($out_path) and $opt{v}; 362 | print STDERR "--[undef] " if !defined($out_path) and $opt{v}; 363 | 364 | if ( !defined($out_path) || !matchesUUID($out_path, $uuid, $arch)) { 365 | undef $out_path; 366 | 367 | for my $func ( 368 | \&getSymbolPathFor_searchpaths, 369 | ) { 370 | my @out_path_arr = &$func($bin,$path,$build,@extra_search_paths); 371 | if(@out_path_arr) { 372 | foreach my $temp_path (@out_path_arr) { 373 | 374 | print STDERR "--[$temp_path] " if defined($temp_path) and $opt{v}; 375 | print STDERR "--[undef] " if !defined($temp_path) and $opt{v}; 376 | 377 | if ( defined($temp_path) && matchesUUID($temp_path, $uuid, $arch) ) { 378 | $out_path = $temp_path; 379 | @out_path_arr = {}; 380 | } else { 381 | undef $temp_path; 382 | print STDERR "-- NO MATCH\n" if $opt{v}; 383 | } 384 | } 385 | } else { 386 | print STDERR "-- NO MATCH\n" if $opt{v}; 387 | } 388 | 389 | last if defined $out_path; 390 | } 391 | } 392 | # if $out_path is defined here, then we have already verified that the UUID matches 393 | if ( !defined($out_path) ) { 394 | print STDERR "Searching in Spotlight for dsym with UUID of $uuid\n" if $opt{v}; 395 | $out_path = getSymbolPathFor_dsymUuid($uuid, $arch); 396 | undef $out_path if ( defined($out_path) && !length($out_path) ); 397 | } 398 | 399 | if (defined($out_path)) { 400 | print STDERR "-- MATCH\n" if $opt{v}; 401 | return $out_path; 402 | } 403 | 404 | print STDERR "## Warning: Can't find any unstripped binary that matches version of $path\n" if $opt{v}; 405 | print STDERR "\n" if $opt{v}; 406 | 407 | return undef; 408 | } 409 | 410 | ########################### 411 | # crashlog parsing 412 | ########################### 413 | 414 | # options: 415 | # - regex: don't escape regex metas in name 416 | # - continuous: don't reset pos when done. 417 | # - multiline: expect content to be on many lines following name 418 | sub parse_section { 419 | my ($log_ref, $name, %arg ) = @_; 420 | my $content; 421 | 422 | $name = quotemeta($name) 423 | unless $arg{regex}; 424 | 425 | # content is thing from name to end of line... 426 | if( $$log_ref =~ m{ ^($name)\: [[:blank:]]* (.*?) $ }mgx ) { 427 | $content = $2; 428 | $name = $1; 429 | 430 | # or thing after that line. 431 | if($arg{multiline}) { 432 | $content = $1 if( $$log_ref =~ m{ 433 | \G\n # from end of last thing... 434 | (.*?) 435 | (?:\n\s*\n|$) # until next blank line or the end 436 | }sgx ); 437 | } 438 | } 439 | 440 | pos($$log_ref) = 0 441 | unless $arg{continuous}; 442 | 443 | return ($name,$content) if wantarray; 444 | return $content; 445 | } 446 | 447 | # convenience method over above 448 | sub parse_sections { 449 | my ($log_ref,$re,%arg) = @_; 450 | 451 | my ($name,$content); 452 | my %sections = (); 453 | 454 | while(1) { 455 | ($name,$content) = parse_section($log_ref,$re, regex=>1,continuous=>1,%arg); 456 | last unless defined $content; 457 | $sections{$name} = $content; 458 | } 459 | 460 | pos($$log_ref) = 0; 461 | return \%sections; 462 | } 463 | 464 | sub parse_images { 465 | my ($log_ref, $report_version) = @_; 466 | 467 | my $section = parse_section($log_ref,'Binary Images Description',multiline=>1); 468 | if (!defined($section)) { 469 | $section = parse_section($log_ref,'Binary Images',multiline=>1); # new format 470 | } 471 | if (!defined($section)) { 472 | die "Error: Can't find \"Binary Images\" section in log file"; 473 | } 474 | 475 | my @lines = split /\n/, $section; 476 | scalar @lines or die "Can't find binary images list: $$log_ref"; 477 | 478 | my %images = (); 479 | my ($pat, $app, %captures); 480 | 481 | # FIXME: This should probably be passed in as an argument 482 | my $default_arch = 'armv6'; 483 | 484 | #To get all the architectures for string matching. 485 | my $architectures = "armv[4-8][tfsk]?|arm64"; 486 | 487 | # Once Perl 5.10 becomes the default in Mac OS X, named regexp 488 | # capture buffers of the style (?pattern) would make this 489 | # code much more sane. 490 | if($report_version == 102 || $report_version == 103) { # Leopard GM 491 | $pat = ' 492 | ^\s* (\w+) \s* \- \s* (\w+) \s* (?# the range base and extent [1,2] ) 493 | (\+)? (?# the application may have a + in front of the name [3] ) 494 | (.+) (?# bundle name [4] ) 495 | \s+ .+ \(.+\) \s* (?# the versions--generally "??? [???]" ) 496 | \? (?# possible UUID [5] ) 497 | \s* (\/.*)\s*$ (?# first fwdslash to end we hope is path [6] ) 498 | '; 499 | %captures = ( 'base' => \$1, 'extent' => \$2, 'plus' => \$3, 500 | 'bundlename' => \$4, 'uuid' => \$5, 'path' => \$6); 501 | } 502 | elsif($report_version == 104 || $report_version == 105) { # Kirkwood 503 | $pat = ' 504 | ^\s* (\w+) \s* \- \s* (\w+) \s* (?# the range base and extent [1,2] ) 505 | (\+)? (?# the application may have a + in front of the name [3] ) 506 | (.+) (?# bundle name [4] ) 507 | \s+ ('.$architectures.') \s+ (?# the image arch [5] ) 508 | \? (?# possible UUID [6] ) 509 | \s* (\/.*)\s*$ (?# first fwdslash to end we hope is path [7] ) 510 | '; 511 | %captures = ( 'base' => \$1, 'extent' => \$2, 'plus' => \$3, 512 | 'bundlename' => \$4, 'arch' => \$5, 'uuid' => \$6, 513 | 'path' => \$7); 514 | } 515 | else { 516 | die "Unsupported crash log version: $report_version"; 517 | } 518 | 519 | for my $line (@lines) { 520 | next if $line =~ /PEF binary:/; # ignore these 521 | 522 | $line =~ s/(&(\w+);?)/$entity2char{$2} || $1/eg; 523 | 524 | if ($line =~ /$pat/ox) { 525 | 526 | # Dereference references 527 | my %image; 528 | while((my $key, my $val) = each(%captures)) { 529 | $image{$key} = ${$captures{$key}} || ''; 530 | #print "image{$key} = $image{$key}\n"; 531 | } 532 | 533 | $image{uuid} = lc $image{uuid}; 534 | $image{arch} = $image{arch} || $default_arch; 535 | 536 | # Just take the first instance. That tends to be the app. 537 | my $bundlename = $image{bundlename}; 538 | $app = $bundlename if (!defined $app && defined $image{plus} && length $image{plus}); 539 | 540 | # frameworks and apps (and whatever) may share the same name, so disambiguate 541 | if ( defined($images{$bundlename}) ) { 542 | # follow the chain of hash items until the end 543 | my $nextIDKey = $bundlename; 544 | while ( length($nextIDKey) ) { 545 | last if ( !length($images{$nextIDKey}{nextID}) ); 546 | $nextIDKey = $images{$nextIDKey}{nextID}; 547 | } 548 | 549 | #avoid duplipcate image 550 | if ($image{uuid} ne $images{$bundlename}{uuid}) { 551 | 552 | # add ourselves to that chain 553 | $images{$nextIDKey}{nextID} = $image{base}; 554 | 555 | # and store under the key we just recorded 556 | $bundlename = $bundlename . $image{base}; 557 | } 558 | } 559 | 560 | # we are the end of the nextID chain 561 | $image{nextID} = ""; 562 | 563 | $images{$bundlename} = \%image; 564 | } 565 | } 566 | 567 | return (\%images, $app); 568 | } 569 | 570 | # if this is actually a partial binary identifier we know about, then 571 | # return the full name. else return undef. 572 | my %_partial_cache = (); 573 | sub resolve_partial_id { 574 | my ($bundle,$images) = @_; 575 | # is this partial? note: also stripping elipsis here 576 | return undef unless $bundle =~ s/^\.\.\.//; 577 | return $_partial_cache{$bundle} if exists $_partial_cache{$bundle}; 578 | 579 | my $re = qr/\Q$bundle\E$/; 580 | for (keys %$images) { 581 | if( /$re/ ) { 582 | $_partial_cache{$bundle} = $_; 583 | return $_; 584 | } 585 | } 586 | return undef; 587 | } 588 | 589 | sub fixup_last_exception_backtrace { 590 | my ($log_ref,$exception,$images) = @_; 591 | my $repl = $exception; 592 | if ($exception =~ m/^.0x/) { 593 | my @lines = split / /, substr($exception, 1, length($exception)-2); 594 | my $counter = 0; 595 | $repl = ""; 596 | for my $line (@lines) { 597 | my ($image,$image_base) = findImageByAddress($images, $line); 598 | my $offset = hex($line) - hex($image_base); 599 | my $formattedTrace = sprintf("%-3d %-30s\t0x%08x %s + %d", $counter, $image, hex($line), $image_base, $offset); 600 | $repl .= $formattedTrace . "\n"; 601 | ++$counter; 602 | } 603 | $log_ref = replace_chunk($log_ref, $exception, $repl); 604 | # may need to do this a second time since there could be First throw call stack too 605 | $log_ref = replace_chunk($log_ref, $exception, $repl); 606 | } 607 | return ($log_ref, $repl); 608 | } 609 | 610 | #sub parse_last_exception_backtrace { 611 | # print STDERR "Parsing last exception backtrace\n" if $opt{v}; 612 | # my ($backtrace,$images, $inHex) = @_; 613 | # my @lines = split /\n/,$backtrace; 614 | # 615 | # my %frames = (); 616 | # 617 | # # these two have to be parallel; we'll lookup by hex, and replace decimal if needed 618 | # my @hexAddr; 619 | # my @replAddr; 620 | # 621 | # for my $line (@lines) { 622 | # # end once we're done with the frames 623 | # last if $line =~ /\)/; 624 | # last if !length($line); 625 | # 626 | # if ($inHex && $line =~ /0x([[:xdigit:]]+)/) { 627 | # push @hexAddr, sprintf("0x%08s", $1); 628 | # push @replAddr, "0x".$1; 629 | # } 630 | # elsif ($line =~ /(\d+)/) { 631 | # push @hexAddr, sprintf("0x%08x", $1); 632 | # push @replAddr, $1; 633 | # } 634 | # } 635 | # 636 | # # we don't have a hint as to the binary assignment of these frames 637 | # # map_addresses will do it for us 638 | # return map_addresses(\@hexAddr,$images,\@replAddr); 639 | #} 640 | 641 | # returns an oddly-constructed hash: 642 | # 'string-to-replace' => { bundle=>..., address=>... } 643 | sub parse_backtrace { 644 | my ($backtrace,$images,$decrement) = @_; 645 | my @lines = split /\n/,$backtrace; 646 | 647 | my $is_first = 1; 648 | 649 | my %frames = (); 650 | for my $line (@lines) { 651 | if( $line =~ m{ 652 | ^\d+ \s+ # stack frame number 653 | (\S.*?) \s+ # bundle id (1) 654 | ((0x\w+) \s+ # address (3) 655 | .*) \s* $ # current description, to be replaced (2) 656 | }x ) { 657 | my($bundle,$replace,$address) = ($1,$2,$3); 658 | #print STDERR "Parse_bt: $bundle,$replace,$address\n" if ($opt{v}); 659 | 660 | # disambiguate within our hash of binaries 661 | $bundle = findImageByNameAndAddress($images, $bundle, $address); 662 | 663 | # skip unless we know about the image of this frame 664 | next unless 665 | $$images{$bundle} or 666 | $bundle = resolve_partial_id($bundle,$images); 667 | 668 | my $raw_address = $address; 669 | if($decrement && !$is_first) { 670 | $address = sprintf("0x%X", (hex($address) & ~1) - 1); 671 | } 672 | 673 | $frames{$replace} = { 674 | 'address' => $address, 675 | 'raw_address' => $raw_address, 676 | 'bundle' => $bundle, 677 | }; 678 | 679 | $is_first = 0; 680 | } 681 | # else { print "unable to parse backtrace line $line\n" } 682 | } 683 | 684 | return \%frames; 685 | } 686 | 687 | sub slurp_file { 688 | my ($file) = @_; 689 | my $data; 690 | my $fh; 691 | my $readingFromStdin = 0; 692 | 693 | local $/ = undef; 694 | 695 | # - or "" mean read from stdin, otherwise use the given filename 696 | if($file && $file ne '-') { 697 | open $fh,"<",$file or die "while reading $file, $! : "; 698 | } else { 699 | open $fh,"<&STDIN" or die "while readin STDIN, $! : "; 700 | $readingFromStdin = 1; 701 | } 702 | 703 | $data = <$fh>; 704 | 705 | 706 | # Replace DOS-style line endings 707 | $data =~ s/\r\n/\n/g; 708 | 709 | # Replace Mac-style line endings 710 | $data =~ s/\r/\n/g; 711 | 712 | # Replace "NO-BREAK SPACE" (these often get inserted when copying from Safari) 713 | # \xC2\xA0 == U+00A0 714 | $data =~ s/\xc2\xa0/ /g; 715 | 716 | close $fh or die $!; 717 | return \$data; 718 | } 719 | 720 | sub parse_OSVersion { 721 | my ($log_ref) = @_; 722 | my $section = parse_section($log_ref,'OS Version'); 723 | if ( $section =~ /\s([0-9\.]+)\s+\(Build (\w+)/ ) { 724 | return ($1, $2) 725 | } 726 | if ( $section =~ /\s([0-9\.]+)\s+\((\w+)/ ) { 727 | return ($1, $2) 728 | } 729 | if ( $section =~ /\s([0-9\.]+)/ ) { 730 | return ($1, "") 731 | } 732 | die "Error: can't parse OS Version string $section"; 733 | } 734 | 735 | sub parse_HardwareModel { 736 | my ($log_ref) = @_; 737 | my $model = parse_section($log_ref, 'Hardware Model'); 738 | $model or return undef; 739 | # HACK: replace the comma in model names because bsd_glob can't handle commas (even escaped ones) in 740 | # the {} groups 741 | $model =~ s/,/\?/g; 742 | $model =~ /(\S+)/; 743 | return $1; 744 | } 745 | 746 | sub parse_SDKGuess { 747 | my ($log_ref) = @_; 748 | 749 | # It turns out that most SDKs are named "lowercased(HardwareModelWithoutNumbers) + os", 750 | # so attempt to form a valid SDK name from that. Any code that uses this must NOT rely 751 | # on this guess being accurate and should fallback to whatever logic makes sense for the situation 752 | my $model = parse_HardwareModel($log_ref); 753 | $model or return undef; 754 | 755 | $model =~ /(\w+)\d/; 756 | $1 or return undef; 757 | 758 | return lc($1) . "os"; 759 | } 760 | 761 | sub parse_report_version { 762 | my ($log_ref) = @_; 763 | my $version = parse_section($log_ref,'Report Version'); 764 | $version or return undef; 765 | $version =~ /(\d+)/; 766 | return $1; 767 | } 768 | sub findImageByAddress { 769 | my ($images,$address) = @_; 770 | my $image; 771 | 772 | for $image (values %$images) { 773 | if ( hex($address) >= hex($$image{base}) && hex($address) <= hex($$image{extent}) ) 774 | { 775 | return ($$image{bundlename},$$image{base}); 776 | } 777 | } 778 | 779 | print STDERR "Unable to map $address\n" if $opt{v}; 780 | 781 | return undef; 782 | } 783 | 784 | sub findImageByNameAndAddress { 785 | my ($images,$bundle,$address) = @_; 786 | my $key = $bundle; 787 | 788 | #print STDERR "findImageByNameAndAddress($bundle,$address) ... "; 789 | 790 | my $binary = $$images{$bundle}; 791 | 792 | while($$binary{nextID} && length($$binary{nextID}) ) { 793 | last if ( hex($address) >= hex($$binary{base}) && hex($address) <= hex($$binary{extent}) ); 794 | 795 | $key = $key . $$binary{nextID}; 796 | $binary = $$images{$key}; 797 | } 798 | 799 | #print STDERR "$key\n"; 800 | return $key; 801 | } 802 | 803 | sub prune_used_images { 804 | my ($images,$bt) = @_; 805 | 806 | # make a list of images actually used in backtrace 807 | my $images_used = {}; 808 | for(values %$bt) { 809 | #print STDERR "Pruning: $images, $$_{bundle}, $$_{address}\n" if ($opt{v}); 810 | my $imagename = findImageByNameAndAddress($images, $$_{bundle}, $$_{address}); 811 | $$images_used{$imagename} = $$images{$imagename}; 812 | } 813 | 814 | # overwrite the incoming image list with that; 815 | %$images = %$images_used; 816 | } 817 | 818 | # fetch symbolled binaries 819 | # array of binary image ranges and names 820 | # the OS build 821 | # the name of the crashed program 822 | # undef 823 | # array of possible directories to locate symboled files in 824 | sub fetch_symbolled_binaries { 825 | 826 | print STDERR "Finding Symbols:\n" if $opt{v}; 827 | 828 | my $pre = "."; # used in formatting progress output 829 | my $post = sprintf "\033[K"; # vt100 code to clear from cursor to end of line 830 | 831 | my ($images,$build,$bundle,@extra_search_paths) = @_; 832 | 833 | # fetch paths to symbolled binaries. or ignore that lib if we can't 834 | # find it 835 | for my $b (keys %$images) { 836 | my $lib = $$images{$b}; 837 | 838 | print STDERR "\r${pre}fetching symbol file for $b$post" if $opt{v}; 839 | $pre .= "."; 840 | 841 | 842 | my $symbol = $$lib{symbol}; 843 | unless($symbol) { 844 | ($symbol) = getSymbolPathFor($$lib{path},$build,$$lib{uuid},$$lib{arch},@extra_search_paths); 845 | if($symbol) { 846 | $$lib{symbol} = $symbol; 847 | } 848 | else { 849 | delete $$images{$b}; 850 | next; 851 | } 852 | } 853 | 854 | print STDERR "\r${pre}checking address range for $b$post" if $opt{v}; 855 | $pre .= "."; 856 | 857 | # check for sliding. set slide offset if so 858 | open my($ph),"-|", "$size -m -l -x '$symbol'" or die $!; 859 | my $real_base = ( 860 | grep { $_ } 861 | map { (/_TEXT.*vmaddr\s+(\w+)/)[0] } <$ph> 862 | )[0]; 863 | close $ph; 864 | if ($?) { 865 | 866 | # 13T5280f: My crash logs aren't symbolicating 867 | # System libraries were not being symbolicated because /usr/bin/size is always failing. 868 | # That's /usr/bin/size doesn't like LC_SEGMENT_SPLIT_INFO command 12 869 | # 870 | # Until that's fixed, just hope for the best and assume no sliding. I've been informed that since 871 | # this scripts always deals with post-mortem crash files instead of running processes, sliding shouldn't 872 | # happen in practice. Nevertheless, we should probably add this sanity check back in once we 21604022 873 | # gets resolved. 874 | $real_base = $$lib{base} 875 | 876 | # call to size failed. Don't use this image in symbolication; don't die 877 | # delete $$images{$b}; 878 | #print STDERR "Error in symbol file for $symbol\n"; # and log it 879 | # next; 880 | } 881 | 882 | if($$lib{base} ne $real_base) { 883 | $$lib{slide} = hex($real_base) - hex($$lib{base}); 884 | } 885 | } 886 | print STDERR "\rdone.$post\n" if $opt{v}; 887 | print STDERR "\r$post" if $opt{v}; 888 | print STDERR keys(%$images) . " binary images were found.\n" if $opt{v}; 889 | } 890 | 891 | # run atos 892 | sub symbolize_frames { 893 | my ($images,$bt) = @_; 894 | 895 | # create mapping of framework => address => bt frame (adjust for slid) 896 | # and for framework => arch 897 | my %frames_to_lookup = (); 898 | my %arch_map = (); 899 | my %base_map = (); 900 | 901 | for my $k (keys %$bt) { 902 | my $frame = $$bt{$k}; 903 | my $lib = $$images{$$frame{bundle}}; 904 | unless($lib) { 905 | # don't know about it, can't symbol 906 | # should have already been warned about this! 907 | # print "Skipping unknown $$frame{bundle}\n"; 908 | delete $$bt{$k}; 909 | next; 910 | } 911 | 912 | # list of address to lookup, mapped to the frame object, for 913 | # each library 914 | $frames_to_lookup{$$lib{symbol}}{$$frame{address}} = $frame; 915 | $arch_map{$$lib{symbol}} = $$lib{arch}; 916 | $base_map{$$lib{symbol}} = $$lib{base}; 917 | } 918 | 919 | # run atos for each library 920 | while(my($symbol,$frames) = each(%frames_to_lookup)) { 921 | # escape the symbol path if it contains single quotes 922 | my $escapedSymbol = $symbol; 923 | $escapedSymbol =~ s/\'/\'\\'\'/g; 924 | 925 | # run atos with the addresses and binary files we just gathered 926 | my $arch = $arch_map{$symbol}; 927 | my $base = $base_map{$symbol}; 928 | my $cmd = "$atos -arch $arch -l $base -o '$escapedSymbol' @{[ keys %$frames ]} | "; 929 | 930 | print STDERR "Running $cmd\n" if $opt{v}; 931 | 932 | open my($ph),$cmd or die $!; 933 | my @symbolled_frames = map { chomp; $_ } <$ph>; 934 | close $ph or die $!; 935 | 936 | my $references = 0; 937 | 938 | foreach my $symbolled_frame (@symbolled_frames) { 939 | 940 | $symbolled_frame =~ s/\s*\(in .*?\)//; # clean up -- don't need to repeat the lib here 941 | 942 | # find the correct frame -- the order should match since we got the address list with keys 943 | my ($k,$frame) = each(%$frames); 944 | 945 | if ( $symbolled_frame !~ /^\d/ ) { 946 | # only symbolicate if we fetched something other than an address 947 | #re-increment any offset that we had to artifically decrement 948 | if($$frame{raw_address} ne $$frame{address}) { 949 | $symbolled_frame =~ s|(.+ \+) (\d+)|$1." ".($2 + 1)|e; 950 | } 951 | 952 | $$frame{symbolled} = $symbolled_frame; 953 | $references++; 954 | } 955 | 956 | } 957 | 958 | if ( $references == 0 ) { 959 | print STDERR "## Warning: Unable to symbolicate from required binary: $symbol\n"; 960 | } 961 | } 962 | 963 | # just run through and remove elements for which we didn't find a 964 | # new mapping: 965 | while(my($k,$v) = each(%$bt)) { 966 | delete $$bt{$k} unless defined $$v{symbolled}; 967 | } 968 | } 969 | 970 | # run the final regex to symbolize the log 971 | sub replace_symbolized_frames { 972 | my ($log_ref,$bt) = @_; 973 | my $re = join "|" , map { quotemeta } keys %$bt; 974 | 975 | my $log = $$log_ref; 976 | $log =~ s#$re# 977 | my $frame = $$bt{$&}; 978 | $$frame{raw_address} ." ". $$frame{symbolled}; 979 | #esg; 980 | 981 | $log =~ s/(&(\w+);?)/$entity2char{$2} || $1/eg; 982 | 983 | return \$log; 984 | } 985 | 986 | sub replace_chunk { 987 | my ($log_ref,$old,$new) = @_; 988 | my $log = $$log_ref; 989 | my $re = quotemeta $old; 990 | $log =~ s/$re/$new/; 991 | return \$log; 992 | } 993 | 994 | ############# 995 | 996 | sub output_log($) { 997 | my ($log_ref) = @_; 998 | 999 | if($opt{'o'}) { 1000 | close STDOUT; 1001 | open STDOUT, '>', $opt{'o'}; 1002 | } 1003 | 1004 | print $$log_ref; 1005 | } 1006 | 1007 | ############# 1008 | 1009 | sub symbolicate_log { 1010 | my ($file,@extra_search_paths) = @_; 1011 | 1012 | print STDERR "Symbolicating...\n" if ( $opt{v} ); 1013 | 1014 | my $log_ref = slurp_file($file); 1015 | 1016 | print STDERR length($$log_ref)." characters read.\n" if ( $opt{v} ); 1017 | 1018 | # get the version number 1019 | my $report_version = parse_report_version($log_ref); 1020 | $report_version or die "No crash report version in $file"; 1021 | 1022 | # setup the tool paths we will need 1023 | my $sdkGuess = parse_SDKGuess($log_ref); 1024 | print STDERR "SDK guess for tool search is '$sdkGuess'\n" if $opt{v}; 1025 | $otool = "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool"; 1026 | $atos = "/Applications/Xcode.app/Contents/Developer/usr/bin/atos"; 1027 | $lipo = "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/lipo"; 1028 | $size = "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/size"; 1029 | 1030 | # read the binary images 1031 | my ($images,$first_bundle) = parse_images($log_ref, $report_version); 1032 | 1033 | if ( $opt{v} ) { 1034 | print STDERR keys(%$images) . " binary images referenced:\n"; 1035 | foreach (keys(%$images)) { 1036 | print STDERR $_; 1037 | print STDERR "\t\t("; 1038 | print STDERR $$images{$_}{path}; 1039 | print STDERR ")\n"; 1040 | } 1041 | print "\n"; 1042 | } 1043 | 1044 | my $bt = {}; 1045 | my $threads = parse_sections($log_ref,'Thread\s+\d+\s?(Highlighted|Crashed)?',multiline=>1); 1046 | for my $thread (values %$threads) { 1047 | # merge all of the frames from all backtraces into one 1048 | # collection 1049 | my $b = parse_backtrace($thread,$images,0); 1050 | @$bt{keys %$b} = values %$b; 1051 | } 1052 | 1053 | # extract hardware model 1054 | my $model = parse_HardwareModel($log_ref); 1055 | print STDERR "Hardware Model $model\n" if $opt{v}; 1056 | 1057 | # extract build 1058 | my ($version, $build) = parse_OSVersion($log_ref); 1059 | print STDERR "OS Version $version Build $build\n" if $opt{v}; 1060 | 1061 | my $exception = parse_section($log_ref,'Last Exception Backtrace', multiline=>1); 1062 | if (defined $exception) { 1063 | ($log_ref, $exception) = fixup_last_exception_backtrace($log_ref, $exception, $images); 1064 | #my $e = parse_last_exception_backtrace($exception, $images, 1); 1065 | my $e = parse_backtrace($exception, $images,1); 1066 | 1067 | # treat these frames in the same was as any thread 1068 | @$bt{keys %$e} = values %$e; 1069 | } 1070 | 1071 | # sort out just the images needed for this backtrace 1072 | prune_used_images($images,$bt); 1073 | if ( $opt{v} ) { 1074 | print STDERR keys(%$images) . " binary images remain after pruning:\n"; 1075 | foreach my $junk (keys(%$images)) { 1076 | print STDERR $junk; 1077 | print STDERR ", "; 1078 | } 1079 | print STDERR "\n"; 1080 | } 1081 | 1082 | @extra_search_paths = (@extra_search_paths, getSymbolDirPaths($model, $version, $build)); 1083 | 1084 | fetch_symbolled_binaries($images,$build,$first_bundle,@extra_search_paths); 1085 | 1086 | # If we didn't get *any* symbolled binaries, just print out the original crash log. 1087 | my $imageCount = keys(%$images); 1088 | if ($imageCount == 0) { 1089 | output_log($log_ref); 1090 | return; 1091 | } 1092 | 1093 | # run atos 1094 | symbolize_frames($images,$bt); 1095 | 1096 | if(keys %$bt) { 1097 | # run our fancy regex 1098 | my $new_log = replace_symbolized_frames($log_ref,$bt); 1099 | output_log($new_log); 1100 | } else { 1101 | #There were no symbols found 1102 | print STDERR "No symbolic information found\n"; 1103 | output_log($log_ref); 1104 | } 1105 | } -------------------------------------------------------------------------------- /CrashReports/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | Default 535 | 536 | 537 | 538 | 539 | 540 | 541 | Left to Right 542 | 543 | 544 | 545 | 546 | 547 | 548 | Right to Left 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | Default 560 | 561 | 562 | 563 | 564 | 565 | 566 | Left to Right 567 | 568 | 569 | 570 | 571 | 572 | 573 | Right to Left 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | --------------------------------------------------------------------------------