In assembly code, how .cfi directive works?
[汇编代码]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | 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 |
[源代码]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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文章,并且知道了每个指令的含义。
在上面的汇编代码中,
但是,我不知道为什么会这样。我所知道的是
我认为堆栈段的工作方式是这样的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | .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。
在过程
1 2 | movl 12(%ebp), %eax movl 8(%ebp), %edx |
但是,在我的计算中,8(%ebp)没有指向调用程序堆栈的顶部。因为,
1)在
2)在
因此,调用方函数堆栈的顶部应以此方式为12(%ebp),并且8(%ebp)指向存储的调用方函数的基指针。
我不知道我在哪里...我需要你的帮助。
-添加
CFI指令是什么意思? (还有更多问题)
这样的问题几乎与我相似。但是没有人清楚地回答这个问题。
取而代之的是,它们告诉需要解散堆栈的工具(调试器,异常解散器)有关框架的结构(以及解散方法)。这些信息不会与指令一起存储,而是存储在程序的另一部分中(请参见注1)。
让我们看一下这个片段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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 |
汇编程序将在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | $ 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 |
请注意
1 2 3 4 5 6 7 8 9 10 11 12 13 | $ 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指令)。
例
例如,让我们看下面的代码片段:
1 2 3 4 | .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 |
1 2 | whatever <- CFA return address (ra) <- %esp |
CFI指令在
1 2 | LOC CFA ebp ra 00000000 esp+4 u c-4 |
在
1 2 3 | whatever <- CFA = %esp + 8 return address (ra) caller %ebp <- %esp (= CFA - 8) |
在
1 2 3 | LOC CFA ebp ra [...] 00000001 esp+8 c-8 c-4 |
笔记
注1:在某些情况下,这些信息是调试信息的一部分,这意味着如果未使用调试信息编译文件,则该信息可能在运行时不存在,并且可能根本不存在于文件中。