现在,让我们尝试在Linux的GCC 4.4.1编译器中编译相同的C / C ++代码:
gcc 1.c -o 1接下来,在IDA反汇编程序的帮助下,让我们看看main()函数是如何创建的。 IDA和MSVC一样,都使用Intel-syntax5。
main proc near
var_10 = dword ptr -10h
push ebp
mov ebp, esp
and esp, 0FFFFFFF0h
sub esp, 10h
mov eax, offset aHelloWorld ; "hello, world\n"
mov [esp+10h+var_10], eax
call _printf
mov eax, 0
leave
retn
main endp
有两行我根本听不懂:
and esp, 0FFFFFFF0h
sub esp, 10h
据我了解,我们将
0FFFFFFF0h
(等于-16)值添加到ESP中,以便将堆栈对齐到16字节边界以进行优化。我的问题是:为什么我们要加-16然后减去16到堆栈中?对我来说似乎毫无意义,难道我们不能直接减去32吗?
其次,如果我很好理解:
该程序以EBP = ESP开头,因为没有在堆栈上。
然后将EBP推入堆栈。假设程序为64位,则ESP现在为EBP-8(因为64位)。现在我们有了ESP!= EBP。
然后我们将ESP的内容复制到EBP中。因此,我们有EBP = ESP和EBP = fristEBP(程序启动时为EBP)-8.
为什么我们需要修改EBP的值? PUSH指令应该更改ESP的值,而不是EBP的值,所以为什么在函数prolog上不修改EBP值会有什么问题?
所以现在有了EBP = ESP,两者都是fristEBP(程序启动时为EBP)-8。所以现在我们将-16添加到堆栈中,因此ESP变为ESP-16(如果我们考虑一直在堆栈中添加-8,则为ESP-24)。 。
-16字节边界与-24有什么关系?
为什么还要用
sub esp, 10h
从堆栈中再次减去16?注意:对不起英语,对不起,如果我问的是愚蠢的问题,这本书还不够清晰,我在网上找不到解释。
#1 楼
在第一个操作码中不是add
。是and
。因此,它将清除地址中最后一个字节的低位半字节。这是完成对齐的方式,而不需要添加任何内容。只有稍后您sub
16才有空间容纳局部变量。为什么我们需要修改EBP的值?
EBP
存储初始ESP
值。 EBP
指向当前的堆栈框架。这是随函数创建的局部变量的地方。在修改EBP
之前,它已存储在堆栈中,以便我们可以在离开函数之前对其进行恢复。评论
好的,我明白了,但是如果我们想清除较低的半字节,为什么还要添加0FFFFFFF0h而不是0FFFFFFF0F?
–黑暗
18年5月31日在9:48
低位半字节是3-0位,那么为什么我们要在7-4位上放0来做到这一点?
–PawełŁukasik
18年5月31日在9:53
我看错了,对不起
–马克
18年5月31日在10:01
好的别担心!
–PawełŁukasik
18年5月31日在10:22
评论
并且不添加将堆栈对齐到16个字节边界,即123456a1和fffffff0将导致123456a0不会有丢失数据的风险吗?