简体   繁体   中英

Can an iOS app include its debug symbols (dSYM file) when installed directly from Xcode?

We have a couple of iOS devices inhouse that I regularly update with an iOS app that I develop. I just connect the device to my developer machine and run the app from Xcode ( Cmd + R ). This way the app gets installed on the connected device.

Later, when the app crashes on any of these devices I'd like to get it back, connect it to Xcode again, open the crash log and see the backtrace symbolicated. Thus reading the file name and line numbers of the call stack where the crash occurred.

As I know so far, I therefor need to preserve the dSYM file – otherwise I wouldn't see the file names and line numbers. This however is a very tedious job: I don't want to archive the app every time I install it "ad hoc" from my computer. I rather would like the debug information/symbols to be copied along with the app onto the device. Then, whenever I get the device back, I open its crashlog and have the crash already symbolicated - without having to preserve the dSYM file (debug information) on my computer.

Is there any chance to achieve that? If yes, what build settings / procedure should I follow?

Why are there "Strip Debug Symbols During Copy" and "Strip Linked Product" build settings that, even when disabled, still don't yield symbolicated crashlogs unless I preserve the matching dSYM file?

Automatically send a crash report from your adhoc builds without dSYM files

I want to share my solution that sends symbolicated crash logs from our inhouse builds without the need for a dSYM file . So you don't need to save the dSYM file every time you create and deploy your premature app inhouse. And you don't need to bother a crash report service such as HockeyApp or Crashlytics when in comes to debug builds only.

I came up with a crash reporting pipeline that automatically posts the symbolicated crash report to a Slack channel. This message is posted the next time the app is started, thus the next run after the crash occurred. All this happens in background without bothering the already annoyed user (at the end his app crashed!).

How it looks like when the app has crashed

崩溃报告消息发送到Slack

The crash report

The link points to the crash report itself that my iOS app automatically uploaded in background as a text file along with the mentioned Slack message:

Build timestamp: Thu Oct  8 11:42:11 CEST 2015
Git commit hash: master-d675928

Incident Identifier: 4F9F2147-CEB3-426C-A122-CF215EB8DC16
CrashReporter Key:   TODO
Hardware Model:      iPad2,5
Process:         Nuimo [4997]
Path:            /private/var/mobile/Containers/Bundle/Application/9CE594BE-D9E7-4070-81FF-B1554C52CB2C/Nuimo.app/Nuimo
Identifier:      com.senic.Nuimo
Version:         1
Code Type:       ARM
Parent Process:  launchd [1]

Date/Time:       2015-10-08 16:52:49 +0000
OS Version:      iPhone OS 8.4.1 (12H321)
Report Version:  104

Exception Type:  SIGTRAP
Exception Codes: #0 at 0x92c8b4
Crashed Thread:  0

Thread 0 Crashed:
0   libswiftCore.dylib                  0x0092c8b4 _TTSf4s_s_d_d___TFSs18_fatalErrorMessageFTVSs12StaticStringS_S_Su_T_ + 72
1   Nuimo                               0x000c80b4 _TFC5Nuimo33NuimoDiscoveryTableViewController11viewDidLoadfS0_FT_T_ + 608
2   Nuimo                               0x000c8204 _TToFC5Nuimo33NuimoDiscoveryTableViewController11viewDidLoadfS0_FT_T_ + 56
3   UIKit                               0x2d46f55d <redacted> + 600
4   UIKit                               0x2d46f2cd <redacted> + 24
5   UIKit                               0x2d9e97ad <redacted> + 2364
6   UIKit                               0x2d9ea65d <redacted> + 256
7   Nuimo                               0x000d2070 _TFC5Nuimo30NuimoDetailsPageViewController18showNuimoSelectionfS0_FT_T_ + 560
8   Nuimo                               0x001750f4 _TPA21 + 32
9   Nuimo                               0x001a0f80 _TTRXFo__dT__XFo_iT__iT__ + 16
10  Nuimo                               0x0017133c _TPA__TTRXFo__dT__XFo_iT__iT__3 + 76
11  Nuimo                               0x00121c64 _TFC5Nuimo6TabBar16setSelectedIndexfS0_FTGSqSi_8animatedSb_T_ + 1972
12  Nuimo                               0x0011fec0 _TFC5Nuimo6TabBars13selectedIndexGSqSi_ + 64
13  Nuimo                               0x00122e98 _TFC5Nuimo6TabBar12didTapButtonfS0_FCSo8UIButtonT_ + 132
14  Nuimo                               0x00122f4c _TToFC5Nuimo6TabBar12didTapButtonfS0_FCSo8UIButtonT_ + 100
15  UIKit                               0x2d4a082b <redacted> + 70
16  UIKit                               0x2d4a07d1 <redacted> + 44
17  UIKit                               0x2d48b375 <redacted> + 584
18  UIKit                               0x2d4a8c9b <redacted> + 258
19  UIKit                               0x2d49fe15 <redacted> + 316
20  UIKit                               0x2d4997f1 <redacted> + 540
21  UIKit                               0x2d46f9b5 <redacted> + 196
22  UIKit                               0x2d6e60ff <redacted> + 14538
23  UIKit                               0x2d46e3b7 <redacted> + 1350
24  CoreFoundation                      0x29dd400f <redacted> + 14
25  CoreFoundation                      0x29dd3423 <redacted> + 222
26  CoreFoundation                      0x29dd1aa1 <redacted> + 768
27  CoreFoundation                      0x29d1d6d1 CFRunLoopRunSpecific + 476
28  CoreFoundation                      0x29d1d4e3 CFRunLoopRunInMode + 106
29  GraphicsServices                    0x316b91a9 GSEventRunModal + 136
30  UIKit                               0x2d4cf445 UIApplicationMain + 1440
31  Nuimo                               0x0013e134 main + 164
32  libdyld.dylib                       0x38b5baaf <redacted> + 2
(Rest stripped...)

