匯編基礎(一)

一.認識匯編語言

要認識匯編語言,還得從編程語言的發展說起,語言有以下幾種分類,其發展都是為了讓我們更容易去操縱計算機:

  • 機器語言:由0和1組成。
  • 匯編語言:用符號代替了0和1,比機器語言更便于閱讀和記憶。
  • 高級語言:Objective-C/Java/C++等,更接近人類語言,方便程序員的使用。

舉個例子??:
以賦值a=b為例(計算上的操作就是將寄存器BX的內容送入寄存器AX):

可以看到我們寫的代碼經過編譯轉換成計算機能夠知道的指令,從而讓計算機進行相應的操作。匯編語言與機器語言一一對應,每一條機器指令都有與之對應的匯編指令。匯編語言可以通過編譯得到機器語言,機器語言可以通過反匯編得到匯編語言。高級語言可以通過編譯得到匯編語言\機器語言,但匯編語言\機器語言幾乎不可能還原成高級語言。

匯編語言有如下特點:
① 可以直接訪問、控制各種硬件設備,比如存儲器、CPU等,能最大限度地發揮硬件的功能。但是知識點過多,開發者需要對CPU等硬件結構有所了解,所以不易于編寫、調試、維護。
② 能夠不受編譯器的限制,對生成的二進制代碼進行完全的控制。
③ 目標代碼簡短,占用內存少,執行速度快??赡軐懙脑次募容^大(相對高級語言比較啰嗦),但是編譯后的目標文件、可執行文件都小得多。

以c = a + b為例:(對比匯編語言和C++語言)

④ 匯編指令是機器指令的助記符,同機器指令一一對應。每一種CPU都有自己的機器指令集\匯編指令集,所以匯編語言不具備可移植性。

也是因為匯編語言的這些特點,讓學習它有了重要的用途:

  • 理解代碼的本質,為編寫高效代碼打下基礎。
  • 理解整個計算機系統的最佳起點和最有效途徑。
  • 編寫驅動程序、操作系統,對性能要求極高的程序或者代碼片段,可與高級語言混合使用(內聯匯編)
  • 軟件安全、病毒分析與防治

iPhone里面用到的是ARM匯編:

  • armv6:iPhone, iPhone2, iPhone3G, 第一代、第二代 iPod Touch
  • armv7:3GS,4, 4S,iPad, iPad2, iPad3, iPad mini, iPod Touch 3G, iPod Touch4
  • armv7s:5, 5C, iPad4
  • arm64:5S 及以后 iPhoneX , iPad Air, iPad mini2以后

二. 計算機硬件結構基礎與匯編

要想學好匯編語言,首先要對CPU等硬件結構有一定的了解。最為關鍵的是需要了解CPU和內存,我們遇到的絕大部分指令都是跟內存、CPU有關的。

App的啟動過程如下:

接下來我們具體講講數據(指定)的交互,其中有這么幾個重要的部件需要了解:
① 總線
② 內存
③ 寄存器

① 總線

每一個CPU芯片都有許多管腳,這些管腳和總線相連,CPU通過總線跟外部器件進行交互。
總線有三種類型:地址總線、數據總線和控制總線。

以CPU從內存的3號單元讀取數據為例:

地址總線負責傳地址(尋址),它的寬度決定了CPU的尋址能力。比如8086的地址總線是20(20根線),尋址能力是1M(2的20次方)(2的10次方就進1個單位,B → KB → M)。
數據總線負責傳數據,它的寬度決定了CPU的單次數據傳輸量,也就是數據傳輸速度。比如從內存中讀取1024字節的數據(1024B),8086(16位,即每次傳2字節)至少要讀(傳輸)512次,80386(32位,即每次傳4字節)至少要讀256次。 → 我們常說的32位和64位CPU指的就是這個
控制總線負責傳控制命令,它的寬度決定了CPU對其他器件的控制類型的多少。

例子??:8088的數據總線寬度是8(8條線,一條只能傳0和1兩種訊號,),8086的數據總線寬度是16,分別向內存中寫入89D8H(這邊4個16進制,需要16位,所以8080只能先傳一半)

