推动和改变 %esp 帧指针
pushing and changing of %esp frame pointer
我有一个小程序,用 C 写的,echo():
/* Read input line and write it back */
void echo() {
char buf[8]; /* Way too small! */
gets(buf);
puts(buf);
}
对应的汇编代码:
1 echo:
2 pushl %ebp //Save %ebp on stack
3 movl %esp, %ebp
4 pushl %ebx //Save %ebx
5 subl , %esp //Allocate 20 bytes on stack
6 leal -12(%ebp), %ebx //Compute buf as %ebp-12
7 movl %ebx, (%esp) //Store buf at top of stack
8 call gets //Call gets
9 movl %ebx, (%esp) //Store buf at top of stack
10 call puts //Call puts
11 addl , %esp //Deallocate stack space
12 popl %ebx //Restore %ebx
13 popl %ebp //Restore %ebp
14 ret //Return
我有几个问题。
为什么%esp分配了20个字节? buf是8个字节,为什么多了12个?
return 地址就在我们推送 %ebp 的正上方? (假设我们把堆栈倒过来画,它向下增长)旧的 %ebp 的目的是什么(当前的 %ebp 指向第 3 行的结果)?
如果我想更改 return 地址(通过输入超过 12 个字节的任何内容),它将更改 echo() returns 的位置。更改旧的 %ebp(即 return 地址前 4 个字节)的结果是什么?是否有可能通过更改旧的 %ebp 来更改 return 地址或回显 returns 的位置?
%ebp 的目的是什么?我知道它是帧指针,但是那是什么?
编译器是否有可能将缓冲区放在不紧邻旧 %ebp 存储位置的位置?就像我们声明 buf[8] 但它将它存储在 -16(%ebp) 而不是第 6 行的 -12(%ebp)?
*从 Computer Systems - A programmer's Perspective 2nd ed 复制的 c 代码和程序集
** 使用 gets()
因为缓冲区溢出
之所以分配20个字节是为了栈对齐。 GCC 4.5+ 生成的代码确保被调用者的本地堆栈 space 与 16 字节边界对齐,以确保编译后的代码可以以明确定义的方式在堆栈上执行对齐的 SSE 加载和存储。出于这个原因,在这种情况下,编译器需要丢弃一些堆栈-space,以确保 gets
/puts
获得正确对齐的帧。
本质上,这就是堆栈的样子,其中每一行都是一个 4 字节的字,除了表示 16 字节地址边界的 ---
行:
...
Saved EIP from caller
Saved EBP
---
Saved EBX # This is where echo's frame starts
buf
buf
Unused
---
Unused
Parameter to gets/puts
Saved EIP
Saved EBP
---
... # This is where gets'/puts' frame starts
正如您希望从我出色的 ASCII 图形中看到的那样,如果不是 "unused" 部分,gets
/puts
将得到未对齐的帧。但是,还要注意,并非 12 个字节未使用;其中4个为参数预留。
Is it ever possible for the compiler to put the buffer somewhere that is not right next to where the old %ebp is stored? Like if we declare buf[8] but it stores it at -16(%ebp) instead of -12(%ebp) on line 6?
当然可以。编译器可以随意组织堆栈。为了可预测地进行缓冲区溢出,您必须查看程序的特定编译二进制文件。
至于 EBP 的目的是什么(从而回答您的问题 2、3 和 5),请参阅有关如何组织调用堆栈的任何介绍性文字,例如 the Wikipedia article.
我有一个小程序,用 C 写的,echo():
/* Read input line and write it back */
void echo() {
char buf[8]; /* Way too small! */
gets(buf);
puts(buf);
}
对应的汇编代码:
1 echo:
2 pushl %ebp //Save %ebp on stack
3 movl %esp, %ebp
4 pushl %ebx //Save %ebx
5 subl , %esp //Allocate 20 bytes on stack
6 leal -12(%ebp), %ebx //Compute buf as %ebp-12
7 movl %ebx, (%esp) //Store buf at top of stack
8 call gets //Call gets
9 movl %ebx, (%esp) //Store buf at top of stack
10 call puts //Call puts
11 addl , %esp //Deallocate stack space
12 popl %ebx //Restore %ebx
13 popl %ebp //Restore %ebp
14 ret //Return
我有几个问题。
为什么%esp分配了20个字节? buf是8个字节,为什么多了12个?
return 地址就在我们推送 %ebp 的正上方? (假设我们把堆栈倒过来画,它向下增长)旧的 %ebp 的目的是什么(当前的 %ebp 指向第 3 行的结果)?
如果我想更改 return 地址(通过输入超过 12 个字节的任何内容),它将更改 echo() returns 的位置。更改旧的 %ebp(即 return 地址前 4 个字节)的结果是什么?是否有可能通过更改旧的 %ebp 来更改 return 地址或回显 returns 的位置?
%ebp 的目的是什么?我知道它是帧指针,但是那是什么?
编译器是否有可能将缓冲区放在不紧邻旧 %ebp 存储位置的位置?就像我们声明 buf[8] 但它将它存储在 -16(%ebp) 而不是第 6 行的 -12(%ebp)?
*从 Computer Systems - A programmer's Perspective 2nd ed 复制的 c 代码和程序集
** 使用 gets()
因为缓冲区溢出
之所以分配20个字节是为了栈对齐。 GCC 4.5+ 生成的代码确保被调用者的本地堆栈 space 与 16 字节边界对齐,以确保编译后的代码可以以明确定义的方式在堆栈上执行对齐的 SSE 加载和存储。出于这个原因,在这种情况下,编译器需要丢弃一些堆栈-space,以确保 gets
/puts
获得正确对齐的帧。
本质上,这就是堆栈的样子,其中每一行都是一个 4 字节的字,除了表示 16 字节地址边界的 ---
行:
...
Saved EIP from caller
Saved EBP
---
Saved EBX # This is where echo's frame starts
buf
buf
Unused
---
Unused
Parameter to gets/puts
Saved EIP
Saved EBP
---
... # This is where gets'/puts' frame starts
正如您希望从我出色的 ASCII 图形中看到的那样,如果不是 "unused" 部分,gets
/puts
将得到未对齐的帧。但是,还要注意,并非 12 个字节未使用;其中4个为参数预留。
Is it ever possible for the compiler to put the buffer somewhere that is not right next to where the old %ebp is stored? Like if we declare buf[8] but it stores it at -16(%ebp) instead of -12(%ebp) on line 6?
当然可以。编译器可以随意组织堆栈。为了可预测地进行缓冲区溢出,您必须查看程序的特定编译二进制文件。
至于 EBP 的目的是什么(从而回答您的问题 2、3 和 5),请参阅有关如何组织调用堆栈的任何介绍性文字,例如 the Wikipedia article.