CFI指令意味着什么? (还有一些问题)

好的,这将是一个很长的问题。 我试图理解“缓冲区溢出”是如何工作的。 我正在阅读通过aleph1 粉碎堆栈以获得乐趣和利润 ,并且刚刚获得了以下代码的反汇编:

void function(int a, int b, int c) { char buffer1[5]; char buffer2[10]; } void main() { function(1,2,3); } 

使用GCC的-S标志的disameembly给了我:

  .file "example1.c" .text .globl function .type function, @function function: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $48, %rsp movl %edi, -36(%rbp) movl %esi, -40(%rbp) movl %edx, -44(%rbp) movq %fs:40, %rax movq %rax, -8(%rbp) xorl %eax, %eax movq -8(%rbp), %rax xorq %fs:40, %rax je .L2 call __stack_chk_fail .L2: leave .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size function, .-function .globl main .type main, @function main: .LFB1: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl $3, %edx movl $2, %esi movl $1, %edi call function popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE1: .size main, .-main .ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2" .section .note.GNU-stack,"",@progbits 

.cfi的文章中没有.cfi指令,我猜他们当时没有使用它们。 我已经在SO上阅读了这个问题 ,我得到它们被GCC用于exception处理。 我还读了另一个关于SO的问题 ,我得到了.LFB0,.LFE0,.LFE1和.LFB1是标签,但我有以下疑问:

  1. 我知道.cfi指令用于exception处理,但我不明白它们的含义。 我一直在这里 ,我看到一些定义,如:

.cfi_def_cfa寄存器,偏移量

.cfi_def_cfa将计算CFA的规则定义为:从寄存器获取地址并向其添加偏移量。

但是,如果你看一下我上面的反汇编, 你找不到任何注册名称 (比如EAX,EBX等),而你在那里找到一个数字(我一般都找到’6’)而且我不喜欢我不知道那应该是一个寄存器。 特别是,任何人都可以解释一下.cfi_def_cfa_offset 16.cfi_offset 6, -16.cfi_def_cfa_register 6.cfi_def_cfa 7, 8是什么意思? 此外, CFA是什么意思? 我问这个是因为主要是在书籍/论文中,程序序言如下:

  pushl %ebp movl %esp,%ebp subl $20,%esp 

但是,现在我认为现代计算机中的程序序列如下:

  .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $48, %rsp 

最初我认为使用CFI指令代替sub助记符来设置偏移量但事实并非如此; 尽管使用了CFI指令,仍然使用sub命令。

  1. 我知道每个程序都有标签。 但是,为什么程序中有多个嵌套标签? 在我的情况下,main有.LFB1和.LFE2标签。 多个标签需要什么? 类似地, function过程具有标签.LFB0,.L2和.LFE0

  2. 两个程序的最后3行似乎用于一些内务处理function(告诉程序的大小,可能?)但我不确定它们是什么意思。 任何人都可以解释他们的意思和用途是什么?

编辑:

(再添一个问题)

  1. CFI指令是否会占用任何空间? 因为在过程“函数”中,每个int参数占用4个字节而它的数量是3,所以所有参数在内存中占用12个字节。 接下来,第一个char数组占用8个字节(向上舍入为5个字节到8个字节),下一个char数组占用12个字节(向上舍入为10个字节到12个字节),因此整个char数组占用20个字节。 对这些全部,参数和局部变量求和只需要12 + 20 = 32个字节。

    但是在过程“function”中,编译器减去48个字节来存储值。 为什么?

根据您在逆向工程中的请求,我将我的评论内容作为答案放在这里(我不知道这是否会继续,因为我看到一个激烈的竞争,向下投票并在那里向上投票)

Lindy Dancer回答了cfi and cfa meanscall frame information )和(c all frame address

.L表示Google中x64 GCC名称中各种花絮的标签。以下格式的所有标签都以.L开头,并以a numeral结尾.L1 , .L2 , .L....infinity是标签

根据Google和一些早期的SO答案, BF表示Function-Begin, EF表示FUNCTION-END

所以.LBF0 , .LBF1 . LBF.....infinity .LBF0 , .LBF1 . LBF.....infinity.LFE0 ,......., .LFE....infinity

表示函数开始,函数在每个函数中结束,编译器可能需要这些函数来处理一些内部需求,所以你应该忘记它们,除非有一个非常严重的需要深入研究编译器内部

另一个标签.L2用于解决函数中的分支指令je

 je .L2 

此外,每个编译器都将对参数和本地的访问权限对齐并填充到某个边界

我不能确定,但​​是对于GCC,我认为x64默认对齐是16个字节,所以如果你请求像奇怪的预订那样

char foo [ 5 ]或
BYTE blah [ 10 ]

即使对于x86 ,索引5 and 10也不对齐

对于5 x86 compiler will assign 8个字节s and for 10 16 bytes

像明智的x64 gcc might assign 16 bytes为每个请求x64 gcc might assign 16 bytes

你实际上不应该担心为什么编译器会做它的function

当你试图理解汇编的逻辑时,只关注地址

如果编译器决定will put x at rbp +/- X它也会also access it at the same location变量的范围或生命周期also access it at the same location

CFI代表呼叫帧信息 。 这是编译器描述函数中发生的事情的方式。 调试器可以使用它来呈现调用堆栈,链接器可以合成exception表,进行堆栈深度分析以及其他类似的事情。

实际上,它描述了存储处理器寄存器等资源以及返回地址的位置。

CFA代表调用帧地址 ,它表示调用者函数的堆栈指针位置的地址。 这需要获取有关堆栈上下一帧的信息。

48是跳过参数和本地人。 5字节数组在8字节边界上对齐,而10字节在16字节边界上对齐。 参数每个需要8个字节,因此参数的3 * 8加上本地的8 + 16给出了24 + 24或48.您可以通过询问每个事物的地址在gdb中看到它。