常見的數據寬度:
位(Bit): 1個位就是1個二進制位0或1。
字節(Byte): 1個字節由8個Bit組成(8位)。內存中的最小單元Byte。
字(Word): 1個字由2個字節組成(16位),這2個字節分別稱為高字節和低字節。
進制占字節數
2進制:一個2進制占1位,1/8個字節。
8進制:一個8進制占3位,3/8個字節。
16進制:一個16進制占4位,1/2個字節。

8088和8086傳輸數據.png
② 內存

內存(Memory)也被稱為內存儲器,其作用是用于暫時存放CPU中的運算數據,以及與硬盤等外部存儲器交換的數據。只要計算機在運行中,CPU就會把需要運算的數據調到內存中進行運算,當運算完成后CPU再將結果傳送出來,內存的運行也決定了計算機的穩定運行。

計算機中有各類存儲器,它們的邏輯連接情況如下:


內存地址范圍(空間大?。┦蹸PU地址總線寬度的限制。比如8086地址總線寬度是20,可以定位2的20次方不同的內存單元,所以內存地址范圍為0x00000~0xFFFFF(1個16位占4),內存空間大小為1M。

各類存儲器地址肯定是不一樣的,他們又不一定是在一塊,所以我們把它想象成一塊虛擬的邏輯存儲器。如下圖:


每段內存地址都有特定的用途:


CPU訪問內存單元時,要給出內存單元的地址。8086有20位地址總線,可以傳送20位的地址,1M的尋址能力(從首地址到尾地址為1M)。但它又是16位結構的CPU,它內部能夠一次性處理、傳輸、暫時存儲的地址為16位。如果將地址從內部簡單地發出,那么它只能送出16位的地址,表現出來的尋址能力只有64KB。
所以,8086采用一種在內部用2個16位地址(段地址和偏移地址)合成的方法來生成1個20位的物理地址。8086是用“起始地址(段地址×16(16位就是進一格,后面補0)) + 偏移地址 = 物理地址”的方式給出物理地址。

8086的尋址方式.png

為了開發方便,我們可以采取分段的方法來管理內存,比如:
地址10000H - 100FFH的內存單元組成一個段,該段的起始地址為10000H,段地址為1000H,大小為100H。地址10000H - 1007FH、10080H - 100FFH的內存單元組成2個段,它們的起始地址為:10000H和10080H,段地址為1000H和1008H,大小都為80H。
偏移地址為16位,變化范圍0-FFFFH,僅用偏移地址來尋址最多可尋64KB個內存單元(尋址能力為64KB,所以一個段的長度最大為64KB)。比如給定段地址1000H,用偏移地址尋址,CPU的尋址范圍為:10000H - 1FFFFH。

③ 寄存器

總線和內存算是CPU外面的東西,接下來我們要說最重要的部分——CPU。
CPU主要是解釋計算機指令以及處理計算機軟件中的數據。它主要由三大部件構成(其內部部件之間由總線相連):


CPU組成.png

對程序員來說,CPU中最主要部件是寄存器,可以通過改變寄存器的內容來實現對CPU的控制。
不同的CPU,寄存器的個數、結構是不相同的。比如:8086是16位結構的CPU,有14個16位的寄存器(每個寄存器可以存放2個字節)。


8086CPU內部的寄存器.png

以通用寄存器為例
AX、BX、CX、DX這4個寄存器通常用來存放一般性的數據,稱為通用寄存器(有時也有特定用途)。通常,CPU會先將內存中的數據存儲到通用寄存器中,然后再對通用寄存器中的數據進行運算。

假設內存中有塊紅色內存空間的值是3,現在想把它的值加1,并將結果存儲到藍色內存空間。會有三個步驟:
① CPU首先會將紅色內存空間的值放到AX寄存器中:mov ax,紅色內存空間
② 然后讓AX寄存器與1相加:add ax,1
③ 最后將值賦值給內存空間:mov 藍色內存空間,ax

像AX、BX、CX、DX這4個寄存器都是16位的,意味著它們能存16個0或1,可以存兩個字節(byte),一個字(word)。上一代8086的寄存器都是8位的,為了保證兼容, AX、BX、CX、DX都可分為2個獨立的8位寄存器來使用。

在匯編的數據存儲中,有2個比較常用的單位:
字節(byte):1個字節由8bit組成,可以存儲在8位寄存器中。
字(word):1個字由2個字節組成,這2個字節分別稱為字的高字節和低字節。
比如上圖中,數據20000(4E20H,0100111000100000B),高字節的值是78,低字節的值是32。1個字可以存在1個16位寄存器中,這個字的高字節、低字節分別存儲在這個寄存器的高8位寄存器(AH)、低8位寄存器(AL)中。

已知,8086在訪問內存時要由相關部件提供內存單元的段地址和偏移地址,送入地址加法器合成物理地址。那是什么部件提供段地址?

段地址在8086的段寄存器中存放,8086有4個段寄存器:CS、DS、SS、ES,當CPU需要訪問內存時由這4個段寄存器提供內存單元的段地址。
CS (Code Segment):代碼段寄存器
DS (Data Segment):數據段寄存器
SS (Stack Segment):堆棧段寄存器
ES (Extra Segment):附加段寄存器

CS(代碼段寄存器)

CS為代碼段寄存器,IP為指令指針寄存器,它們指示了CPU當前要讀取指令的地址。任意時刻,8086CPU都會將CS:IP指向的指令作為下一條需要取出執行的指令。

CS寄存器.png

步驟如下:
① CS、IP中的內容送入地址加法器,生成物理地址20000H。
② 地址加法器將物理地址送入輸入輸出控制電路。
③ 輸入輸出控制電路將物理地址20000H送上地址總線。
④ 從內存20000H單元開始存放的機器指令(B8 23 01)通過數據總線被送入CPU。(為什么是3個呢?應該是這三個組成一個完整的機器指令,有的是兩個組成的)
⑤ 輸入輸出控制電路將指令(B8 23 01)送入指令緩沖器。
讀取一條指令后,IP中的值自動增加,以使CPU可以讀取下一條指令。因當前讀入的指令(B8 23 01)為三個字節,所以IP中的值加3。此時,CS:IP 指向內存單元2000:0003。
⑥ 執行控制器執行指令(B8 23 01),即mov ax 0123H。
⑦ 指令被執行后AX中的內容為0123H。
此時,CPU將從內存單元2000:0003處讀取指令。
⑧ CPU從內存20003H處讀取指令(BB 03 30)入指令緩存器,IP中的值加3。
后面指令以此類推。。。

在8086CPU啟動或復位后,CS:IP 被設置為FFFFH:0000H,所以FFFF0H單元中的指令是開機后的第一條指令。

CPU從何處執行指令是由CS、IP中的內容決定的,我們可以通過改變CS、IP的內容來控制CPU執行目標指令。

jmp指令(跳轉)

8086提供了一個mov指令(傳送指令),可以用來修改大部分寄存器的值,比如mov ax,10、mov bx,20、mov cx,30、mov dx,40。但是,mov指令不能用于設置CS、IP的值,8086沒有提供這樣的功能。
8086提供了另外的指令來修改CS、IP的值,這些指令統稱為轉移指令,最簡單的是jmp指令。

jmp 段地址:偏移地址 ?? 用指令中給出的段地址修改CS,偏移地址修改IP
jmp 直接值??用直接值修改IP
jmp 某一合法寄存器??用寄存器中的值修改IP

例子??:
jmp 2AE3:3,執行后CS = 2AE3H,IP = 0003H,CPU將從2AE33H處讀取指令。

DS(地址段寄存器)

CPU要讀寫一個內存單元時,必須要先給出這個內存單元的地址,在8086中,內存地址由段地址和偏移地址組成,8086中有一個DS段寄存器,通常用來存放要訪問數據的段地址。
舉個例子??:

mov bx,1000H
mov dx,bx
mov al,[0]

上面3條指令的作用將10000H(1000:0)中的內存數據賦值到al寄存器中。mov al,[address]的意思將DS:address中的內存數據賦值到al寄存器中,由于al是8位寄存器,所以是將一個字節的數據賦值給al寄存器(由于8086不支持將數據直接送入段寄存器中,mov ds,1000H是錯誤的)。

上面的例子中是賦值給al(8位),如果是賦值給ax呢(16位)?那就是字型數據的傳遞(2個字節,16位)。
在內存中,一個地址放一個字節的數據(8位,兩個16進制,如34),傳遞字型意味著要2個內存單元。那是從低內存開始或者從高內存開始,這就涉及到大小端的概念。

  • 大端模式,是指數據的高字節保存在內存的低地址中,而數據的低字節保存在內存的高地址中(高低\低高)
  • 小端模式,是指數據的高字節保存在內存的高地址中,而數據的低字節保存在內存的低地址中(高高\低低)

比如我們常見的x86就是小端模式,ARM小端大端都可以。

大小端.png

舉個例子??:

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
mov 內存單元,內存單元 //這個是錯誤的

add和sub指令

add 寄存器,數據 // 比如add ax,8
add 寄存器,寄存器 // 比如add ax,bx
add 寄存器,內存單元 // 比如add ax,[0]
add 內存單元,寄存器 // 比如add [0],ax

sub 寄存器,數據 // 比如sub ax,8
sub 寄存器,寄存器 // 比如sub ax,bx
sub 寄存器,內存單元 // 比如sub ax,[0]
sub 內存單元,寄存器 // 比如sub [0],ax

棧(棧段)

棧:是一種具有特殊的訪問方式的存儲空間(后進先出)
8086會將CS作為代碼段的段地址,將CS:IP指向的指令作為下一條需要取出執行的指令。
8086會將DS作為數據段的段地址,mov ax,[address]就是取出DS:address的內存數據放到ax寄存器中。
8086會將SS作為棧段的段地址,任意時刻,SS:SP指向棧頂元素。
8086提供了PUSH(入棧)和POP (出棧)指令來操作棧段的數據
比如push ax是將ax的數據入棧,pop ax是將棧頂的數據送入ax。
??諘r,SS:SP指向??臻g最高地址單元的下一個單元(就是棧的下面,棧外了)。

push.png
pop.png

push和pop指令
在8086中,push、pop操作的數據都是2個字節的,也就是以字為單位的。

push 寄存器 // 將一個寄存器中的數據入棧
pop 寄存器 //用一個寄存器接收出棧的數據
push 段寄存器 // 將一個段寄存器中的數據入棧
pop 段寄存器 //用一個段寄存器接收出棧的數據
push 內存單元 // 將一個內存單元處的字(也就是開始的兩個內存單元)入棧
pop 內存單元 //用一個內存單元接收出棧的數據

舉個例子??:

mov ax,1000H
mov dx,ax //dx = 1000H
push [0] //將1000:0處的字壓入棧中
pop [2] //出棧的數據送入1000:2處

段的總結

我們可以用一個段存放數據,將它定義為“數據段”;
我們可以用一個段存放代碼,將它定義為“代碼段”
我們可以用一個段當作棧,將它定義為“棧段”。

對于數據段,將它的段地址放在DS中,用mov、add、sub等訪問內存單元的指令時,CPU就將我們定義的數據段中的內容當做數據來訪問;
對于代碼端,將它的段地址放在CS中,將段中第一條指令的偏移地址放在IP中,這樣CPU就將執行我們定義的代碼段中的指令;
對于棧段,將它的段地址放在SS中,將棧頂單元的偏移地址放在SP中,這樣CPU在需要進行棧操作的時候,比如執行push、pop指令等,就將我們定義的棧段當成??臻g來用。

三.完整的匯編程序

完整的匯編程序包含匯編指令和偽指令。
匯編指令如mov、add、sub等,有對應的機器指令,可以被編譯為機器指令,最終被CPU執行。偽指令如assume、 segment、ends、end等,沒有對應的機器指令,由編譯器解析,最終不被CPU執行。

// 聲明一下code段是cs段、代碼段
assume cs:code
// segment和ends的作用是定義一個段,code是我們取的段名
code segment
    mov ax, 1122h
    mov bx, 3344h
    add ax, bx     
    
    // 正常退出程序
    mov ax, 4c00h
    int 21h    
code ends 

// 編譯器遇到end時,就結束對源程序的編譯
end 

使用匯編語言編寫一個完整的程序,步驟大致如下:
編寫源代碼 → 編譯、鏈接 → 調試、運行

源程序->可執行文件.png

在完整的匯編程序中,我們會看到常見的幾種語法。

  • 中斷int
  • 循環loop
  • call和ret指令

中斷int
可以通過指令int n產生中斷,n是中斷碼,內存中有一張中斷向量表,用來存放中斷碼對應中斷處理程序的入口地址。
CPU在接收到中斷信號后,暫停當前正在執行的程序,跳轉到中斷碼對應的中斷向量表地址處,去執行中斷處理程序。

常見中斷:

  • int 10h用于執行BIOS中斷
  • int 3是“斷點中斷”,用于調試程序
  • int 21h用于執行DOS系統功能調用,AH寄存器存儲功能號

循環loop
loop指令和cx配合使用,用于循環執行重復的操作,類似于高級語言中的for、while循環。
loop指令的執行流程,讓cx的值減一,判斷cx的值。

       mov ax, 2h
       mov cx, 5    // 5為循環次數
s:     add ax, ax   // s為標號
       loop  s      // 循環執行標號的程序,即 add ax, ax 

call和ret指令
call是將下一條指令的偏移地址入棧后,轉到標號處執行指令。

call 標號

ret是將棧頂的值出棧,賦值給ip。

四.棧幀

棧幀就是一個函數執行的環境,包括:參數、局部變量、返回地址等。
每個函數都有自己對應的棧幀,用來保存(或者說保護)自己的數據。

棧幀的介紹.png
棧幀的詳情1.png
棧幀的詳情2.png

以下是關于函數執行過程,棧幀的變化(對應的圖片如下):

  1. push 參數
  2. push 函數的返回地址
  3. push bp (保留bp之前的值,方便以后恢復)
  4. mov bp, sp (保留sp之前的值,方便以后恢復)
  5. sub sp,空間大小 (分配空間給局部變量)
  6. 保護可能要用到的寄存器
  7. 使用CC(int 3)填充局部變量的空間
  8. --------執行業務邏輯--------
  9. 恢復寄存器之前的值
  10. mov sp, bp (恢復sp之前的值)
  11. pop bp (恢復bp之前的值)
  12. ret (將函數的返回地址出棧,執行下一條指令)
  13. 恢復棧平衡 (add sp,參數所占的空間)
棧幀.jpeg
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 語言的發展 機器語言: 由0和1組成的機器指令. 如:0101 0001 1101 0110匯編語言(assemb...
    hfzhangzhang閱讀 305評論 0 0
  • 匯編語言的發展 機器語言 由0和1組成的機器指令. 加:0100 0000 減:0100 1000 乘:1111 ...
    宵衣旰食閱讀 615評論 1 0
  • 機器語言:由0和1組成的機器指令 高級語言:更接近人類的語言如 oc,swift,c... 一條匯編指令和一條機器...
    領悟12138閱讀 464評論 0 0
  • 引言 什么是匯編語言?答:匯編語言是計算機語言,通俗來講就是人類與計算機(CPU)交流的橋梁,計算機不認識人類的語...
    struggle3g閱讀 1,963評論 1 6
  • 王爽匯編全書知識點大綱 第一章 基礎知識 機器語言 匯編語言的產生 匯編語言的組成 存儲器 cpu對存儲器的讀寫 ...
    2c3ba901516f閱讀 2,440評論 0 1