1. 程式人生 > >組合語言筆記(全)(長篇警告)

組合語言筆記(全)(長篇警告)

組合語言

最近系統的學了下組合語言,下面是學習筆記,用的書是清華大學出版社出版的組合語言第三版,作者王爽(最經典的那版)。

基礎知識

組合語言指令組成
  • 彙編指令:機器碼的助記符,有對應的機器碼。
  • 偽指令:沒有對應的機器碼,編譯器執行,機器不執行。
  • 其他符號:如+-*/有編譯器識別,無對應機器碼。
CPU與外部器件互動需要
  • 儲存單元地址(地址資訊)
  • 器件選擇,讀寫命令(控制資訊)
  • 資料(資料資訊)
匯流排

匯流排就是一根根導線的集合,分為

  • 地址匯流排,越寬(數量越多)代表可以定址的範圍越大
  • 資料匯流排,越寬代表一次性讀寫的資料越多(8根1位元組)
  • 控制匯流排,越寬代表對器件控制操作越多
小結

彙編指令和機器指令一一對應

每一種cpu都有自己的彙編指令集

在儲存器中指令和資料都是二進位制,沒有任何區別

CPU可以直接使用的資訊存放在儲存器中(記憶體)

介面卡

CPU無法直接控制顯示器,鍵盤等的外圍裝置,但CPU通過直接控制這些外圍裝置在主機板上的介面卡來控制這些裝置。

儲存器

隨機儲存器(RAM):帶電儲存,關機丟失,可讀可寫

  • 用於存放CPU使用的絕大部分程式和資料,主隨機儲存器由裝在主機板上的RAM和擴充套件插槽的RAM組成。
  • 其他介面卡上也可能有自己的RAM

只讀儲存器(ROM):關機不丟,只能讀取

  • 主機板上的ROM裝有系統的BIOS(基本輸入輸出系統)。

  • 其他介面卡上也可能有自己的ROM,一般裝著相應的BIOS。

(P10圖)

記憶體地址空間

以上這些記憶體都和CPU匯流排相連,CPU都通過控制匯流排向他們發出記憶體讀寫命令。所以CPU都把他們當記憶體對待,看做一個一個由若干儲存單元組成的邏輯儲存器,即記憶體地址空間(一個假想的邏輯儲存器P11圖)。

記憶體地址空間中的各個不同的地址段代表不同的儲存裝置,記憶體地址空間大小收到CPU地址匯流排長度限制。

暫存器

內部匯流排

之前討論的匯流排是CPU控制外部裝置使用的匯流排,是將CPU和外部部件連線的。而CPU內部由暫存器,運算器,控制器等組成,由內部匯流排相連,內部匯流排負責連線CPU內部的部件。

通用暫存器

8086CPU暫存器都是16位的,一共14個,分別是AX,BX,CX,DX,SI,DI,SP,BP,IP,CS,SS,DS,ES,PSW。其中AX,BX,CX,DX四個暫存器通常存放一般性的資料,稱為通用暫存器。

而且為了相容上一代的8位暫存器,這四個暫存器可以拆開成兩個8位的暫存器來使用。稱為AH,AL,BH,BL,CH,CL,DH,DL。低八位(編號0-7)構成L暫存器,高八位構成H暫存器。

8086CPU可以處理以下兩種資料

  • 位元組byte,8位
  • 字word,連個位元組,16位。分別稱為高位位元組和低位位元組。
簡單的彙編指令
指令 操作 高階語言
mov ax,18 將18存入AX暫存器 AX=18
add ax,8 將AX暫存器中的數加8 AX=AX+8
mov ax,bx 將BX中的資料存入AX AX=BX
add ax,bx 將AX中的資料和BX中的資料相加存入AX AX=AX+BX

彙編指令或暫存器名稱不區分大小寫。

注:AX暫存器當做兩個8位暫存器al和ah使用的時候,CPU就把他們當做兩個8位暫存器使用,而不會看成是一個16未分開,即如果al進行加法運算C5+93=158,即add al,93,al會變成58,ax則是0058而不是0158。

CPU位結構

16位結構的CPU指的是運算器一次最多處理16位資料,暫存器寬度16,暫存器和運算器之間通路也是16位。

CPU表示實體地址

如果物理匯流排寬度超過暫存器寬度,CPU定址方法是兩個暫存器輸出一個地址,當地址匯流排寬度20的時候,P21圖。一個暫存器輸出短地址,另一個輸出偏移地址。然後通過地址加法器合併為一個20位的地址,然後通過內部匯流排送給控制電路,控制電路通過地址匯流排送給記憶體。

公式:實體地址=段地址x16+偏移地址(這裡的x16其實就是左移四位,P21圖)

雖然這麼表示,但記憶體並沒有被分為一段一段的,是CPU劃分的段。段地址x16稱為基礎地址,所以我們可以根據需求把任意的基礎地址加上不超過一個暫存器表示的最長(64KB)的偏移地址來表示地址。而且一個實際地址往往可以有各種不同的方法表示,通常我們表示21F60H這個地址通過下面方法:

  • 2000:1F60
  • 2000H段中的1F60單元中
