簡體   English   中英

使用“ORG 0x0000”時以 8086 實模式從內存中讀取

[英]Reading from memory in 8086 real mode while using 'ORG 0x0000'

我一直在擺弄 x86-16 程序集並使用 VirtualBox 運行它。 出於某種原因,當我從內存中讀取並嘗試將其打印為字符時,我得到的結果與我的預期完全不同。 但是,當我將字符硬編碼為指令的一部分時,它工作正常。 這是代碼:

ORG 0
BITS 16

push word 0xB800        ; Address of text screen video memory in real mode for colored monitors
push cs
pop ds                  ; ds = cs
pop es                  ; es = 0xB800
jmp start

; input = di (position*2), ax (character and attributes)
putchar:
    stosw
    ret

; input = si (NUL-terminated string)
print:
    cli
    cld
    .nextChar:
        lodsb   ; mov al, [ds:si] ; si += 1
        test al, al
        jz .finish
        call putchar
        jmp .nextChar
    .finish:
        sti
        ret

start:
    mov ah, 0x0E
    mov di, 8

    ; should print P
    mov al, byte [msg]
    call putchar

    ; should print A
    mov al, byte [msg + 1]
    call putchar

    ; should print O
    mov al, byte [msg + 2]
    call putchar

    ; should print !
    mov al, byte [msg + 3]
    call putchar

    ; should print X
    mov al, 'X'
    call putchar

    ; should print Y
    mov al, 'Y'
    call putchar

    cli
    hlt

msg: db 'PAO!', 0

; Fill the rest of the bytes upto byte 510 with 0s
times 510 - ($ - $$) db 0

; Header
db 0x55
db 0xAA

打印標簽和其中的說明可以忽略,因為我還沒有使用它,因為我一直在嘗試打印存儲在內存中的字符。 我用 FASM 和 NASM 組裝了它,但遇到了同樣的問題,這顯然是我的錯。

它打印出如下內容: 虛擬盒子

組織指令

當您在匯編程序頂部指定ORG指令(如ORG 0x0000 )並使用BITS 16時,您是在通知NASM ,在將標簽解析為代碼和數據時,將生成的絕對偏移量將基於指定的起始偏移量在ORG中(16 位代碼將被限制為一個WORD /2 字節的偏移量)。

如果您在開始處有ORG 0x0000並放置一個標簽start:在代碼的開頭, start將具有 0x0000 的絕對偏移量。 如果您使用ORG 0x7C00 ,則標簽start將具有 0x7c00 的絕對偏移量。 這將適用於任何數據標簽和代碼標簽。

我們可以簡化您的示例,以查看在處理數據變量和硬編碼字符時生成的代碼中發生了什么。 盡管這段代碼並不完全執行與您的代碼相同的操作,但它足夠接近以顯示哪些有效,哪些無效。

使用ORG 0x0000的示例:

BITS 16
ORG 0x0000

start:
    push cs
    pop  ds      ; DS=CS
    push 0xb800
    pop es       ; ES = 0xB800 (video memory)
    mov ah, 0x0E ; AH = Attribute (yellow on black)

    mov al, byte [msg]
    mov [es:0x00], ax   ; This should print letter 'P'
    mov al, byte [msg+1]
    mov [es:0x02], ax   ; This should print letter 'A'
    mov al, 'O'
    mov [es:0x04], ax   ; This should print letter 'O'
    mov al, '!'
    mov [es:0x06], ax   ; This should print letter '!'

    cli
    hlt

msg: db "PA"

; Bootsector padding
times 510-($-$$) db 0
dw 0xAA55

如果你要在VirtualBox上運行它,前 2 個字符將是垃圾,而O! 應該正確顯示。 我將在本答案的其余部分中使用此示例。


VirtualBox / CS:IP / Segment:Offset 對

