寄存器
用于解決處理器與內(nèi)存之前的數(shù)據(jù)存儲效率問題存在的。
IA-32平臺寄存器核心組有下面幾種.
- 通用寄存器 8個32位寄存器 用于存儲正在訪問的數(shù)據(jù)
- 段寄存器 6個16位寄存器 用于處理內(nèi)存訪問
- 指令指針 單一的32位寄存器 指向要執(zhí)行的下一條指令碼
- 浮點數(shù)據(jù) 8個80位寄存器 用于浮點數(shù)學數(shù)據(jù)
- 控制 5個32位寄存器 用于確定處理器的操作模式
- 調(diào)試 8個32位寄存器 用于在調(diào)試處理器時包含信息
通用寄存器
用于臨時的存儲數(shù)據(jù)。
-
eax
用于操作數(shù)和結(jié)果數(shù)據(jù)的累加器 -
ebx
指向數(shù)據(jù)內(nèi)存段中數(shù)據(jù)的指針 -
ecx
字符串和循環(huán)操作的計數(shù)器 -
edx
I/O指針 -
edi
用于字符串的目標的數(shù)據(jù)指針 -
esi
用于字符串操作的源的數(shù)據(jù)指針 -
esp
堆棧指針 -
ebp
堆棧數(shù)據(jù)指針
32位eax
、ebx、ecx、edx 可以通過16位和8位名稱引用
段寄存器
用于引用內(nèi)存位置。ia-32處理器允許3種不同的訪問系統(tǒng)內(nèi)存方法:
- 平坦內(nèi)存模式 所有指令數(shù)據(jù)堆棧都包含在相同的地址空間中
- 分段內(nèi)存模式 把內(nèi)存劃分為獨立段的組
- 實地址內(nèi)存模式 此模式下所有的段寄存器都指向零線性地址凡怎,不會被程序改動。
可用的段寄存器
- cs 代碼段
包含指向內(nèi)存中代碼段的指針 代碼段是內(nèi)存中存儲指令碼的位置 程序不能顯示的加載改變cs寄存器 - ss 堆棧段 包含傳遞給程序中的函數(shù)和過程的數(shù)據(jù)值
- ds 數(shù)據(jù)段
- es 附加段指針
- fs 附加段指針
- gs 附加段指針
每個段寄存器都是16位赊抖,包含指向內(nèi)存特定段的起始位置的指針统倒。
指令指針寄存器
EIP寄存器有時稱為程序計數(shù)器,用于跟蹤要執(zhí)行的下一條指令碼氛雪。應(yīng)用程序不能直接修改指令指針房匆。
控制寄存器
-
CR0
控制操作模式和處理器狀態(tài)的系統(tǒng)標志 -
CR1
沒有被使用 -
CR2
內(nèi)存頁面錯誤信息 -
CR3
內(nèi)存頁面目錄信息 -
CR4
支持處理器特性和說明處理器特性能力的標志
標志
處理器標志用于確定操作是否成功。
標志被分為3組:
- 狀態(tài)標志
- 控制標志
- 系統(tǒng)標志
狀態(tài)標志
-
cf
進位標志 -
pf
奇偶校驗標志
用于表明數(shù)學操作中結(jié)果寄存器是否包含錯誤數(shù)據(jù)报亩,如果結(jié)果中為1的位的總數(shù)是偶數(shù)浴鸿,奇偶校驗標志設(shè)置為1,否則為0 -
zf
零標志 如果操作結(jié)果為零弦追,零標志就被設(shè)置為1岳链,經(jīng)常用作確定數(shù)學操作結(jié)果是否為零 -
af
輔助進位標志 如果寄存器的第三位發(fā)生進位或者借位操作,該標志設(shè)置為1 -
sf
符號標志 表明結(jié)果是正值還是負值 -
of
溢出標志
控制標志
Heading
當前只有一個控制標志DF標志劲件,用于控制處理器處理字符串方式掸哑。
當DF設(shè)置為1,字符串指令自動遞減內(nèi)存地址到下一個字節(jié)零远,0為遞增
系統(tǒng)標志
略
匯編程序
程序的組成
匯編語言程序由定義好的段構(gòu)成苗分,三個最常用的段:
- 數(shù)據(jù)段 聲明帶有初始值的數(shù)據(jù)元素,程序中的變量
- bss段 聲明使用零值初始化的數(shù)據(jù)元素 常用做程序緩沖區(qū)
- 文本段 每個程序必須有文本段牵辣。用于聲明指令碼
定義段
.section
命令語句聲明段摔癣,有一個參數(shù)用于聲明段的類型。
bss段總是在文本段之后
定義起始點
_start
標簽用于表明程序的起始點。
.globl
命令聲明程序入口點供填。
程序模板:
.section .data
#<帶有初始值的數(shù)據(jù)>
.section .bss
#<使用0初始化的數(shù)據(jù)>
.section .text
.globl _start
_start:
#<Code>
例子拐云,用于讀取cpu信息
# as hello_asm.s -o hello_asm.o
# ld hello_asm.o -e _start -o hello_asm
# ./hello_asm
.section __DATA,__data #for linux .section .data
output:
.ascii "The processor Vender ID is 'xxxxxxxxxxxx'\n"
.section __TEXT,__text #for linux .section .text
.globl _start
_start:
movl $0 ,%eax
cpuid
movl $output, %edi
movl %ebx,28(%edi)
movl %edx,32(%edi)
movl %ecx,36(%edi)
movl $4,%eax
movl $1,%ebx
movl $output,%ecx
movl $42 ,%edx
int $0x80
movl $1,%eax
movl $0,%ebx
int $0x80
匯編中是用C庫函數(shù)
以printf函數(shù)為例
.section .data
output:
.asciz "The processor vender id is '%s'\n"
.section .bss
.lcomm buffer ,12
.section .text
.globl _start
_start:
movl $0,%eax
movl %ebx, (%edi)
movl %edx, 4(%edi)
movl %ecx, 8(%edi)
pushl $buffer
pushl $output
call printf
addl $8 ,%esp
pushl $0
call exit
需注意的是,在64位機上編譯上面程序需要強制32位編譯近她,用gcc -m32 、as --32選項
使用as編譯
as --32 -o printcpu.o printcpu.s
ld -dynamic-linker /lib/ld-linux.so.2 -o cpu2 -lc printcpu.o
./cpu2
使用gcc編譯
使用gcc匯編程序時需要注意gcc連接器查找的是main
標簽作為入口膳帕,所以需要將程序的.globl
命令修改成下面這樣
.section .text
.globl main
main:
gcc -m32 -o cpu printcpu.s
./cpu
第五章 傳送數(shù)據(jù)
5.1 定義數(shù)據(jù)元素
5.1.1 數(shù)據(jù)段
程序的數(shù)據(jù)段是最常見的定義數(shù)據(jù)元素的位置粘捎。
使用.data
指令來聲明數(shù)據(jù)段。在這個段中聲明的任何數(shù)據(jù)元素都保留在內(nèi)存中并可以被匯編語言程序中的指令讀取和寫入危彩。
有另一種類型的數(shù)據(jù)段.rodata
攒磨,該數(shù)據(jù)段中定義的數(shù)據(jù)元素只能只讀
數(shù)據(jù)段中定義數(shù)據(jù)需要兩個語句:一個標簽、一個命令汤徽。
標簽相當于c語言的變量名娩缰。
命令相當于c語言的類型聲明關(guān)鍵字
命令 | 數(shù)據(jù)類型 |
---|---|
.ascii | 文本字符串 |
.asciz | 以空字符結(jié)尾的文本字符串 |
.byte | 字節(jié) |
.double | 雙精度浮點數(shù) |
.float | 單精度浮點數(shù) |
.int | 32位整數(shù) |
.long | 32位整數(shù)(和int相同) |
.octa | 16字節(jié)整數(shù) |
.quad | 8字節(jié)整數(shù) |
.short | 16位整數(shù) |
.single | 單精度浮點數(shù)(和.float相同) |
舉個栗子:
output:
.ascii "the string example\n"
pi:
.float 3.1415926
;可以在一行里定義多個值
sizes:
.long 100,150,200,250,300
;把長整型(4字節(jié))值100放在sizes引用開始的內(nèi)存位置中,類似數(shù)組谒府,每個長整型占用4個字節(jié) 32位拼坎,可以通過訪問內(nèi)存位置sizes+8訪問200這個值
.section .data
msg:
.ascii "This is a test message"
factors:
.double 37.5,45.33,12.30
height:
.int 54
length:
.int 62,35,47
下面的圖描述了數(shù)據(jù)在內(nèi)存中存放的情況
73頁圖
5.1.2 定義靜態(tài)符號(常量值)
.equ
命令用于定義常量值
.equ factor ,3
.equ LINUX_SYS_CALL, 0x80
;引用靜態(tài)數(shù)據(jù)元素
movl $LINUX_SYS_CALL, %eax
5.1.3 bss段
bss段數(shù)據(jù)元素和數(shù)據(jù)段中的定義有些不同,無需聲明特定的數(shù)據(jù)類型完疫。
命令 | 描述 |
---|---|
.comm | 聲明未初始化的數(shù)據(jù)的通用內(nèi)存區(qū)域 |
.lcomm | 聲明未初始化的數(shù)據(jù)的本地通用內(nèi)存區(qū)域 |
本地通用內(nèi)存區(qū)域是為不會從本地匯編代碼之外進行訪問的數(shù)據(jù)保留的泰鸡。
命令格式: .comm symbol, length
例子:待定,未明白
.section .bss
5.2 傳遞數(shù)據(jù)元素
注:在csapp里對此指令的講述更好
MOV
指令用作通用數(shù)據(jù)傳送指令壳鹤。用于在內(nèi)存和寄存器之間傳送數(shù)據(jù)盛龄。
5.2.1 mov指令
MOV
基本格式:
movx source, dest
根據(jù)數(shù)據(jù)元素的長度不同movx有以下變種
- movl 用于32位長字值
- movw 用于16位字值
- movb 用于8位字節(jié)值
把32位eax寄存器值傳送給32位ebx寄存器:movl %eax, %ebx
對于16位寄存器:movw %ax,%bx
對于8位寄存器:movb %al,%bl
mov的操作指令可用于以下類型的傳送:
- 立即數(shù)傳送給通用寄存器
- 立即數(shù)傳送給內(nèi)存
- 通用寄存器傳送給另一個通用寄存器
- 通用寄存器傳送給段寄存器
- 段寄存器傳送給通用寄存器
- 通用寄存器傳送給控制寄存器
- 控制寄存器傳送給通用寄存器
- 通用寄存器傳送給調(diào)試寄存器
- 調(diào)試寄存器傳送給通用寄存器
- 內(nèi)存位置傳送給通用寄存器
- 內(nèi)存位置傳送給段寄存器
- 通用寄存器傳送給內(nèi)存位置
- 段寄存器傳送給內(nèi)存位置
5.2.2 立即數(shù)傳送給寄存器和內(nèi)存
立即數(shù)的每個值前面需要加上$
符號
例子:
movl $0, %eax ;move the value 0 to he eax register
movl $0x80, %ebx ;move the hexadecimal value 80 to the ebx register
movl $100, height ;move the value 100 to the height memory location
5.2.3 在寄存器之間傳遞數(shù)據(jù)
8個通用寄存器eax,ebx,ecx,edx,edi,esi,ebp,esp是用于保存數(shù)據(jù)的最常用寄存器,這些寄存器內(nèi)容可以傳送給可用的任何其他類型寄存器芳誓,和通用寄存器不同余舶,專用寄存器只能傳送給通用寄存器,或者接收通用寄存器傳送過來的內(nèi)容锹淌。
movl %eax, %ecx # move 32-bits
movw %ax, %cx #move 16-bits
5.2.4 在內(nèi)存和寄存器之間傳送數(shù)據(jù)
1 把數(shù)值從內(nèi)存?zhèn)魉偷郊拇嫫?/p>
movl value, %eax
指令把value標簽指定的內(nèi)存位置的數(shù)值傳送給eax,傳送value標簽引用的內(nèi)存位置開始的4字節(jié)數(shù)據(jù)匿值,movb
用于1字節(jié),movw
用于2字節(jié)
.section .data
value:
.int 1
.section .text
.globl _start
_start:
nop
movl value, %ecx
movl $1, %eax
movl $0, %ebx
int $0x80
2 把數(shù)值從寄存器傳送給內(nèi)存
movl %ecx, value
指令把ecx寄存器里的4字節(jié)數(shù)據(jù)傳送給value標簽指定內(nèi)存位置
3 使用變址的內(nèi)存位置
values:
.int 10,15,20,25,30,35,40,45,50,55,60
變址內(nèi)存模式葛圃,內(nèi)存位置由以下因素確定
- 基址
- 添加到基址上的偏移地址
- 數(shù)據(jù)元素的長度
- 確定選擇哪個數(shù)據(jù)元素的變址
表達式的格式:base_address (offset_address,index,size)
獲取的數(shù)值位于:base_address+offset_address_index * size
例子千扔,引用values數(shù)組中20:
movl $2, %edi
movl values(, %edi,4), %eax
例子,輸出values數(shù)組中的數(shù)值
.section .data
output:
.asciz "The value is %d\n"
values:
.int 10,15,20,25,30,35,40,45,50,55,60
.section .text
.globl _start
_start:
nop
movl $0,%edi
loop:
movl values(,%edi,4),%eax
pushl %eax
pushl $output
call printf
addl $8,%esp
inc %edi
cmpl $11,%edi
jne loop
movl $0, %ebx
movl $1, %eax
int $0x80
#mac下
.section __DATA,__data
output:
.asciz "The value is %d\n"
values:
.int 10,15,20,25,30,35,40,45,50,55,60
.section __TEXT,__text
.globl _main
_main:
nop
movl $0,%edi
loop:
movl values(,%edi,4),%eax
pushl %eax
pushl $output
call _printf
addl $8,%esp
inc %edi
cmpl $11,%edi
jne loop
movl $0, %ebx
movl $1, %eax
int $0x80
4 使用寄存器間接尋址
使用指針訪問存儲在內(nèi)存位置中的數(shù)據(jù)稱為間接尋址(indirect addressing)
當使用標簽引用內(nèi)存位置中包含的數(shù)值時库正,可以通過在指令中的標簽加上$
符號獲取數(shù)值的內(nèi)存位置的地址
movl $values, %edi
用于把values標簽引用的內(nèi)存地址傳送給edi寄存器
在平坦內(nèi)存模型中曲楚,所有內(nèi)存地址都是使用32位數(shù)字表示的
movl %ebx, (%edi)
如果edi寄存器外邊沒有括號,指令只是把ebx寄存器內(nèi)容加載到edi寄存器中褥符,如果edi寄存器外邊有括號龙誊,那么指令就把ebx寄存器的內(nèi)容傳送給edi寄存器中包含的內(nèi)存位置
movl %edx, 4(%edi)
這條指令把edx值放在edi寄存器指向位置之后的4字節(jié)內(nèi)存位置,也可以放到相反方向:
movl %edx, -4(%edi)
此處有疑問書中寫的是-4(&edi)
演示間接尋址模式:
;? as -gstabs -o movtest.o mvotest4.s
;? ld movtest.o -e _start -o movtest
.section .data
values:
.int 10,15,20,25,30,35,40,45,50,55,60
.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
可以通過echo命令查看程序退出碼
? asm ./movtest
? asm echo $?
100
5.3 條件傳送指令
條件傳送指令功能是mov指令發(fā)生在特定的條件下喷楣。
舊式的匯編語言中會看到下面代碼:
dec %ecx ;ecx遞增1 如果ecx寄存器沒有溢出 進位標志沒有設(shè)置位1
jz continue ;jnc指令跳轉(zhuǎn)continue
movl $0, %ecx ;如果溢出了 ecx設(shè)置為0
continue:
條件傳送指令可以避免處理jmp指令趟大,提高速度
5.3.1 cmov 指令
指令格式如下: cmovx source, destination
x是一個或者兩個字母的代碼鹤树,表示將觸發(fā)傳送操作的條件,條件取決于eflags寄存器當前值:
EFLAGS | 名稱 | 描述 |
---|---|---|
CF | 進位(carry)標志 | 進位或者借位 |
OF | 溢出overflow標志 | 整數(shù)值過大或者過小 |
PF | 奇偶校驗parity標志 | 寄存器包含數(shù)學操作造成的錯誤數(shù)據(jù) |
SF | 符號標志 | 指出結(jié)果為正還是負 |
ZF | 零zero標志 |
5.3.2 使用comv指令
下面是選擇整數(shù)中最大一個的程序
.section .data
output:
.asciz "The largest value is %d\n"
values:
.int 105,222,23,56,52,445,25,53,117,5
.section .text
.globl main
main:
nop
movl values, %ebx
movl $1, %edi
loop:
movl values(,%edi,4), %eax
cmp %ebx,%eax
cmova %eax,%ebx
inc %edi
cmp $10, %edi
jne loop
pushl %ebx
pushl $output
call printf
addl $8, %esp
pushl $0
call exit
5.4交換數(shù)據(jù)
如果需要交換兩個寄存器的數(shù)據(jù)逊朽,指令碼就像下面這樣:
movl %eax, %ecx
movl %ebx, %eax
movl %ecx, %ebx
需要增加一個臨時保存數(shù)據(jù)的寄存器來交換數(shù)據(jù)罕伯,數(shù)據(jù)交換指令就解決了這個問題
5.4.1
指令 | 描述 |
---|---|
XCHG | 在兩個寄存器之間或者寄存器和內(nèi)存位置之間交換 |
BSWAP | 反轉(zhuǎn)一個32位寄存器中的字節(jié)順序 |
XADD | 交換兩個值并且把總和存儲在目標操作數(shù)中 |
CMPXCHG | 把一個值和一個外部值進行比較,并且交換它和另一個值 |
CMPXCHG8B | 比較兩個64位值并且交換他們 |
1. XCHG
在兩個通用寄存器之間或者寄存器和內(nèi)存位置之間交換數(shù)據(jù)值叽讳。
格式如下:
xchg operand1, operand2
operand1或者operand2可以是通用寄存器追他,也可以是內(nèi)存位置(但是二者不能都是內(nèi)存位置),可以對任何通用的8岛蚤,16邑狸,32位寄存器使用這個命令,但是兩個操作數(shù)的長度必須相同
當一個操作數(shù)是內(nèi)存位置時涤妒,處理器LOCK信號被自動標明单雾,防止在交換過程中任何其他處理器訪問這個內(nèi)存位置。
XCHG指令可能對程序性能有不良影響
2. BSWAP
第0-7位和第24-31位進行交換她紫,第8-15位和第6-23位交換
例子:
.section .text
.globl _start
_start:
nop
movl $0x12345678, %ebx
bswap %ebx
movl $1, %eax
int $0x80
運行結(jié)果:
? asm gcc -o swaptest -gstabs swaptest.s
? asm ./swaptest
? asm gdb -q swaptest
Reading symbols from swaptest...done.
(gdb) break *main+1
Breakpoint 1 at 0x80483bc: file swaptest.s, line 5.
(gdb) run
Starting program: /home/dodola/asm/swaptest
Breakpoint 1, main () at swaptest.s:5
5 movl $0x12345678, %ebx
(gdb) step
6 bswap %ebx
(gdb) print/x $ebx
$1 = 0x12345678
(gdb) step
7 movl $1, %eax
(gdb) print/x $ebx
$2 = 0x78563412
(gdb)
3. XADD
XADD指令用于交換兩個寄存器或者內(nèi)存位置和寄存器的值硅堆,把兩個值相加,然后把結(jié)果存儲在目標位置(寄存器或者內(nèi)存位置)犁苏,指令格式:
xadd source, destination
其中source必須是寄存器硬萍,destination可以是寄存器也可以是內(nèi)存位置,并且destination包含相加的結(jié)果围详。
4. CMPXCHG
//待
5. CMPXCHG8B
//待
5.4.2 使用數(shù)據(jù)交換指令
冒泡排序
.section .data
values:
.int 105,235,61,315,134,221,53,145,117,5
.section .text
.globl main
main:
movl $values,%esi
movl $9,%ecx
movl $9,%ebx
loop:
movl (%esi), %eax
cmp %eax, 4(%esi)
jge skip
xchg %eax, 4(%esi)
movl %eax,(%esi)
skip:
add $4,%esi
dec %ebx
jnz loop
dec %ecx
jz end
movl $values, %esi
movl %ecx, %ebx
jmp loop
end:
movl $1, %eax
movl $0,%ebx
int $0x80