在上篇文章中我們具體介紹了匯編語(yǔ)言的一些背景, 以及 8086匯編 工作原理, 在這篇文章中具體講解8086匯編指令, 看完這個(gè)有助于學(xué)習(xí)ARM匯編. 文章后面手把手教你怎么利用 MASM 編譯執(zhí)行程序.
轉(zhuǎn)移指令
8086 提供了一個(gè) mov
指令, 傳送指令, 可以用來(lái)修改大部分寄存器的值,
CPU 從何處執(zhí)行指令是由 CS, IP 中的內(nèi)容決定的, 我們可以通過(guò) Jmp
這類(lèi)轉(zhuǎn)移指令, 來(lái)修改 CS, IP 的值來(lái)控制 CPU 執(zhí)行目標(biāo)指令.
- jmp 2AE3: 3, 執(zhí)行后: CS=2AE3H, IP= 0003H, CPU將從 2AE33H 處讀取指令.
jmp 段地址: 偏移地址 : 用指令中的段地址, 來(lái)修改 CS 的值, 用偏移地址來(lái)修改 IP 的值.
- jmp 2AE3: 3, 執(zhí)行后: CS=2AE3H, IP= 0003H, CPU將從 2AE33H 處讀取指令.
-
- jmp ax, 指令執(zhí)行前: ax= 1000H, CS=2000H. IP=0003H
指令執(zhí)行后: ax= 1000H, CS=2000H. IP=0003H
- jmp 某一合法寄存器 : 用寄存器中的值來(lái)修改IP.
- jmp ax, 在含義上類(lèi)似, mov IP, ax.
- jmp ax, 指令執(zhí)行前: ax= 1000H, CS=2000H. IP=0003H
- jmp 0100H, 直接改變 IP 的值.
DS和[address]
CPU 要讀寫(xiě)一個(gè)內(nèi)存單元時(shí), 必須要先指出內(nèi)存單元的地址, 在 8086 中, 內(nèi)存地址(物理地址) 由 段地址 和 偏移地址 組成.
- 8086 中有一個(gè) DS段寄存器, 通常用來(lái)存放要訪問(wèn)的數(shù)據(jù)的段地址
對(duì) al寄存器 寫(xiě)數(shù)據(jù)
mov bx, 1000H
mov ds, bx
mov al, [0]
- 上面3條指令的作用將10000H(1000:0)中的內(nèi)存數(shù)據(jù)賦值到 al寄存器 中.
- mov al,[address] 的意思將 DS:address 中的內(nèi)存數(shù)據(jù)賦值到 al寄存器 中.
- 由于 al 是8位寄存器,所以是將一個(gè)字節(jié)的數(shù)據(jù)賦值給 al寄存器.
- 8086不支持將數(shù)據(jù)直接送入到 段寄存器, 不能直接 mov ds, 1000H
對(duì) al寄存器 讀數(shù)據(jù)
mov bx, 1000H
mov ds, bx
mov [0], al
大端模式和小端模式
- 大端模式(Big Endian): 指數(shù)據(jù)的
高字節(jié)
保存在內(nèi)存的低地址
中,而數(shù)據(jù)的低字節(jié)
保存在內(nèi)存的高地址
中 ( 高低\低高 ) - 小端模式(Little Endian): 指數(shù)據(jù)的
高字節(jié)
保存在內(nèi)存的高地址
中,而數(shù)據(jù)的低字節(jié)
保存在內(nèi)存的低地址
中 ( 高高\(yùn)低低 )
大端模式: PowerPC, IBM, Sun
小端模式: x86, DEC.
ARM 既可以工作在大端模式, 也可以工作在小端模式.
mov 指令, add 指令, sub 指令
add , sub 和 mov 一樣, 都有兩個(gè)操作對(duì)象.
mov 寄存器, 數(shù)據(jù) 比如: mov ax, 8
mov 寄存器, 寄存器 比如: mov ax, bx
mov 寄存器, 內(nèi)存單元 比如: mov ax, [0]
mov 內(nèi)存單元, 寄存器 比如: mov [0], ax
棧
棧 是一種具有特殊訪問(wèn)方式的空間 (后進(jìn)先出, LIFO, Last In First Out)
- 8086 會(huì)將 CS 作為代碼段的段地址评肆,將 CS:IP 指向的指令作為下一條需要取出執(zhí)行的指令
- 8086 會(huì)將 DS 作為數(shù)據(jù)段的段地址定嗓,mov ax,[address] 就是取出 DS:address 的內(nèi)存數(shù)據(jù)放到 ax寄存器 中.
- 8086 會(huì)將 SS 作為棧段的段地址闷祥,任意時(shí)刻笨觅,SS:SP 指向棧頂元素.
- 8086 提供了 PUSH (入棧) 和 POP (出棧) 指令來(lái)操作 棧段 的數(shù)據(jù).
- 比如 push ax 是將 ax的數(shù)據(jù)入棧申窘,pop ax 是將棧頂?shù)臄?shù)據(jù)送入ax
push ax
- SP = SP - 2, SS: SP 指向當(dāng)前棧頂前面的單元, 以 當(dāng)前棧頂 前面的單元為新的棧頂.
-
將 ax 中的內(nèi)容送入到 SS: SP 指向的內(nèi)存單元處, SS:SP 此時(shí)指向新棧頂.
pop ax
- 將 SS: SP 指向的內(nèi)存單元處的數(shù)據(jù)送入 ax 中.
- SP = SP + 2, SS: SP 指向當(dāng)前棧頂下面的單元, 以 當(dāng)前棧頂 下面的單元為新的棧頂.
值得注意的是, 隨著 push 和 pop 的操作, 原來(lái)內(nèi)存單元上的數(shù)據(jù)并沒(méi)有被清楚掉, 而是隨著 SP 的變化, 稱為垃圾數(shù)據(jù), 當(dāng)有新的數(shù)據(jù) push 進(jìn)來(lái)時(shí), 原數(shù)據(jù)會(huì)被覆蓋.
對(duì)于上面這種 10000H ~ 1000FH 這段椩曳海空間, 初始狀態(tài)為空, 此時(shí) SS = 1000H, SP應(yīng)該為 0010H, 因?yàn)闂十籍?諘r(shí), SS: SP 指向棧空間最高地址單元的下一個(gè)單元.
push 與pop
push 寄存器 ; 將一個(gè)寄存器中的數(shù)據(jù)入棧
pop 寄存器 ; 出棧, 用一個(gè)寄存器接收出棧的數(shù)據(jù)
push 內(nèi)存單元 ; 將一個(gè)內(nèi)存單元處的字入棧
pop 內(nèi)存單元 ; 出棧, 用一個(gè)內(nèi)存字單元接收出棧的數(shù)據(jù)
mov ax, 1000H
mov ds, ax ; 內(nèi)存單元的段地址放在 ds 中
push [0] ; 將 1000: 0 處的字壓入棧.
pop [2] ; 出棧, 出棧的數(shù)據(jù)送入 1000: 2處
將 10000H ~ 1000FH 這段空間當(dāng)做棧, 此時(shí)為空棧, 設(shè)置 AX = 001AH, BX = 001BH, 利用棧, 交換 AX 和 BX 的數(shù)據(jù).
mov ax, 1000H
mov ss, ax
mov sp, 0010H ; 棿浇福空時(shí), SP 指向棧頂(椆蠢酰空間的最高地址)單元的下一單元
mov ax, 001AH
mov bx, 001BH
push ax
push bx
pop ax
pop bx
小結(jié):
- 對(duì)于 數(shù)據(jù)段 , 將它的段地址放在 DS 中, 用 mov, add, sub 等訪問(wèn)內(nèi)存單元的指令時(shí), CPU 就將我們定義的數(shù)據(jù)段中的內(nèi)容當(dāng)做數(shù)據(jù)來(lái)訪問(wèn).
- 對(duì)于 代碼段 , 將它的段地址放在 CS 中, 將段中的第一條指令的偏移地址放在 IP 中, 這樣 CPU 就將執(zhí)行我們定義的代碼段中的指令.
- 對(duì)于 棧段 , 將它的段地址放在 SS 中, 將棧頂單元的偏移地址放在 SP 中, 這樣 CPU 在需要進(jìn)行 pop / push 等操作時(shí), 就將我們定義的棧段當(dāng)做棧空間來(lái)用.
終于可以開(kāi)始我們的第一個(gè)完整的匯編程序.
MASM (Microsoft Macro Assembler) 是微軟公司微處理器家族開(kāi)發(fā)的匯編開(kāi)發(fā)環(huán)境, 因?yàn)镸ASM工具 是一個(gè)exe的執(zhí)行文件, 所以為了在 Mac OS 上能使用它, 我們需要借助DOSBox.
DOSBox 是一款模擬器軟件, 為本地的 DOS 程序提供執(zhí)行環(huán)境, (DOS指 磁盤(pán)操作系統(tǒng)), 最早期是為了運(yùn)行計(jì)算機(jī)程序設(shè)計(jì), 目前已經(jīng)支持 Windows, Mac OS 多個(gè)平臺(tái), 簡(jiǎn)單來(lái)說(shuō), 它就是一個(gè)計(jì)算機(jī)的模擬, 我們可以在官網(wǎng)下載最新的 DOSBox.
使用 DOSBox
-
雙擊打開(kāi)程序, 默認(rèn)在 Z 盤(pán), 可以使用 dir, 查看系統(tǒng)的情況
-
為了方便處理, 在 用戶目錄下 新建一個(gè) LCDOSBox 文件夾, 用來(lái)存儲(chǔ)我們編寫(xiě)的代碼文件, 以及 MASM 工具. 這些工具可以在這里下載.
在 DOSBox 界面掛載我們的文件目錄, 這里
c
指的是 虛擬c盤(pán),~/LCDOSBox
指虛擬的文件夾位置.
Z:\> mount c ~/LCDOSBox
- 進(jìn)入 c 盤(pán), 直接執(zhí)行當(dāng)前目錄中的 debug.exe 程序.
Z:\> c:
C:\> debug
這里的 -r 指令, 用來(lái)顯示寄存器的內(nèi)容.
- 編寫(xiě)我們的asm文件.
; 數(shù)據(jù)段
data segment
string db 'Hello World! $'
data ends
; 代碼段
code segment
assume cs: code, ds: data ; 聲明代碼段和數(shù)據(jù)段
start:
mov ax, data
mov ds, ax ; 設(shè)置 ds 為數(shù)據(jù)段
mov ah, 9h ; 功能號(hào) 9h 代表在屏幕顯示字符串
;mov dx, offset string ; ds: dx 代表字符串的地址
lea dx, string ; 同上 lea指令的功能是將存儲(chǔ)單元的有效地址(偏移地址)傳送到目的操作數(shù)
int 21h ; 執(zhí)行 DOS 系統(tǒng)功能調(diào)用
mov ah, 4ch ; 功能號(hào) 4ch 代表程序退出
int 21h
code ends
end start ; 遇到 end 停止編譯 開(kāi)始執(zhí)行 start 里的內(nèi)容
對(duì)于這個(gè)程序, 有幾點(diǎn)我需要作說(shuō)明
- 匯編程序由 2 類(lèi)指令組成
- 匯編指令: 如 mov, add, sub 等, 它是有對(duì)應(yīng)的機(jī)器指令, 可以被編譯器編譯成機(jī)器指令, 被 CPU 執(zhí)行.
- 偽指令: 如 assume, segment, ends, end 等, 他們沒(méi)ban有對(duì)應(yīng)的機(jī)器指令, 由編譯器解析, 最終不被 CPU 執(zhí)行.
- 注釋符號(hào)用
;
- assume: 聲明 code段 是 cs 段, data段是 ds 段.
- segment 和 ends 的作用是定義一個(gè)段, segment 代表一個(gè)段的開(kāi)始, ends代表一個(gè)段的結(jié)束.
-
int 21h
, 用于執(zhí)行 DOS 系統(tǒng)功能調(diào)用, 這是一個(gè)中斷.
-
編譯并鏈接生成 exe 文件.
image.png
如果你在利用 DOSBox 執(zhí)行程序的時(shí)候碰到 Unable to open input file:xxx.asm 的錯(cuò)誤, 但是代碼文件確實(shí)存在. 可能是由于文件的名字系統(tǒng)的差別造成, 你可以這樣做
- 命令行操作是支持 tab + Hellox, 這樣會(huì)快速補(bǔ)全文件名, 即使補(bǔ)全的名字和你實(shí)際的文件名不一致, 也沒(méi)關(guān)系, 實(shí)際上系統(tǒng)是認(rèn)可的.
- 而且, 文件名最好是英文, 而且是小寫(xiě), 這樣 通過(guò) tap 鍵 補(bǔ)全的文件名通常不會(huì)錯(cuò).
如果你在執(zhí)行的過(guò)程中碰到你需要輸入的, 直接 enter 鍵 跳過(guò).
- 調(diào)試程序
C:\> debug
-
-u 將內(nèi)存中的機(jī)器指令翻譯成匯編指令.
- -u 段地址: 偏移地址 可以將內(nèi)存中內(nèi)容翻譯成對(duì)應(yīng)的匯編指令.
-
-r 查看修改, CPU寄存器中的內(nèi)容.
- -r 寄存器的名稱, 可以修改寄存器的值.
- -g [value] 執(zhí)行到目標(biāo)行盏筐,如:-g f 為執(zhí)行到第16行, 注意查看此時(shí) CS: IP 的值的變化
- -t [value] 執(zhí)行機(jī)器指令 如:-t 2 為執(zhí)行2條指令后停下來(lái). 默認(rèn)執(zhí)行一條指令. 注意查看此時(shí)CS: IP 的值的變化.
-
-d 查看內(nèi)存中的內(nèi)容
- d 段地址: 偏移地址 , 查看特定位置的內(nèi)存數(shù)據(jù)
- d 段地址: 起始偏移地址 結(jié)尾偏移地址 , 查看特定范圍內(nèi)的地址
- -e 段地址: 偏移地址 數(shù)據(jù)串 修改特定位置的內(nèi)存數(shù)據(jù).
- -a, -a 段地址: 偏移地址 可以從某位置開(kāi)始寫(xiě)入?yún)R編指令.
- -q 退出debug
中斷
中斷是由于軟件的或硬件的信號(hào)围俘,使得CPU暫停當(dāng)前的任務(wù),轉(zhuǎn)而去執(zhí)行另一段子程序.
- 硬中斷(外中斷), 由外部設(shè)備(比如網(wǎng)卡琢融、硬盤(pán))隨機(jī)引發(fā)的界牡,比如當(dāng)網(wǎng)卡收到數(shù)據(jù)包的時(shí)候,就會(huì)發(fā)出一個(gè)中斷.
- 軟中斷(內(nèi)中斷), 由執(zhí)行中斷指令產(chǎn)生的漾抬,可以通過(guò)程序控制觸發(fā).
- 從本質(zhì)上來(lái)講, 中斷 是一種電信號(hào)宿亡,當(dāng)設(shè)備有某種事件發(fā)生時(shí),它就會(huì)產(chǎn)生中斷纳令,通過(guò)總線把電信號(hào)發(fā)送給中斷控制器挽荠。如果中斷的線是激活的,中斷控制器就把電信號(hào)發(fā)送給處理器的某個(gè)特定引腳泊碑。處理器于是立即停止自己正在做的事坤按,跳到中斷處理程序的入口點(diǎn)毯欣,進(jìn)行中斷處理.
- 在匯編程序中, 可以通過(guò)
int n
產(chǎn)生中斷.- n 是中斷碼馒过,內(nèi)存中有一張中斷向量表,用來(lái)存放中斷碼對(duì)應(yīng)中斷處理程序的入口地址
- CPU在接收到中斷信號(hào)后酗钞,暫停當(dāng)前正在執(zhí)行的程序腹忽,跳轉(zhuǎn)到中斷碼對(duì)應(yīng)的中斷向量表地址處,去執(zhí)行中斷處理程序.
- 常見(jiàn)中斷包含以下:
int 10h
用于執(zhí)行BIOS中斷,int 3
是“斷點(diǎn)中斷”砚作,用于調(diào)試程序,int 21h
用于執(zhí)行DOS系統(tǒng)功能調(diào)用窘奏,AH
寄存器存儲(chǔ)功能號(hào).
指令要處理的數(shù)據(jù)長(zhǎng)度
8086指令能處理2種 尺寸的數(shù)據(jù), byte , word, 還有其他指令集中的 dword , 通過(guò)這來(lái)指明需要操作內(nèi)存的數(shù)據(jù)長(zhǎng)度.
-
mov byte ptr [0], 20H , 將 20H 放入 0 位置內(nèi)存的
字節(jié)
單元, 占 1 個(gè)字節(jié). -
mov word ptr [0] 20H, 將 20H 放入 0 位置內(nèi)存的
字
單元, 占 2 個(gè)字節(jié). - mov dword ptr [0] 20H, 將 20H 放入 0 位置內(nèi)存的內(nèi)存單元, 占 4 個(gè)字節(jié).
比如 pop [0], push [0] 操作的數(shù)據(jù)長(zhǎng)度默認(rèn)只能是 2 個(gè)字節(jié).