Linux perf_events 注释帧指针混乱
Linux perf_events annotation frame pointer confusion
I 运行 sudo perf record -F 99 find /
后跟 sudo perf report
并选择 "Annotate fdopendir" 这是前七个说明:
push %rbp
push %rbx
mov %edi,%esi
mov %edi,%ebx
mov [=14=]x1,%edi
sub [=14=]xa8,%rsp
mov %rsp,%rbp
第一条指令似乎是在保存调用者的基帧指针。我相信说明 2 到 5 与这个问题无关,但这里是为了完整性。说明 6 和 7 让我感到困惑。 rbp 到 rsp 的分配不应该发生在之前 从 rsp 中减去 0xa8 吗?
x86-64 System V ABI 不需要制作传统/遗留堆栈框架。这看起来 接近 传统的堆栈框架设置,但这绝对不是因为在第一个 push %rbp
.
之后没有 mov %rsp, %rbp
我们看到编译器生成的代码只是将 RBP 用作临时寄存器,并用它来保存指向堆栈上局部变量的指针。这只是一个巧合的是,这恰好在 push %rbp
之后的某个时间涉及指令 mov %rsp, %rbp
。 这不是在制作栈帧。
在 x86-64 系统 V 中,RBX 和 RBP 是仅有的 2 个 "low 8" 调用保留的寄存器,因此在某些情况下可以在没有 REX 前缀的情况下使用(例如 push/pop,以及在寻址模式中使用时),节省代码大小。 GCC 更喜欢在 saving/restoring 任何 R12..R15 之前使用它们。 What registers are preserved through a linux x86-64 function call(对于指针,使用 mov
复制它们总是需要 64 位操作数大小的 REX 前缀,因此比 32 位整数节省更少,但 gcc 仍然适用于 RBX 然后 RBP ,按此顺序,当它需要 save/restore 在函数中调用保留的 regs 时。)
我的系统 (Arch Linux) 上 /lib/libc.so.6
(glibc) 的反汇编显示 fdopendir
的代码生成相似但不同。在进行函数调用之前,您过早地停止了反汇编。这阐明了为什么它需要一个调用保留的临时寄存器:它需要在整个调用中使用 reg 中的 var。
00000000000c1260 <fdopendir>:
c1260: 55 push %rbp
c1261: 89 fe mov %edi,%esi
c1263: 53 push %rbx
c1264: 89 fb mov %edi,%ebx
c1266: bf 01 00 00 00 mov [=10=]x1,%edi
c126b: 48 81 ec a8 00 00 00 sub [=10=]xa8,%rsp
c1272: 64 48 8b 04 25 28 00 00 00 mov %fs:0x28,%rax # stack-check cookie
c127b: 48 89 84 24 98 00 00 00 mov %rax,0x98(%rsp)
c1283: 31 c0 xor %eax,%eax
c1285: 48 89 e5 mov %rsp,%rbp # save a pointer
c1288: 48 89 ea mov %rbp,%rdx # and pass it as a function arg
c128b: e8 90 7d 02 00 callq e9020 <__fxstat>
c1290: 85 c0 test %eax,%eax
c1292: 78 6a js c12fe <fdopendir+0x9e>
c1294: 8b 44 24 18 mov 0x18(%rsp),%eax
c1298: 25 00 f0 00 00 and [=10=]xf000,%eax
c129d: 3d 00 40 00 00 cmp [=10=]x4000,%eax
c12a2: 75 4c jne c12f0 <fdopendir+0x90>
....
c12c1: 48 89 e9 mov %rbp,%rcx # pass the pointer as the 4th arg
c12c4: 89 c2 mov %eax,%edx
c12c6: 31 f6 xor %esi,%esi
c12c8: 89 df mov %ebx,%edi
c12ca: e8 d1 f7 ff ff callq c0aa0 <__alloc_dir>
c12cf: 48 8b 8c 24 98 00 00 00 mov 0x98(%rsp),%rcx
c12d7: 64 48 33 0c 25 28 00 00 00 xor %fs:0x28,%rcx # check the stack cookie
c12e0: 75 38 jne c131a <fdopendir+0xba>
c12e2: 48 81 c4 a8 00 00 00 add [=10=]xa8,%rsp
c12e9: 5b pop %rbx
c12ea: 5d pop %rbp
c12eb: c3 retq
这是非常愚蠢的代码生成; gcc 可以在第二次需要它时简单地使用 mov %rsp, %rcx
。我将其称为优化失败。它从不需要调用保留寄存器中的指针,因为它始终知道它相对于 RSP 的位置。
(即使它没有恰好达到 RSP+0,lea something(%rsp), %rdx
和 lea something(%rsp), %rcx
在需要两次时也完全没问题,总成本可能低于 saving/restoring RBP + 所需的 mov
说明。)
或者它可以使用 mov 0x18(%rbp),%eax
而不是 rsp 在该寻址模式下保存一个字节的代码大小。避免在函数调用之间直接引用 RSP 可减少英特尔 CPU 需要插入的堆栈同步微指令的数量。
I 运行 sudo perf record -F 99 find /
后跟 sudo perf report
并选择 "Annotate fdopendir" 这是前七个说明:
push %rbp
push %rbx
mov %edi,%esi
mov %edi,%ebx
mov [=14=]x1,%edi
sub [=14=]xa8,%rsp
mov %rsp,%rbp
第一条指令似乎是在保存调用者的基帧指针。我相信说明 2 到 5 与这个问题无关,但这里是为了完整性。说明 6 和 7 让我感到困惑。 rbp 到 rsp 的分配不应该发生在之前 从 rsp 中减去 0xa8 吗?
x86-64 System V ABI 不需要制作传统/遗留堆栈框架。这看起来 接近 传统的堆栈框架设置,但这绝对不是因为在第一个 push %rbp
.
mov %rsp, %rbp
我们看到编译器生成的代码只是将 RBP 用作临时寄存器,并用它来保存指向堆栈上局部变量的指针。这只是一个巧合的是,这恰好在 push %rbp
之后的某个时间涉及指令 mov %rsp, %rbp
。 这不是在制作栈帧。
在 x86-64 系统 V 中,RBX 和 RBP 是仅有的 2 个 "low 8" 调用保留的寄存器,因此在某些情况下可以在没有 REX 前缀的情况下使用(例如 push/pop,以及在寻址模式中使用时),节省代码大小。 GCC 更喜欢在 saving/restoring 任何 R12..R15 之前使用它们。 What registers are preserved through a linux x86-64 function call(对于指针,使用 mov
复制它们总是需要 64 位操作数大小的 REX 前缀,因此比 32 位整数节省更少,但 gcc 仍然适用于 RBX 然后 RBP ,按此顺序,当它需要 save/restore 在函数中调用保留的 regs 时。)
我的系统 (Arch Linux) 上 /lib/libc.so.6
(glibc) 的反汇编显示 fdopendir
的代码生成相似但不同。在进行函数调用之前,您过早地停止了反汇编。这阐明了为什么它需要一个调用保留的临时寄存器:它需要在整个调用中使用 reg 中的 var。
00000000000c1260 <fdopendir>:
c1260: 55 push %rbp
c1261: 89 fe mov %edi,%esi
c1263: 53 push %rbx
c1264: 89 fb mov %edi,%ebx
c1266: bf 01 00 00 00 mov [=10=]x1,%edi
c126b: 48 81 ec a8 00 00 00 sub [=10=]xa8,%rsp
c1272: 64 48 8b 04 25 28 00 00 00 mov %fs:0x28,%rax # stack-check cookie
c127b: 48 89 84 24 98 00 00 00 mov %rax,0x98(%rsp)
c1283: 31 c0 xor %eax,%eax
c1285: 48 89 e5 mov %rsp,%rbp # save a pointer
c1288: 48 89 ea mov %rbp,%rdx # and pass it as a function arg
c128b: e8 90 7d 02 00 callq e9020 <__fxstat>
c1290: 85 c0 test %eax,%eax
c1292: 78 6a js c12fe <fdopendir+0x9e>
c1294: 8b 44 24 18 mov 0x18(%rsp),%eax
c1298: 25 00 f0 00 00 and [=10=]xf000,%eax
c129d: 3d 00 40 00 00 cmp [=10=]x4000,%eax
c12a2: 75 4c jne c12f0 <fdopendir+0x90>
....
c12c1: 48 89 e9 mov %rbp,%rcx # pass the pointer as the 4th arg
c12c4: 89 c2 mov %eax,%edx
c12c6: 31 f6 xor %esi,%esi
c12c8: 89 df mov %ebx,%edi
c12ca: e8 d1 f7 ff ff callq c0aa0 <__alloc_dir>
c12cf: 48 8b 8c 24 98 00 00 00 mov 0x98(%rsp),%rcx
c12d7: 64 48 33 0c 25 28 00 00 00 xor %fs:0x28,%rcx # check the stack cookie
c12e0: 75 38 jne c131a <fdopendir+0xba>
c12e2: 48 81 c4 a8 00 00 00 add [=10=]xa8,%rsp
c12e9: 5b pop %rbx
c12ea: 5d pop %rbp
c12eb: c3 retq
这是非常愚蠢的代码生成; gcc 可以在第二次需要它时简单地使用 mov %rsp, %rcx
。我将其称为优化失败。它从不需要调用保留寄存器中的指针,因为它始终知道它相对于 RSP 的位置。
(即使它没有恰好达到 RSP+0,lea something(%rsp), %rdx
和 lea something(%rsp), %rcx
在需要两次时也完全没问题,总成本可能低于 saving/restoring RBP + 所需的 mov
说明。)
或者它可以使用 mov 0x18(%rbp),%eax
而不是 rsp 在该寻址模式下保存一个字节的代码大小。避免在函数调用之间直接引用 RSP 可减少英特尔 CPU 需要插入的堆栈同步微指令的数量。