如何“手动”符号化 [NSThread callStackSymbols](获取 atos 的起始地址)(iOS)

Posted

技术标签:

【中文标题】如何“手动”符号化 [NSThread callStackSymbols](获取 atos 的起始地址)(iOS)【英文标题】:How to "manually" symbolicate [NSThread callStackSymbols] (get the start address for atos) (iOS) 【发布时间】:2015-12-08 14:36:12 【问题描述】:

目标:

我想象征[NSThread callStackSymbols]的“输出”。

旁注:

我知道如何处理崩溃日志。但是,我需要调查一些我想查看调用堆栈的问题。不幸的是,这些天框架的地址都是<redacted>。在正确的点(或在最后 - 请参阅我的问题的结尾)造成崩溃是不可接受的,但如果我找不到其他解决方案,这将是要走的路。

我必须在设备上运行我的测试,所以我不能使用模拟器。

目前的做法:

当我这样称呼时:

NSLog(@"call stack:\n%@", [NSThread callStackSymbols]);

我得到这个输出:

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
)

(此输出中的“转换”是应用程序。)

现在我可以使用这个命令来“符号化”地址:

xcrun atos -o /path/to/Conversion.app -arch arm64 -l 0x0???

这样运行(当然,-l 的值要合适),我可以输入0x000694b5 之类的地址,它会输出-[ViewController viewDidLoad] + 128 之类的东西。当然问题是起始地址(-l 选项的值)。

当我有崩溃日志时,我可以得到这些。但是,我想在没有崩溃的情况下逃脱。

问题:

Q1:我能否在运行时确定起始地址并将其包含在日志输出中,以便将其提供给 atos -l 选项?

编辑:看起来可能是这样的:(感谢 NSProgrammer 的回答 https://***.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);

/编辑

(因为我对框架的方法调用感兴趣,所以我当然需要框架的起始地址。应用程序的起始地址经常变化(随机化),我还不知道框架的起始地址是否是随机的。 )

Q2:还有其他方法可以调查调用堆栈中的方法吗? (断点在我的场景中也相当笨拙。)

编辑:

Q3:如何符号化框架的地址?例如,我在哪里可以找到 UIKit 的 dSYM(或其他任何东西)?

(例如,我有一些东西在:~/Library/Developer/Xcode/ios\ DeviceSupport/9.1\ \(13B143\)/Symbols/System/Library/Frameworks/UIKit.framework/。我会在这里查看更多细节。)

/编辑

也许是一个解决方案:

一种方法可能是将日志输出保存到文件中,并在测试结束时导致应用程序崩溃。这样,崩溃日志将显示起始地址以及来自日志的调用堆栈信息,我应该能够符号化callStackSymbols 输出。下次我会试试的。

【问题讨论】:

如果 symbolicatecrash 不起作用,有几种方法可以做到这一点,包括 atos、dwarfdump 和 lldb。有一篇关于完整符号化过程的非常好的文章 here 可能会提供一些额外的帮助 【参考方案1】:

到目前为止,将 log sn-ps 添加到崩溃日志中效果很好,所以我现在将其添加为答案。 (当然欢迎更好的答案:-))

准备工作:

在源码的相关位置添加callStackSymbols日志记录:

NSLog(@"call stack:\n%@", [NSThread callStackSymbols]);

使应用崩溃(例如打开某个屏幕时):

strcpy(0, "000");

将日志输出重定向到文件:

FILE *logfile = freopen([pathForLog cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);

执行:

在 Xcode 中运行应用程序一次,它会安装在设备上,然后停止它。 然后直接在设备上启动app,这样Xcode就不会crash了。

使用应用程序记录调用堆栈(可以多次)。

最终使应用程序崩溃(在我的情况下,打开某个调用错误 strcpy 的屏幕)。

获取文件:

在Xcode中打开“Window -> Devices”,选择设备,选择app并下载app容器,这样就可以提取日志文件了。

在同一屏幕中打开设备日志并导出崩溃日志。

在 Xcode 中打开“Window -> Projects”,选择项目并使其在 Finder 中显示派生数据文件夹(派生数据路径右侧的小箭头按钮)。在派生数据文件夹中导航到“Build/Products/Debug-iphoneos/”并复制 MyApp.app 和 MyApp.app.dSYM。

调整崩溃日志:

对我来说,在“二进制图像:”之前添加额外的线程部分很有效。所以找到“二进制图像:”的位置。插入一行“线程 9999:”。复制并粘贴调用堆栈转储并删除前导空白列,使其看起来像这样:

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:
...

“Thread 9999:”行使symbolicatecrash 脚本想要象征下一行。我选择了 9999,所以我知道这些是我添加的部分。

运行符号化:

找到symbolicatecrash 脚本:

$ find /Applications/Xcode.app -name symbolicatecrash -type f
/Applications/Xcode.app/Contents/SharedFrameworks/DTDeviceKitBase.framework/Versions/A/Resources/symbolicatecrash
$ 

符号化命令几乎是这样的:

$ symbolicatecrash myapp.crash MyApp.app.dSYM > myapp-sym.crash

您需要设置 DEVELOPER_DIR 并预先添加脚本的路径,所以最终它看​​起来像这样:

$ 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

或包装以获得更好的观看乐趣:

$ 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

结果:

符号化的 sn-ps 现在看起来像这样:

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:
...

除非我找到更简单的方法,否则这将是我现在要走的路。创建一个从日志文件中提取调用堆栈、删除前导空格并将其插入崩溃日志的脚本可能很有用。

【讨论】:

这让我非常接近,但我从来没有让符号工作。我的解决方案是获取一个真正的崩溃日志,并用我的手动堆栈跟踪替换一个线程的内容。在该文件上运行 symbolicatecrash 有效【参考方案2】:

符号崩溃

Apple 随 XCode 一起发布了一个脚本,可以加速整个崩溃报告的符号化过程。如果您有 dSYM、您的应用程序二进制文件和崩溃报告,这可能是最简单的符号化方法。您不必担心任何地址 - 此脚本将解析整个故障转储文件并使用 ATOS 将所有地址解析为符号。

在您的系统上找到“symbolicatecrash”:

cd /Applications/Xcode.app
find . -name symbolicatecrash

如果 DEVELOPER_DIR 环境变量不存在,则导出它

export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"

将您的 .app 二进制文件、崩溃报告和 .dSYM 文件复制到一个临时文件夹(例如 ~/tmp)。 像下面的示例一样运行脚本:

/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash -v ApteligentExampleApp.crash ApteligentExampleApp.app.dSYM/

如果一切顺利,脚本应该象征您的整个崩溃文件并将结果输出到您的终端窗口。此脚本不会执行您使用 ATOS 或其他工具手动无法执行的任何操作,但它会更快地为您提供所需的内容。

Source

【讨论】:

以上是关于如何“手动”符号化 [NSThread callStackSymbols](获取 atos 的起始地址)(iOS)的主要内容,如果未能解决你的问题,请参考以下文章

NSThread

调用 [NSThread detachNewThreadSelector...] 使 UIImageView setImage 停止工作

iOS多线程篇:NSThread

16iOS多线程篇:NSThread

iOS多线程篇:NSThread简单介绍和使用

ios线程的五种使用方式