段暫存器與指令指標暫存器

8086CPU有四個段暫存器:CS,DS,SS,ES

除此之外,IP暫存器稱為指令指標暫存器,所以任意時刻可以讀取從CSx16+IP單元開始,讀取一條指令執行。也就是說,CPU將IP指向的內容當做指令執行。

P26圖,CPU執行一段指令。另外,8086CPU開機時CS被置為FFFFH,IP被置為0000H,也就是說剛開機的第一條指令從FFFF0H開始讀取執行。

CPU將CS:IP指向的記憶體中的內容當做指令,一條指令被執行了,那一定被CS:IP指向過。

修改CS,IP

CS和IP暫存器不可以使用傳送指令mov來改變,而能改變CS,IP內容的指令是轉移指令。

jmp指令用法:

  • jmp 段地址:偏移地址 同時修改CS和IP的值 如jmp 2AE3:3 結果CS=2AE3H IP=0003H
  • jmp 某一合法暫存器 只修改IP的值 如jmp ax,將IP的值置為AX中的值(AX不變)
小結

8086CPU有四個段暫存器,CS是用來存放指令的段地址的段暫存器

IP用來存放指令的偏移地址

CS:IP指向的內容在任意時刻會被當做指令執行

使用轉移指令修改CS和IP的內容

實驗

Debug命令:

  • R:檢視,改變CPU暫存器內容
    • 直接-r檢視暫存器內容
    • -r 暫存器名,改變暫存器內容
  • D:檢視記憶體中內容
    • -d直接檢視
    • -d 段地址:偏移地址 檢視固定地址開始的內容
    • -d 段地址:偏移地址 結尾偏移地址 檢視指定範圍記憶體
  • E:改寫記憶體中內容
    • -e 起始地址 資料 資料 資料 …
    • 提問方式修改 -e 段地址:偏移地址 從這個地址開始一個一個改,空格下一個,回車結束
    • 也可以寫入字元 ‘a’
  • U:將記憶體中的機器指令翻譯成彙編指令
    • -u 段地址:偏移地址
  • T:執行一條機器指令
    • -t 執行cs:ip指向的命令
  • A:以彙編指令格式在記憶體中寫入一條機器指令
    • -a 段地址:偏移地址 從這個地址開始一行一行的寫入彙編語句

暫存器(記憶體訪問)

記憶體到暫存器的儲存

暫存器是16位的,可以存放一個字即兩個位元組,而記憶體中的一個儲存單元是一位元組。所以一個暫存器可以存兩個儲存單元的內容,高地址儲存單元存在高位位元組中,低地址儲存單元存在低位位元組中。

字單元:存放一個字型資料的兩個地址連續的記憶體單元。

DS暫存器

與CS類似,DS暫存器存放的是要從記憶體中讀取的資料的段地址。我們想要使用mov指令從記憶體10000H(1000:0)中的資料送給AL時,如下:

mov al,[0]

後面的[0]指的是記憶體的偏移地址是0,CPU會自動從DS暫存器中提取段地址,所以應該首先將段地址1000H寫入DS暫存器中。但卻不能直接使用mov ds,1000指令,只能從其他暫存器中轉傳入DS暫存器。所以完整命令如下:

mov bx,1000
mov ds,bx
mov al,[0]

當然,從AL暫存器中將資料送入記憶體只要反過來使用mov就可以了,mov [0],al

如果需要傳輸字型數,只要使用對應的16位暫存器就可以了,傳輸的是以相應地址開始的一個字型資料(連續兩個位元組)。如mov [0],cx。

mov,add,sub

mov常見語法:

mov 暫存器,資料       mov ax,8
mov 暫存器,暫存器     mov ax,bx
mov 暫存器,記憶體單元    mov ax,[0]
mov 記憶體單元,暫存器    mov [0],ax
mov 段暫存器,暫存器    mov ds,ax
mov 暫存器,段暫存器    mov ax,ds

add,sub常見語法:

add 暫存器,資料        add ax,8
add 暫存器,暫存器      add ax,bx
add 暫存器,記憶體單元    add ax,[0]
add 記憶體單元,暫存器    add [0],ax
subadd一樣

注意,add,sub不可以操作段暫存器。

棧是一種後進先出的儲存空間,從棧頂出棧入棧。LIFO(last in first out)

入棧指令:push ax ax中的資料送入棧頂

出棧指令:pop ax 棧頂送入ax

入棧和出棧指令都是以字為單位的。P58圖

棧暫存器SS,SP與push,pop

CPU通過SS暫存器和SP暫存器來知道棧的範圍,段暫存器SS存放的是棧頂的段地址,SP暫存器存放的是棧頂的偏移地址。所以,任意時刻SS:SP指向棧頂元素。

指令push ax執行過程:

  1. SP=SP-2,SP指標向前移動兩格代表新棧頂
  2. AX中的資料送入SS:SP目前指向的記憶體字單元,P59圖

所以棧頂在低地址,棧底在高地址。初始狀態下,SP指向棧底的下一個單元。

反之pop ax執行過程相反。