在 Virtual Box 的情況下,在加載物理地址 0x00007c00 的引導扇區后,它將有效地執行相當於FAR JMP到 0x0000:0x7c00 的操作。 FAR JMP (或等效的)不僅會跳轉到給定的地址,還會將CSIP設置為指定的值。 FAR JMP到 0x0000:0x7c00 將設置CS = 0x0000 和IP = 0x7c00。

如果不熟悉 16 位段:偏移量對背后的計算以及它們如何映射到物理地址,那么本文檔是理解該概念的一個相當好的起點。 從 16 位段獲取物理內存地址的一般方程:偏移對是(segment<<4)+offset = 20-bit physical address

由於 VirtualBox 使用 0x0000:0x7c00 的CS:IP ,它將在 (0x0000<<4)+0x7c00 = 20 位物理地址 0x07c00 的物理地址開始執行代碼。 請注意,不能保證在所有環境中都是如此。 由於段:偏移對的性質,引用物理地址 0x07c00 的方法不止一種。 有關正確處理此問題的方法,請參閱此答案末尾的部分。


您的引導加載程序出了什么問題?

假設我們使用的是 VirtualBox 並且上一節中的上述信息被認為是正確的,那么在進入我們的引導加載程序時CS = 0x0000 和IP = 0x7c00。 如果我們使用示例代碼(使用ORG 0x0000 )我寫在這個答案的第一部分並查看反匯編信息(我將使用objdump輸出)我們會看到這個:

objdump -Mintel -mi8086 -D -b binary --adjust-vma=0x0000 boot.bin

00000000 <.data>:
   0:   0e                      push   cs
   1:   1f                      pop    ds
   2:   68 00 b8                push   0xb800
   5:   07                      pop    es
   6:   b4 0e                   mov    ah,0xe
   8:   a0 24 00                mov    al,ds:0x24
   b:   26 a3 00 00             mov    es:0x0,ax
   f:   a0 25 00                mov    al,ds:0x25
  12:   26 a3 02 00             mov    es:0x2,ax
  16:   b0 4f                   mov    al,0x4f
  18:   26 a3 04 00             mov    es:0x4,ax
  1c:   b0 21                   mov    al,0x21
  1e:   26 a3 06 00             mov    es:0x6,ax
  22:   fa                      cli
  23:   f4                      hlt
  24:   50                      push   ax          ; Letter 'P'
  25:   41                      inc    cx          ; Letter 'A'
        ...
 1fe:   55                      push   bp
 1ff:   aa                      stos   BYTE PTR es:[di],al

由於在匯編成二進制文件時 ORG 信息丟失,我使用--adjust-vma=0x0000以便第一列值(內存地址)從 0x0000 開始。 我想這樣做是因為我在原始匯編代碼中使用了ORG 0x0000 我還在代碼中添加了一些注釋,以顯示我們的數據部分在哪里(以及字母PA在代碼后面的位置)。

如果你在 VirtualBox 中運行這個程序,前 2 個字符將是亂碼。 那是為什么呢? 首先回憶一下 VirtualBox 通過將CS設置為 0x0000 並將IP設置為 0x7c00 來訪問我們的代碼。 此代碼然后將CS復制到DS

   0:   0e                      push   cs
   1:   1f                      pop    ds

由於CS為零,因此DS為零。 現在讓我們看看這一行:

   8:   a0 24 00                mov    al,ds:0x24

ds:0x24實際上是我們數據部分中msg變量的編碼地址。 偏移量為 0x24 的字節中有值P (0x25 有A )。 您可能會看到哪里出了問題。 我們的DS = 0x0000 所以mov al,ds:0x24實際上和mov al,0x0000:0x24一樣。 此語法無效,但我將DS替換為 0x0000 以表明觀點。 0x0000:0x24是我們的代碼在執行時將嘗試從中讀取字母P的位置。 可是等等。 即物理地址(0x0000<<4)+0x24 = 0x00024。 該內存地址恰好位於中斷向量表中間的內存底部。 顯然這不是我們想要的!

