I want to know how to properly do it, because the way I'm doing it isn't working.
When setting the BP
register with 7C00h, then setting the SP
register with BP
, then pushing some ASCII, then getting the data from the memory to print it with INT 10h
, it works just fine.
mov ax, 7C00h
mov bp, ax
mov sp, bp
push 'A'
mov ah, 0Eh
mov al, [7BFEh]
int 10h
The actual output is
A
But when I do this:
mov ax, 7C00h
mov ss, ax
mov bp, ax
mov sp, bp
...
It stops working. The interrupt is called, the cursor moves, but nothing is printed. Also setting SS
to 0 doesn't work. Please give a hand.
Real Mode Real Mode is a simplistic 16-bit mode that is present on all x86 processors. Real Mode was the first x86 mode design and was used by many early operating systems before the birth of Protected Mode. For compatibility purposes, all x86 processors begin execution in Real Mode.
This logical address is then translated into physical address by using A * 0x10 + B. As x86 CPU starts running in real mode which you probably don't want to use and will either want to switch to 32bit mode with paging enabled or 64 bit mode (aka long mode) we need to change mode of execution somehow.
Note that you can still use 32-bit addressing modes in Real Mode, simply by adding the "Address Size Override Prefix" (0x67) to the beginning of any instruction. Your assembler is likely to do this for you, if you simply try to use a 32-bit addressing mode.
Title x86 Real/Protected Mode Programming Author Ahmad Golchin Created Date 3/2/2021 9:34:44 AM
Looking at that 7C00h value, you're probably working on a bootloader.
And you want the stack to reside below the bootloader.
An important choice that you have to make will be how you want to proceed with the segmented addressing scheme that is in effect at start-up.
This indicates that the first byte of the code will be at offset 7C00h. For this to work well, you'll have to initialize the segment registers to 0000h. Remember that the bootloader was loaded by BIOS at linear address 00007C00h which is equivalent to segment:offset pair 0000h:7C00h.
If you're going to change the SP
register, then also change the SS
segment register. You don't know what it contains at the start of your code and you should (most) always modify these registers in tandem. First assign SS
and directly after assign SP
. A mov
or a pop
to SS
blocks many kinds of interruptions between this and the following instruction so that you can safely set a consistent (2-register) stackpointer.
mov ss, ax mov bp, ax <== This ignored the above safeguard! mov sp, bp
ORG 7C00h
mov bp, 7C00h
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax ; \ Keep these close together
mov sp, bp ; /
push 'A' ; This writes 0000h:7BFEh
mov bx, 0007h ; DisplayPage and GraphicsColor
mov al, [7BFEh] ; This requires DS=0
mov ah, 0Eh ; BIOS.Teletype
int 10h
As an alternative and because you've setup BP=7C00h
, you could read the stacked character viamov al, [bp-2]
.
This indicates that the first byte of the code will be at offset 0000h. For this to work well, you'll have to initialize some of the segment registers to 07C0h. Remember that the bootloader was loaded by BIOS at linear address 00007C00h which is equivalent to segment:offset pair 07C0h:0000h.
Because the stack must go below the bootloader, the SS
segment register will be different from the other segment registers!
ORG 0000h
mov bp, 7C00h
mov ax, 07C0h
mov ds, ax
mov es, ax
xor ax, ax
mov ss, ax ; \ Keep these close together
mov sp, bp ; /
push 'A' ; This writes 0000h:7BFEh
mov bx, 0007h ; DisplayPage and GraphicsColor
mov al, [bp-2] ; This uses SS by default
mov ah, 0Eh ; BIOS.Teletype
int 10h
I've included this one to show that a linear address has many translations to segment:offset.
ORG 0200h
indicates that the first byte of the code will be at offset 0200h. For this to work well, you'll have to initialize the segment registers to 07A0h. Remember that the bootloader was loaded by BIOS at linear address 00007C00h which is equivalent to segment:offset pair 07A0h:0200h.
Because the 512-bytes stack goes below the bootloader, the SS
segment register will again be equal to the other segment registers!
ORG 0200h
mov bp, 0200h
mov ax, 07A0h
mov ds, ax
mov es, ax
mov ss, ax ; \ Keep these close together
mov sp, bp ; /
push 'A' ; This writes 07A0h:01FEh
mov bx, 0007h ; DisplayPage and GraphicsColor
mov al, [bp-2] ; This uses SS by default
mov ah, 0Eh ; BIOS.Teletype
int 10h
You can also fetch the character with mov al, [01FEh]
.
The correct way to set BP
is to not bother. You have no reason to waste one of the 7 precious general purpose registers for "stack frame pointer" to match poorly designed calling conventions from some other language you're not using. Also note that some BIOS functions (e.g. "int 0x10, ah=0x13, write string") use BP
to pass parameters.
For the same reason, you also have no reason to pass parameters on the stack. For example; for your "print character" code you could pass the character to print in AL
and delete the mov al, ...
to make the code smaller (which is extremely important if you're writing "must fit in < 512 bytes" boot code, which is also part of the reason you don't want to waste space setting up and destroying useless stack frame pointers).
For ss:sp
; they should be treated as a pair (that describes the address of the stack); and you'll want to choose a location for where you want your stack (based on how you're planning to use all the other memory). I'd recommend drawing a little "my physical memory layout" diagram (assuming that you will want to use other areas of memory for various things - an area where more boot code is loaded, an area for a disk IO buffer used when loading a kernel, somewhere to put video mode information, somewhere to put the firmware's memory map, ...).
Note that (at least in my experience) most people diddling with real mode boot code end up wanting to switch between real mode and either protected mode or long mode (whether they realize it initially or not); and in that case it's a lot easier to set all segment registers to zero so that "offset in segment" is (almost) always equal to "physical address" (and if you don't you'll probably end up with bugs caused by accidentally getting segmentation wrong). Note that if SS is zero in real mode (and "SS.base" is zero in protected mode) you can zero extend SP
(e.g. "movzx esp,sp
") and continue using the same stack for both real mode and 32-bit protected mode. Also, (after a quick "does the CPU meet my minimum requirements?" check) you can use 32-bit instructions in real mode; and (if ESP
has been zero extended) you can do things like (e.g.) "mov al,[esp+10]
" in real mode if/when you need more flexible (32-bit) addressing modes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With