匯編學習筆記


匯編語言是為了解決早期機器指令過於復雜難記發明的,本質就是使用一些特殊字母代替機器指令,運行前由編譯器翻譯為機器指令。所以的匯編是最接近機器語言的語言,它可以面向硬件編程,我們使用高級語言,如 c,c++,java到最后都必須轉為匯編。通常情況下我們並不會使用匯編編程,因為它太復雜,很難進行大型項目編寫,而且匯編基本都是針對硬件編程,完全不可移植。但是學習匯編卻也是必要的,它可以大大提升我們對計算機、對操作系統、對其他語言的理解。我就是因為想要森如學習操作系統才開始學習匯編,以下是簡要的學習筆記(學習筆記而不是教程,只是一些總結歸納,而且基本都是概念性的東西,不能當做教程,甚至可能只有本人能看懂,請做好心理准備)。 

匯編的學習資料不是很豐富,但是好書也還是不少的,這里推薦王爽老師的《匯編語言》,內容詳略有度通俗易懂,我的學習基本就是基於這本書,此外再推薦一個網站 http://c.biancheng.net/asm/ ,王爽老師的書側重原理,此網站側重用法,請結合使用

 

學習環境准備

  因為早期的書都是基於windows系統,所以如果在windows系統上學習匯編就方便的很多,本人因為某些原因裝的是linux 系統,與書本上的環境對不上,所以環境准備就多了一個門檻。linux上當然也是可以直接編寫匯編的,但是其語法規則和windows上差別很大,十分詭異,而且和書本對應不上,而且我本質不是為了深入研究匯編,所以不想過多研究不同的環境,就在linux上搭建了一個dos環境,如果有人和我一樣,請往下看

  linux上運行.exe程序基本都是基於虛擬機,wine ,freedos,dosemu等等,但是並不是所有的虛擬機都適合學習匯編,這里推薦dosbox,學習匯編因為要使用到debug.exe,dosbox對這個調試工具的支持很好,而且它十分小巧,也是同類型運行速度最快的虛擬機。美中不足是界面太丑了,分辨率不可調(我的電腦不可調整)。如果對此不介意的話dosbox是首選,速度夠快,dosemu 也非常好用,功能比dosbox更強大,重要的是界面好看

  學習匯編最基本的需要使用到三個工具,匯編器,鏈接器,調試工具,這三個工具在的windows 和linux上都有,也有跨平台的,因為我的學習是基於dos 的,所以我的使用的分別是masm.exe \ link.exe \ debug.exe 

  實際上匯編並不像c語言,Java那樣可以一招鮮吃遍天,除了平台會導致匯編形式不同,計算機的位數也會導致匯編的代碼不同(本文就是16位匯編)操作系統就使用了好幾種匯編語言,如果不是為了深入研究匯編, 就沒必要糾結於學習哪種好或者是不是都要學的問題,按照書本或者自己喜歡的語法來學習,以學習原理為主

 

計算機基本知識

1、計算機基本組成

1)計算機有存儲器 | 運算器 | 控制器| 輸入設備 |輸出設備 五部分組成
2)計算機的五部分是概念分類,實際的物理設備有很多,比如輸入設備就有鍵盤和鼠標,存儲設備有磁盤、內存、寄存器等等,這些物理設別互相配合使計算機能完成復雜的任務。既然需要配合,部件之間就必須要進行通信,計算機的所有部件之間的通信斗依賴總線
3)總線是計算機的一個概念,實際上就是一個集成電路。計算機的所有部件都連接在一個集成電路上,互相之間可以通過集成電路傳遞信息
4)計算機之間並不是隨意的通信,隨意的配合,而是需要有人調度,這個調度者就是CPU

 2、CPU基本知識 

