说我有这个
main.cpp
:
#include <iostream>
int foo() {
return 123;
}
int main() {
int a = 1234;
std::cout << foo() + a << std::endl;
return 0;
}
然后我编译它:
clang++ -g -O0 main.cpp
现在我有了这个
a.out
,我可以通过dsymutil -s a.out
查看符号表:
...
[ 11] 00000001 2e (N_BNSYM ) 01 0000 0000000100003ce0
[ 12] 00000002 24 (N_FUN ) 01 0000 0000000100003ce0 '__Z3foov'
[ 13] 00000001 24 (N_FUN ) 00 0000 0000000000000008
[ 14] 00000001 4e (N_ENSYM ) 01 0000 0000000100003ce0
...
所以,这个
foo
函数位于 3ce0
。
现在,使用 LLDB 在此函数处设置断点:
% lldb a.out
(lldb) target create "a.out"
Current executable set to '/Users/royshi/tmp/a.out' (arm64).
(lldb) b foo
Breakpoint 1: where = a.out`foo() + 4 at main.cpp:4:3, address = 0x0000000100003ce4
(lldb)
现在,请注意断点位于
(0x000000010000)3ce4
,它显示为“where = a.out`foo() + 4 at main.cpp:4:3”,这与 3ce4
地址一致。
所以显然断点超出
foo
的地址 (3ce0
) 4 个字节。
我想知道为什么会有 4 个字节的差异?
编辑:
按照上面的步骤,我尝试删除 dSYM 文件夹(将其移走),然后再次设置断点,它最终位于
foo
的地址(即没有额外的 4 个字节)。所以,它似乎与调试符号以某种方式相关。
# moving a.out.dSYM to somewhere unaccessible
% mv a.out.dSYM tmp2
% lldb a.out
(lldb) target create "a.out"
Current executable set to '/Users/royshi/tmp/a.out' (arm64).
(lldb) b foo
Breakpoint 1: where = a.out`foo(), address = 0x0000000100003ce0
(lldb) ^D
jasonharper 明白这样做的原因。如果您在函数的最开始处停止,在运行序言并设置新的堆栈帧之前,那么堆栈展开可能看起来不正确,并且在序言之后以堆栈形式给出的变量值将全部为关闭,如果您不知道发生了什么,这会令人不安。因此,默认情况下,当您在函数符号上设置断点时,lldb 将其向前移动到函数序言的末尾。
至于为什么调试信息可能会影响 lldb 如何确定序言结束位置:事实证明,调试信息中的 DWARF 行表有一个“序言结束”标记。然而,如果您没有调试信息,lldb 会回退到从函数开头扫描指令并将它们与“已知的序言模式”进行匹配。后者更像是有根据的猜测,而编译器可以准确地知道这一点。所以这两种计算有时确实不同。
顺便说一句,如果您不希望出现这种行为,请执行以下操作:
(lldb) break set -n <my_function> --skip-prologue 0