8086 彙編學習 Part 2

ChillsLove發表於2024-04-05

暫存器及資料儲存

CPU組成

  • 運算器進行資訊處理
  • 暫存器進行資訊儲存
  • 控制器協調各種器件進行工作
  • 內部匯流排先實現 CPU 內各個器件之間的聯絡

暫存器

暫存器是 CPU 內部的資訊儲存單元。
8086 CPU 有 14 個暫存器:

  • 通用暫存器:AX, BX, CX, DX
  • 變址暫存器:SI,DI
  • 指標暫存器:SP,BP
  • 指令指標暫存器: IP
  • 段暫存器: CS, SS, DS, ES
  • 標誌暫存器: PSW
    8086 CPU 所有的暫存器都是 16 位的,可以存放兩個位元組。

通用暫存器

一個 16 位暫存器儲存一個 16 位的資料
每一位儲存一個二進位制位能儲存的最大值為 \(2^{16} - 1\) (FFFFH)

如何相容 8 位暫存器環境下編寫的程式?

通用暫存器均可以分為兩個獨立的 8 位暫存器使用,AX 可以分為 AH 和 AL,BX, CX, DX 同理

“字”在暫存器中的儲存

8086 是 16 位 CPU,字長 為 16 bit
一個可以存在一個 16 位暫存器中

  • 這個字的高位位元組存在這個暫存器的高 8 位暫存器
  • 這個字的低位位元組存在這個暫存器的低 8 位暫存器

mov指令

  • MOV (通用暫存器) , (資料)
  • MOV (暫存器) , (暫存器)
  • MOV (暫存器) , (記憶體單元)
  • MOV (記憶體單元) , (暫存器)
  • MOV (段暫存器) , (通用暫存器)
    彙編指令不區分大小寫
    數值預設為 10 進位制,寫入 16 進位制需在末尾新增 'H'
    在低位暫存器出現溢位時,不會進位到高位

確定實體地址的方法

CPU 訪問記憶體單元時要給出記憶體單元的地址。
所有記憶體單元構成的儲存空間是一個一維的線性空間。
每一個記憶體單元都有唯一的地址,叫實體地址
8086 有 20 位地址匯流排,可傳送 20 位地址,定址能力是 \(2^{20} = 1 M\)
8086 是 16 位結構的 CPU,運算器一次最多處理 16 位的資料,暫存器的最大寬度為 16 位。
在 8086 內部處理的、傳輸、暫存的地址也是 16 位,定址能力只有 64 KB

如何處理地址匯流排 20 位的定址能力受限於 16 位地址長度這一問題?

用兩個 16 位地址(段地址和偏移地址)合成一個 20 位的實體地址。
地址加法器合成實體地址的方法: \(實體地址 = 段地址 * 16 + 偏移地址\)

記憶體的分段表示法

記憶體並沒有分段,段的劃分來自於 CPU
Assembly336b8fa9b12d81a3d.png
段地址 : 偏移地址
同一段記憶體可以有多種分段方案

  • \(段地址 * 16\) 必然是 16 的倍數, 所以一個段的起始地址也一定是 16 的倍數。
  • 偏移地址為 16 位,16 位地址的定址能力為 64K,所以一個段的長度最大為 64K。
  • 可以用不同的段地址和偏移地址形成同一個實體地址。
    8086 有豐富的取址方式,因此偏移地址可以用多種方法提供。

段暫存器

段暫存器存放段地址,段暫存器只能由暫存器賦值,不支援用字面值直接賦值。
8086 CPU 有 4 個段地暫存器

  • CS 程式碼段暫存器
  • DS 資料段暫存器
  • SS 棧段暫存器
  • ES 附加段暫存器

Debug

Debug 是 DOS 系統中著名的除錯程式。
使用 Debug 程式,可以檢視 CPU 各種暫存器中的內容、記憶體的情況,並且在機器指令級跟蹤程式的執行。

常用 Debug 命令

  • R 命令 檢視、改變 CPU 暫存器的內容
    -R 檢視所有暫存器內容
    -R (暫存器名) (資料) 修改特定暫存器中的資料
  • D 命令 檢視記憶體中的內容
    -D 列出預設地址記憶體處的 128 個位元組的內容,每次偏移 128 個位元組
    -D (段地址:偏移地址) 列出記憶體中指定地址處的內容
    -D (段地址:偏移地址) (結尾偏移地址) 列出記憶體中指定地址範圍內的內容
  • E 命令 改變記憶體中的內容
    -E (段地址:偏移地址) (資料1) (資料2) .... 改變記憶體中的內容
    -E (段地址:偏移地址) 逐個詢問式修改,空格代表接受並繼續回車代表修改結束
  • U 命令將記憶體中的機器指令翻譯成彙編指令
    -U (段地址:偏移地址)
  • A 命令以彙編指令的格式在記憶體中寫入機器指令
    -A (段地址:偏移地址) 逐行輸入彙編指令
  • T 命令執行機器指令
    -T 執行 CS:IP 處的指令
  • Q 命令推出 Debug

