彙編筆記
暫存器register
學習組合語言,首先必須瞭解兩個知識點:暫存器和記憶體模型。
先來看暫存器。CPU
本身只負責運算,不負責儲存資料。資料一般都儲存在記憶體之中,CPU
要用的時候就去記憶體讀寫資料。但是,CPU
的運算速度遠高於記憶體的讀寫速度,為了避免被拖慢,CPU
都自帶一級快取和二級快取。基本上,CPU
快取可以看作是讀寫速度較快的記憶體。
但是,CPU
快取還是不夠快,另外資料在快取裡面的地址是不固定的,CPU
每次讀寫都要定址也會拖慢速度。因此,除了快取之外,CPU
還自帶了暫存器(register
),用來儲存最常用的資料。也就是說,那些最頻繁讀寫的資料(比如迴圈變數),都會放在暫存器裡面,CPU
優先讀寫暫存器,再由暫存器跟記憶體交換資料。
記憶體模型:Heap
-
程式執行過程中,對於動態的記憶體佔用請求(比如新建物件,或者使用
malloc
命令),系統就會從預先分配好的那段記憶體之中,劃出一部分給使用者,具體規則是從起始地址開始劃分(實際上,起始地址會有一段靜態資料,這裡忽略)。舉例來說,使用者要求得到10個位元組記憶體,那麼從起始地址0x1000
開始給他分配,一直分配到地址0x100A
,如果再要求得到22個位元組,那麼就分配到0x1020
。 -
這種因為使用者主動請求而劃分出來的記憶體區域,叫做
Heap
(堆)。它由起始地址開始,從低位(地址)向高位(地址)增長。\(Heap\) 的一個重要特點就是不會自動消失,必須手動釋放,或者由垃圾回收機制來回收
記憶體模型: Stack
-
除了
Heap
以外,其他的記憶體佔用叫做Stack
(棧)。簡單說,Stack
是由於函式執行而臨時佔用的記憶體區域。 -
系統開始執行
main
函式時,會為它在記憶體裡面建立一個幀(frame
),所有main
的內部變數(比如a
和b
)都儲存在這個幀裡面。main
函式執行結束後,該幀就會被回收,釋放所有的內部變數,不再佔用空間。 -
在函式內部在呼叫一個函式
process()
的時候,系統也會為該函式再新建一個幀,一般來說呼叫棧有多少層,就有多少幀。 -
當函式執行結束之後,它的幀會被回收,系統回到函式中斷執行的地方繼續執行上一個幀的流程。透過這個機制,可實現函式的層層呼叫,且每一層函式都可以使用自己的本地變數
-
Stack
是由記憶體區域的結束地址開始分配,從高位到地位分配
CPU
指令
使用 g++ file.cpp -S out.s
或者gcc file.cpp -S out.s
,可用C/C++
生成相應的彙編程式碼
#include<stdio.h>
void hello()
{
int a=1,b=2;
a=a+b;
printf("hello world\n");
}
int main()
{
int a=1,b=2;
a=a+b;
hello() ;
return 0;
}
.LC0:
.string "hello world"
hello():
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 1
mov DWORD PTR [rbp-8], 2
mov eax, DWORD PTR [rbp-8]
add DWORD PTR [rbp-4], eax
mov edi, OFFSET FLAT:.LC0
call puts
nop
leave
ret
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 1
mov DWORD PTR [rbp-8], 2
mov eax, DWORD PTR [rbp-8]
add DWORD PTR [rbp-4], eax
call hello()
mov eax, 0
leave
ret
push
指令
push rbp
push
是CPU
指令,rbp
是該指令的運算元.一個CPU
指令可以有0到多個運算元
暫存器
IA-32
體系結構中有10個32位和6個16位處理器暫存器。暫存器分三類:
-
通用暫存器
-
資料暫存器
-
指標暫存器
-
索引暫存器
-
-
控制暫存器
-
段暫存器
指令系統
80x86定址方式
-
定址:尋找運算元的地址。
-
定址方式:尋找運算元的方式
80x86
指令格式
- 指令助記符 +運算元1,+運算元2,+運算元3
- 指令的運算元個數可以是0,1,2,3個
如何確定偏移地址的值
- 與資料有關的十種方式,
- 立即定址
- 暫存器定址
- 直接定址
- 暫存器間接定址
- 暫存器相對定址
- 基址變址定址
- 相對基址變址定址
- 比例變址定址
- 基址比例變址定址
- 相對基址比例變址定址
- 與轉移地址有關的4種定址方式
- 段內直接定址
- 段內間接定址
- 段間直接定址
- 段間間接定址
定址方式
立即定址方式
MOV AX, 5
MOV AX, 05H
把十六進位制數05H
移動到通用暫存器AX
上
暫存器定址方式(無EA)
- 指令所要的運算元已儲存在暫存器中或者把目標數存入暫存器
MOV AX , BX
- 指令中可以引用的暫存器及其符號名稱:
8位暫存器 | AH、AL、BH、BL、CH、CL、DH、DL |
8位暫存器 | AX、BX、CX、DX、SI、DI、SP、BP和段暫存器等 |
8位暫存器 | EAX、EBX、ECX、EDX、ESI、EDI、ESP、EBP。 |
-
DST 和SRC的字長一致
-
CS不能用MOV指令改變
直接定址方式
-
指令所要的運算元存放在記憶體中,在指令中直接給出該運算元的有效地址
-
實體地址=\((DS)\times16d+EA\)
-
MOV AX , [2000H]
-
MOV AX, VALUE
-
MOV AX , [VALUE]
-
-
實體地址=\((ES)\times16d+EA\)
-
MOV AX , ES:[2000H]
-
MOV AX, ES:VALUE
-
MOV AX , ES:[VALUE]
-
在通常情況下,運算元存放在資料段中,所以,其實體地址將有資料段暫存器DS和指令中的有效地址直接形成但如果使用段超越字首,那麼運算元可存放在其他段中。如:
MOV ES:[1000H], AX
-
立即定址:
1234H
-
直接定址;
[1234H]
-
暫存器間接定址方式→EA基址/變址
MOV AX,[BX]
或MOV AX,ES:[BX]
暫存器相對定址方式→EA=基址/變址+位移量
-
MOV AX,COUNT[SI]
或MOV AX,3000H[SI]
-
MOV AX,[COUNT+SI]
或MOV AX,[3000H+SI]
-
MOV AX,ES:COUNT[SI]
或MOV AX,ES:[COUNT+SI]
-
運算元在儲存器中,其有效地址是一個基址暫存器(
BX
、BP
)或變址暫存器(SI
、DI
的內容和指令中的8位/16位偏移量之和。
基址變址定址方式→EA=
基址+變址
-
運算元在儲存器中,其有效地址是一個基址暫存器(BX、BP)和一個變址暫存器(
SI
、DI
)的內容之和。MOV AX,[BX][SI]
或MOV AX,[BX+SI]
MOV AX,ES:[BX][SI]
或MOV AX,ES:[BX+SI]
相對基址變址定址方式→EA=基址+變址+位移量
MOV AX,COUNT[BX][SI]
或MOV AX,-46H[BX][SI]
MOV AX,COUNT[BX+SI]
或MOV AX,0246H[BX+SI]
MOV AX,[COUNT+BX+SI]
或MOV AX,[-56H+BX+SI]
MOV AX,ES:COUNT[BX][SI]
或MOV AX,ES:[0246H+BX+SI]
相對比例變址定址→EA=變址×比例因子+位移量
-
MOV EBX,[EAX][EDX*8]
或MOV EBX,
-
[EAX+EDX\*8]
或MOV EBX,[ESP][EAX*2]
-
MOV EAX,TABLE[EBP][EDI*4]
或MOV EAX,[TABLE+EBP+EDI\*4]
8位/16位/32位的位移量
注意1:比例因子只能與32位變址暫存器:EAX、EBX、ECX、EDX、EBP、ESI、
EDI聯用;且比例因子只能為1、2、4或8;
注意2: 8)、9)、10)只能是32位定址,沒有16位定址;
-
基址比例變址定址→EA=基址+變址×比例因子
-
相對基址比例變址定址→EA=基址+變址×比例因子+位移
彙編程式
-
段結束:
xxx ends
(xxx 即為段名)
-
程式結束:
end
-
程式返回:
-
MOV AX 4C00H INT 21
-
彙編debug指令
在DOSbox
中輸入debug
即可進入debug
模式
-
-d xxxx:xxxx
即可讀取相應的記憶體的資訊。 (dump) -
-e xxxx:xxxx aa bb cc dd
把aa bb cc dd
寫入對應的地址中。 -
-e 0000:0000 12.AB 34.CD
把記憶體對應的12
修改成AB
,34
修改成CD
。 -
-a
輸入彙編指令 -
-t
執行-a
輸入的彙編指令。
.......回頭再補吧,debug 指令先放一下。
彙編基本指令
常用指令
-
add
-
add ax ,10
, 意思就是ax +=10
。 -
sub
sub ax ,10
意思就是ax-=10
。
-
mul
- 兩個相乘的數,要麼都是八位,要麼都是十六位.
- 如果是8位,一個預設放在AL中 ,另一個放在8位reg 或記憶體位元組單元中。
- 如果是16位,一個預設放在AX中,另一個放在16位reg 或記憶體位元組單元中。
- 兩個相乘的數,要麼都是八位,要麼都是十六位.
-
div
-
shl
shl al ,1
左移一位。
-
shr
shr al ,1
右移一位。
-
rol
rol al ,1
迴圈左移。
-
ror
ror al ,1
迴圈右移
-
rcr
rcr al ,1
帶進位的迴圈右移。
-
rcl
rcl al ,1
帶進位的迴圈左移。
-
inc
inc ax
自增。
-
dec
dec ax
自減。
-
xchg
xchg ax, bx
交換。
-
mov bx , word ptr [0]
- 位擴充。
-
OFFSET
指定偏移地址指向的內容
[BX]
。 (等價於DS:[BX]
)
SEG
可以獲取段地址。 -
中斷指令
-
int 0
- 除法除以0 的中斷
-
MOV AX , 4C00H INT 21H
- 退出程式
-
-
jmp
跳轉指令jmp 1000:0003
EA 1000: 0003
就是jmp的記憶體中顯示的格式,代表跳轉到 1000:0003
-
push ax
:-
注意 若
ax : 1234H
,則它在記憶體裡面的儲存方式是: 34 12 , 從低到高的順序, 內部順序不變 -
棧的儲存順序是從後往前依次儲存的.
-
-
pop ax
:- 將棧頂元素彈出並賦值給
ax
- 將棧頂元素彈出並賦值給
段地址和偏移地址
段地址*16 +偏移地址 = 實體地址
0000:0100
:前面的是段地址 , : 後面的是偏移地址。
DS 暫存器的使用
mov AX, [60]
相當於把 DS : 60 處的兩個位元組存入AX 中
MOV DS , BX
可以把暫存器裡的值賦給DS ,但不能把立即數賦值給DS。
MOV DS , 1000 (該操作是不被允許的)
- 未宣告段地址的情況下預設段地址就是 DS。
宣告變數
DATA SEGMENT
_DATA DB 100(?)
;100 就是多少個? ,?代表待定
DATA ENDS
暫存器的種類及作用
-
8086 CPU 有14 個暫存器 , 他們的名稱為: AX , BX , CX , DX , SI , DI ,SP , BP , IP , CS , SS , DS , ES , PSW , 所有的的暫存器都是16位的,可以放兩個位元組。
-
AX
: 累加器。與
mul
和div
作用有關。 -
BX : 基地址暫存器,可以儲存地址並訪問
- 地址表達方式 :
240B:1001
- 地址表達方式 :
-
AX ,BX ,CX ,DX
都屬於通用暫存器,即可以把一個16位暫存器拆成兩個8位暫存器使用。
段暫存器 CS : IP
CS:IP
即為可執行彙編指令的儲存的位置。- 組合語言中程式碼和資料是不加區分的,都存在於記憶體裡面.
棧暫存器 SS: SP
:
任意時刻 , SS:SP
指向棧頂元素
-
壓棧
sp -=2
-
彈出
sp+=2
-
當棧已空的時候, 如果繼續彈出, 計算機會繼續進行 把sp指向的記憶體彈出並進行
sp+=2
的操作 . -
棧的容量
-
空棧:在進行堆疊操作前,為空棧。此時SP應預置一個初值。該值為堆疊空間的大小
-
SP初值=堆疊空間的最大容量
例:
SP=0008H
。則最大容量為8個位元組。SP指向當前的棧頂。
-
如何避免棧頂超界??
只能透過人為注意棧頂是否超界的問題