8086CPU並不會自己檢測push是否會超棧頂,pop是否會超棧底。

push和pop可以加暫存器,段暫存器,記憶體單元(直接偏移地址[address])

指定棧空間通常通過指定SS來進行,如:

指定10000H~1000FH為棧空間
mov ax,1000
mov ss,ax
mov sp 0010

注:將一個暫存器清零 sub ax,ax 兩個位元組,mov ax,0 三個位元組

注:若設定一個棧段為10000H~1FFFFH,棧空的時候SP=0(要知道入棧操作先SP-2,然後再送入棧)

實驗

Debug中的t命令一次執行一條指令,但如果執行的指令修改了ss段暫存器,下一條命令也會緊跟著執行(中斷機制)。

簡單程式設計

一個組合語言程式
  1. 編寫
  2. 編譯(masm5.0)
  3. 連線
一些偽指令功能
assume cs:codesg

codesg segment

mov ax,0123
mov bx,0456
add ax,bx
add ax,ax

mov ax,4c00
int 21

codesg ends

end

涉及到的一些知識:

  • XXX segment···XXXends
    • segment和ends成對出現,代表一個段的開始和結束。
    • 一個彙編程式可以有多個段,程式碼,資料和棧等,至少要有一個段。
  • end
    • end代表一個彙編程式結束,遇到end編譯器停止編譯。
  • assume
    • assume 假設,假設某一個段暫存器和程式中的一個段關聯。
    • 可以理解為將這個段暫存器指向程式段的段地址
  • 標號(codesg)
    • 一個標號代表一個地址
  • 程式返回mov ax,4c00 int 21
    • 暫時記住這兩條指令代表程式返回

編譯和連線方法,P83。

注:編譯器只能發現語法錯誤而無法發現邏輯錯誤。

CPU執行一個程式,需要有另一個程式將它載入進記憶體(即將CS:IP指向它),一般情況下我們通過DOS執行這個.exe,所以是DOS程式將它載入進入記憶體。當這個程式執行結束,再返回DOS程式繼續執行。如果是DOS呼叫Debug呼叫.exe,那麼先返回Debug再返回DOS。

DOS載入一個.exe時,先在記憶體中找到一段記憶體,起始段地址SA,然後分配256位元組的PSP區域,用來和被載入程式通訊。在之後的段地址SA+10就是程式開始的段地址。CS:IP指向它,DS=SA。

注:在Debug中,最後的int 21指令要使用P命令執行。

[BX]和loop指令

記憶體單元的描述

記憶體單元可以使用[數字]表示,當然也可以使用[暫存器]表示,如[bx],mov ax,[bx],mov al,[bx]

為了表示方便,使用()來表示一個記憶體單元或暫存器中的內容,如(ax),(20000H),或((dx)*16+(bx))表示ds:bx中的內容,但不可寫為(1000:0),((dx):0H)。而(X)中的內容由具體暫存器名或運算來決定。

我們使用idata來表示常亮。所以以下語句可以這麼寫:mov ax,[idata] mov ax,idata。

loop指令

loop指令格式:loop 標號。

loop指令通常用來實現迴圈功能,當執行loop指令時,CPU進行兩步操作:

  1. (cx)=(cx)-1
  2. (cx)不為零則跳至標號處執行程式。

所以CX中存放的是迴圈次數,一個簡單的例子如下(計算2^12):

assume cs:code
code segment

mov ax,2

mov cx,11
s:add ax,ax
loop s

mov ax,4c00h
int 21h

code ends
end

所以使用loop注意三點:

  1. 先設定cx的值 mov cx,迴圈次數
  2. 設定標號與執行迴圈的程式段 s:執行程式段
  3. 在程式段最後寫loop loop

注:在組合語言中,資料不能以字母開頭,所以大於9fffH的資料,要在開頭加0,如0A000H

注:debug中G命令 g 0012表示CPU從當前CS:IP開始一直執行到0012處暫停。P命令可以將loop部分一次執行完畢,直到(CX)=0,或使用g loop的下一條命令。

Debug和masm編譯器對指令的不同處理

mov ax,[0]這條指令在Debug和masm中有著不同的解釋,Debug是將DS:0記憶體中的資料送給AX,而masm中則是mov ax,0,即將0送入AX。

解決方法1:先將偏移地址送入BX,然後再使用mov ax,[bx]

解決方法2:直接顯式給出地址,如mov al,ds:[0] (相應的段暫存器還有CS,SS,ES這些在組合語言中可以稱為“段字首”)當然,這種寫法通過編譯器之後會變成Debug中的mov al,[0]

注:inc bx bx值加一

安全的程式設計空間

在之前沒有提到的一個問題,如果在寫程式之前不看一眼要操作的記憶體,就直接開始使用的話,萬一改寫了記憶體中重要的系統資料,可能會引起系統崩潰。所以我們一般在一個安全的記憶體空間中操作。一般作業系統和合法程式都不會使用0:200~0:2ff這256位元組的空間,所以我們可以在這裡操作。