CS、IP 與程式碼段

CPU 將記憶體中 CS:IP 指向的內容當作指令執行。

8086 讀取和執行指令

  1. 從 CS:IP 指向記憶體單元讀取指令,讀取的指令進入指令緩衝器
  2. IP = IP + 所讀指令的長度,從而指向下一條指令
  3. 執行指令,跳轉到步驟 1
  4. 重複此過程

jmp 指令

執行何處的指令,取決於 CS:IP 指向的資料
可以透過改變 CS 和 IP 中的內容,來控制 CPU 要執行的目標指令
Bebug 可以改變 CS 和 IP 的值,但是 Debug 是除錯手段,並非程式方式。
8086 CPU 不提供對 IP 修改的指令,不能使用 MOV 指令修改,只能由 CPU 自行修改
使用轉移指令 jmp

  • jmp (段地址:偏移地址) 用段地址修改 CS ,偏移地址修改 IP
  • jmp (某一合法暫存器) 用某一合法暫存器的資料修改 IP

記憶體中”字“的儲存

8086 CPU 中,16 位作為 1 個字。
16 位的字,高 8 位存放高位元組,低 8 位存放低位元組,從而實現儲存在 1 個 16 位的暫存器中。

字單元

子單元由兩個地址連續的記憶體單元組成,存放一個字型資料
在一個字單元中,低地址單元存放低位位元組,高地址單元存放高位位元組

用 DS 和 [addres] 實現字的傳送

CPU 要讀取一個記憶體單元的時候,必須給出這個記憶體單元的地址
在 8086 PC 中,記憶體地址由段地址和偏移地址組成(段地址:偏移地址)
用 DS 和 [address] 配合,用 DS 暫存器存放要訪問的資料的段地址
偏移地址用 [...] 形式給出
MOV 指令中,只寫 [address] 時,預設段地址就是 DS 。

DS 和資料段

對記憶體單元中資料的訪問

對於 8086 , 可以根據需要將一組記憶體單元定義為一個段

  • \(實體地址 = 段地址 * 16 + 偏移地址\)
  • 將一組長度為 \(N(N \leq 64K)\) 、地址連續、起始地址為 16 的倍數的記憶體單元當作專門儲存資料的記憶體空間,從而定義了一個資料段。
    用 DS 存放資料段的段地址,用指令訪問資料段中的具體單元,單元地址由 [address]指出。

加法 add 和減法 sub 指令

  • add (暫存器) , (資料)
  • add (暫存器) , (暫存器)
  • add (暫存器) , (記憶體單元)
  • add (記憶體單元) , (暫存器)
    段暫存器不能作為被加數,add 指令的引數不能全為記憶體單元
  • sub (暫存器) , (資料)
  • sub (暫存器) , (暫存器)
  • sub (暫存器) , (記憶體單元)
  • sub (記憶體單元) , (暫存器)
    段暫存器不能作為被減數,sub 指令的引數不能全為記憶體單元

棧及棧操作的實現

棧是一種只能在一端進行插入或刪除操作的資料結構。
棧有兩個基本的操作

  • 入棧:將一個新的元素放到棧頂
  • 出棧:從棧頂取出一個元素
    棧頂元素總是最後入棧,需要出棧時,又最先被從棧中取出。
    棧的操作規則:LIFO (Last In First Out ,後進先出)
    CPU 提供的棧機制,支援用棧的方式訪問記憶體空間,可以將一段記憶體當作棧來使用。
    PUSH (暫存器) 將暫存器中的資料送入棧,實質上就是一種記憶體傳送指令,與 MOV 不同的是,PUSH 指令訪問的記憶體單元的地址不作為引數,而是由 SS:SP 所指向的。
    POP (暫存器) 從棧頂取出資料送入暫存器,實質同 PUSH。
    棧頂暫存器 SS 存放棧頂的段地址,棧頂指標暫存器 SP 存放棧頂的偏移地址,
    SS:SP 任意時刻都指向棧頂元素

棧指令執行過程

  • PUSH (暫存器)
    1. SP = SP - 2
    2. 將暫存器中的內容送入 SS:SP 指向的記憶體單元處,SS:SP 此時指向新棧頂
  • POP (暫存器)
    1. 將 SS:SP 指向的記憶體單元處的資料送入暫存器中
    2. SP = SP + 2
    3. SS:SP 指向當前棧頂下面的單元,以當前棧頂下面的單元為新的棧頂。

棧頂越界問題

棧空時使用 POP 指令或棧滿時使用 PUSH 指令,都會導致棧頂越界
8086 CPU 不保證對棧的操作不會越界,需要在程式設計時注意。

相關文章