x86 Memory Map

Photo by Timothée Geenens on Unsplash
Photo by Timothée Geenens on Unsplash
在 x86 PC 起動後,它會處在 real mode。此時,我們可以存取 1 MB 以下的 memory。然而,BIOS 也會使用一些 memory。因此,我們必須要知道 BIOS 佔據哪些範圍,才能夠避開它們

在 x86 PC 起動後,它會處在 real mode。此時,我們可以存取 1 MB 以下的 memory。然而,BIOS 也會使用一些 memory。因此,我們必須要知道 BIOS 佔據哪些範圍,才能夠避開它們。本文章將介紹在 real mode 下的 memory map,以及如何透過 BIOS 取得大於 1 MB 的 memory map。

Memory Map

在一般的 x86 PC 開機之後,它會進入 real mode。此時的 memory map 如下圖。

在 16-bit real mode 下,bootloader 或 kernel 需要 interrupt vector table(IVT)處理 IRQ,所以我們不可以覆寫 IVT 區域。它們也會呼叫 BIOS 的函式,而 BDA 和 EBDA 是 BIOS 函式儲存資料的地方,所以我們也不可以覆寫 BDA 和 EBDA 區域。但是,當 bootloader 或 kernel 將要進入 32-bit protected mode,我們就可以使用 IVT 和 BDA 區域,因為我們不會再使用 BIOS 函式。

雖然進入 32-bit protected mode 後,我們不會再使用 BIOS 函式,但是我們還是不能覆寫 EBDA 區域。因為 System Management Mode(SMM)會使用到 EBDA。另外,在不同的 BIOS 下,EBDA 的大小是不同的。它的結束位址永遠是 0x9FFFF,但開頭位址是變動的。不過,開頭位址總是會大於 0x80000。

x86 Memory Map.
x86 Memory Map.

Detecting Memory – E820

由於 EBDA 是變動的大小,加上 kernel 也需要知道 RAM 的總大小,因此 BIOS 提供一些方法讓 bootloader 或 kernel 可以詢問可用和不可用的 memory 範圍,也就是 memory map。在 PC 的演進過程中,BIOS 陸陸續續地提供一些函式讓我們可以取得 memory map。我們將只介紹如何使用 E820 方法,其餘的方法請參照 Detecting Memory (x86)

E820 是指透過 INT 0x15、EAX=0xE820 呼叫 BIOS 函式。每一次呼叫,BIOS 會將一個 Address Range Descriptor 填入到指定的地方。所以,我們必須要持續地呼叫,直到取得所有的 descriptors。

以下是 INT 0x15 E820h 的輸入。第一次呼叫時,我們要將 EBX 設為 0。設置 ES:DI 來告訴 BIOS 將下一個 Address Range Descriptor 寫入到哪裡。根據不同的 BIOS,Address Range Descriptor 可能是 20 bytes 或是 24 bytes,所以最好將 ECX 設定為 24 bytes。最後,將 EDX 設為 0x534D4150,其代表 SMAP。

RegisterContentDescription
EAXFunction CodeE820h。
EBXContinuation上一次的呼叫會填入值到此 register。第一次呼叫時,填入 0。
ES:DIBuffer PointerBIOS 會將 Address Range Descriptor 填入到此位址。
ECXBuffer SizeBuffer 的大小。
EDXSignature‘SMAP’(0x534D4150)。由 BIOS 用於驗證呼叫者是否在請求 system map 資訊。
Input to the INT 0x15 E820h Call.

以下是 INT 0x15 E820h 的輸出。呼叫函式後,如果有錯誤,CF 會被設置。EAX 會被設為 SMAP。如果這是最後一個 descriptor,EBX 會被設為 0。

RegisterContentDescription
CFCarry FlagCarry flag 沒有被設置的話,表示沒有錯誤發生。
EAXSignature‘SMAP’(0x534D4150)。
ES:DIBuffer Pointer回傳 Address Range Descriptor pointer。
ECXBuffer SizeBIOS 回傳填入到 buffer 的大小。
EBXContinuation包含 continuation value 來取得下一個 Address Range Descriptor。回傳 0 的話,表示這是最後一個 descriptor。
Output from INT 0x15 E820h Call.