學習組合語言的目的就是直接和硬體對話,而不理會作業系統,這在DOS(真實模式)下是可以做到的,但在windows或Unix這種執行與CPU保護模式的作業系統上卻是不可能的,因為這種作業系統已經將CPU全面嚴格的管理了。

段字首的使用

將ffff:0~ffff:b中的資料轉存入0:200~0:20b中:

assume cs:code
code segment

mov ax,0ffffh
mov ds,ax

mov ax,0020h
mov es,ax

mov bx,0

mov cx,12
s:mov dl,[bx]
mov es:[bx],dl
inc bx
loop s

mov ax,4c00h
int 21h

code ends
end

[bx]直接使用的時候預設段字首是ds,但要使用其他的段字首,如es就要在前面加上。

程式的段

資料段

一般一個程式想要使用記憶體空間,有兩種方法,在程式載入的時候系統分配或在需要使用的時候向系統申請,我們先考慮第一種情況。所以我們應事先將所需的資料存入記憶體中的某一段中,但我們又不可以隨意的指定記憶體地址,以下面的求8個數據累加和的程式碼為例:

assume cs:code
code segment

dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h

mov bx,0
mov ax,0

mov cx,8
s:add ax,cs:[bx]
add bx,2
loop s

mov ax,4c00h
int 21h

code ends
end

程式碼第一行的dw是定義字型別資料,define word的意思。這裡定義了8個字型別資料,佔16位元組。由於是在程式最開始定義的dw,所以資料段的偏移地址為0,也就是說第一個資料0123h的地址是CS:[0]第二個0456h的地址是CS:[2]以此類推。

所以這個程式載入之後CS:IP指向的是資料段的第一個資料,我們要是想成功執行,需要把IP置10,指向第一條指令mov bx,0,所以我們想要直接執行(不在Debug中調整IP)的話,需要指定程式開始的地方:

···
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h

start:mov bx,0
···
code ends
end start

在第一條指令前加start,後面的end變成end start,end除了通知編譯器程式在哪裡結束之外,也可以通知程式的入口在哪,也就是第一條語句,在這裡編譯器就知道了mov bx,0是程式的第一條指令。也就是說,我們想要CPU從何處開始執行程式,只要在源程式中使用end 標號指定就好了。

所以有如下框架:

assume cs:code
code segment
···資料···
start:
···程式碼···
code ends
end start
棧段

看下面一段使8個數逆序存放的程式碼:

assume cs:codesg
codesg segment

dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

start:mov ax,cs
mov ss,ax
mov sp,30h

mov bx,0
mov cx,8
s:push cs:[bx]
add bx,2
loop s

mov bx,0
mov cx,8
s0:pop cs:[bx]
add bx,2
loop s0

mov ax,4c00h
int 21h

codesg ends
end start

在定義了8個字型資料之後,又定義了16個取值為0的字型資料,用作棧空間。所以dw這個定義不僅僅用來定義資料,也可以用來開闢記憶體空間留給之後的程式使用。

資料,程式碼,棧的程式段

在8086CPU中,一個段的長度最大為64KB,所以如果我們將資料或棧空間定義的比較大,就不能像前面一樣程式設計了。我們需要將程式碼,資料,棧放入不同的段中:

assume cs:code,ds:data,ss:stack
data segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
data ends

stack segment
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
srack ends

code segment
start:mov ax,stack
mov ss,ax
mov sp,20h

mov ax,data
mov ds,ax

mov bx,0

mov cx,8
s:push [bx]
add bx,2
loop s

mov bx,0

mov cx,8
s0:pop [bx]
add bx,2
loop s0

mov ax,4c00h
int 21h

code ends
end start

我們可以這樣在寫程式碼時就將程式分為幾個段,這段程式碼中,mov ax,data的意思是將data段的段地址送入ax暫存器。但我們不可以使用mov ds,data這樣是錯誤的,因為在這裡data被編譯器視為一個數值。

在這裡將資料命名為data,程式碼命名為code,棧命名為stack只是為了方便閱讀,CPU並不能理解,和start,s,s0一樣,只在源程式中使用。而assume cs:code,ds:data,ss:stack這段程式碼也並不能讓CPU的cs,ds,ss指向對應的段,因為assume是偽指令,CPU並不認識,它是由編譯器執行的。源程式中end start語句指明瞭程式的入口,在這個程式被載入後,CS:IP被指向start處,開始執行第一條語句,這樣CPU才會將code段當做程式碼執行。而當CPU執行

mov ax,stack
mov ss,ax
mov sp,20h

這三條語句後才會將stack段當做棧空間開使用。也就是說,CPU如何區分哪個段的功能,全靠我們使用匯編指令對ds,ss,cs暫存器的內容設定來指定。

靈活定位記憶體地址

and和or指令

and:邏輯與指令,按位與運算,如:

mov al,01100011B
and al,00111011B

執行結果是al=00100011B,所以我們想要把某一位置零的時候可以使用and指令。

or:邏輯或指令,按位或運算,如:

mov al,01100011B
or al,00111011B

執行結果是al=01111011B,or指令可以將相應位置1。

ASCII碼和字元形式的資料

