Linux汇编之堆栈操作

汇编 2015年03月05日 ,

LINUX汇编堆栈操作指令

在前面介绍的数据传送指令条件传送指令数据交换指令都可以操作内存元素。枫竹梦介绍另一种内存元素——堆栈。在Linux汇编语言(ASM)中,堆栈是最被误解的项目之一,经常给程序员带来麻烦。

堆栈工作方式

在数据段中,数据元素按照连续的方式存放到数据段中,从最低的位置开始存放,向更高的内存位置依次存放。堆栈的行为与此恰恰相反,堆栈被保留在内存区的末尾位置,并且当数据放在堆栈中时,它向低地址进行增长。

堆栈

堆栈底部包含程序运行时由操作系统存放到这里的数据元素。运行程序时使用的任何参数都被传送到堆栈中,接下来存放程序数据的区域。是典型的“后进先出”型结构。

形象的理解堆栈结构,可以想象平时把洗好的盘子会依次放在之前洗的的上面,而在使用时,是从上面的一个开始使用。堆栈与此类似。

作为汇编语言程序员,责任是跟踪堆栈中有什么数据,并且正确地获得数据。在函数间传递参数时,堆栈是重要的桥梁。

压入和弹出数据

PUSH

把新的数据存到堆栈中称为压入(pushing),指令为PUSH。

PUSHX source

X是一个字符的代码,表示数据的长度:

  • l表示32位数据
  • w表示16位数据
  • b表示8位数据

其中source可以是如下类型数据:

  • 16位寄存器值
  • 32位寄存器值
  • 16位内存值
  • 32位内存值
  • 16位段寄存器
  • 8位立即数
  • 16位立即数
  • 32位立即数

长度代码必须和指令中声明的数据元素匹配,如:

pushl %eax # 将32位的eax寄存器中值压入堆栈
pushw %ax # 将16位的ax寄存器中的值压入堆栈
pushl $100 # 将立即数100作为32位值压入堆栈
pushl data # 将data引用的32位值压入堆栈
pushl $data # 将data内存地址值压入堆栈

POP

将堆栈中的数据取出称为弹出(pop),指令为POP。

POPX destination

其中X的含义与PUSHX中的X相同。

其中destination可以是如下类型数据:

  • 16位寄存器
  • 32位寄存器
  • 16位段寄存器
  • 16内存地址
  • 32位内存地址

长度代码必须和指令中声明的数据元素匹配,不能将堆栈中的数据弹出到立即数中。POP指令的几个例子如:

popl %ecx # 将堆栈中接下来的32位数据弹出到ECX寄存器
popw %cx # 将堆栈中接下来的16位数据弹出到CX寄存器
popl value # 将堆栈中接下来的32位数据弹出到value引用的内存位置

结合数据的压入与弹出,实例将不数据压入,然后再弹出,代码如下:

# pushpop.s - By furzoom @ Mar 4, 2015
.section .data
data:
.int 100
.section .text
.globl _start
_start:
nop
movl $24420, %ecx
movw $350, %bx
movb $100, %al
pushl %ecx
pushw %bx
pushl %eax
pushl data
pushl $data

popl %eax
popl %eax
popl %eax
popw %ax
popl %eax

movl $1, %eax
movl $0, %ebx
int $0x80

这个示例代码的主要目的是为了了解堆栈的工作方式,当使用PUSH或者POP指令时,监测ESP寄存器的变化。

使用-gstabs参数汇编该程序,连接后进行调试程序如下:

[mn@furzoom asm]$ as -gstabs -o pushpop.o pushpop.s
[mn@furzoom asm]$ ld -o pushpop pushpop.o
[mn@furzoom asm]$ gdb -q pushpop
Reading symbols from /home/mn/Desktop/Documents/asm/pushpop...done.
(gdb) break *_start+1
Breakpoint 1 at 0x8048075: file pushpop.s, line 9.
(gdb) break 18
Breakpoint 2 at 0x804808f: file pushpop.s, line 18.
(gdb) break 24
Breakpoint 3 at 0x8048095: file pushpop.s, line 24.
(gdb) run
Starting program: /home/mn/Desktop/Documents/asm/pushpop