1)CPU是計算機的核心, 負責數據的運算和計算機的控制。顯然CPU不是一個單一的設備,構成CPU的主要有三個部件:控制器、運算器和寄存器。
2)控制器是負責計算機資源調度的,比如數據的讀寫,網絡請求的收發。當cpu收到一個指令后,控制器就通過總線將指令發送給相關的部件
3)計算機的總線分為三種
 *地址總線:主要負責內存的尋址,每個cpu都存在多個內存總線,每個總線都只能傳遞0或者1,那么地址總線的組合就可以傳遞一個二進制碼, 如果一個cpu存在n個地址總線,它最多能訪問2^n方個地址碼,那此計算機的最大可用內存也就只能是 2^n 字節。可使用內存的大小受此限制
 *數據總線:負責cpu和其他組件的數據傳遞,原理和地址總線一樣,如果數據總線個數為8,每次就能傳遞1字節數據
 *控制總線:負責cpu對其他組件的控制,控制總線是一系列總線的統稱,cpu有很多控制總線,每個控制總線斗代表一種基礎控制指令,比如讀數據、寫數據,指令的組合使用可以完成一些復雜控制
4)CPU的運算器負責數據的運算,既然要運算數據就必須有數據的來源和結果的存儲位置,這就是寄存器的作用,作為cpu計算的數據來源和結果的存儲位置.當然寄存器的作用遠不止如此
5)每個CPU 都包含多個寄存器,每個寄存器各有作用,有的作為cpu運算數據來源,有的保存指令的存儲位置,而且每個cpu的寄存器數量和分類都不同,這里以8086cpu 介紹

3、存儲器基本知識

1)存儲器是一個統稱,磁盤、內存、寄存器都是存儲器,這里主要介紹內存
2)CPU進行數據計算的直接數據來源是寄存器,而寄存器的數據來源是內存了。cpu需要計算時就將數讀取到寄存器,然后從寄存器再獲取到數據進真正的計算。
3)cpu想要使用數據首先必須要找到數據,內存就好象存儲數據的小房間,每個房間都有一個獨一無二的門牌號,這個門牌號就是內存的物理地址。cpu要進行數據讀寫就必須給內存提供一個物理地址,告訴內存需要哪塊數據或者數據要存儲在哪塊內存,這就是尋址。
4)cpu從內存讀寫數據分為三步 (以數據讀取舉例)向內存發出一個內存區編號 > 發出指令告知內存需要讀取這個編號的內容 > 內存將數據發送給cpu
5)內存其實也是多種存儲器的統稱,這些存儲器包括主存儲器,RAM存儲器,ROM存儲器,顯卡存儲器,網卡存儲器等等,這些存儲器都是獨立的設備,但是它們都和總線相連,於是CPU在邏輯上把它們當作一個內存,用地址空間加以區分,每個存儲器占用特殊的一段地址空間

4、段和棧

1)我們在編程時經常會聽到數據段、代碼段、堆、棧等概念,這些概念都是方便內存管理的,對於計算機來說並沒有這么復雜的概念,cpu只知道存儲空間,一些高級的抽象概念只不過是人們為了內存管理方便,把某些區域的內存規定為某類數據的專門存儲地。換句話說你可以規定任意內存作為專門存儲源代碼的地方,這塊內存就是代碼段
2)計算機中有一個基礎的概念,棧。棧是一種數據結構,特點是后進先出,市面上集合所有的cpu都通過某種方式實現了這種數據結構。cpu只有存儲空間的概念, 自然不會天然的存在這種復雜的數據結構,棧的本質還是一段存儲空間,只不過計算機通過某種方式保證,后進入這段空間的數據先出去,先進入這段空間的數據后出去。只要能實現這個特點,就能說這是一個棧
3)計算機實現棧結構的方式也比較簡單,使用一個寄存器保存棧頂的物理地址,在存取數據時更新此寄存器的數據,保證這個寄存器的物理地址時刻指向棧頂,雖然cpu能時刻記錄棧頂元素的位置,但是的它並不記錄棧的大小, 也就是說你可以一直向棧里面壓入元素,就很可能造成棧越界,所以使用棧的時候,棧越界是必須要考慮的 