在組合語言中我們可以使用’···’的方式指明資料是以字元形式給出的,編譯器會自動將它們轉化為ASCII碼。例如:

assume cs:code,ds:data
data segment
db 'unIX'
db 'foRK'
data ends
code segment
start:mov al,'a'
mov bl,'b'
mov ax,4c00h
int 21h
code ends
end start

db和dw類似,只不過定義的是位元組型資料,然後通過’unIX’相繼在接下來四個位元組中寫下75H,6EH,49H,58H即unIX的ASCII值。同理,mov al,’a’也是將’a’的ASCII值61H送入al暫存器。

使用and和or指令改變一串字串字母的大小寫,將第一串全變為大寫,第二串全變為小寫:

首先分析ASCII碼:

大寫  十六進位制    二進位制         小寫  十六進位制    二進位制
 A      41      01000001        a       61     01100001
 B      42      01000010        b       62     01100010
 C      43      01000011        c       63     01100011

可見,只有第5位(從右往左數,從0開始計數)在大寫和小寫的二進位制中是不一樣的,所以我們只要把所有字母的二進位制第五位置零,那就是大寫,置1就是小寫。程式碼如下:

assume cs:codesg,ds:datasg

datasg segment
db 'BaSiC'
db 'iNfOrMaTiOn'
datasg ends

codesg segment
start:mov ax,datasg
mov ds,ax
mov bx,0

mov cx,5
s:mov al,[bx]
and al,11011111B
mov [bx],al
inc bx
loop s

mov bx,5

mov cx,11
s0:mov al,[bx]
or al,00100000B
mov [bx],al
inc bx
loop s0

mov ax,4c00h
int 21h

codesg ends
end start
[bx+idata]的記憶體表示方法與陣列處理

除了使用[bx]來表示一個記憶體單元外,我們還可以使用[bx+idata]來表示一個記憶體單元,他表示的意思是偏移地址為(bx)+idata(bx中的數值加idata)的記憶體單元。當然也可寫為[idata+bx],除此之外還可寫為,200[bx],[bx].200。

既然有了這種表示方法,我們就可以使用這種方法來運算元組,剛才將兩個字串改變大小寫的程式碼的迴圈部分可以如下優化:

···
s:mov al,[bx]
and al,11011111B
mov [bx],al
mov al,[5+bx]
or al,00100000B
mov [5+bx],al
inc bx
loop s
···

當然也可寫為0[bx]和5[bx],注意這種寫法和C語言中陣列的相似之處:C語言中陣列表示為a[i],組合語言中表示為5[bx]。

SI和DI暫存器

SI和DI功能和BX相似,但不可以拆分為兩個8位暫存器。也就是說下面程式碼等價:

mov bx|si|di,0
mov ax,[bx|si|di]
mov ax,[bx|si|di+123]

所以在這裡可以使用更方便的方式:[bx+si]和[bx+di],這兩個式子表示偏移地址為(bx)+(si)的記憶體單元,使用方法如:mov ax,[bx+si]等價於mov ax,[bx][si]。

當然,有了這些表示方法,自然就有[bx+si+idata]和[bx+di+idata],相似的,也可以寫成

mov ax,[bx+200+si]
mov ax,[200+bx+si]
mov ax,200[bx][si]
mov ax,[bx].200[si]
mov ax,[bx][si].200

那我們總結一下這些記憶體定址方法:

  • [idata]用一個常量表示偏移地址,直接定位一個記憶體單元
  • [bx]用一個變量表示偏移地址,定位一個記憶體單元
  • [bx+idata]用一個常量和一個變量表示偏移地址,可在一個起始地址的基礎上間接定位一個記憶體單元
  • [bx+si]用兩個變量表示偏移地址
  • [bx+si+idata]用兩個變數和一個常量表示偏移地址

使用雙迴圈,使用一個暫存器暫存cs的值,如:

···
mov cx,4
s0:mov dx,cx
mov si,0

mov cx,3
s:mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s

add bx,16
mov cx,dx
loop s0
···

假如迴圈比較複雜,沒有多餘的暫存器可用,我們可以使用記憶體暫存cx或其他資料:

···
dw 0
···
mov cx,4
s0:mov ds:[40H],cx
mov si,0

mov cx,3
s:mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s

add bx,16
mov cx,ds:[40H]
loop s0
···

這麼使用的話注意需要在資料段宣告用來暫存的記憶體,好在程式載入時分配出來。當然,在需要暫存的地方,還是建議使用棧:

···
dw 0,0,0,0,0,0,0,0
···
mov ax,stacksg
mov ss,ax
mov sp,16
···
mov cx,4
s0:push cx
mov si,0

mov cx,3
s:mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s

add bx,16
pop cx
loop s0
···

資料處理的兩個基本問題

兩個基本問題
  1. 處理的資料在什麼地方
  2. 要處理的資料有多長

接下來的討論中,使用reg來表示一個暫存器,使用sreg來表示一個段暫存器。所以:

  • reg:ax,bx,cx,dx,ah,al,bh,bl,ch,cl,dh,dl,sp,bp,si,di
  • sreg:ds,ss,cs,es
bx,si,di和bp

