[汇编代码]
main:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $32, %esp
movl $5, 20(%esp)
movl $3, 24(%esp)
movl 24(%esp), %eax
movl %eax, 4(%esp)
movl 20(%esp), %eax
movl %eax, (%esp)
call add
movl %eax, 28(%esp)
movl $0, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.globl add
.type add, @function
add:
.LFB1:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $16, %esp
movl 12(%ebp), %eax
movl 8(%ebp), %edx
addl %edx, %eax
movl %eax, -4(%ebp)
movl -4(%ebp), %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
[源代码]
int add(int k, int l);
int main(int argc, char **argv) {
int a, b, ret;
a = 5;
b = 3;
ret = add(a, b);
return 0;
}
int add(int k, int l) {
int x;
x = k + l;
return x;
}
我正在研究汇编语言级别的c函数调用约定。
如您所知,.cfi用于添加调试信息。我已经阅读了一些cfi文章并了解了每个指令的含义。
在上面的汇编代码中,.cfi_def_cfa_offset 8
和.cfi_offset 5 -8
指令连续出现。这种情况发生在'main'函数和'add'函数中。
但是,我不知道为什么会这样。我所知道的是.cfi_def_cfa_offset
和.cfi_offset
用于制作保留内存来存储调试信息。在此代码中,该偏移量首先设置为+8,第二个设置为-8。结果是......没有剩余的存储空间来存储cfi。我是对的吗?
我认为堆栈段的工作方式是这样的。
.cfi_startproc
|-------------|
| whatever | <- %esp = CFA ↑ increase address
|-------------|
| | ↓ stack grow
|_____________|
.pushl %ebp
|-------------|
| whatever |
|-------------|
| %ebp | <- %esp
|_____________|
.cfi_def_cfa_offset 8
|-------------|
| whatever | <- %esp
|-------------|
| whatever |
|-------------|
| %ebp |
|-------------|
.cfi_offset 5 -8
|-------------|
| whatever |
|-------------|
| whatever |
|-------------|
| %ebp | <- %esp
|-------------|
subl $32, %esp
|-------------|
| whatever |
|-------------|
| %ebp |
|-------------|
| |
|-------------|
| |
|-------------|
| |
|-------------|
| |
|-------------|
| |
|-------------|
| |
|-------------|
| |
|-------------|
| | <- %esp
|-------------|
movl $5, 20(%esp)
|-------------|
| whatever |
|-------------|
| %ebp |
|-------------|
| |
|-------------|
| |
|-------------|
| 5 |
|-------------|
| |
|-------------|
| |
|-------------|
| |
|-------------|
| |
|-------------|
| | <- %esp
|-------------|
依旧......
问题2。
在过程add
,来自调用者函数的参数被移动到被调用函数寄存器。
movl 12(%ebp), %eax
movl 8(%ebp), %edx
但是,在我的计算中,8(%ebp)并未指向调用者堆栈的顶部。因为,
1)在pushl %ebp
,%esp减去4
2)在cfi_offset 5, -8
,%esp被删除8(这样,我忽略了.cfi_def_cfa_offset 8
。我不确定)
因此,调用函数堆栈的顶部应该是12(%ebp),而8(%ebp)指向调用函数的存储基指针。
我不知道我不知道的地方......我需要你的帮助。
- 增加
What do the CFI directives mean? (and some more questions)
这个问题与我几乎相似。但没有人能清楚地回答这个问题。
答案 0 :(得分:6)
.cfi
directives 不生成任何汇编代码。它们不执行,并且不根本不会更改调用框架的布局。
相反,他们告诉需要展开堆栈(调试器,异常退绕)的工具有关框架的结构(以及如何展开它)。这些信息不会与说明书一起存储,而是存储在程序的另一部分中(参见注释1)。
让我们看一下这个片段:
main:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $32, %esp
movl $5, 20(%esp)
movl $3, 24(%esp)
movl 24(%esp), %eax
movl %eax, 4(%esp)
movl 20(%esp), %eax
movl %eax, (%esp)
call add
movl %eax, 28(%esp)
movl $0, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
汇编程序将汇总.text
段中的指令并编译另一部分.cfi
或.eh_frame
中的.debug_frame
指令:
$ gcc -m32 -g test.s -c -o a.out
$ objdump -d a.out
[...]
00000000 <main>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 e4 f0 and $0xfffffff0,%esp
6: 83 ec 20 sub $0x20,%esp
9: c7 44 24 14 05 00 00 movl $0x5,0x14(%esp)
10: 00
11: c7 44 24 18 03 00 00 movl $0x3,0x18(%esp)
18: 00
19: 8b 44 24 18 mov 0x18(%esp),%eax
1d: 89 44 24 04 mov %eax,0x4(%esp)
21: 8b 44 24 14 mov 0x14(%esp),%eax
25: 89 04 24 mov %eax,(%esp)
28: e8 fc ff ff ff call 29 <main+0x29>
2d: 89 44 24 1c mov %eax,0x1c(%esp)
31: b8 00 00 00 00 mov $0x0,%eax
36: c9 leave
37: c3 ret
注意main
函数的代码中只有指令的存在。 CFI在其他地方:
$ readelf -wF a.out
Contents of the .eh_frame section:
00000000 00000014 00000000 CIE "zR" cf=1 df=-4 ra=8
LOC CFA ra
00000000 esp+4 c-4
00000018 0000001c 0000001c FDE cie=00000000 pc=00000000..00000038
LOC CFA ebp ra
00000000 esp+4 u c-4
00000001 esp+8 c-8 c-4
00000003 ebp+8 c-8 c-4
00000037 esp+4 u c-4
CFI是描述帧布局的信息(不是本机CPU指令)。
例如,让我们看一下这个片段:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
.cfi_startproc
cfi_startproc
初始化函数的CFI。此时,CFA(Canonical Frame Address,它是调用者帧中%rsp的地址)由%esp + 4
给出(因为调用者在call
指令中按下了返回地址):< / p>
whatever <- CFA
return address (ra) <- %esp
CFI指令在.eh_frame
:
LOC CFA ebp ra
00000000 esp+4 u c-4
.cfi_def_cfa_offset
和.cfi_offset
在pushl %ebp
指令处,此问题不再适用:cfa ≠ %esp + 4
,因为%esp
已更改。在这条指令之后,我们有cfa = %esp + 8
。调试器需要知道这一点,并且.cfi_def_cfa_offset 8
指令正在.eh_frame
部分为调试器生成合适的信息:.cfi_def_cfa_offset 8
将偏移量设置为cfa = %esp + 8
中的8。
whatever <- CFA = %esp + 8
return address (ra)
caller %ebp <- %esp (= CFA - 8)
pushl %ebp
的目的是将%ebp
的值保存在堆栈上的调用者中。调试器需要知道该值的保存位置,以便展开堆栈并恢复调用者帧。 .cfi_offset 5, -8
指令指示调试器注册5(%ebp
)由cfa - 8
中的上一条指令保存。
此信息可在.eh_frame
表的下一个条目中找到:
LOC CFA ebp ra
[...]
00000001 esp+8 c-8 c-4
注1:在某些情况下,这些信息是调试信息的一部分,这意味着它可能在运行时不存在,如果文件未使用调试信息编译,则可能根本不存在于文件中。