數據的傳送

前面講了定義數據元素,既然定義了數據元素,那麼就需要知道如何處理這些數據元素。數據元素位於內存中,並且處理器很多指令要使用寄存器,所以處理數據元素的第一個步驟就是在內存和寄存器之間傳送它們。數據傳送指令為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
$

书籍推荐