在8086CPU中,只有這四個暫存器可以使用[···]來進行記憶體定址,可以單個出現,或以下面組合出現(常數可以隨意出現在這些表示方法中):

  • bx+si/di
  • bp+si/di

注:如果使用了bp來定址,而沒有顯式的表明段地址,預設使用ss段暫存器,如:

mov ax,[bp]              ;(ax)=((ss)*16+(bp))
mov ax,[bp+idata]        ;(ax)=((ss)*16+(bp)+idata)
mov ax,[bp+si]           ;(ax)=((ss)*16+(bp)+(si)+idata)
資料的位置

絕大部分機器指令都是用來處理資料的,基本可分為讀取,寫入,運算。在機器指令這個層面上,並不關心資料是什麼,而關心指令執行前資料的位置。一般資料會在三個地方,CPU內部,記憶體,埠。

組合語言中使用三個概念來表示資料的位置:

  • 立即數(idata)
    • 對於直接包含在機器指令中的資料,在組合語言中稱為立即數
    • 例:mov ax,1 add bx,2000h
  • 暫存器
    • 指令要處理的資料在暫存器中,在彙編指令中給出相應暫存器名
    • 例:mov ax,bx mov ds,ax
  • 段地址(SA)和偏移地址(EA)
    • 指令要處理的資料在記憶體中,在指令中使用[X]方式給出,SA在某個段暫存器中
    • 例:mov ax,[0] mov ax,[di]

總結一下定址方式:

定址方式 含義 名稱
[idata] EA=idata;SA=(DS) 直接定址
[bx|si|di|bp] EA=(bx|si|di|bp);SA=(DS) 暫存器間接定址
[bx|si|di|bp+idata] EA=(bx|si|di|bp+idata);SA=(DS) 暫存器相對定址
[bx|bp+si|di] EA=(bx|bp+si|di);SA=(DS|SS) 基址變址定址
[bx|bp+si|di+idata] EA=(bx|bp+si|di+idata);SA=(DS|SS) 相對基址變址定址
資料的長度

8086CPU中可以指定兩種尺寸的資料,byte和word,所以在使用資料的時候要指明資料尺寸。

  • 在有暫存器參與的時候使用暫存器的種類區分
    • 字:mov ax,1
    • 位元組:mov al,1
  • 在沒有暫存器參與的時候,使用X ptr指明記憶體單元長度,X是word或byte
    • 字:mov word ptr ds:[0],1 add word ptr [bx],2
    • 位元組:mov byte ptr ds:[0],1 add byte ptr [bx],2
  • 其他預設指明處理型別的指令
    • push [1000H],push預設只進行字操作

靈活使用定址方式的例子,修改下面記憶體空間中的資料:

段seg:60

起始地址 內容
00 ‘DEC’
03 ‘Ken Oslen’
0C 137
0E 40
10 ‘PDP’
···
mov ax,seg
mov ds,ax
mov bx,60h

mov word ptr [bx].0ch,38    ;第三欄位改為38

add word ptr [bx].0eh,70    ;第四欄位改為70

mov si,0
mov byte ptr [bx].10h[si],'v'   ;修改最後一個欄位的三個字元
inc si
mov byte ptr [bx].10h[si],'A'
inc si
mov byte ptr [bx].10h[si],'X'
···

這段程式碼中地址的使用類似c++中結構體的使用。[bx].idata.[si],就類似與c++中的dec.cp[i]。dec是結構體,cp是結構體中的字串成員,[i]表示第幾個字元。

div指令

div是除法指令,需要注意以下三點:

  • 除數:8位或16位,在一個reg或記憶體單元中
  • 被除數:預設在AX或DX中,如果除數8位,被除數則為16位,放在AX中;如果除數16位,則被除數32位,在DX和AX中,DX存放高16位,AX放低16位。
  • 結果,除數8位,結果(商)存放在AL中,AH存放餘數;如果除數16位,則AX存放商,DX存放餘數

格式:div reg或div 記憶體單元,所以div byte ptr ds:[0]表示:

(al)=(ax)/((ds)*16+0)的商;
(ah)=(ax)/((ds)*16+0)的餘數;

div word ptr es:[0]表示:

(al)=[(dx)*10000H+(ax)]/((es)*16+0)的商
(ah)=[(dx)*10000H+(ax)]/((es)*16+0)的餘數

例:計算100001/100,因為100001(186A1H)大於65535,則需要存放在ax和dx兩個暫存器,那麼除數100只能存放在一個16位的暫存器中,實現程式碼:

mov dx,1
mov ax,86A1H
mov bx,100
div bx

執行之後(ax)=03E8H(1000),(dx)=1。

偽指令dd

dd是一個偽指令,類似dw,但dd是用來定義dword(double word,雙字),如:

dd 1  ;2字,4位元組
dw 1  ;1字,2位元組
db 1  ;1位元組

將data段中第一個資料除以第二個資料,商存入第三個資料:

···
data segment
dd 100001
dw 100
dw 0
data ends
···
mov ax,data
mov ds,ax
mov ax,ds:[0]
mov dx,ds:[2]
div word ptr ds:[4]
mov ds:[6],ax
···

