Retrieving memory information in a bootloader is essential for understanding the layout and availability of system memory. Memory detection allows you to determine where different memory regions are available and which areas should not be overwritten, like reserved memory for system devices or the BIOS.
The different sections of the memory in the x86 architecture is termed into two parts:
- Low Memory
- Extended Memory
Low Memory (Conventional Memory)
Low memory refers to the first 1 MB of the memory address space in an x86 system. This is the memory that is directly addressable in real mode.
Extended Memory
Extended memory refers to all memory above the 1 MB boundary in an x86 system. This memory is not directly addressable in real mode but can be accessed in protected mode or through special mechanisms in real mode.
1️⃣ Detecting Low Memory
"Low memory
" is the available RAM below 1MB, and usually below 640KB
. There are two BIOS functions to get the size of it.
INT 0x12
: Detecting Conventional Memory
Out: AX = Number of Kilobytes of conventional memory available in the system.
When you invoke INT 12h
, the BIOS returns the size of conventional memory in kilobytes in the AX
register.
The INT 0x12
call will return AX = total number of KB. The AX value measures from 0, up to the bottom of the EBDA (of course, you probably shouldn't use the first 0x500 bytes of the space either -- i.e. the IVT or BDA).
; Clear carry flag
clc
; Switch to the BIOS (= request low memory size)
; Call BIOS interrupt 12h to get conventional memory size
int 0x12
; After this, AX will contain the size of conventional memory in KB
; For demonstration, let's store the result at a memory location
mov [conventional_memory_size], ax
; The carry flag is set if it failed
jc .Error
; AX = amount of continuous memory in KB starting from 0.
;; Print the value of AX
conventional_memory_size resw 1 ; Reserve space for the memory size
int 0x12
: This line invokes BIOS interrupt12h
. After this interrupt call, theAX
register contains the size of conventional memory in kilobytes.mov [conventional_memory_size], ax
: This line moves the value inAX
(which contains the size of conventional memory) to a reserved memory location namedconventional_memory_size
.
Example in the TheTaaJ:
We have created a function GetLowerMemorySize
, which doesn't takes any parameters and just executes the interrupt 0x12
and prints the size of lower memory if success otherwise prints the error string. This function is called from the stage2.asm
file. Both code is given below along with the output which i got in QEMU
.
stage2/includes/memory.inc
:
%ifndef _STAGE_2_MEMORY_INC_
%define _STAGE_2_MEMORY_INC_
BITS 16
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;*********************************
; GetLowerMemorySize
; IN: Nothing
; OUT: Nothing
GetLowerMemorySize:
pusha
clc ; Clear the carry flag
int 0x12 ; Interrupt to get the number of low (conventional) memory
jc .GetLowerMemory_Error
; if carry flag is not set means function successfully returned
; size of lower memory in AX register
; AX is having the count in KB
mov si, sLowerMemoryCountSentence ; display lower memory count sentence.
call PrintString16BIOS
;; Now print the size from the AX in decimal
call PrintWordNumber ; This ones display the contents of AX in decimal
call PrintNewline ; \n
popa ; pop all saved registers.
ret ; return to the caller
; Trap code
.GetLowerMemory_Error:
mov si, sInt10hFailed ; Error string
call PrintString16BIOS
call PrintNewline ; \n
hlt
; ********************************
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Variables
sLowerMemoryCountSentence db 'Lower Memory Size (In KB) : ', 0
sInt10hFailed db 'Int 0x10 Failed to return lower memory size', 0
%endif
stage2.asm
:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Detect Size of Lower (Conventional) Memory
call GetLowerMemorySize
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Output:

You can get the complete code at: GitHub - The-Jat/TheTaaJ at a43cec2062d6eb99a37a1154a854dfb3de4ead93
2️⃣ Detecting Extended Memory
There are variety of other method of getting high memory, however none is much effective as the int 15h, ah = E820
. The older method is int 12h
which is not reliable.
Ⅰ INT 15h, AH=88h
: Detecting Extended Memory:
When you call INT 15h
with AH
set to 88h
, the BIOS returns the amount of extended memory (above 1 MB) in kilobytes in the AX
register. This is useful for determining the amount of additional memory available in systems with more than 1 MB of RAM.
Ⅱ Using INT 15h, EAX = E820h to Get the Memory Map
The E820h
function is more advanced compared to the older INT 12h
and INT 15h, AH = 88h
calls. It returns information about all memory regions, including reserved areas, usable memory, and memory-mapped I/O regions.
Memory Map Entry Structure:
Each memory region is described by a structure that includes the base address, length, and type of the memory region. The structure typically looks like this:
struct e820_entry {
uint64_t base_addr; // Base address of the memory region
uint64_t length; // Length of the memory region in bytes
uint32_t type; // Type of the memory region
uint32_t acpi_attributes; // Only vaid if ECX = 24 (0x18)
};
What is int 0x15
?
The int 0x15
BIOS interrupt is a general-purpose interrupt used for various system services. It provides functions related to:
- System configuration
- Extended memory
- Advanced Power Management (APM)
- System keyboard intercepts
- Block moves in memory
Steps to Detect High Memory (Extended Memory):
1 Prepare the Registers and Call BIOS Interrupt:
- Set
EAX
to0xE820
to specify the function that you want to retrieve the memory map. - Set
EBX
to 0 for the first call. For subsequent calls, use the value ofEBX
returned by the previous call. - Set
ECX
to the size of the buffer you are providing for the memory map entry, which is24
the size of each memory descriptor in bytes. - Set
EDX
to the magic number0x534D4150
('SMAP' in ASCII) to identify the call. This is a signature to ensure that the BIOS returns valid data. - Set
ES:DI
to point to a buffer that will hold the returned memory map entry.
2 Parse the Returned Data:
- After the interrupt call, if
CF
(Carry Flag) is clear,EAX
will still be0x534D4150
and the buffer pointed to byES:DI
will contain the memory map entry. The memory map entry is a 24-byte structure for each region of memory. - The returned structure typically looks like this:
struct e820_entry {
uint64_t base_address;
uint64_t length;
uint32_t type;
uint32_t acpi_attributes; // Only valid if ECX = 24 (0x18)
};
Offset | Size | Description |
---|---|---|
0x00 | 8 bytes | Base Address (64-bit) |
0x08 | 8 bytes | Length (64-bit) |
0x10 | 4 bytes | Type (32-bit) |
0x14 | 4 bytes | Reserved |
- Memory Map Entry Structure:
- Base Address (64-bit)
- Offset: 0
- Size: 8 bytes
- Description: The starting address of the memory range.
- Length (64-bit)
- Offset: 8
- Size: 8 bytes
- Description: The length (size) of the memory range in bytes.
- Type (32-bit)
- Offset: 16
- Size: 4 bytes
- Description: The type of memory in this range. The type indicates how this memory range should be used basically the purpose of it.
- Only regions of type
1
(usable) should be considered for loading an operating system or programs.
- ACPI 3.0 Extended Attributes (32-bit)
- Offset: 20
- Size: 4 bytes
- Description: Extended attributes defined in the ACPI 3.0 specification. (Optional and may not be provided by all BIOS implementations)
- Base Address (64-bit)
- `base_address` is the starting address of the memory region.
- `length` is the length of the memory region.
- `type` defines the type of the memory region:
- 1: Usable (RAM)
- 2: Reserved
- 3: ACPI Reclaimable
- 4: ACPI NVS
- 5: Bad Memory
3 Repeat the Call:
- Continue calling the interrupt with the updated
EBX
untilEBX
returns 0, indicating that there are no more entries.
Here is an example of how this might be done in assembly language:
; Setup registers for e820 call
mov eax, 0xE820 ; Function number
mov ebx, 0 ; Continuation value, 0 for first call
mov ecx, 24 ; Size of the structure
mov edx, 0x534D4150 ; SMAP signature
mov edi, buffer ; Pointer to the buffer to store the result
int 0x15 ; BIOS interrupt
jc no_more_entries ; If carry flag is set, no more entries
; If we get here, we've successfully retrieved a memory map entry
; The buffer now contains the e820 entry structure
; Process the buffer as needed
;...
; Prepare for the next call
mov eax, 0xE820
mov ecx, 20
mov edx, 0x534D4150
mov edi, buffer
; EBX was updated by the BIOS to continue where it left off
int 0x15
; Repeat until no more entries
...
no_more_entries:
; Handle end of memory map
Note:
- Ensure that the buffer pointed to by
EDI
is large enough to hold the structure. The size can be 20 bytes or 24 bytes if the optional ACPI 3.0 attributes are included.
Below is the Complete Example Code:
In the code given below we have used the int 0x15
and its function E801
to get the memory map and stored it at location MEMLOCATION_MEMORY_MAP
which is 0x9000
defined in defines.inc
.
defines.inc
:
%define MEMLOCATION_MEMORY_MAP 0x9000
memory.inc
:
SetupMemory:
; ********************************
; SetupMemory
; ********************************
SetupMemory:
; Save state
pusha ; saves the state of all general-purpose registers on the stack.
; Print the reading memory map statement:
mov si, sReadingMemoryMapSentence
call PrintString16BIOS
call PrintNewline
; Step 1, try to get memory map
mov edi, MEMLOCATION_MEMORY_MAP ; Set EDI to the memory location
; where the memory map will be
; stored.
mov dword [dataStructureObj + DataStructure.MemMapAddr], edi ; Save this address in the data structure
; `dataStructureObj` at offset DataStructure.MemMapAddr
;; Print the Memory Map address on screen in hex with its statement.
mov si, sMemMapAddrSentence
call PrintString16BIOS
mov word dx, [dataStructureObj + DataStructure.MemMapAddr]
call PrintWordHex
call PrintNewline
;; Memory Map function to retreive the memory map entries using BIOS interrupt 0x15, function E820
call LoadMemoryMap
; Save entry count in structure
mov dword [dataStructureObj + DataStructure.MemMapLength], ebp
;; Print the Memory map count
mov word ax, [dataStructureObj + DataStructure.MemMapLength]
mov si, sMemMapLengthSentence
call PrintString16BIOS
call PrintWordNumber
call PrintNewline
; Restore
popa
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LoadMemoryMap:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ********************************
; LoadMemoryMap
; IN:
; - ES:EDI: Destination Buffer
; OUT:
; - EBX: 0 -> Success, 1 -> Err
; ********************************
LoadMemoryMap:
; Save buffer
push edi
; Clear out space for memory map (1 Page)
xor eax, eax ; Zero out EAX, common and efficient way to clear a register
mov ecx, 1024 ; counter
rep stosd ; `stosd` = string instruction that uses `EDI` register as a
; destination pointer. It writes the value of `EAX`
; to the memory location pointed to by `EDI`
; `rep` = repeat the `stosd` instruction `ECX` times (1024)
; times
; The whole block code zeroes out 1024 double words (each 4
; bytes) of memory start from the address to by `EDI`
; It effectively fills a 4096 bytes (1024*4) block of memory
; with zeroes.
; Restore
pop edi
; Setup INT 0x15
mov eax, 0xE820 ; Function number = E820
xor ebx, ebx ; Zero out ebx
xor ebp, ebp ; Zero out base pointer
mov ecx, 24 ; Size of the structure (including ACPI attributes)
mov edx, BIOS_MEMMAP_SIGNATURE ; SMAP signature
; Ready, make sure we force a valid ACPI 3.X Entry
mov [es:di + 20], dword 1
int 0x15 ; Read Memory Map
jc .NoMemoryMap ; unsuccessful call if carry flag is set.
; Restore EDX for safety (EAX contains signature on success)
mov edx, BIOS_MEMMAP_SIGNATURE
cmp eax, edx ; Compare the signature
jne .NoMemoryMap ; if mismatch unsuccessful
; If ebx is 0, only 1 entry, we cant use that for anything
test ebx, ebx ; Performs bitwise AND operation between EBX
; and then set the processor's flag based on the result.
; This is common idiom in assembly language to check
; is a register is zero or not.
je .NoMemoryMap ; if zero, means no memory map
; Proceed if memory map is valid
jmp .MediasRes
; Each call returns one memory map entry at a time.
; Loop for subsequent int 0x15 calls
.cLoop:
; Query time
mov eax, 0xE820 ; Function number for memory map query
mov ecx, 24 ; Size of memory map structure
mov [es:di + 20], dword 1 ; Set ACPI attribute to 1
int 0x15 ; Invoke BIOS interrupt 0x15
jc .Done ; Jump to Done if carry flag is set (end of memory map)
mov edx, BIOS_MEMMAP_SIGNATURE
.MediasRes:
; Parse Entry
test ecx, ecx ; Test if ecx is zero
je .SkipEntry ; Jump to SkipEntry if ecx is zero
cmp cl, 20 ; Compare lower byte of ecx with 20
jbe .Acpi2 ; Jump to Acpi2 if cl <= 20
test byte [es:di + 20], 1 ; Test if ACPI attribute bit 0 is set
je .SkipEntry ; Jump to Skip Entry if ACPI attribute bit 0 is set
; ACPI 2.0 Entry
.Acpi2:
; Acpi2 Entry
; Get lower dword of memory length
mov ecx, [es:di + 8]
; Check if lowe dword is not zero
test ecx, ecx ; Test if ecx is zero
jne .GoodEntry ; Jump to GoodEntry if ecx is not zero
; Get higher dword of memory length
mov ecx, [es:di + 12] ; Get higher dword of memory length
test ecx, ecx ; Test if ecx is zero
je .SkipEntry
.GoodEntry:
; Increase entry count & move to next
inc ebp ; Increment ebp (entry count)
add edi, 0x20 ; Move edi to the next memory map entry (each entry is 32 bytes)
.SkipEntry:
; If ebx resets to 0, list is complete
test ebx, ebx ; Test if ebx is zero
jne .cLoop ; Jump to cLoop if ebx is not zero
jmp .Done ; Jump to done if ebx is zero
.NoMemoryMap:
mov si, sNoMemoryMapSentence
call PrintString16BIOS
call PrintNewline
mov ebx, 1 ; set ebx to 1 (indicating failure)
mov ebp, 0 ; Clear ebp (entry count)
ret ; Return
.Done:
mov ebx, 0 ; Set ebx to 0 (indicating success)
ret
stage2.asm
:
call SetupMemory ; calls the setup memory
Output:

Ⅲ Retrieve Memory Size using Int 15h, AX = E801h
:
INT 15h, AX=E801h
This function provides a way to determine the amount of installed memory beyond the 16MB boundary, which is useful for systems with larger memory configurations.
To call this function, you need to set AX
to 0xE801
and then invoke the interrupt 0x15
. The BIOS will return the memory information in the registers AX
, BX
, CX
, and DX
.
Registers on Return
- AX: Number of contiguous 1 KB blocks of memory between 1 MB and 16 MB.
- BX: Number of contiguous 1 KB blocks of memory above 16 MB.
- CX: If the value in
AX
is less than or equal to 15, thenCX
contains the same value asAX
. Otherwise, it contains the number of contiguous 64 KB blocks of memory between 16 MB and 4 GB. - DX: If the value in
AX
is less than or equal to 15, thenDX
contains the same value asBX
. Otherwise, it contains the number of contiguous 64 KB blocks of memory above 4 GB.
Input:
AX = 0xE801
Output:
- If successful:
CX
= Number of contiguous kilobytes of memory between 1MB and 16MB.DX
= Number of 64KB blocks of memory above 16MB.BX
= Number of contiguous kilobytes of memory below 1MB (optional and usually not used).AX
= Number of kilobytes of memory above 16MB (alternative toDX
).
Complete Example:
We will have a function named as GetMemorySize
which invokes the interrupt 0x15
with AH = E801
, and returns the amount of memory between 1MB and 16MB in EAX and amount of memory above 16MB mark in EBX.
We will call this function from the SetupMemory
function in memory.inc file just after we got the memory map.
; ********************************
; GetMemorySize
; OUT:
; - EAX: KB between 1 mb and 16 mb (0 on err)
; - EBX: Num of 64 Kb blocks above 16 mb (0 on err)
; ********************************
GetMemorySize:
; Clear registers
xor ecx, ecx
xor edx, edx
; Setup INT 0x15
mov eax, 0xE801 ; EAX = 0xE801 (function code for getting memory size)
int 0x15
; call BIOS interrupt 0x15
; Check for error by examining the Carry Flag (CF)
jc .Error ; If CF is set, jump to .Error handling
; Check if the function is unsupported (AH = 0x86)
cmp ah, 0x86 ; Compare AH with 0x86
je .Error ; If equal, jump to error handling
; Check if the command is invalid (AH = 0x80)
cmp ah, 0x80 ; Compare AH with 0x80
je .Error ; If equal, jump to error handling
; It is possible that bios uses ECX/EDX instead
test ecx, ecx ; Test if ECX is zero
je .UseEAX ; If ECX is zero, jump to use EAX values
; If ECX is not zero, BIOS returned values in ECX and EDX
mov eax, ecx ; EAX = ECX (KB between 1 MB and 16 MB)
mov ebx, edx ; EBX = EDX (number of 64 KB blocks above 16 MB)
.UseEAX:
; If ECX was zero, use the values already in EAX and EBX
ret ; Return with EAX and EBX set
.Error:
; In case of an error, set EAX and EBX to 0
mov eax, 0 ; EAX = 0 (error indication)
mov ebx, 0 ; EBX = 0 (error indication)
ret