【版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途】
堆栈是内存中用于存放数据的专门保留的区域,该区域的数据存放和删除方式比较特殊。一般内存区域数据元素按照连续的方式存放到数据段,在数据段中最低内存开始存放,然后向更高的内存位置依次存放。而堆栈保留在内存区域的末尾位置,并且在当数据存放在堆栈中时,它向下增长。程序运行时使用的任何命令行参数都被送入堆栈中,并且堆栈指针被设置为指向数据元素的底部。
当每个数据被添加到堆栈数据区域中时,使用一个指针跟踪堆栈的开始位置。寄存器esp包含堆栈起始位置的内存地址,所以在编写程序时,不允许把esp寄存器用作其它用途,如果堆栈起始位置丢失了,程序运行将出现意想不到的结果。在编写汇编语言时,主要工作时跟踪堆栈中的数据,不需要手动去设置堆栈指针,IA-32指令集包含指令用于访问堆栈数据的指令。
把新的数据放到堆栈中称为入栈,其对应指令为push,格式为:
pushx source
x是一个字符,表示数据长度,类似mov指令,但只能是l(32位)和w(16位)。source为push操作的数据元素,可以是16位或32位寄存器、16或32位内存值、16位段寄存器、8位或16位或32位立即数值。
从堆栈中获取数据使用pop命令,其格式为:
popx dest
x表示数据长度,dest为接受数据的目标,可以是16位或32为寄存器、16或32为内存位置,16位段寄存器。
如下一个示例可以帮助理解堆栈是如何工作的:
#pushpop.s
.section .data
data:
.int 111
.section .text
.globl _start
_start:
nop
movl $91, %eax
movw $45, %bx
movb $22, %cl
pushl %eax
pushw %bx
pushl %ecx
pushl data
pushl $data
popl %edx
popl %edx
popl %edx
popw %dx
popl %edx
movl $0, %ebx
movl $1, %eax
int $0x80
使用上一节Makefile编译链接程序,在调试器中运行该程序,并且监视esp寄存器的值。
(gdb) b _start
Breakpoint 1 at 0x8048074: file pushpop.s, line 9.
(gdb) r
Starting program: /home/allen/as/3_pushpop/pushpop
Breakpoint 1, _start () at pushpop.s:9
9 nop
(gdb) info registers
eax 0x0 0
ecx 0x0 0
edx 0x0 0
ebx 0x0 0
esp 0xbffff3c0 0xbffff3c0
ebp 0x0 0x0
esi 0x0 0
edi 0x0 0
eip 0x8048074 0x8048074 <_start>
eflags 0x212 [ AF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x0 0
(gdb)
然后单步执行,查看寄存器值:
(gdb) print/x $esp
$3 = 0xbffff3c0
(gdb) s
12 movb $22, %cl
(gdb) print/x $esp
$4 = 0xbffff3c0
(gdb) s
14 pushl %eax
(gdb) print/x $esp
$5 = 0xbffff3c0
(gdb) s
15 pushw %bx
(gdb) print/x $esp
$6 = 0xbffff3bc
(gdb) s
16 pushl %ecx
(gdb) print/x $esp
$7 = 0xbffff3ba
(gdb) s
17 pushl data
(gdb) print/x $esp
$8 = 0xbffff3b6
(gdb) s
18 pushl $data
(gdb) print/x $esp
$9 = 0xbffff3b2
(gdb)
可以发现esp指针移动了,没push一次,esp寄存器递减了,指向新的堆栈起始位置。这说明堆栈在内存中确实向下扩展了。
执行完所有pop命令:
(gdb) s
24 popl %edx
(gdb) s
26 movl $0, %ebx
(gdb) print/x $esp
$12 = 0xbffff3c0
(gdb)
发现在所有数据出栈之后,堆栈其实位置恢复到之前数据进栈的地址了。pop命令每弹出一个数据,esp寄存器递增,显示堆栈长度在内存中向上扩展了。