總結一下div相關:

  • div後面跟的是除數
  • 被除數位數是除數兩倍
  • 被除數存在ax中或ax+dx(ax低,dx高)
  • 商在ax或al中,餘數在ah或dx中(高餘數,低商)
dup

dup是一個操作符,由編譯器識別,和db,dw,dd配合使用,如:

db 3 dup (0)表示定義了三個值是0的位元組,等價於db 0,0,0

db 3 dup (1,2,3)等價於db 1,2,3,1,2,3,1,2,3 共九個位元組

db 3 dup (‘abc’,’ABC’)等價於db ‘abcABCabcABCabcABC’

綜上,db|dw|dd 重複次數 dup (重複內容)

轉移指令原理

轉移指令

可以修改IP或同時修改CS,IP的系統指令稱為轉移指令,可分為以下幾類:

  • 轉移行為:
    • 只修改IP,稱為段內轉移,如jmp ax
    • 同時修改CS和IP,稱為段間轉移,如jmp 1000:0
  • 修改範圍(段內轉移):
    • 短轉移:修改IP範圍-128~127
    • 近轉移:修改IP範圍-32768~32767
  • 轉移指令分類:
    • 無條件轉移:jmp
    • 條件轉移
    • 迴圈指令
    • 過程
    • 中斷
offset操作符

offset是由編譯器處理的符號,它能去的標號的偏移地址,如:

start:mov ax,offset start
s:mov ax,offset s

這裡就是將start和s的偏移地址分別送給ax,也就是0和3

jmp指令

jmp是無條件轉移指令,可以只修改IP也可以同時修改CS和IP,只要給出兩種資訊,要轉移的目的地址和專一的距離。

依據位移的jmp指令:jmp short 標號(轉到標號處執行指令)。這個指令實現的是段內短轉移,對IP修改範圍是-128~127,指令結束後CS:IP指向標號的地址,如:

0BBD:0000   start:mov ax,0  (B80000)
0BBD:0003   jmp short s   (EB03)
0BBD:0005   add ax,1    (050100)
0BBD:0008   s:inc ax    (40)

執行之後ax值為1,因為跳過了add指令。

還應注意的是,jmp short短轉移指令並不會在機器碼中直接寫明需要轉移的地址(0BBD:0008),jmp的機器碼是EB03並沒有包含轉移的地址,這裡的轉移距離是相對計算而出的地址,來看下面的執行過程:

  1. (CS)=0BBDH,(IP)=0006H,CS:IP指向EB03(jmp short s)
  2. 讀取指令EB03進入指令緩衝器
  3. (IP)=(IP)+指令長度,即(IP)=(IP)+2=0008H,之後CS:IP指向add ax,1
  4. CPU指向指令緩衝器中的指令EB03
  5. 執行之後(IP)=000BH,指向inc ax

在jmp short s的機器碼中,包含的並不是轉移的地址,而是轉移的位移,這裡的位移是相對計算出來的,用8位一位元組來表示,所以表示範圍是-128~127,用補碼錶示。計算方法如是,8位位移=標號處地址-jmp下一條指令的地址。當然還有一種類似的指令是jmp near ptr 標號,是近轉移,原理一樣,只是表示位移的是字型別16位,表示範圍-32768~32767。

相關推薦

組合語言筆記長篇警告

組合語言 最近系統的學了下組合語言,下面是學習筆記,用的書是清華大學出版社出版的組合語言第三版,作者王爽(最經典的那版)。 組合語言 基礎知識

HDU 5727 Necklace排列+二分圖匹配

color algorithm target () mark ++ int turn open http://acm.split.hdu.edu.cn/showproblem.php?pid=5727 題意:現在有n個陽珠子和n個陰珠子,現在要把它們串成項鏈,要求是陰陽珠

彈窗提示插件局提示彈窗

osi .html global 遮罩 filter ase 方便 timeout obi 彈窗可以說是每個項目都會用到的一個東西,彈窗有很多種,有系統默認的,網上也有一堆插件。默認的彈窗一般不好看,都會被設計嫌棄的,如果用插件的話,又比較占資源空間,所有我開發的項

GIL局解釋器鎖

只有一個 str 使用 同時 需要 實現 空閑 多線程 能力 GIL(全局解釋器鎖) 每個線程在執行的過程都需要先獲取GIL 作用:在單核的情況下實現多任務(多線程),保證同一時刻只有一個線程可以執行代碼,因此造成了我們使用多線程的時候無法實現並行

springCloud分散式事務實戰分散式事務綜合大測試,共10篇

啟動註冊中心,啟動redis ,啟動事務管理器 啟動mysql 啟動微服務1 ,啟動微服務2(1)正常事務測試:不丟擲異常,兩邊都插入 1 設計沒有故障整合服務@TxTransaction(isStart=true) //關鍵 4 @Overridebr/>@Transactionalpublic

洛谷P2518 [HAOI2010]計數排、組合數

