Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting a human readable stacktrace from swift

Tags:

ios

swift

I am working on an Apple iOS project for the first time and I have a need to retrieve a stacktrace that is human readable, with the class nams and line numbers etc that I can store as a string for logging and/or future storing somewhere.

I have got a stacktrace of sorts but it seems to be the symbols so its not overly readable to see what's actually happened.

Below is the code that I currently have:

Thread.callStackSymbols.forEach({print($0)})

An example of the stacktrace is below

0   TestCrash                           0x0000000104056e1c $s9TestCrash14ViewControllerC03btnaB0yyypF + 1480
1   TestCrash                           0x0000000104057320 $s9TestCrash14ViewControllerC03btnaB0yyypFTo + 76
2   UIKitCore                           0x00000001843d2fc4 -[UIApplication sendAction:to:from:forEvent:] + 96
3   UIKitCore                           0x0000000183d70c80 -[UIControl sendAction:to:forEvent:] + 220
4   UIKitCore                           0x0000000183d70fc4 -[UIControl _sendActionsForEvents:withEvent:] + 352
5   UIKitCore                           0x0000000183d6f924 -[UIControl touchesEnded:withEvent:] + 532
6   UIKitCore                           0x000000018440d034 -[UIWindow _sendTouchesForEvent:] + 1112
7   UIKitCore                           0x000000018440e920 -[UIWindow sendEvent:] + 3824
8   UIKitCore                           0x00000001843ea2ac -[UIApplication sendEvent:] + 608
9   UIKitCore                           0x000000018446f8bc __processEventQueue + 13600
10  UIKitCore                           0x0000000184467434 __eventFetcherSourceCallback + 108
11  CoreFoundation                      0x00000001803654f4 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
12  CoreFoundation                      0x00000001803653f4 __CFRunLoopDoSource0 + 204
13  CoreFoundation                      0x000000018036474c __CFRunLoopDoSources0 + 256
14  CoreFoundation                      0x000000018035ed94 __CFRunLoopRun + 760
15  CoreFoundation                      0x000000018035e58c CFRunLoopRunSpecific + 572
16  GraphicsServices                    0x000000018b9c2740 GSEventRunModal + 160
17  UIKitCore                           0x00000001843ccbf0 -[UIApplication _run] + 964
18  UIKitCore                           0x00000001843d19d0 UIApplicationMain + 112
19  libswiftUIKit.dylib                 0x00000001b220f328 $s5UIKit17UIApplicationMainys5Int32VAD_SpySpys4Int8VGGSgSSSgAJtF + 100
20  TestCrash                           0x0000000104058308 $sSo21UIApplicationDelegateP5UIKitE4mainyyFZ + 120
21  TestCrash                           0x0000000104058280 $s9TestCrash11AppDelegateC5$mainyyFZ + 48
22  TestCrash                           0x000000010405834c main + 32
23  libdyld.dylib                       0x0000000180223cbc start + 4

The reason for this is I want to be able to upload the stack to a server for logging (yes I know about crash reporting services, I'm creating my own version).

In the xcode build settings I've set Deployment Postprocessing, Strip Debug Symbols During Copy and Strip Linked Product but it doesn't seem to make much difference.

I've also looked at dwarfdump and atos projects for Linux (which is the server type where the logs will be sent) but none of these seem to work as it either just gives me hex values again or dwarfdump just throws me some error but both of these projects I've found seem to be unsupported and quite old now so I'm guessing somethings changed with out the code is compiled which means this no longer works, hence why trying to avoid the build process from stripping the debug information so its available programatically.

like image 882
Boardy Avatar asked Oct 08 '19 20:10

Boardy


1 Answers

So I think first of all that re-implementing a crash reporting solution like Firebase Crashlytics is non-trivial and NOT advisable given the quality of the service that Crashlytics provides for free. Crash reporting is hard because you need to avoid crashes in the framework itself because of low-level interfacing with the memory addresses, function pointers, OS signals, and multithreading without type safety and garbage collection (in C++). It is also notoriously hard to test giving the wide variety of possible crashes, app states, different devices (needs to be lightweight for older devices), and edge cases with deadlocks/threading/memory leaks. If you STILL want to go ahead, I'll outline the main considerations below.

The issue with runtime symbolication is implementing the API to read the debug symbols, upload symbols, and intercept exception handlers. callStackSymbols is not intended to be machine-readable so you'd have to reimplement the call stack parser using functions like callStackReturnAddresses.

This is non-trivial because not only does the crash have to be symbolicated, but needs to be uploaded to the server/cached (usually done by busy waiting while checking if the app is still active) before the app is terminated because of the crash. This means that the code needs to run highly efficiently to generate the stack frame with file and line number metadata (C++ is best for that).

It's challenging to perform asynchronous uploads without high-level C++ libraries, and do persistent local caching without using the native Foundation APIs (the events also need to be queued on a dedicated thread, using locks to ensure synchronisation). In addition to language barriers, the names need to be symbolicated and demangled with thread information (each stack frame needs to also include the state of every other thread at the time). The crash stack trace does include the method name by default (mangled), but additional information like the file name and line number needs to be uploaded at app runtime. The hex numbers you are seeing are memory addresses, so these need to be converted at runtime to the appropriate object metadata and sent. The way to intercept exceptions in C is to use Signal Handlers - intercepting the iOS kernel signals for:

{SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGSYS, SIGTRAP};

If the signal is not one of these, you can still send a request but use an "unknown" signal handler to intercept the crash.

That being said it's probably feasible to stitch together a crash reporter using debug symbols. You can do that just once when you've archived the app (you don't need a physical device). You can do this once when sending the app to the App Store. Fastlane can make this easier with an output directory (an alternative is to use the environment variable DWARF Destination Folder with xcodebuild). You can then parse debug symbols on the server.

Find the .xcarchive and do this to zip together the dsyms of the main target and any linked frameworks:

ditto -c -k --keepParent -rsrc <AppName>.xcarchive/dSYMs/*.dSYM <AppName>-${PRODUCT_VERSION}-dSYM.zip

You mentioned you don't know where to find the debug symbols. If you want to know where the archives are located, you can go to the Xcode Organizer, Archives, and then right-click Show in Finder. Then right click to Show Package Contents to see the dSYMs.

Even if you are happy with a fairly rudimentary crash reporter you also need to encrypt server uploads as the codebase could be reverse-engineered when sending the raw crash logs! You also would need to manually check the Build UUIDs to ensure the same app version which crashed is being symbolicated (the symbolication will fail otherwise).

An alternative to this would be to use crash logs provided by the App Store, (downloadable in Xcode). The advantage of this is mainly ease of setup, integration with Xcode, and quality of reports. You can also get Jetsam memory log warnings if you stick with Apple. The server itself would then be easily able to automate crash report construction using the dSYMs and a script:

//Put dsyms and crash log in one folder and navigate to that folder
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
cp -i /Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash ./
./symbolicatecrash unsymbolicated.crash > symbolicated.crash

I hope this pointed you in the right direction; it's unfortunately not possible to give you the complete code for a fully functional crash reporting solution (it's too broad, complex, and not to mention WAY out of scope for a SO question). :-)

like image 152
Pranav Kasetti Avatar answered Nov 12 '22 14:11

Pranav Kasetti