寄存器專題

寄存器是學習匯編的核心,在匯編中,程序員能夠用指令讀寫的只有寄存器,並以此實現對cpu的控制。

在學習寄存器之前需要學習兩個基本概念:段地址和偏移地址

段地址和偏移地址

cpu的尋址能力是由寄存器的寬度決定,我們常說的16位cpu其實就是指它的寄存器寬度是16位,一個16位寬度的cpu每次尋址最多能找到2^16內存單元。

計算機的內存管理能力是由地址總線數量決定的,一個cpu含有16根地址總線,計算機就可以管理2^16個內存單元

8086CPU 寄存器有16字節,但是cpu的地址總線卻有20根,這就造成了一個矛盾,根據地址總線的數量計算,cpu的尋址空間最大為2^20,但是根據寄存器的寬度計算,cpu每次尋址最大只能找到2^16的位置。為了解決這個問題人們發明了段地址和偏移地址的概念。其實概念很簡單,既然單個寄存器無法滿足尋址要求,那就使用兩個寄存器組合好了,我們可以用一個寄存器保存一個基礎地址(段地址),用另一個寄存器保存一個補充地址(偏移地址)。兩個組合就可以得到一個更大的地址
例子:
問題:用兩個三位數AAA,BBB表示一個四位數CCCC(2538)
實現:我們可以約定兩個三位數的組合規則是 AAA * 10 + BBB,那么我們就可以把兩者組合寫作 250 * 10 + 038

基於以上例子的原理得到
物理地址 = 段地址 * 16 + 偏移地址

 

寄存器分類與用途介紹

每種CPU 的寄存器數量和用途都是不同,8086cpu含有14個寄存器 ax | bx | cx | dx | ds | ss | cs | es |  sp | bp | si | di | ip | psw,下面介紹這些寄存器的用途

1、被用來存放一般性數據的寄存器被稱為數據寄存器:ax | bx | cx | dx。8086cpu寄存器都是16位的,而上一代寄存器都是8位的,為了向下兼容,這四個16位通用寄存器都可以被分成兩個獨立的8位寄存器使用,高8位用H表示,低8位用L表示。如AX 可以被分為 AH,AL。數據寄存器沒有特殊用途,一般就是存取數據,或者作為數據中轉站

2、存儲段地址的寄存器被稱為段寄存器:cs | ds | ss | es ,8086cpu尋址是依靠段地址加偏移地址的,以上的四個寄存器就是保存段地址,一般使用一個類型段寄存器保存一種數據的段地址,比如cs段寄存器專門用於保存代碼段,ds用於保存數據段,ss保存棧段, 但是這並不是強制的

  • cs(Code Segment): 代碼段寄存器,cs 是8086cpu中非常重要的寄存器,它和ip寄存器(指令指針寄存器,保存偏移地址)組合形成代碼段物理地址,指示cpu將要運行的下一句代碼的物理地址。
  • ds(Data Segment):數據段寄存器。匯編中進場需要將內存中的數據移動到寄存器計算,如mov  ax, [0] ,這個命令是將偏移地址為0 的 內存單元的數據移動至ax,我們知道一個單獨的便宜地址是無法定位物理地址的,那么這個指令是不是錯了呢。並不是, cpu默認將ds寄存器內的數據當做段地址,所以要使用上面這個指令一般還會加上 mov  ds,xxx ,初始化ds的值
  • ss(Stack Segment):棧段寄存器。cpu中棧的實現就是依靠寄存器保存棧頂元素的物理地址,當棧數據更新時,該寄存器的值也同步更新,這個寄存器就是ss,與之配合的保存偏移地址的寄存器就是sp和bp,ss:sp需要時刻指向棧頂元素,不可更改,那我們想要訪問棧中的其他元素怎么辦呢,將sp的地址傳遞給bp,由 ss:bp 去尋找元素
  • es(Extra  Segment):  擴展段寄存器,我們尋址是必須使用段急寄存器的,但是段寄存器數量有限,可能在有些程序中會不夠用,此時就可以使用es寄存器