So what did I learn from the crash report? It showed me:

  1. the build time stamp
  2. the git commit hash of the build (quiet handy to reproduce the build)
  3. some device information
  4. the error stack trace – the app actually crashed in class NuimoDiscoveryTableViewController , method viewDidLoad . Unfortunately no line number, but that's more than nothing!

How to make your adhoc builds send a symbolicated crash report

Here's a how-to-guide in case you want to implement the same pipeline into your iOS app:

1. Include PLCrashReporter into your application

PLCrashReporter is a free open-source crash reporting library that is used by paid services such as HockeyApp. It allows for on-device symbolication of the error stack trace.

Assuming that you are using CocoaPods, include in your Podfile :

pod 'PLCrashReporter'

If you are using Swift add these header files to your bridging header :

#import "PLCrashReport.h"
#import "PLCrashReporter.h"
#import "PLCrashReportTextFormatter.h"

2. Start your custom crash reporter when your app has launched

Run your crash reporter when your app has finished launching. This way the crash reporter can first send any pending crash report from the last application run and enable PLCrashReporter to symbolicate and store crash reports for future failures.

class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
#ifdef DEBUG
        CrashReporter.sharedReporter.applicationDidFinishLaunching()
        ...
#end
    }

As you can see, the crash reporter is only set up for the Debug configuration as it's explicitly not recommended for release builds. Not only because the symbolication takes a few seconds to happen when a crash occurs. This said, add a compiler flag for your debug builds .

3. Implement your custom crash report pipeline

class CrashReporter {
    static let sharedReporter = CrashReporter()

    func applicationDidFinishLaunching() {
        let crashReporter = PLCrashReporter(configuration: PLCrashReporterConfig(signalHandlerType: PLCrashReporterSignalHandlerType.BSD, symbolicationStrategy: PLCrashReporterSymbolicationStrategy.All))
        if crashReporter.hasPendingCrashReport() {
            if let crashData = crashReporter.loadPendingCrashReportData(), let crashReport = try? PLCrashReport(data: crashData) {
                let gitCommitHash = "Git commit hash: " + ((NSBundle.mainBundle().infoDictionary ?? [:])["GitCommitHash"] as? String ?? "(not set)")
                let buildTimestamp = "Build timestamp: " + ((NSBundle.mainBundle().infoDictionary ?? [:])["BuildTimestamp"] as? String ?? "(not set)")
                let message = PLCrashReportTextFormatter.stringValueForCrashReport(crashReport, withTextFormat: PLCrashReportTextFormatiOS)
                sendCrashReport("\(buildTimestamp + "\n" +  gitCommitHash + "\n" + message)")
            }
            crashReporter.purgePendingCrashReport()
        }
        if !crashReporter.enableCrashReporter() {
            print("Cannot enable crash reporter")
        }
    }

    func sendCrashReport(message: String) {
        // Now send the crash report to Slack, Twitter, Facebook, via Email, ...
    }

Make sure to enable the PLCrashReporter at the end!

4. Additional step: Include a build timestamp and the git branch/tag and the commit hash used for the build

My implementation of CrashReporter includes the build time stamp and the git branch/tag and commit hash. That allows me to easily reproduce the buggy code base. Here's how to include these additional information. It's however not necessary to make the sample implementation above running.

Under Project -> Build Phases add a new Run Script Phase and paste this bash script that writes the build time and the git details into your project's Info.plist :

echo "Writing build time and git commit hash to Info.plist"

git_branch=$(git symbolic-ref --short -q HEAD)
git_tag=$(git describe --tags --exact-match 2>/dev/null)
git_branch_or_tag="${git_branch:-${git_tag}}"
git_hash=$(git log -1 --format="%h")
build_time=$(date)

echo "${BUILT_PRODUCTS_DIR}/${EXECUTABLE_FOLDER_PATH}/Info.plist"
echo "${git_branch_or_tag}-${git_hash}"

info_plist="${BUILT_PRODUCTS_DIR}/${EXECUTABLE_FOLDER_PATH}/Info.plist"
/usr/libexec/PlistBuddy -c "Set :GitCommitHash '${git_branch_or_tag}-${git_hash}'" "${info_plist}"
/usr/libexec/PlistBuddy -c "Set :BuildTimestamp '${build_time}'" "${info_plist}"

It seems to be necessary to add two empty key value pairs for these plist keys into your Info.plist beforehand. Add one named BuildTimestamp and another one named GitCommitHash . Both of type String .

5. Make your app crash!

Now place an assertionFailure() in your code and run your app. Make sure to run the app when Xcode is not connected, otherwise Xcode will handle this error before the crash reporter can react. When you successfully crashed your app, restart it with or without Xcode connected to your app, and you should see your crash reporter generating a crash report message as shown in the introduction of this answer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM