linux平台学x86汇编(六):数据的传送

【版权声明:尊重原创,转载请保留出处: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
$


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。