【发布时间】:2014-08-19 04:05:48
【问题描述】:
好的,这将是一个很长的问题。我试图了解“缓冲区溢出”是如何工作的。我正在阅读 aleph1 的 Smashing the stack for fun and profit 并且刚刚获得了以下代码的反汇编:
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
}
void main() {
function(1,2,3);
}
使用 GCC 的 -S 标志的差异给了我:
.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 指令不在 Aleph1 的论文中,我猜当时没有使用它们。我已阅读 this question on SO 并且知道它们被 GCC 用于异常处理。我还阅读了another question on SO,我知道 .LFB0、.LFE0、.LFE1 和 .LFB1 是标签,但我有以下疑问:
- 我知道 .cfi 指令用于异常处理,但我不明白它们的含义。我一直是here,我看到了一些定义,例如:
.cfi_def_cfa 寄存器,偏移量
.cfi_def_cfa 定义了计算 CFA 的规则为:取地址来自 注册并添加偏移量。
但是,如果您查看我在上面放置的反汇编代码,您没有找到任何寄存器名称(例如 EAX、EBX 等),而是在那里找到了一个数字(I通常找到'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
但是,现在我认为现代计算机中的程序prolog如下:
.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 行似乎用于一些内务处理功能(可能是告诉过程的大小?)但我不确定它们是什么意思。谁能解释一下它们的含义和用途?
编辑:
(增加一个问题)
-
CFI 指令是否占用空间?因为在程序“function”中,每个int参数占用4个字节,个数为3,所以所有参数在内存中占用12个字节。接下来,第一个
char数组占用 8 个字节(将 5 个字节向上舍入为 8 个字节),下一个char数组占用 12 个字节(将 10 个字节向上舍入为 12 个字节),因此整个char数组占用 20 个字节。所有这些加起来,参数和局部变量只需要 12+20=32 个字节。但在“函数”过程中,编译器减去 48 个字节来存储值。为什么?
【问题讨论】:
-
这些是 GAS 识别并用于发出展开相关数据的宏。用于 C++ 的 Itanium ABI 描述了这需要什么(GCC 实现了该 ABI)。展开数据存储在其他地方(在目标文件中查找
eh-frame 部分);它本质上是通过使用指令指针作为键的关联查找来定位的。 -
如果你编译为 C 和 C++,你会得到不同的结果吗?另外,永远不要
void main()。这是int main()。 -
本页解释了使用 cfi 指令来获取 Dwarf-2 调试信息,从而避免了帧指针 - logix.cz/michal/devel/gas-cfi
-
您可以在不展开表的情况下进行编译以摆脱它们(使用
-fno-asynchronous-unwind-tables)。也就是说,它们不会以任何方式影响生成的机器代码,它们绝对不会“替换”任何sub指令。您可以放心地忽略它们。