x86 Memory Map

Photo by Timothée Geenens on Unsplash
Photo by Timothée Geenens on Unsplash
After the x86 PC boots, it will be in real mode. At this time, we can access memory below 1 MB. However, the BIOS also uses some memory. Therefore, we must know which areas the BIOS occupies in order to avoid them.

After the x86 PC boots, it will be in real mode. At this time, we can access memory below 1 MB. However, the BIOS also uses some memory. Therefore, we must know which areas the BIOS occupies in order to avoid them. This article will introduce the memory map in real mode and how to obtain a memory map larger than 1 MB through BIOS.

Memory Map

After a normal x86 PC boots, it will enter real mode. The memory map at this time is as shown below.

In 16-bit real mode, the bootloader or kernel needs the interrupt vector table (IVT) to handle IRQ, so we cannot overwrite the IVT area. They will also call BIOS functions, and BDA and EBDA are where BIOS functions store data, so we cannot overwrite the BDA and EBDA areas. However, when the bootloader or kernel is going to enter 32-bit protected mode, we can use the IVT and BDA areas because we will no longer use the BIOS functions.

Although we will no longer use BIOS functions after entering 32-bit protected mode, we still cannot overwrite the EBDA area. Because System Management Mode (SMM) uses EBDA. In addition, the size of EBDA is different under different BIOS. Its end address is always 0x9FFFF, but its starting address is variable. However, the starting address will always be greater than 0x80000.

x86 Memory Map.
x86 Memory Map.

Detecting Memory – E820

Since EBDA is a variable size, and the kernel also needs to know the total size of RAM, the BIOS provides some functions for the bootloader or kernel to query the usable and unusable memory ranges, that is, the memory map. During the evolution of PC, BIOS has successively provided some functions that allow us to obtain memory map. We will only introduce how to use the E820, please refer to Detecting Memory (x86) for the rest of the methods .

E820 refers to calling the BIOS function through INT 0x15 and EAX=0xE820. Each time it is called, the BIOS will fill in an Address Range Descriptor at the specified place. So, we have to keep calling until we get all the descriptors.

The following is the input for INT 0x15 E820h. On the first call, we set EBX to 0. Set ES:DI to tell the BIOS where to write the next Address Range Descriptor. Depending on the BIOS, the Address Range Descriptor may be 20 bytes or 24 bytes, so it is best to set the ECX to 24 bytes. Finally, set EDX to 0x534D4150, which represents SMAP.

RegisterContentDescription
EAXFunction CodeE820h
EBXContinuationContains the continuation value to get the next range of physical memory. If this is the first call, EBX must contain 0.
ES:DIBuffer PointerPointer to an Address Range Descriptor structure that the BIOS fills in.
ECXBuffer SizeThe length in bytes of the structure passed to the BIOS.
EDXSignature‘SMAP’ (0x534D4150) Used by the BIOS to verify the caller is requesting the system map information to be returned in ES:DI.
Input to the INT 0x15 E820h Call.

The following is the output of INT 0x15 E820h. After calling the function, if there is an error, CF will be set. EAX will be set to SMAP. If this is the last descriptor, EBX will be set to 0.

RegisterContentDescription
CFCarry FlagNon-Carry – Indicates No Error.
EAXSignature‘SMAP’ (0x534D4150), Signature to verify correct BIOS revision.
ES:DIBuffer PointerReturned Address Range Descriptor pointer. Same value as on input.
ECXBuffer SizeNumber of bytes returned by the BIOS in the address range descriptor.
EBXContinuationContains the continuation value to get the next address range descriptor. A return value of zero means that this is the last descriptor.
Output from INT 0x15 E820h Call.

Each time NT 0x15 E820h is called, the BIOS will fill in the next descriptor with the address pointed to by ES:DI. The following is the structure of Address Range Descriptor. Base address is 64-bit, which is the starting address of the range. Length is also 64-bit, which indicates the length of the range. The size of extended attribute is 32 bits. This field is only available in ACPI 3.0. This is why the Address Range Descriptor Structure may be 20 bytes or 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 AttributesSee the Extended Attributes for Address Range Descriptor Structure
Address Range Descriptor Structure

Type is the type of the range, and its size is 32-bit.

ValueMnemonicUsable by OSDescription
1AddressRangeMemoryYesAvailable RAM and usable by the OS.
2AddressRangeReservedNoIn use or reserved by the system and is not to be included in the allocatable memory pool of the OS.
3AddressRangeACPIYesACPI Reclaim Memory。This range is available RAM usable by the OS after it reads the ACPI tables.
4AddressRangeNVSNoACPI NVS Memory。This range of addresses is in use or reserved by the system and must not be used by theOS.
5AddressRange UnusableNoThis range of addresses contains memory in which errors have been detected.This range must not be used by OS.
Address Range Types.

The following is the output obtained after calling INT 0x15 E820 in Bochs.

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.

The following is the output obtained after calling INT 0x15 E820 in QEMU.

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.

Example

The following code shows how to call INT 0x15 E820 to read the 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

Conclusion

In addition to the E820h, there are several ways to query the memory map. If GRUB is installed, the memory map can be obtained through GRUB. If the BIOS is UEFI, UEFI also provides a better way to obtain the memory map. The good thing about the E820h is that most BIOS will provide this method.

Reference

Leave a Reply

Your email address will not be published. Required fields are marked *

You May Also Like
Photo by Patrick on Unsplash
Read More

x86-64 Calling Conventions

Calling conventions refers to the specifications that the two functions should follow when one function calls another function. For example, how to pass parameters and a return value ​​between them. Calling conventions are part of the application binary interface (ABI).
Read More
Photo by Lanju Fotografie on Unsplash
Read More

Makefile

Makefile is the most commonly used compilation tool in Linux. Stuart Feldman created it at Bell Labs in 1967. Although it may be older than you and me, it is still active nowadays.
Read More