Breakpoint 1, _start () at pushpop.s:9
9         movl $24420, %ecx
(gdb) print /x $esp
$1 = 0xbfffe7b0
(gdb) cont
Continuing.

Breakpoint 2, _start () at pushpop.s:18
18        popl %eax
(gdb) print /x $esp
$2 = 0xbfffe79e
(gdb) s
19        popl %eax
(gdb) print /x $eax
$3 = 0x80490a4
(gdb) print /x &data
$4 = 0x80490a4
(gdb) s
20        popl %eax
(gdb) print $eax
$5 = 100
(gdb) s
21        popw %ax
(gdb) print $eax
$6 = 100
(gdb) s
22        popl %eax
(gdb) print $eax
$7 = 350
(gdb) s

Breakpoint 3, _start () at pushpop.s:24
24        movl $1, %eax
(gdb) print $eax
$8 = 24420
(gdb)

可以发现在压入数据之前,ESP寄存器为值为0xbfffe7b0,当压入所有数据后ESP寄存器为值为0xbfffe79e。当数据元素被添加到堆栈中时,ESP寄存器的值会减少,表明堆栈在内存中向下扩展的。同时当从堆栈中弹出数据时,ESP寄存器的值会增大。

压入和弹出所有寄存器

对于需要保存所有寄存器的状态时,除也将每一个寄存器的状态压入堆栈外,还提供了更方便的指令来完成这样的任务。PUSHA指令按EAX、ECX、EDX、EBX、ESP、EBP、ESI、EDI的顺序压入堆栈。POPA按照相反的顺序弹出堆栈。一些汇编器会将PUSHAPOPA强制执行16位操作数,而PUSHADPOPAD强制执行32位操作数,而另一些汇编器将其看作是相同的指令。具体指令如下:

指令 描述
PUSHA/POPA 压入或者弹出所有16位通用寄存器
PUSHAD/POPAD 压入或者弹出所有32位通用寄存器
PUSHF/POPF 压入或者弹出EFLAGS寄存器的低16位
PUSHFD/POPFD 压入或者弹出EFLAGS寄存器的全部32位

通过如下实例代码进行分析:

# pusha.s - By furzoom @ Mar 4, 2015
.section .text
.globl _start
_start:
nop
movl $1, %eax
movl $4, %edi
pusha
movl $5, %eax
popa
movl $1, %eax
movl $0, %ebx
int $0x80

使用-gstabs参数汇编该程序,连接后进行调试程序如下:

[mn@furzoom asm]$ as -gstabs -o pusha.o pusha.s
[mn@furzoom asm]$ ld -o pusha pusha.o
[mn@furzoom asm]$ gdb -q pusha
Reading symbols from /home/mn/Desktop/Documents/asm/pusha...done.
(gdb) break *_start+1
Breakpoint 1 at 0x8048055: file pusha.s, line 6.
(gdb) run
Starting program: /home/mn/Desktop/Documents/asm/pusha

Breakpoint 1, _start () at pusha.s:6
6         movl $1, %eax
(gdb) s
7         movl $4, %edi
(gdb) s
8         pusha
(gdb) print /x $esp
$1 = 0xbfffe7b0
(gdb) s
9         movl $5, %eax
(gdb) print /x $esp
$2 = 0xbfffe790
(gdb) s
10        popa
(gdb) print /d $eax
$3 = 5
(gdb) s
11        movl $1, %eax
(gdb) print /d $eax
$4 = 1
(gdb) print /x $esp
$5 = 0xbfffe7b0
(gdb)

手动使用ESP和EBP寄存器

PUSHPOP不是把数据压入和弹出堆栈的唯一指令。也可以通过ESP寄存器作为内存指针,手工地把数据存放到堆栈中。

很多程序把ESP寄存器的值复制到EBP寄存器,而不是使用ESP寄存器本身。汇编语言中常常将EBP指针指向函数的工作堆栈的基址。访问参数时,指令相对于EBP值引用这些参数。

(完)

如无特别说明,本站文章皆为原创,若要转载,务必请注明以下原文信息:
日志标题:《Linux汇编之堆栈操作》
日志链接:http://furzoom.com/linux-asm-stack-instruction/
博客名称:枫竹梦

发表评论

插入图片

NOTICE1:请申请gravatar头像,没有头像的评论可能不会被回复!

回到顶部