Search code examples
cassemblyx86real-mode

Kernel Dev: Setting ES:DI in real mode


I'm working on a toy kernel for fun and education (not a class project). I'm starting work on my memory manager, so I'm trying to get the memory map from BIOS using an INT 0x15, EAX=E820 call while still in Real Mode. I'm adapting my function from the osdev wiki (here, in the section "Getting an E820 Memory Map"). However, I want this to be a function I can call from my C code, so I'm trying to change it a bit. I want it to take two arguments: a pointer to where to store the map entries, and a pointer to an integer which will be incremented by the number of entries in the table.

According to the wiki, ES:DI needs to be pointing at where the data should be stored, so I split my first argument into two (the segment selector, pointer_to_map / 16, and the offset, pointer_to_map % 16). Here's part of C code:

typedef struct SMAP_entry {
    unsigned int baseL; // Base address, a QWORD
    unsigned int baseH;
    unsigned int lengthL; // Length, a QWORD
    unsigned int lengthH;
    unsigned int type; // entry type
    unsigned int ACPI; // extra data from ACPI 3.0
} SMAP_entry_t;

SMAP_entry_t data[100];
kprint("Pointer: ");
kprint_int((int) data, 16);
kprint_newline();

int res = 0;
read_mem_map(((int) data) / 16, ((int) data) % 16, &res);
kprint("res: ");
kprint_int(res, 16);
kprint_newline();

Here's part of my ASM code:

; performs a INT 0x15, eax=0xE820 call to find the memory map
; inputs: the pointer to the data table / 16, the pointer % 16, a pointer to an dword (int) which will be
;       incremented by the number of entries after this function returns.
; preserves: no registers except esi
read_mem_map:
    mov es, [esp + 4]           ; set es to the value of the first argument
    mov di, [esp + 8]           ; set di to the value of the second argument

That's all I'm pasting in because the program triple-faults and shuts down the VM there. By moving ret commands around, I found that the function crashes on the very first line. If I comment out the call in C, then everything works as you'd expect.

I've read through Google that there's almost never a reason to set ES:DI directly, and in the code that I've found which does, they set it to a literal. How should I set ES:DI and if I shouldn't set it directly, how should I make the C and ASM interact in the correct way?


Solution

  • Each of the segment registers (on 80x86) have a visible part, and several hidden fields (the segment base, the segment limit and the segment's attributes - read/write, privilege level, etc).

    In protected mode; when you load a segment register the CPU uses the visible part as an index into either the GDT or LDT, and loads the segment's hidden fields from that descriptor (in the GDT or LDT).

    In real mode; the CPU does something completely different - it only sets the segment base to "visible part * 16" and doesn't use any (GDT, LDT) table.

    Given the fact that you're using a 32-bit pointer to the data table and a 32-bit stack pointer (e.g. mov es, [esp + 4]); I assume your C code is in 32-bit protected mode. This is completely incompatible with real mode, partly because segment loads work completely differently and partly because the default operand/address size is 32-bit and not 16-bit.

    All BIOS functions are designed for real mode. They can't be used in protected mode.

    Basically; I'd recommend:

    • pass the pointer to the data table to your assembly as a 32-bit integer/pointer (and not 2 separate 16-bit integers)
    • call a "go to real mode" function (which will be slightly tricky, as you'd also be switching from a 32-bit stack to a 16-bit stack and will need a "32-bit return instruction" in 16-bit code).
    • split the pointer to the data table into its segment and offset in assembly, and load the segment (which should work correctly as you're in real mode now)
    • call the BIOS function (which should work correctly as you're in real mode now)
    • call a "go to protected mode" function (which will be slightly tricky again, including a "16-bit return instruction" in 32-bit code).
    • return to the (32-bit protected mode) caller

    Instructions for switching from real mode to protected mode, and switching from protected mode to real mode, are included in Intel's system programmer's guide. :)