博客 / 詳情

返回

王爽《彙編語言(第4版)》讀書筆記(第1-3章)

寫這個系列文章的主要目的是記錄書中重要的知識點,並和大家分享一些個人理解與實踐。由於筆記中的知識點比較零散,而書中系統的介紹了一個 x86-16 處理器在實模式下的工作原理以及如何使用匯編語言與其進行“溝通”,所以推薦想要系統學習的朋友們去學習這本書。當我們掌握了實模式的工作原理之後,就可以進一步研究後來出現的其他運行模式(如保護模式)。除此之外,熟悉彙編語言有助於我們掌握上層語言(如 C)的執行原理,因為它們都要對彙編(機器碼)進行抽象,而彙編程序就是基於 CPU 的執行機理寫出來的。

前言

學習彙編的兩個最根本目的

  • 充分獲得底層編程的體驗(在一個沒有操作系統的環境中面向硬件編程。所以本書中不會涉及彙編器
  • 深刻理解機器運行程序的機理
一門課程是由相互關聯的知識構成的,這些知識在一本書中如何組織則是一種信息組織和加工的藝術。學習是一個循序漸進的過程(物有本末,事有終始,之所先後,則近道矣!),但並不是所有的教學都是以這種方式完成的,這並不是我們所希望的事情,因為任何不以循序漸進的方式進行的學習,都將出現盲目探索和不成系統的情況,最終學習到的也大都是相對零散的知識,並不能建立起一個系統的知識結構。非循序漸進的學習,也達不到循序漸進學習所能達到的深度,因為後者是步步深入的,每一步都以前一步為基礎。

第一章

彙編語言的組成

  • 彙編指令:機器碼的助記符(與 ISA 中的機器碼一一對應)
  • 偽指令:沒有對應的機器碼,由編譯器執行,CPU 並不執行(主要作用是引導彙編器進行編譯
  • 標號(Label):一個標號代表了一個地址
  • 其他符號:如 + - * / 等,由編譯器識別,沒有對應的機器碼

系統總線

電子計算機能處理、傳輸的信息都是電信號,電信號要用導線傳送。所以總線從物理上來講就是一根根導線的集合。

  • 地址總線的寬度決定了尋址能力(8086 地址總線寬度為 20 位)
  • 數據總線的寬度決定了 CPU 和外界的數據傳送速度(傳送數據需要的次數
  • 控制總線是一些不同控制線的集合。有多少根控制線,就意味着 CPU 提供了對外部器件的多少種控制。所以,它的寬度決定了 CPU 對外部器件的控制能力

存儲器

存儲器可以理解為由 N 個大小相同的存儲單元組成,所以説存儲單元是存儲器最小的存儲單位
存儲單元的大小取決於存儲器的編址方式。在現代計算機中:每個存儲單元的大小通常是 1 字節。

一個存儲單元有地址內容兩個屬性:

  • 地址:每個存儲單元的唯一編號(用於供 CPU 訪存時來定位存儲單元)
  • 內容:存儲單元存放的內容即 指令 或 數據

輸入/輸出設備

I/O 設備通常被劃分為兩個部分:外設I/O 接口

  • 外設就是連接在計算機外部的設備,如:鼠標、鍵盤、打印機、顯示器、網絡等。由於外設的種類繁多,不同外設之間的差異很大(比如:接口、信號、數據傳輸率等差異),而 CPU 與外部器件的交互方式比較單一(CPU 的想法是:我給出一個地址,來讓我操作對應的數據即可),導致它無法與外設直接連接。這時,I/O 接口就出現了。
  • I/O 接口就是連接在 CPU 和外設之間的“中轉站”。它的出現讓 CPU 和外設之間實現了“解耦”:CPU 只需像往常一樣,給出一個地址與之交互;它來幫助 CPU 完成與不同外設之間的交互。

    • I/O 接口主要負責信號轉換(如:數字信號<->模擬信號、串行信號<->並行信號)、協調、數據緩衝(解決 CPU 和外設的速度差異)等工作。
    • I/O 接口中包含許多寄存器,如:數據輸入寄存器用來保存來自外設輸入的數據、數據輸出寄存器用來保存 CPU 向外設輸出的數據、控制寄存器、狀態寄存器等等,這些寄存器被稱為 I/O 端口。與存儲器的存儲單元一樣,每個 I/O 端口都會對應一個地址。

存儲器和 I/O 接口的編址方式

存儲器和 I/O 接口的編址方式通用有兩種:

  1. Memory Mapped I/O——存儲器和 I/O 接口統一編址(使用同一個地址空間)
  2. I/O Mapped I/O——存儲器和 I/O 接口分開編址(使用兩個獨立的地址空間。常用於 RISC 中,因為 RISC 指令的特徵能避免這種編址方式存在的一些弊端)

本書中介紹的是 Memory Mapped I/O 方式:

  • 8086 主存的地址空間為:0~9FFFF (640KB)
  • 8086 顯存的地址空間為:A0000~BFFFF (128KB)
  • 8086 只讀存儲器的地址空間為:C0000~FFFFF (256KB)
    地址空間總大小:1024KB (1MB) = 2^20 bytes, 所以 8086 對外提供了 20 根地址總線。
    FFFF0H 單元中的指令是 8086 開機後執行的第一條指令

第二章

N 位結構(N 位機、字長為 N 位)特性

  • 運算器每次最多能支持 N 位的算數運算或邏輯運算
  • 寄存器的最大位寬通常是 N 位
  • 寄存器和運算器之間的內部通路支持 N 位的數據傳輸
  • 外部數據總線的寬度通常是 N 位(8088 比較特殊,對外提供的數據總線是 8 位)

8086的段

注意:物理內存並沒有分段,段的邏輯劃分來自於 CPU8086 物理地址的生成方式:基地址(段基址*16) + 偏移地址 = 物理地址”),使得可以用分段的方式來管理內存。(這種分段方式的弊端是:不同段對應的內存可能是:完全獨立、部分重合、完全重合的,如 2000:01001:FFF0 物理地址完全重合)

一個段的起始地址(基地址)一定是 16 的倍數;偏移地址為 16 位,16 位地址的尋址能力為 64KB, 所以在 8086 處理器中一個段的長度最大為 64KB.

8086 中 CS:IP 表示的內存區域代表着指令。8086 中沒有提供 mov 類指令來修改 IP 寄存器,只能通過特殊指令(如轉移指令)改變。轉移指令詳見第九章內容。

實驗一:查看 CPU 和內存,用機器指令和彙編指令編程

MacOS 中搭建 DOS 環境

  1. 下載 dosbox: https://www.dosbox.com/download.php?main=1
  2. 創建目錄,作為掛載點

    mkdir ~/DOS
  3. 將下載的 DOSBox-0.74-3-3.dmg 包裏面的內容拷貝至 ~/DOS 目錄中
  4. 下載 debug.exe, 並將其拷貝至 ~/DOS/bin 目錄中
  5. 修改 dosbox 的配置文件,將掛載等操作加入到 [autoexec] 中(這樣每次啓動 dosbox 會自動執行這些命令)

    # vi ~/Library/Preferences/DOSBox\ 0.74-3-3\ Preferences
    # 在 [autoexec] 中加入以下命令:
    mount c ~/DOS
    C:
    set PATH=%PATH%;C:\bin\
  6. 啓動 DOSBox.app
  7. 執行 debug, 可以進行調試了!

    debug

debug 的常用參數選項(可以和 GDB 進行類比)

# 寄存器
-r         # 查看所有寄存器的內容
-r ax      # 修改寄存器內容

# 內存
-d 1000:0    # 從指定內存單元開始,顯示 128 個內存單元的內容(128字節)
-d           # 繼續顯示“接下來的” 128 個內存單元的內容
-d 1000:0 9  # 格式————“d 段地址:偏移地址 結尾的偏移地址”
-e 1000:0 1 2 3 'a' 'b' 'c' "str"  # 修改指定內存單元開始的連續字節內容
-e 1000:0    # 修改指定內存單元開始的連續字節內容(通過“回車、空格、空格……回車”進行交互)

# 指令(使用 -e 直接輸入機器碼的16進制進行編輯)
-u 1000:0  # 從指定內存單元開始,顯示其對應的指令(機器碼+彙編)
-u         # 繼續顯示“接下來的”指令
-t         # 執行一條指令
-a 1000:0  # 以彙編的形式在指定內存中寫入指令
-a         # 以彙編的形式在“接下來的”內存中寫入指令

# 退出 debug
-quit
-q

# 補充
-d|e|u|a 段寄存器名:偏移地址(邏輯地址)  # 如:-a ds:0, -e cs:0
-g 偏移地址  # 如:-g 0012 執行 [CS:IP~CS:0012) 之間的指令,相當於打【斷點】
-p  # 執行 `int 21H` 指令時,需要用 -p; 在循環中,使用 -p 可以完成所有循環的執行

第三章

起始地址為 N 的字單元簡稱為:N 地址字單元

8086 指令操作數規則(瞭解即可,必要時查閲《Intel 開發手冊(第二卷)》來確定具體的格式):

  • 不支持將【立即數】直接送入【段寄存器】的操作
  • 不支持將【立即數】直接送入【內存單元】的操作
  • 不支持操作數不能同時是兩個【內存單元】
  • 支持的形式這裏不一一列舉,參見書中 3.4(第53頁)

處理器通過 ss(棧基址) 和 sp(棧指針) 來確定一個棧。

入棧指令 pushsp 的影響,比如:push ax

  • sp=sp-2
  • ax 內容寫入棧頂

出棧指令 pop 對 sp 的影響,比如:pop ax

  • 棧頂內容寫入 ax
  • sp=sp+2(注:剛剛棧頂的內容不會被擦除,下次使用時覆蓋即可)

棧頂越界問題

書中説 8086 硬件層面沒有做這種控制,在 debug 中測試了確實是。但在保護模式中,提供了“段界限檢查”機制。

user avatar timliu_621f402489e7c 頭像
1 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.