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