3、變址寄存器:si  | di。si是源變址寄存器,di是目的變址寄存器,常用於串指令操作,定位串的原始位置和目標位置 。比如將一個字符串從一個內存地址拷貝到另一個內存地址,可以使用ds:si 記錄此字符串原來的地址, ds:di 記錄數據將要被拷貝到的目標地。除了這種單獨的使用方式,si | di還可以和bx , bp 結合使用用於尋址。形如 mov ax, [bx + si] ,這里 [bx + si] 代表偏移地址,其值等於bx內的數據 +  si內的數據,它的默認段地址在ds中。當si | di 與bp集合使用,段地址默認在es中

4、最特殊的寄存器標志寄存器:psw。標志寄存器名副其實, 此寄存器不像其他寄存器一樣存儲一個數據,它是按位起作用的,也就是說此寄存器的每一位都含有特殊的含義的,8086cpu標志寄存器有16位,按理說它可以標示16個特殊的含義,事實卻不是如此,它只使用了0,2,4,6,7,8,9,10,11這9個位,其他的位沒有意義,下面講每一個位的含義

ZF標志:位於第6位,與計算指令相關,如果指令計算結果為0,ZF = 1,計算結果不為0,結果為0

PF標志:位於第2位,奇偶標志位,奇偶標志位,記錄相關指令執行結束后,其結果的二進制表示中1的個數是否為偶數,為偶數 pf =1,否則 pf = 0

SF標志:位於第7位,符號標志位,記錄相關指令結束后結果是否為負數,結果為負,sf=1,否則,sf=0

OF標志:位於第11位,計算溢出標志位,記錄相關指令結束后,結果是否發生了溢出,發生溢出,of=1,否則of=0

 

端口

  計算機中的各種硬件都鏈接在總線上,以此互相通信,接受cpu調度,這些部件都通過芯片連接在總線上,這些芯片大體分為存儲芯片(各種存儲器)和接口芯片(網卡,顯卡等)。cpu把所有的存儲芯片當做一個整體,全部看做內存,然后進行統一編號,在訪問的時候就不需要區分他們的種類了,只要根據編號來區分就可以了。接口芯片與存儲芯片不同,每個芯片中都含有一個可由cpu讀寫的寄存器,cpu不會直接 操作接口芯片,而且通過與每個芯片的寄存器溝通,間接控制芯片。為了管理方便,cpu把這些外部寄存器也進行編號, 通信時直接根據編號定位寄存器。這個編號就是端口

 

中斷 

中斷是CPU的一種能力,簡單來說就是cpu可以在執行一段程序的中間暫停,轉去執行其他的程序,任何一個通用的cpu都具有這種中斷能力。中斷分為內中斷和外中斷,下面分開介紹

內中斷: 

  當cpu內部發生某些特殊的事情(比如除法錯誤),需要運算器立即進行處理, 計算機會產生一個“中斷信息”發送給cpu,cpu執行完當前正在執行的指令后會根據這個中斷信息的描述轉去執行處理這個特殊事情的處理程序,這就是內中斷。觸發中斷的事件被稱為中斷源, 處理中斷的程序稱為中斷處理程序。中斷源是固定的是計算機事先定義好的,而中斷處理程序是可以由程序員編寫的

  cpu是如何識別接收到的中斷源是什么,由如何定位中斷處理程序的呢。其實在計算機內部,每個中斷源都有一個獨一無二的中斷類型碼(8位),比如除發錯誤的中斷類型碼是0,當程序發生除零錯誤,計算機就會生成中斷信息,此中斷信息就包含了中斷類型碼。cpu接收到中斷類型碼就知道了中斷類型,然后去尋找中斷處理程序。那么cpu是如何通過8位的中斷類型碼找到16位的中斷處理程序地址的呢 ,或許你會想中斷處理程序的地址也包含在中斷信息中,事實卻不是這樣。其實計算機中實現存在一個中斷向量表,這個表保存着終端類型和它對應的處理程序的地址,cpu獲取到了中斷類型,在這個表里面查詢一下就知道中斷處理程序的地址了。中斷類型碼是8位的,標示計算機中最多能存在256個中斷類型和的256個 中斷處理程序, 不用擔心,現在計算機的中斷類型還遠沒有達到256個

  中斷處理程序是可以由我們自定義編寫的, 具體的做法是 1)編寫某個中斷處理程序 2)將程序存到某塊內存3)用這塊內存的起始地址替換中斷向量表對應中斷類型的處理程序地址(安裝) 

  除了自然產生的中斷,我們也可以編程手動引發中斷,使用  int n 指令就可以引發一個中斷讓cpu來處理, n 即為中斷類型編號

