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是标签,但我有以下疑问:
- 我知道.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
命令。
-
我知道每个程序都有标签。 但是,为什么程序中有多个嵌套标签? 在我的情况下,main有.LFB1和.LFE2标签。 多个标签需要什么? 类似地,
function
过程具有标签.LFB0,.L2和.LFE0 -
两个程序的最后3行似乎用于一些内务处理function(告诉程序的大小,可能?)但我不确定它们是什么意思。 任何人都可以解释他们的意思和用途是什么?
编辑:
(再添一个问题)
-
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 means
( call 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中看到它。