3

我已经从 iOS 应用程序的 dSYM 文件中解析出地址、文件名和行号。我基本上有一个将地址映射到文件名和行号的表,这对调试很有帮助。

为了获得actual lookup address,我使用崩溃报告中的堆栈跟踪地址并使用此答案中指定的公式:https ://stackoverflow.com/a/13576028/2758234 。所以像这样。

(actual lookup address) 
= (stack trace address) + (virtual memory slide) - (image load address)

我使用那个地址并在我的桌子上查找它。我得到的文件名是正确的,但是行号始终指向被调用的函数或方法的末尾,而不是在堆栈跟踪上调用以下函数的实际行。

我在某处读过,不记得在哪里,必须取消标记帧地址,因为它们对齐以使系统指针大小加倍。所以对于 32 位系统,指针大小是 4 字节,所以我们使用 8 字节去标记,使用如下公式:

(de-tagged address) = (tagged address) & ~(sizeof(uintptr_t)*2 - 1)

其中uintptr_t是用于 Objective-C 中指针的数据类型。

这样做之后,查找排序工作,但我必须做一些事情,比如找到小于或等于去标签地址的最近地址

问题 #1
为什么我必须取消标记堆栈帧地址?为什么堆栈跟踪中的地址没有指向正确的位置?

问题 #2
有时在崩溃报告中似乎缺少帧。例如,如果function1()调用function2()which callsfunction3()which callsfunction4(),在我的堆栈跟踪中,我将看到如下内容:

0  Exception
1  function4()
2  function3()
4  function1()

并且(上面的第 2 帧)的堆栈跟踪地址function3()甚至没有指向正确的行号(但它是正确的文件),即使在取消标记之后也是如此。即使我让 Xcode 表示崩溃报告,我也看到了这一点。

为什么会这样?

4

1 回答 1

4

对于问题 #1,iOS 崩溃报告中的地址包含三个需要考虑的组成部分:应用程序的原始加载地址、应用程序启动时添加到该地址的随机滑动值以及在二进制。在崩溃报告的末尾,应该有一行显示二进制文件的实际加载地址。

要计算幻灯片,您需要从崩溃报告中获取实际加载地址并减去原始加载地址。这会告诉您应用到此特定应用程序启动的随机滑动值。

我不确定你是如何得出你的表的——问题可能就在那里。您可能需要使用 lldb 进行仔细检查。您可以将您的应用程序加载到 lldb 并告诉 lldb 它应该加载到地址 0x140000(这将是您的崩溃报告中的实际加载地址,不要担心幻灯片和原始加载地址)

% xcrun lldb
(lldb) target create -d -a armv7 /path/to/myapp.app
(lldb) target modules load -f myapp __TEXT 0x140000

现在 lldb 已将您的二进制文件加载到此崩溃报告的实际加载地址。您可以在 lldb 中执行所有常用查询,例如

(lldb) image lookup -v -a 0x144100

对地址 0x144100(可能出现在您的崩溃报告中)进行详细查找。

您还可以在 lldb 中使用target modules dump line-table. 例如,我编译了一个 hello-world Mac 应用程序:

(lldb) tar mod dump line-table a.c
Line table for /tmp/a.c in `a.out
0x0000000100000f20: /tmp/a.c:3
0x0000000100000f2f: /tmp/a.c:4:5
0x0000000100000f39: /tmp/a.c:5:1
0x0000000100000f44: /tmp/a.c:5:1
(lldb) 

我可以更改二进制文件的加载地址并尝试再次转储行表:

(lldb) tar mod load -f a.out __TEXT 0x200000000
section '__TEXT' loaded at 0x200000000
(lldb) tar mod dump line-table a.c
Line table for /tmp/a.c in `a.out
0x0000000200000f20: /tmp/a.c:3
0x0000000200000f2f: /tmp/a.c:4:5
0x0000000200000f39: /tmp/a.c:5:1
0x0000000200000f44: /tmp/a.c:5:1    
(lldb) 

我不确定我是否理解您对地址的去标签所做的事情。调用堆栈上的地址是这些函数的返回地址,而不是调用指令 - 所以这些可能指向实际方法调用/调度源代码行之后的行,但是当您查看源代码时通常很容易理解代码。如果您所有的查找都指向方法的结尾,我认为您的查找方案可能有问题。

至于问题 #2,如果帧 #0(当前正在执行的帧)是未设置堆栈帧的叶函数或正在设置的过程中,则帧 #1 的展开有时会有点棘手一个堆栈帧。在这些情况下,可以跳过第 1 帧。但是一旦你超过了第 1 帧,尤其是在手臂上,展开不应错过任何帧。

当一个被标记的函数调用另一个函数时,有一个非常边缘的皱纹,noreturn函数的最后一条指令可能是一个调用——没有函数结尾——因为它知道它永远不会再次获得控制权。很不常见。但是在这种情况下,一个简单的符号会给你一个指向内存中下一个函数的第一条指令的指针。调试器等人在进行符号/源代码行查找时使用了一种技巧,即从返回地址中减去 1 来回避这个问题,但这并不是临时符号化器通常需要担心的事情。并且您必须小心不要对当前执行的函数(第 0 帧)执行 decr-pc 技巧,因为函数可能刚刚开始执行,并且您不想将 pc 备份到前一个函数中并错误地符号化.

于 2013-09-08T13:51:50.873 回答