“奇怪”地址的原因例如main+0
, main+1
, main+3
, main+6
等等,是因为每条指令占用的字节数是可变的。例如:
main+0: push %ebp
是一个单字节指令,所以下一条指令位于main+1
。另一方面,
main+3: and $0xfffffff0,%esp
是一个三字节指令,因此之后的下一条指令位于main+6
.
而且,既然你在评论中问为什么movl
似乎需要可变数量的字节,对此的解释如下。
指令长度不仅取决于opcode(例如movl
)以及寻址模式operands以及(操作码正在操作的东西)。我没有专门检查你的代码,但我怀疑
movl $0x1,(%esp)
指令可能更短,因为不涉及偏移量 - 它只是使用esp
作为地址。而类似的东西:
movl $0x2,0x4(%esp)
需要一切movl $0x1,(%esp)
does, plus偏移量的额外字节0x4
.
事实上,这是一个调试会话,显示了我的意思:
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
c:\pax> debug
-a
0B52:0100 mov word ptr [di],7
0B52:0104 mov word ptr [di+2],8
0B52:0109 mov word ptr [di+0],7
0B52:010E
-u100,10d
0B52:0100 C7050700 MOV WORD PTR [DI],0007
0B52:0104 C745020800 MOV WORD PTR [DI+02],0008
0B52:0109 C745000700 MOV WORD PTR [DI+00],0007
-q
c:\pax> _
您可以看到带有偏移量的第二条指令实际上与没有偏移量的第一条指令不同。它多了一个字节(5 个字节而不是 4 个字节,用于保存偏移量)并且实际上具有不同的编码c745
代替c705
.
您还可以看到,可以用两种不同的方式对第一条和第三条指令进行编码,但它们基本上执行相同的操作。
The and $0xfffffff0,%esp
指导是一种强迫的方式esp
处于特定的边界上。这用于确保变量的正确对齐。现代处理器上的许多内存访问如果遵循对齐规则(例如 4 字节值必须与 4 字节边界对齐),将会更加高效。如果您不遵守这些规则,一些现代处理器甚至会引发故障。
遵循此说明后,您可以保证esp
均小于或等于其先前值and与 16 字节边界对齐。
The gs:
前缀只是意味着使用gs
段寄存器来访问内存而不是默认的。
指令mov %eax,-0xc(%ebp)
意思是获取内容ebp
寄存器,减去 12 (0xc
),然后输入值eax
进入该内存位置。
重新解释一下代码。你的function
函数基本上是一大无操作。生成的程序集仅限于堆栈帧设置和拆卸,以及使用上述方法的一些堆栈帧损坏检查%gs:14
内存位置。
它从该位置加载值(可能类似于0xdeadbeef
)进入堆栈帧,完成其工作,然后检查堆栈以确保它没有被损坏。
在这种情况下,它的工作就没什么了。所以你看到的只是功能管理的东西。
堆栈建立发生在function+0
and function+12
。之后的一切都是在中设置返回代码eax
并拆除堆栈框架,包括损坏检查。
相似地,main
包括堆栈帧设置,推送参数function
, 呼叫function
,拆除堆栈框架并退出。
注释已插入到以下代码中:
0x08048428 <main+0>: push %ebp ; save previous value.
0x08048429 <main+1>: mov %esp,%ebp ; create new stack frame.
0x0804842b <main+3>: and $0xfffffff0,%esp ; align to boundary.
0x0804842e <main+6>: sub $0x10,%esp ; make space on stack.
0x08048431 <main+9>: movl $0x3,0x8(%esp) ; push values for function.
0x08048439 <main+17>: movl $0x2,0x4(%esp)
0x08048441 <main+25>: movl $0x1,(%esp)
0x08048448 <main+32>: call 0x8048404 <function> ; and call it.
0x0804844d <main+37>: leave ; tear down frame.
0x0804844e <main+38>: ret ; and exit.
0x08048404 <func+0>: push %ebp ; save previous value.
0x08048405 <func+1>: mov %esp,%ebp ; create new stack frame.
0x08048407 <func+3>: sub $0x28,%esp ; make space on stack.
0x0804840a <func+6>: mov %gs:0x14,%eax ; get sentinel value.
0x08048410 <func+12>: mov %eax,-0xc(%ebp) ; put on stack.
0x08048413 <func+15>: xor %eax,%eax ; set return code 0.
0x08048415 <func+17>: mov -0xc(%ebp),%eax ; get sentinel from stack.
0x08048418 <func+20>: xor %gs:0x14,%eax ; compare with actual.
0x0804841f <func+27>: je <func+34> ; jump if okay.
0x08048421 <func+29>: call <_stk_chk_fl> ; otherwise corrupted stack.
0x08048426 <func+34>: leave ; tear down frame.
0x08048427 <func+35>: ret ; and exit.
我认为原因是%gs:0x14
从上面可能很明显,但为了以防万一,我将在这里详细说明。
它使用此值(哨兵)放入当前堆栈帧,以便函数中的某些内容执行一些愚蠢的操作,例如将 1024 字节写入堆栈上创建的 20 字节数组,或者在您的情况下:
char buffer1[5];
strcpy (buffer1, "Hello there, my name is Pax.");
然后哨兵将被覆盖,函数末尾的检查将检测到这一点,调用失败函数让您知道,然后可能中止以避免任何其他问题。
如果它放置0xdeadbeef
到堆栈上,这被更改为其他内容,然后是xor
with 0xdeadbeef
将产生一个非零值,该值在代码中检测到je
操作说明。
相关部分解释如下:
mov %gs:0x14,%eax ; get sentinel value.
mov %eax,-0xc(%ebp) ; put on stack.
;; Weave your function
;; magic here.
mov -0xc(%ebp),%eax ; get sentinel back from stack.
xor %gs:0x14,%eax ; compare with original value.
je stack_ok ; zero/equal means no corruption.
call stack_bad ; otherwise corrupted stack.
stack_ok: leave ; tear down frame.