外中斷:  

  計算機除了運算,還要對其他的部件進行控制做一些 其他的事,比如打字,於是cpu必須能接受外部設備的請求,並處理請求。當點擊鍵盤,鍵盤的芯片就會通過寄存器給cpu發送一個中斷指令讓cpu盡快來處理這個事件,終於中斷處理的過程和內中斷沒什么太大差別

 

編程

 1、基本指令介紹

匯編語言簡單來說就是用一些單詞指令代替機器指令,運行時由翻譯軟件將單詞翻譯成機器指令,所以每個機器指令在匯編中都有對應的匯編指令,類似獲取數據,加減乘除。當然,匯編語言鎖含有的指令比機器指令多很多,主要分為三種

1)匯編指令:機器碼的助記碼,每個有對應的機器碼
2)偽指令:用於編譯器操作的輔助指令,沒有對應機器碼
3)符號體系:基本運算符號,供編譯器使用,沒有對應機器碼

如下是一個最簡單的匯編代碼,中間是常用指令的介紹, 其他的就是一個最簡單的匯編程序的基本結構

assume cs:code ;將cs寄存器和代碼段關聯(會使用就好,不必深究)

code segment ;聲明一個段(類似c語言的一個函數名加左括號)
start: ;設置程序開始位置
; --------------------- 代碼編寫開始 -----------------------

;數據定義
db 0,0,0,0 ;以字節方式定義數據
dw 0,0,0,0 ;以字符方式定義數據
dd 0,0,0,0 ;以雙字符方式定義數據
db 100 dup(0) ;直接定義100個值為0的字節型數據
db 100 dup('a') ;直接定義100個值為 'a' 的字節型數據
db 100 dup('ab') ;直接定義100個值為 'a' 的字節型數據
db 100 dup('ab',1) ;直接定義100個值為 'a' 的字節型數據

;數據的移動
mov ax,1111B ;將二進制數到寄存器ax
mov ax,1 ;將十進制數移動到寄存器ax
mov ax,1111H ;將十六進制數移動到寄存器ax
mov ax,'a' ;字符寫法
mov ax,bx ;將bx中的數據移動到ax
mov ax,[0] ;將 (段地址為ds,偏移地址為0) 地址處的數據移動到ax
mov ax,[bx] ;將 (段地址為ds,偏移地址為bx寄存器中的內容) 處的數據移動到ax
mov ax,[bx+1] ;將 (段地址為ds,偏移地址為bx寄存器中的內容 + 1) 處的數據移動到ax
mov ax,[bx+si+10*2] ;將 (段地址為ds,偏移地址為bx寄存器中的內容 + si 寄存器的內容 + 1)處的數據移動到ax
;加減乘除
add ax,1 ;ax自增 1 ,相當於 ax = ax + 1
add ax,bx ;ax自增bx ,相當於 ax = ax + bx
sub ax,1 ;ax自減 1 ,相當於 ax = ax - 1
sub ax,bx ;ax自減bx ,相當於 ax = ax - bx
inc ax ;ax自增 1 ,相當於 ax = ax + 1
dec ax ;ax自減 1 ,相當於 ax = ax - 1
;乘法規則:兩個乘數的位數要么都是8位要么都是16位.8位乘法,一個乘數默認放在ah,結果放在ax中;16位乘法,一個乘數默認放在ax中,結果的高位放在dx,低位放在ax
mul bl ;8位乘法,相當於 ah * bl
mul byte ptr ds:[0] ;8位乘法,byte ptr表示取一個字節的數據作為乘數
mul bx ;16位乘法,相當於 ax * bx
mul word ptr ds:[0] ;16位乘法,word ptr表示取一個字符的數據作為乘數
;除法規則:被除數默認放在ax.除數為8位,al存儲商,ah存儲余數;16位除法,ax存儲商,bx存儲余數
div bl ;8位除法,相當於 ah * bl
div byte ptr ds:[0] ;8位除法,byte ptr表示取一個字節的數據作為除數
div bx ;16位除法,相當於 ax * bx
div word ptr ds:[0] ;16位除法,word ptr表示取一個字符的數據作為除數

