【版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet,文章仅供学习交流,请勿用于商业用途】
前面讲了定义数据元素,既然定义了数据元素,那么就需要知道如何处理这些数据元素。数据元素位于内存中,并且处理器很多指令要使用寄存器,所以处理数据元素的第一个步骤就是在内存和寄存器之间传送它们。数据传送指令为mov,其为汇编语言中最常用的指令之一。
mov指令的基本格式如下:
movx source, dest
其中source和dest的值可以是内存地址、存储在内存中的数值、指令语句定义的数据值、或寄存器。GNU汇编器的mov指令必须要声明传送的数据元素的长度,通过把一个附加字符添加到mov之后来指明该长度,所以mov指令就变成了movx,其中x可以是这些字符:l(32位)、w(16位)、b(8位)。
但是使用mov指令时,不是所有位置都可以传送给所有位置,是由一些限制的,下面看看mov指令可以做位置的值传送。
movl $0, %eax #把0值传送给寄存器eax
movl $100, var #把值100传送到内存var的位置
在每个值前面加上$符号,表示该值是立即数,立即数也可以是16进制,如0x40,0xff。
在寄存器之间传送数据是最快的传送方式,所以数据尽可能保存在处理器寄存器中,这样可以减少访问内存位置所花时间。通用寄存器的内容可以传送给其它任何类型的寄存器,而专用寄存器(控制、调试、段寄存器)的内容只能和通用寄存器的内容相互传送。
movl %eax, %ecx #把eax寄存器中数据传送到ecx寄存器。
movl value, %eax # value指定内存位置
movl %eax, value # 把eax寄存器中4字节数据传送到value指定的内存位置。
使用变址的内存位置。如下,在一个命令中多内存指定多个值:
values:
.int 10,20,30,40,50,60,70
这样就创建了存放在内存中连续的一系列数据值(类似于高级语言的数组)。在引用数组中的数据时,必须使用变址系统来确定你要访问的那一个内存位置。内存位置由下面表达式来确定:
base_addr(offset_addr, index, size)
其中为0的值可以忽略,但仍然需要逗号作占位符。offset_addr和index的必须是寄存器,size的值可以是数字。如需要引用前面给出的values数组中的值40,是可以使用如下指令将values内存开始的第4个值传送到eax寄存器中:
movl $3, %edi # 类似于C语言,下标从0开始。
movl values(, %edi, 4), %eax
使用寄存器间接寻址。寄存器不但可以保存数据,也可以保存内存地址,使用寄存器中内存地址访问内存位置的数据称为间接寻址。当使用变量引用的内存位置的数据时,通过在指令中变量前面加$符号就可以获得其内存位置的地址(类似于C语言取地址符号&)。下面命令把变量引用的内存地址传送给edi寄存器:
movl $value, %edi
命令
movl %ebx, (%edi)
把ebx寄存器中的值传送给edi寄存器中包含的内存位置,如果没有括号,指令就把ebx寄存器中的值加载到edi寄存器。
命令
movl %edx, 4(%edi)
把edx寄存器中的值存放到edi寄存器指向位置之后4个字节的内存地址中。数字可以是负值,如果为-4则存放到之前4个字节的内存位置中。
下面给一个寄存器间接寻址的示例:
.section .data
values:
.int 10, 20, 30, 40, 50, 60, 70, 80, 90
.section .text
.globl _start
_start:
nop
movl values, %eax
movl $values, %edi
movl $100, 4(%edi)
movl $1, %edi
movl values(, %edi, 4), %ebx
movl $1, %eax
int $0x80
为了节省编译时间,我们编写一个Makefile文件:
# Makefile for linux as
SRC_BIN=mov
SRC=$(wildcard *.s)
SRC_OBJ=$(SRC:.s=.o)
all: $(SRC_BIN)
$(SRC_BIN): $(SRC_OBJ)
ld -o $@ $<
$(SRC_OBJ): $(SRC)
$(AS) -o $@ $< --gstabs
clean:
$(RM) $(SRC_OBJ) $(SRC_BIN) *~
.PHONY:
all clean
编译链接生成可执行文件。
$ make
as -o mov.o mov.s --gstabs
ld -o mov mov.o
现在调试运行该程序后:
$ gdb mov
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-60.el6)
...
Reading symbols from /home/allen/as/2_mov/mov...done.
(gdb) b _start
Breakpoint 1 at 0x8048074: file mov.s, line 8.
(gdb) r
Starting program: /home/allen/as/2_mov/mov
Breakpoint 1, _start () at mov.s:8
8 nop
(gdb)
首先查看内存位置values内存中的值:
(gdb) x/9d &values
0x804909c <values>: 10 20 30 40
0x80490ac <values+16>: 50 60 70 80
0x80490bc <values+32>: 90
(gdb)
在查看寄存器的值:
(gdb) info register
eax 0x0 0
ecx 0x0 0
edx 0x0 0
ebx 0x0 0
esp 0xbffff3e0 0xbffff3e0
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)
然后单步运行该程序,把第一个数据元素从values数组加载到寄存器eax:
(gdb) s
9 movl values, %eax
(gdb) print $eax
$1 = 0
(gdb) s
10 movl $values, %edi
(gdb) print $eax
$2 = 10
(gdb)
可以看到eax寄存器的值为数组的第一个元素,继续单步执行,监视被加载待edi寄存器中的values内存地址:
(gdb) n
11 movl $100, 4(%edi)
(gdb) n
12 movl $1, %edi
(gdb) print/x $edi
$3 = 0x804909c
(gdb)
可以看到该地址和values的地址一样。下一条汇编代码
movl $100, 4(%edi)
将100传送到edi寄存器指向的地址的四字节之后的内存地址,也就是values数组的第二个数据元素。
(gdb) x/9d &values
0x804909c <values>: 10 100 30 40
0x80490ac <values+16>: 50 60 70 80
0x80490bc <values+32>: 90
(gdb)
通过x命令看到数组第二个元素已经更改了。
后面汇编代码是将数组第二个元素100放到ebx寄存器中,将系统调用号1(SYS_exit)放到eax中,最后执行中断。这样持续的退出码就应该是100。在执行mov程序之后在shell中查看检查该值:
$ ./mov
$ echo $?
100
$