題目連結:https://www.luogu.org/problemnew/show/P2518 思路:長度不足就補0到長度一樣,然後按位列舉下去,比這個數這一位小那麼後面的位置就是一個有重複元素的排列(高中數學問題...),然後減去這一位的出現次數,這題是找數位dp例題找到的,可這壓根不是

用pytorch實現多層感知機MLP)連線神經網路FC分類MNIST手寫數字體的識別

1.匯入必備的包 1 import torch 2 import numpy as np 3 from torchvision.datasets import mnist 4 from torch import nn 5 from torch.autograd import Variable 6

MySQL定時備份量備份+增量備份

參考 zone7_ 的 實戰-MySQL定時備份系列文章 說明 產品上線後,資料非常非常重要,萬一哪天資料被誤刪,那麼就gg了,準備跑路吧。 所以要對線上的資料庫定時做全量備份和增量備份。 增量備份的優點是沒有重複資料,備份量不大,時間短。但缺點也很明顯,需要建立在上次完全備份及完全備份之後所有的增量才

微服務分散式事務實戰分散式事務綜合大測試,共10篇

準備: 啟動註冊中心; 啟動redis ; 啟動事務管理器 啟動mysql ; 啟動微服務1 ; 啟動微服務2 (1)正常事務測試: 不丟擲異常,兩邊都插入 1 設計沒有故障整合服務 @TxTransaction(isStart=true) @Over

數學名著譯叢-普林斯頓數學指南3卷+英文原版

資源連結:https://pan.baidu.com/s/18reVlTaRz9BeaGimgh6rlw 《數學名著譯叢-普林斯頓數學指南(全3卷)[英]T·高爾斯-齊民友(譯)-2014》+英文原版 《The princeton companion to ma

Others-大資料平臺Lambda架構淺析量計算+增量計算

大資料平臺Lambda架構淺析(全量計算+增量計算) 2016年12月23日 22:50:53 scuter_victor 閱讀數:1642 標籤: spark大資料lambda 更多

FCN卷積神經網路

原文連結: http://mp.weixin.qq.com/s?__biz=MzI1NTE4NTUwOQ==&mid=2650324665&idx=1&sn=3022e7e75a4bad0acdde36fe3edf565e&scene=5&a

10.2-棧Java筆記:最面的IO技術

java上節我們聊到「IO技術概念及入門」這節我們繼續聊一下IO技術的分類。Java中流的概念細分按流的方向分類:輸入流:數據流向是數據源到程序(InputStream、Reader結尾的流)輸出流:數據流向是程序到目的地(OutPutStream、Writer結尾的流)按處理的數據單元:字節流:按照字節讀取

10.1-棧Java筆記:最面的IO技術

javaIO技術對於任何程序設計語言而言,輸入輸出(Input/Output)系統都是非常核心的功能。程序運行需要數據,數據的獲取往往需要跟外部系統進行通信,外部系統可能是文件、數據庫、其他程序、網絡、IO設備等等。外部系統比較復雜多變,那麽我們有必要通過某種手段進行抽象、屏蔽外部的差異,從而實現更加便捷的編

Python+Selenium筆記:操作警告和彈出框

smi top web cell 錯誤 ext script ava mozilla #之前發的 driver.switch_to_alert() 這句雖然可以運行通過,但是會彈出警告信息(這種寫法3.x不建議使用) 改成 driver.switch_to.alert就不

Android圖片載入框架最解析,玩轉Glide的回撥與監聽筆記

參考原文:Android圖片載入框架最全解析(四),玩轉Glide的回撥與監聽 回撥的原始碼實現 的Target物件傳入到GenericRequest當中,而Glide在圖片載入完成之後又會回撥GenericRequest的onResourceReady()方法,onReso

Android圖片載入框架最解析,Glide強大的圖片變換功能筆記

參考原文:Android圖片載入框架最全解析(五),Glide強大的圖片變換功能 一個問題 百度這張logo圖片的尺寸只有540258畫素,但是我的手機的解析度卻是10801920畫素,而我們將ImageView的寬高設定的都是wrap_content,那麼圖片的寬度應該只有

Android圖片載入框架最解析,深入探究Glide的快取機制(筆記)

原文地址:Android圖片載入框架最全解析(三),深入探究Glide的快取機制 筆記: 1.Glide快取簡介 2.快取Key EngineKey 重寫了equals()和hashCode()方法,保證只有傳入EngineKey的所有引數都相同的情況下才認為是

Android圖片載入框架最解析,實現帶進度的Glide圖片載入功能筆記

參考原文:Android圖片載入框架最全解析(七),實現帶進度的Glide圖片載入功能 擴充套件目標 對Glide進行功能擴充套件,使其支援監聽圖片下載進度的功能 開始 dependencies { compile 'com.github.bumptech.glid

Android圖片載入框架最解析,探究Glide的自定義模組功能(筆記)

參考原文:Android圖片載入框架最全解析(六),探究Glide的自定義模組功能 自定義模組的基本用法 自定義模組功能可以將更改Glide配置,替換Glide元件等操作獨立出來,使得我們能輕鬆地對Glide的各種配置進行自定義,並且又和Glide的圖片載入邏輯沒有任何交集,