Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to "manually" symbolicate [NSThread callStackSymbols] (get the start address for atos) (iOS)

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.

like image 566
Rainer Schwarze Avatar asked Dec 08 '15 14:12

Rainer Schwarze


1 Answers

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.

like image 145
Rainer Schwarze Avatar answered Oct 12 '22 21:10

Rainer Schwarze