有幾種方法可以解決這個問題。 最簡單(也是首選的方法)是實際將適當的段放入DS中,而不依賴於我們的程序運行時CS可能是什么。 由於我們將ORG設置為 0x0000,因此我們需要數據段 ( DS ) = 0x07c0。 段:偏移量對 0x07c0:0x0000 = 物理地址 0x07c00。 這是我們的引導加載程序的地址。 所以我們所要做的就是通過替換來修改代碼:

    push cs
    pop  ds      ; DS=CS

和:

    push 0x07c0
    pop  ds      ; DS=0x07c0 

VirtualBox中運行時,此更改應提供正確的輸出。 現在讓我們看看為什么。 此代碼沒有更改:

   8:   a0 24 00                mov    al,ds:0x24

現在執行時DS =0x07c0。 這就像說mov al,0x07c0:0x24 0x07c0:0x24 ,這將轉換為 (0x07c0<<4)+0x24 = 0x07c24 的物理地址。 這就是我們想要的,因為我們的引導加載程序由 BIOS 從該位置開始物理地放入內存中,因此它應該正確引用我們的msg變量。

故事的道德啟示? 無論您對ORG使用什么,當我們啟動程序時, DS寄存器中都應該有一個適用的值。我們應該明確設置它,而不是依賴於CS中的內容。


為什么要打印立即值?

使用原始代碼,前 2 個字符打印出亂碼,但后兩個字符沒有。 正如在上一節中討論的那樣,前 2 個字符無法打印是有原因的,但是最后 2 個字符呢?

讓我們更仔細地檢查第三個字符O的反匯編:

  16:   b0 4f                   mov    al,0x4f        ; 0x4f = 'O'

由於我們使用了立即數(常量)值並將其移入寄存器AL中,因此字符本身被編碼為指令的一部分。 它不依賴於通過DS寄存器進行的內存訪問。 因此,最后 2 個字符可以正確顯示。


Ross Ridge 的建議及其在 VirtualBox 中的工作原理

Ross Ridge 建議我們使用ORG 0x7c00 ,您觀察到它有效。 為什么會這樣? 該解決方案是否理想?

使用我的第一個示例並將ORG 0x0000修改為ORG 0x7c00 ,然后組裝它。 objdump會提供這個反匯編:

objdump -Mintel -mi8086 -D -b binary  --adjust-vma=0x7c00 boot.bin

boot.bin:     file format binary   
Disassembly of section .data:

00007c00 <.data>:
    7c00:       0e                      push   cs
    7c01:       1f                      pop    ds
    7c02:       68 00 b8                push   0xb800
    7c05:       07                      pop    es
    7c06:       b4 0e                   mov    ah,0xe
    7c08:       a0 24 7c                mov    al,ds:0x7c24
    7c0b:       26 a3 00 00             mov    es:0x0,ax
    7c0f:       a0 25 7c                mov    al,ds:0x7c25
    7c12:       26 a3 02 00             mov    es:0x2,ax
    7c16:       b0 4f                   mov    al,0x4f
    7c18:       26 a3 04 00             mov    es:0x4,ax
    7c1c:       b0 21                   mov    al,0x21
    7c1e:       26 a3 06 00             mov    es:0x6,ax
    7c22:       fa                      cli
    7c23:       f4                      hlt
    7c24:       50                      push   ax          ; Letter 'P'
    7c25:       41                      inc    cx          ; Letter 'A'
        ...
    7dfe:       55                      push   bp
    7dff:       aa                      stos   BYTE PTR es:[di],al

VirtualBox 在跳轉到我們的引導加載程序時將CS設置為 0x0000。 我們的原始代碼然后將CS復制到DS ,因此DS = 0x0000。 現在觀察ORG 0x7c00指令對我們生成的代碼做了什么:

    7c08:       a0 24 7c                mov    al,ds:0x7c24

請注意我們現在如何使用偏移量 0x7c24! 這就像mov al,0x0000:0x7c24是物理地址 (0x0000<<4)+0x7c24 = 0x07c24。 這是加載引導加載程序的正確內存位置,也是我們的msg字符串的正確位置。 所以它有效。