;與|或,將兩個值的二進制數進行與或操作
and al,11H ;將al和十六進制數11H與操作,結果保存在al
and al,ah ;將al和ah與操作,結果保存在al
or al,11H ;或的規則和與規則一致
or al,ah

;轉移指令,可以修改寄存器cs和ip的指令被稱為轉移指令
jmp short 標號 ;段內短轉移,cs不變,ip改變, ip的變化范圍為 -128 - 127
jmp near ptr 標號 ;段內近轉移,cs不變,ip改變, ip的變化范圍為 -32768 - 32767
jmp far ptr 標號 ;段間轉移,cs變化
jmp word ptr 內存單元地址 ;以內存中存儲的值作為轉移的偏移地址
jmp dword ptr 內存單元地址 ;以內存中存儲的值作為轉移的偏移地址
jcxz ;條件轉移,當cx=0時ip值變化
ret ;使用棧中的數據修改ip內容實現近轉移,相當於:ip = ss*16+sp ;sp=sp+2
retf ;使用棧中的數據修改cs和ip內容實現遠轉移,相當於:ip=ss*16+sp; sp=sp+2; cs=ss*16+sp; sp=sp+2
call             ;將當前指令壓如棧中,然后轉移,不能實現短轉移

; --------------------- 代碼編寫結束 ------------------------
code ends ;代聲明碼段結束(類似c函數的右括號)
end ;整個程序結束

 

 2、規結構化編程以及特殊關鍵字介紹

assume ds:data,ss:stack,cs:code                       ;把某個段寄存器與特定的段關聯,表明這個寄存器專門用於保存一種特定的數據

;定義數據段
data segment
    db 100,dup(0)
data ends

;定義棧段
stack segment
    db 100,dup(0)   
stack ends
;注意:無論是數據段還是棧段,本質都是計算機為程序分配的一塊內存,本程序中我們並沒有指定這塊內存在什么位置,操作系統在加載程序的時候會為程序自動的分配一塊空間
;一般操作系統分配的這塊內存空間以 ss:0 為起始位置,但是我們在程序中想要獲取這個位置的話不需要使用ss寄存器的形式,直接使用 mov ax,data 就可以獲取到數據的段地址
;至於數據的偏移地址是需要我們根據實際情況計算的

;定義代碼段
code segment
start:        mov ax,offset  start                    ;offset 用於獲取標號相對寄存器ip保存值的偏移地址,相當於 :mov ax,0
second:       mov ax,offset  second                   ;相當於mov ax,3 (前一個指令占用三個字節)
              
              mov cx,10
s:            add ax,2                                ;loop 用於循環,寄存器cx保存的數字是循環次數,每循環一次,cx減1,,cx等於0時跳出循環
              loop s                                  ;loop 跳轉到標號s 處執行程序

              jmp short s
              jmp  a 
              

code ends

end start

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM