博客 / 詳情

返回

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

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

第四章

一個有意義的彙編程序中至少要有一個段,即:代碼段。在 masm 中可以用 assume 將有特定用途的段特定的段寄存器關聯起來。

彙編程序從寫出到執行的過程

編程 -> 1.asm -> 編譯 -> 1.obj -> 連接 -> 1.exe -> 加載 -> 內存中的程序 -> 運行

所有語言編寫的代碼,都要經過類似方式最終被轉換成計算機可以識別的機器碼。比如:C 語言在編譯系統(gcc,as,ld)中的處理過程是這樣的:
編程 -> 1.c -> 預處理 -> 1.i -> 編譯 -> 1.s -> 彙編 -> 1.o -> 鏈接 -> a.out

EXE 文件中程序的加載過程

參見:圖 4.20(與 Linux 加載執行可執行文件(ELF)的過程類似)

實驗三:編程、編譯、連接、跟蹤

  1. 下載 edit 編輯器(edit.com),將其拷貝至可執行程序目錄 ~/DOS/bin
  2. ~/DOS/mytest/ 目錄中使用 edit 編輯以下內容,然後保存到 t1.asm 文件中

    assume cs:codesg
    
    codesg segment
      mov  ax, 2000H
      mov  ss, ax
      mov  sp, 0
      add  sp, 10
      pop  ax
      pop  bx
      push ax
      push bx
      pop  ax
      pop  bx ; 這 4 行實現了交換 ax、bx 中的數據
    
      ; 程序返回
      mov  ax, 4c00H
      int  21H
    codesg ends
    
    end
  3. 編譯、連接

    • 下載 masm5.0 彙編器(masm.exe) 和 Overlay Linker3.60 連接器(link.exe),將其拷貝至 ~/DOS/bin 目錄下
    • 啓動 dosbox, 使用 編譯器連接器 進行編譯和連接

      cd mytest
      
      ###### 編譯 (參見4.4節) ######
      masm
      # 根據提示輸入
      Source filename [.ASM]: c:\mytest\t1
      Object filename [ti.OBJ]: c:\mytest\t1
      Source listing  [NUL.LST]: [按下回車]
      Cross-reference [NUL.CRF]: [按下回車]
      
      ###### 連接 (參見4.5節) ######
      link
      # 根據提示輸入
      Object Modules [.OBJ]: t1
      Run File [T1.EXE]: [按下回車]
      List File [NUL.MAP]: [按下回車]
      Libraries [.LIB]: [按下回車]
    • 編譯、連接的簡化方式:

      masm c:\mytest\t1
      link t1
  4. 執行和跟蹤

    # 執行(加不加 .exe 後綴都可以)
    t1
    t1.exe
    
    # 跟蹤(注:必須要加 .exe 後綴)
    debug t1.exe # 注意:要使用 -p 調試 "int 21H" 指令!!!

第五章

要完整的描述一個內存數據,需要的信息

  • 地址(即首地址、起始地址)
  • 類型(即上下文,可以確定該信息由多少個連續的存儲單元組成,以及該內存區域的佈局情況

cx 寄存器和 loop 指令實現循環

  1. cx 中存放循環次數
  2. loop 指令中的標號所標識的地址要在前面
  3. 循環執行的程序段寫在標號和 loop 指令之間

loop 指令的內部執行過程

  • 先進行 cx=cx-1, 然後判斷 cx 的值是否等於 0

    • 是:執行下一條指令(在取址階段之後~執行 loop 之前,會將 IP 更新為 IP+loop指令長度
    • 否:繼續執行循環體(在取址階段之後~執行 loop 之前,會將 IP 更新為 標號代表的地址)

其他內容

5.4節 説明了 masm 編譯器將 mov ax,[0] 識別為 mov ax,0 的解決辦法——顯示指定段前綴

注意,我們在純 DOS 方式(實模式)下,可以不理會 DOS, 直接用匯編語言去操作真實的硬件,因為運行在 CPU 實模式下的 DOS, 沒有能力對硬件系統進行全面、嚴格的管理。但在 Windows 2000、Unix 這些運行於 CPU 保護模式下的操作系統中,不理會操作系統,用匯編語言去操作真實的硬件,是根本不可能的。硬件已被這些操作系統利用 CPU 保護模式所提供的功能全面而嚴格的管理了。

DOS 中 0:200~0:2ff 這 256 個字節的空間一般是安全的。

實驗四:[bx]和loop的使用

練習(2): 向 0:200~0:23F 內存單元內,依次寫入十進制數字:0~63

; file: exp4_2.asm
assume cs:codesg

codesg segment
    mov ax, 0020H ; 0020:0~0020:3F 與 0:200~0:23F 物理地址重合,只是表示不同的段
    mov ds, ax    ; 使用 0020 作為段基址可以讓【偏移量】和【要寫入的數字】保持一致

    mov bx, 0
    mov cx, 64
 s: mov [bx], bl  ; 注意:1.每次寫入一字節 2.`mov [dx], bl` 報錯(見寄存器尋址規則)
    inc bx
    loop s

    mov ax, 4c00H
    int 21H
codesg ends

end

練習(3): 將 mov ax, 4c00H 指令前的指令複製到 0020:0000 開始的內存單元中

; file: exp4_3.asm
assume cs:codesg

codesg segment
    mov ax, cs
    mov ds, ax
    mov ax, 0020H
    mov es, ax
    mov bx, 0
    mov cx, 17H ; 如何知道要複製的指令字節長度是 "17H"? 是使用 debug 調試得到的
 s: mov al, [bx]
    mov es:[bx], al
    inc bx
    loop s

    mov ax, 4c00H
    int 21H
codesg ends

end

第六章

可執行文件的組成

  • 描述信息(彙編器、連接器對源程序中相關偽指令進行處理所得到的),如程序的入口地址
  • 程序(數據、指令)

6.1節 在代碼段中使用數據,程序框架:

assume cs:codesg

codesg segement
        數據
        …………
 start: 代碼
        …………
codesg ends

end start

將數據、代碼、棧放入不同段的意義在於:

  1. 讓程序的結構清晰明瞭,提高程序的可讀性
  2. 讓每個段獨佔 64KB 大小的地址空間(實現段隔離)
  3. 內存對齊,方便尋址,有利於代碼優化:對於每個段,起始地址的偏移地址都是 0000, 而將數據全部定義在代碼段中時,數據會依次緊湊排列(不會對齊)

實驗五:編寫、調試具有多個段的程序

練習(5): 將 a 段和 b 段中的數據同一列相加,結果存入 c 段對應列中(實現方法一)

; file: exp5_5.asm
assume cs:code

a segment
    dw 1,2,3,4,5,6,7,8
a ends

b segment
    dw 1,2,3,4,5,6,7,8
b ends

c segment
    dw 0,0,0,0,0,0,0,0
c ends
; 程序退出前,通過 "-d 076c:0 2f" 能夠看到以上3個段實際的內存數據(雖然是3個段,但它們佔用的內存是連續的)

code segment
start: mov bx, 0
       mov cx, 8

    s: mov ax, a
       mov ds, ax
       mov dx, ds:[bx]  ; 獲取 a 段的值,存入 dx
       
       mov ax, b
       mov ds, ax
       add dx, ds:[bx]  ; 獲取 b 段的值,計算加法,存入 dx

       mov ax, c
       mov ds, ax
       mov ds:[bx], dx  ; dx 寫入 c 段中

       ;add bx, 2
       inc bx
       inc bx
       loop s

       mov ax, 4c00H
       int 21H
code ends

end start

練習(5): 實現方法二——使用寄存器+偏移量進行尋址

; file: exp5_5_1.asm
assume cs:code

a segment
    db 1,2,3,4,5,6,7,8
a ends

b segment
    db 8,7,6,5,4,3,2,1
b ends

c segment
    db 0,0,0,0,0,0,0,0
c ends
; 程序退出前,通過 "-d ds:0 2f" 能夠看到以上3個段實際的內存數據(雖然是3個段,但它們佔用的內存是連續的)

code segment
start: mov ax, a
       mov ds, ax

       mov bx, 0
       mov cx, 8

    s: mov dx, ds:[bx]  
       add dx, ds:[bx+16]
       mov ds:[bx+32], dx

       inc bx
       loop s

       mov ax, 4c00H
       int 21H
code ends

end start

練習(6): 用 push 指令將 a 段中前 8 個字型數據,逆序存儲到 b 段中

; file: exp5_6.asm
assume cs:code

a segment
    dw 1,2,3,4,5,6,7,8,9,0aH,0bH,0cH,0dH,0eH,0fH,0ffH
a ends

b segment
    dw 0,0,0,0,0,0,0,0
b ends

code segment
start: mov ax, a
       mov ds, ax

       mov ax, b
       mov ss, ax
       mov sp, 10H

       mov bx, 0
       mov cx, 8
    s: push ds:[bx]
       ;add bx, 2
       inc bx
       inc bx
       loop s

       mov ax, 4c00H
       int 21H
code ends

end start

第七章

在彙編程序中,可以用單引號引用字符,編譯器將把它們轉換為相應的 ASCII 碼。如:

db 'unIX'
; 相當於
db 75H, 6EH, 49H, 58H

mov al, 'a'
; 相當於
mov al, 61H

大小寫字母轉換問題

對於字母的 ASCII 碼,有以下特徵:

  1. 所有字母 [a-zA-Z] 的 ASCII 碼值,二進制的最高位1都在第7位,即可表示為:01** ****
  2. 同一字母,大寫碼值+20H=小寫碼值即相差 32 (2^5)

基於以上特徵得出結論:對於同一字母的大小寫碼值,它們的 8 位二進制中,只有第6位有差異,其它位是相同的,即:

  • 大寫字母的特徵:ASCII 碼值(十進制)為 65~90, 第 6 位一定是:0

    • 小寫字母轉大寫:將第6位變成 0 即可,如:

      mov al, 'a'
      and al, dfH  ; 二進制:11011111B
  • 小寫字母的特徵:ASCII 碼值(十進制)為 97~122, 第 6 位一定是:1

    • 大寫字母轉小寫:將第6位變成 1 即可,如:

      mov al, 'A'
      or al, 00100000B

以下幾種尋址方式,作用相同

; case1——[bx+idata]
mov ax, [bx+200]
mov ax, [200+bx]
mov ax, 200[bx]
mov ax, [bx].200

; case2——[bx+si]或[bx+di]
mov ax, [bx+si]
mov ax, [bx][si]

; case3——[bx+si+idata]或[bx+di+idata]
mov ax, [bx+si+200]
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

問題 7.6(對 [bx(或 si、di)+idata] 的應用)

; 將 datasg 段中每個單詞的頭一個字母改為大寫字母
assume cs:codesg,ds:datasg

datasg segment
  db '1. file         '  ; 16 bytes
  db '2. edit         '
  db '3. search       '
  db '4. view         '
  db '5. options      '
  db '6. help         '
datasg ends

codesg segment
start: mov ax, datasg
       mov ds, ax
       mov bx, 0
       mov cx, 6
    s: mov al, [bx+3]
       and al, 11011111B  ; 小寫轉大寫,使用與運算將第 6 位設置為 0, 其他位不變
       mov [bx+3], al
       add bx, 16
       loop s

       mov ax, 4c00H
       int 21H
codesg ends

end start

問題 7.7(對 [bx+si(或 di)] 的應用)

; 將 datasg 段中每個單詞改為大寫字母
assume cs:codesg,ds:datasg

datasg segment
  db 'ibm             '  ; 16 bytes
  db 'dec             '
  db 'dos             '
  db 'vax             '
datasg ends

codesg segment
start: mov ax, datasg
       mov ds, ax
       mov bx, 0
       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

       mov ax, 4c00H
       int 21H
codesg ends

end start

實驗六——對問題 7.9 進行編程:將 datasg 段中每個單詞的前4個字母改為大寫字母

; file: exp6.asm
assume cs:codesg,ds:datasg,ss:stacksg

stacksg segment
  dw 0, 0, 0, 0, 0, 0, 0, 0
stacksg ends

datasg segment
  db '1. display      '
  db '2. brows        '
  db '3. replace      '
  db '4. modify       '
datasg ends

codesg segment
start: mov ax, stacksg
       mov ss, ax
       mov sp, 16
       mov ax, datasg
       mov ds, ax

       mov bx, 0
       mov cx, 4
   s0: push cx

       mov si, 0
       mov cx, 4
    s: mov al, [bx+3+si]
       and al, 11011111B
       mov [bx+3+si], al
       INC si
       loop s

       add bx, 16
       pop cx
       loop s0

       mov ax, 4c00H
       int 21H
codesg ends

end start

第八章——之前章節的總結

8086 中寄存器尋址的規則

偏移地址(寄存器的有效組合)

  1. 在 8086 中只有 bx, si, bi, bp 這4個寄存器可以用在 "[ ]" 中來進行內存單元的尋址
  2. 這4個寄存器可以單個出現,或只能以如下組合出現(bx, bp 不能配合;si, di 不能配合):

    • bx + si
    • bx + di
    • bp + si
    • bp + di
  3. 以上所有的出現方式,都可以再和立即數 idata 進行加減

段地址

  1. 只要在 "[ ]" 中使用了寄存器 bp, 如果沒有顯示指定段地址,那麼段地址就在 ss
  2. 如果在 "[ ]" 中沒使用寄存器 bp, 並且沒有顯示指定段地址,那麼段地址就在 ds

尋址方式總結

  • 直接尋址——[立即數]
  • 寄存器間接尋址——[bx|si|bi|bp]
  • 寄存器相對尋址——[bx|si|bi|bp+立即數]
  • 基址變址尋址——[bx|bp+si|bi]
  • 相對基址變址尋址——[bx|bp+si|bi+立即數]

8086 機器指令中要處理的數據的位置及如何表達這個位置

位置 表達方式
CPU 內部——寄存器 使用寄存器名,如:ax
CPU 內部——指令緩衝器 使用立即數來表達
內存 指令中給出偏移地址段地址在相應的段寄存器中

8.6節 C語言結構體與彙編的【尋址語法】類比

8.7節 8086 中除法的計算規則

彙編指令:div 除數(在內存或某個寄存器中),除數要麼是 8 位,要麼是 16 位

8位除數 16位除數
被除數 16位——在 ax 32位——dx 存高16位、ax 存低16位
al ax
餘數 ah dx

實驗七:尋址方式在結構化數據訪問中的應用

將 data 段中的數據按照書中指定的格式寫入到 table 段中,並計算 21 年中的人均收入(取整),結果也按照格式保存在 table 段中

; file: exp7.asm
assume cs:codesg

data segment
  db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
  db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
  db '1993','1994','1995'
  ; 以上是表示這21個【年份】的21個字符串(84字節)

  dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
  dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
  ; 以上是表示這21年【每年總收入】的21個 dword 型數據(84字節)

  dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
  dw 11542,14430,15257,17800
  ; 以上是表示這21年【每年僱員人數】的21個 word 型數據(42字節)
data ends

table segment
  db 21 dup ('year summ ne ?? ')  ; 16 bytes
  ; 相當於聲明 21 個:db 'year summ ne ?? '
table ends

codesg segment
start: mov ax, data
       mov ds, ax
       mov ax, table
       mov es, ax

       mov si, 0   ;【每年僱員人數】數組中,元素的偏移地址(2字節遞增)
                   ;【年份】和【每年總收入】這兩個數組中,元素的偏移地址(4字節遞增,等於 si*2)
       mov di, 0   ; table 段數據(結構體數組),每行開始的地址(每行表示一個結構體元素)
       mov cx, 21
    s: push si
       add si, si  ; si*2

       ;【年份】數組的起始地址(相對於 data 段的偏移量)是 0
       mov ax, [si]
       mov es:[di], ax
       mov ax, [si+2]
       mov es:[di+2], ax
       mov byte ptr es:[di+4], 20H  ; 空格(ASCII值)

       ;【總收入】數組的起始地址(相對於 data 段的偏移量)是 84
       mov ax, [84+si]    ; 低位字節寫入 ax (注意:字節序!!)
       mov es:[di+5], ax
       mov dx, [84+si+2]  ; 高位字節寫入 dx
       mov es:[di+7], dx
       mov byte ptr es:[di+9], 20H

       ;【僱員數】數組的起始地址(相對於 data 段的偏移量)是 168
       pop si
       div word ptr [168+si]  ; 先計算【人均收入】,商存入 ax
       mov dx, [168+si]       ; 再獲取僱員數
       mov es:[di+10], dx
       mov byte ptr es:[di+12], 20H

       mov es:[di+13], ax  ; 人均收入
       mov byte ptr es:[di+15], 20H

       add si, 2
       add di, 16
       loop s

       mov ax, 4c00H
       int 21H
codesg ends

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

發佈 評論

Some HTML is okay.