使用ORG 0x7c00是個壞主意嗎? 不,沒關系。 但我們有一個微妙的問題需要解決。 如果另一個 Virtual PC 環境或真實硬件沒有使用 0x0000:0x7c00 的CS:IP FAR JMP到我們的引導加載程序,會發生什么情況? 這個有可能。 許多物理 PC 的 BIOS 實際上相當於遠跳到0x07c0:0x0000 正如我們已經看到的那樣,這也是物理地址0x07c00 在那種環境下,當我們的代碼運行時CS = 0x07c0。 如果我們使用將CS復制到DS的原始代碼, DS現在也有 0x07c0。 現在觀察在這種情況下這段代碼會發生什么:

    7c08:       a0 24 7c                mov    al,ds:0x7c24

在這種情況下DS = 0x07c0。 當程序實際運行時,我們現在有類似於mov al,0x07c0:0x7c24的東西。 呃,那看起來很糟糕。 這轉化為物理地址是什么意思? (0x07c0<<4)+0x7c24 = 0x0F824。 它位於我們的引導加載程序之上的某個位置,它將包含計算機啟動后出現的所有內容。 可能為零,但應假定為垃圾。 顯然不是我們的味精字符串被加載的地方!

那么我們如何解決這個問題呢? 為了修改 Ross Ridge 的建議,並聽取我之前給出的關於將DS明確設置為我們真正想要的段的建議(不要假設CS是正確的然后盲目地復制到DS )我們應該在引導加載程序啟動時將 0x0000 放入DS如果我們使用ORG 0x7c00 所以我們可以改變這段代碼:

ORG 0x7c00

start:
    push cs
    pop  ds      ; DS=CS

到:

ORG 0x7c00

start:
    xor ax, ax   ; ax=0x0000
    mov ds, ax   ; DS=0x0000

在這里,我們不依賴CS中不受信任的值。 我們只需將DS設置為在給定我們使用的ORG的情況下有意義的段值。 您可以像之前那樣壓入 0x0000 並將其彈出到DS中。 我更習慣於將寄存器歸零並將其移至DS

通過采用這種方法,使用CS中的什么值到達我們的引導加載程序並不重要,代碼仍會為我們的數據引用適當的內存位置。


不要假設第一階段由 CS:IP=0x0000:0x7c00 的 BIOS 調用

在我在之前的 StackOverflow 回答中寫的一般引導加載程序技巧中,技巧 #1 非常重要:

  • 當 BIOS 跳轉到您的代碼時,您不能依賴具有有效值或預期值的 CS、DS、ES、SS、SP 寄存器。 當你的引導加載程序啟動時,它們應該被適當地設置。 您只能保證您的引導加載程序將從物理地址 0x07c00 加載並運行,並且引導驅動器號已加載到 DL 寄存器中。

BIOS 可以使用jmp 0x07c0:0x0000對我們的代碼進行 FAR JMP(或等效),一些仿真器和真實硬件就是這樣做的。 其他人像 VirtualBox 一樣使用jmp 0x0000:0x7c00

我們應該通過將DS明確設置為我們需要的值來解決這一問題,並將其設置為對我們在ORG指令中使用的值有意義的值。


概括

不要假設CS是我們期望的值,也不要盲目地將CS復制到DS 明確設置DS

如果我們如前所述將DS適當地設置為 0x07c0,則您的代碼可以固定為使用您最初擁有的ORG 0x0000 這可能看起來像:

ORG 0
BITS 16

push word 0xB800        ; Address of text screen video memory in real mode for colored monitors
push 0x07c0
pop ds                  ; DS=0x07c0 since we use ORG 0x0000
pop es

或者我們可以像這樣使用ORG 0x7c00

ORG 0x7c00
BITS 16

push word 0xB800        ; Address of text screen video memory in real mode for colored monitors
push 0x0000
pop ds                  ; DS=0x0000 since we use ORG 0x7c00
pop es

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM