Goal:
I want to symbolicate the "output" of [NSThread callStackSymbols]
.
Side Notes:
I know how to do that with crash logs. However, I need to investigate some problems where I would like to look at the call stack. Unfortunately these days the framework's addresses are all <redacted>
. Causing a crash at the right points (or at the end - see end of my question) is not really acceptable, but if I cannot find another solution, it will be the way to go.
I have to run my tests on a device, so I cannot use the Simulator.
Current approach:
When I call this:
NSLog(@"call stack:\n%@", [NSThread callStackSymbols]);
I get this output:
2015-12-08 15:04:03.888 Conversion[76776:4410388] call stack:
(
0 Conversion 0x000694b5 -[ViewController viewDidLoad] + 128
1 UIKit 0x27259f55 <redacted> + 1028
...
9 UIKit 0x274f67a7 <redacted> + 134
10 FrontBoardServices 0x2b358ca5 <redacted> + 232
11 FrontBoardServices 0x2b358f91 <redacted> + 44
12 CoreFoundation 0x230e87c7 <redacted> + 14
...
16 CoreFoundation 0x23038ecd CFRunLoopRunInMode + 108
17 UIKit 0x272c7607 <redacted> + 526
18 UIKit 0x272c22dd UIApplicationMain + 144
19 Conversion 0x000767b5 main + 108
20 libdyld.dylib 0x34f34873 <redacted> + 2
)
("Conversion" in this output is the app.)
Now I can use this command to "symbolicate" addresses:
xcrun atos -o /path/to/Conversion.app -arch arm64 -l 0x0???
Run like that (of course with a proper value for -l
), I can enter addresses like 0x000694b5
and it will spit out something like -[ViewController viewDidLoad] + 128
. Of course the problem is the start address (the -l option's value).
When I have a crash log, I can get those. However, I would like to get away without a crash.
Questions:
Q1: Can I determine the start address at runtime and maybe include it in the log output so I can feed it to the atos -l
option?
EDIT: It looks like this is possible like this: (thanks to NSProgrammer for answer https://stackoverflow.com/a/12464678/1396265)
#import <mach-o/dyld.h>
...
intptr_t slide = _dyld_get_image_vmaddr_slide(0);
const struct mach_header * load_addr = _dyld_get_image_header(0);
NSLog(@"slide %lx load addr %lx", (long)slide, (long)load_addr);
/EDIT
(As I am interested in the framework's method calls, I certainly need the start addresses of the frameworks. The app's start address changes frequently (randomization), I do not yet know, whether or not the framework's start addresses are randomized.)
Q2: Are there other approaches to investigating the methods in the call stack? (breakpoints are also rather clumsy in my scenario.)
EDIT:
Q3: How can I symbolicate the addresses of the frameworks? For instance where can I find the dSYM (or whatever it takes) for UIKit?
(For instance I have something at: ~/Library/Developer/Xcode/iOS\ DeviceSupport/9.1\ \(13B143\)/Symbols/System/Library/Frameworks/UIKit.framework/
. I'll look into more details here.)
/EDIT
Maybe a solution:
One way might be, to save the log output to a file, and at the end of the tests cause a crash in the app. That way the crash log would reveal the start addresses and with the call stack information from the logs I should be able to symbolicate the callStackSymbols
output. I will try that next.
Adding log snippets to the crash log worked well so far, so I'll add this as an answer for now. (Better answers are definitely welcome :-) )
Preparations:
Add callStackSymbols logging at the relevant places in the source code:
NSLog(@"call stack:\n%@", [NSThread callStackSymbols]);
Make the app crash (for instance when a certain screen is opened):
strcpy(0, "000");
Redirect logging output to a file:
FILE *logfile = freopen([pathForLog cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
Execution:
In Xcode run the app once, so it gets installed on the device, and then stop it. Then start the app directly on the device, so Xcode won't catch the crash.
Use the app so that the call stack is logged (can be many times).
Eventually make the app crash (in my case, open a certain screen which calls the bad strcpy).
Getting the files:
In Xcode open "Window -> Devices", select the device, select the app and download the app container, so the log file can be extracted.
In the same screen open the device logs and export the crash log.
In Xcode open "Window -> Projects", select the project and make it show the derived data folder in the Finder (little arrow button to the right of the derived data path). In the derived data folder navigate to "Build/Products/Debug-iphoneos/" and copy MyApp.app and MyApp.app.dSYM.
Adjust the crash log:
For me it worked to add extra thread sections right before "Binary Images:". So find the location of "Binary Images:". Insert a line "Thread 9999:". Copy&paste the call stack dumps and remove the leading whitespace columns so that it looks like this:
2015-12-09 15:28:58.971 MyApp[21376:3050653] call tree (
Thread 9999:
0 MyApp 0x00000001001d95f8 -[MyClass myMethod:] + 100
1 UIKit 0x000000018a5fc2ac <redacted> + 172
2 UIKit 0x000000018a5d5ca4 <redacted> + 88
3 UIKit 0x000000018a5d5b8c <redacted> + 460
4 UIKit 0x000000018a5d5cc0 <redacted> + 116
5 UIKit 0x000000018a5d5b8c <redacted> + 460
6 UIKit 0x000000018a5d5cc0 <redacted> + 116
7 UIKit 0x000000018a5d5b8c <redacted> + 460
8 UIKit 0x000000018a8e85ac <redacted> + 460
9 UIKit 0x000000018a5d4abc <redacted> + 96
10 UIKit 0x000000018a935b7c <redacted> + 344
11 UIKit 0x000000018a9306f8 <redacted> + 124
12 UIKit 0x000000018aa584d8 <redacted> + 44
13 UIKit 0x000000018a933d9c <redacted> + 188
14 UIKit 0x000000018a70b668 <redacted> + 116
15 UIKit 0x000000018a70b454 <redacted> + 252
16 UIKit 0x000000018a70af38 <redacted> + 1404
17 UIKit 0x000000018a70a9a8 <redacted> + 124
18 UIKit 0x000000018a616d3c <redacted> + 312
19 UIKit 0x000000018a616bc4 <redacted> + 108
20 QuartzCore 0x0000000189dddc2c <redacted> + 284
21 libdispatch.dylib 0x000000019a3a96a8 <redacted> + 16
22 libdispatch.dylib 0x000000019a3aedb0 _dispatch_main_queue_callback_4CF + 1844
23 CoreFoundation 0x00000001850001f8 <redacted> + 12
24 CoreFoundation 0x0000000184ffe060 <redacted> + 1628
25 CoreFoundation 0x0000000184f2cca0 CFRunLoopRunSpecific + 384
26 GraphicsServices 0x000000018ff94088 GSEventRunModal + 180
27 UIKit 0x000000018a644ffc UIApplicationMain + 204
28 MyApp 0x0000000100093918 main + 124
29 libdyld.dylib 0x000000019a3da8b8 <redacted> + 4
Binary Images:
...
The "Thread 9999:" line makes the symbolicatecrash
script want to symbolicate the next lines. I chose 9999 so I know, that these were my added sections.
Run symbolication:
Find the symbolicatecrash
script:
$ find /Applications/Xcode.app -name symbolicatecrash -type f
/Applications/Xcode.app/Contents/SharedFrameworks/DTDeviceKitBase.framework/Versions/A/Resources/symbolicatecrash
$
The symbolication command goes almost like this:
$ symbolicatecrash myapp.crash MyApp.app.dSYM > myapp-sym.crash
You need to set DEVELOPER_DIR and prepend the path to the script, so eventually it looks like this:
$ DEVELOPER_DIR='/Applications/Xcode.app/Contents/Developer' /Applications/Xcode.app/Contents/SharedFrameworks/DTDeviceKitBase.framework/Versions/A/Resources/symbolicatecrash myapp.crash MyApp.app.dSYM > myapp-sym.crash
Or wrapped for better viewing pleasure:
$ DEVELOPER_DIR='/Applications/Xcode.app/Contents/Developer' ...
/Applications/Xcode.app/Contents/SharedFrameworks/ ...
DTDeviceKitBase.framework/Versions/A/Resources/ ...
symbolicatecrash myapp.crash MyApp.app.dSYM > myapp-sym.crash
The result:
The symbolicated snippets now look like this:
2015-12-09 15:28:58.971 MyApp[21376:3050653] call tree (
Thread 9999:
0 MyApp 0x00000001001d95f8 -[MyClass myMethod:] (MyFile.m:15)
1 UIKit 0x000000018a5fc2ac -[UIScrollView _willMoveToWindow:] + 172
2 UIKit 0x000000018a5d5ca4 __85-[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:]_block_invoke + 88
3 UIKit 0x000000018a5d5b8c -[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:] + 460
4 UIKit 0x000000018a5d5cc0 __85-[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:]_block_invoke + 116
5 UIKit 0x000000018a5d5b8c -[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:] + 460
6 UIKit 0x000000018a5d5cc0 __85-[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:]_block_invoke + 116
7 UIKit 0x000000018a5d5b8c -[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:] + 460
8 UIKit 0x000000018a8e85ac __UIViewWillBeRemovedFromSuperview + 460
9 UIKit 0x000000018a5d4abc -[UIView(Hierarchy) removeFromSuperview] + 96
10 UIKit 0x000000018a935b7c __71-[UIPresentationController _initViewHierarchyForPresentationSuperview:]_block_invoke596 + 344
11 UIKit 0x000000018a9306f8 -[UIPresentationController transitionDidFinish:] + 124
12 UIKit 0x000000018aa584d8 -[_UICurrentContextPresentationController transitionDidFinish:] + 44
13 UIKit 0x000000018a933d9c __56-[UIPresentationController runTransitionForCurrentState]_block_invoke_2 + 188
14 UIKit 0x000000018a70b668 -[_UIViewControllerTransitionContext completeTransition:] + 116
15 UIKit 0x000000018a70b454 -[UITransitionView notifyDidCompleteTransition:] + 252
16 UIKit 0x000000018a70af38 -[UITransitionView _didCompleteTransition:] + 1404
17 UIKit 0x000000018a70a9a8 -[UITransitionView _transitionDidStop:finished:] + 124
18 UIKit 0x000000018a616d3c -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 312
19 UIKit 0x000000018a616bc4 -[UIViewAnimationState animationDidStop:finished:] + 108
20 QuartzCore 0x0000000189dddc2c CA::Layer::run_animation_callbacks(void*) + 284
21 libdispatch.dylib 0x000000019a3a96a8 _dispatch_client_callout + 16
22 libdispatch.dylib 0x000000019a3aedb0 _dispatch_main_queue_callback_4CF + 1844
23 CoreFoundation 0x00000001850001f8 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
24 CoreFoundation 0x0000000184ffe060 __CFRunLoopRun + 1628
25 CoreFoundation 0x0000000184f2cca0 CFRunLoopRunSpecific + 384
26 GraphicsServices 0x000000018ff94088 GSEventRunModal + 180
27 UIKit 0x000000018a644ffc UIApplicationMain + 204
28 MyApp 0x0000000100093918 main (main.m:16)
29 libdyld.dylib 0x000000019a3da8b8 <redacted> + 4
Binary Images:
...
Unless I find an easier approach, this will be the way to go for now for me. It may be useful to create a script which pulls the call stacks from the log file, removes the leading whitespace and inserts those into the crash log.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With