├── 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 | 
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 |
50 |
51 |
52 |
53 |
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 | \([[:xdigit:]]{32})?\>? (?# 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 | \([[:xdigit:]]{32})?\>? (?# 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 |
665 |
666 |
667 |
--------------------------------------------------------------------------------