每一次呼叫 NT 0x15 E820h,BIOS 會將下一個 descriptor 填入 ES:DI 所指的位址。以下是 Address Range Descriptor 的結構。Base address 是 64-bit,它是該 range 的開頭位址。Length 也是 64-bit,它指出該 range 的長度。Extended attribute 的大小是 32 bits。ACPI 3.0 才會有這一個欄位。這也是為何 Address Range Descriptor Structure 可能是 20 bytes 或 24 bytes。

Offset in BytesNameDescription
0BaseAddrLowLow 32 Bits of Base Address
4BaseAddrHighHigh 32 Bits of Base Address
8LengthLowLow 32 Bits of Length in Bytes
12LengthHighHigh 32 Bits of Length in Bytes
16TypeAddress type of this range
20Extended Attributes請參照 Extended Attributes for Address Range Descriptor Structure
Address Range Descriptor Structure

Type 是該 range 的型態,它的大小是 32-bit。

ValueMnemonicUsable by OSDescription
1AddressRangeMemoryYes可用的 RAM,且可被 OS 使用。
2AddressRangeReservedNo保留給 system 使用,OS 不可以使用。
3AddressRangeACPIYesACPI Reclaim Memory。在讀取 ACPI tables 之後,OS 可以使用。
4AddressRangeNVSNoACPI NVS Memory。保留給 system 使用,OS 不可以使用。
5AddressRange UnusableNo包含已偵測到的壞 memory。OS 不可以使用。
Address Range Types.

以下是在 Bochs 中,呼叫 INT 0x15 E820 後,得到的輸出。

Base AddressLengthType
0x0000 0000 0000 00000x0000 0000 0009 FC001 – Free Memory
0x0000 0000 0009 FC000x0000 0000 0000 04002 – Reserved Memory
0x0000 0000 000E 80000x0000 0000 0001 80002 – Reserved Memory
0x0000 0000 0010 00000x0000 0000 01F0 00001 – Free Memory
0x0000 0000 FFFC 00000x0000 0000 0004 00002 – Reserved Memory
Address Range Descriptors read from Bochs.

以下是在 QEMU 中,呼叫 INT 0x15 E820 後,得到的輸出。

Base AddressLengthType
0x0000 0000 0000 00000x0000 0000 0009 FC001 – Free Memory
0x0000 0000 0009 FC000x0000 0000 0000 04002 – Reserved Memory
0x0000 0000 000F 00000x0000 0000 0001 00002 – Reserved Memory
0x0000 0000 0010 00000x0000 0000 07EE 00001 – Free Memory
0x0000 0000 07FE 00000x0000 0000 0002 00002 – Reserved Memory
0x0000 0000 FFFC 00000x0000 0000 0004 00002 – Reserved Memory
Address Range Descriptor read from QEMU.

範例

以下程式碼顯示如何呼叫 INT 0x15 E820 來讀取 memory map。

    .code16
    .text
start:
    movw $0x8000, %di
    movl %ebp, %ebp # Count the number of descriptors read
    xorl %ebx, %ebx # Set the Continuation to 0 for the first call

repeat:
    movl $0x534D4150, %edx # Set the Signature 'SMAP'
    movl $24, %ecx # Request for 24 bytes
    movl $0xE820, %eax
    int $0x15

    jc failed # if the call was failed

    # Verify the signature 'SMAP'
    movl $0x534D4150, %edx
    cmpl %eax, %edx
    jne failed

    # Test if ebx is 0, meaning this is the last descriptor
    testl %ebx, %ebx
    je end

    addw $24, %di
    addl $1, %ebp
    jmp repeat

end:
    jmp .

failed:
    hlt

結語

除了 E820h 之外,還有數種方法可以詢問 memory map。如果有安裝 GRUB 的話,則可以透過 GRUB 來取得 memory map。如果 BIOS 是 UEFI 的話,UEFI 也有提供更好的方式來取得 memory map。E820h 的好處是,大部分的 BIOS 都會提供此方法。

參考

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

You May Also Like
Photo by Lanju Fotografie on Unsplash
Read More

Makefile

Makefile 是 Linux 中最常用的編譯工具了。Stuart Feldman 在 1967 的 Bell Labs 裡創造了它。雖然它可能比你我的年紀都還大,但是它現在還是依然地活躍。本文章將介紹 Makefile 的一些基本語法。
Read More
Photo by NEOM on Unsplash
Read More

ELF

ELF file 是 Linux 使用的檔案格式。不管是要學習 linkers 或是反組譯,都需要了解 ELF file。本文章將介紹 ELF 檔案格式。
Read More