What is a Stack?
- A stack is a Last-In-First-Out (LIFO) data structure. It's like a stack of plates where you can only add or remove the top plate.
Why Use a Stack?
- Stacks are commonly used for storing data temporarily, especially during subroutine calls, to manage local variables, and for passing arguments to functions.
Stack in x86 Assembly:
Registers Related to Stack:
- Stack Pointer (
SP
orESP
): Points to the top of the stack. - Stack Segment (
SS
): Points to the segment of memory containing the stack. - Base Pointer (
BP
orEBP
): Sometimes used as a reference point for accessing function arguments and local variables within stack frames.
Instructions for Stack Manipulation:
- Push: Adds data onto the stack.
- Pop: Removes data from the stack.
Stack Setup in Bootloader
1. Setting Up the Stack Segment (SS
):
- In real-mode x86 assembly, the default stack segment is often set to
0x0000
. However, for bootloader development, you typically set it to a suitable memory location, such as0x9000
or higher. - This is done using the
MOV
instruction to load the desired stack segment value into theSS
register.
mov ax, 0x9000 ; Set up stack segment at 0x9000
mov ss, ax ; Load the stack segment into the SS register
mov ax, 0x9000
loads0x9000
into theAX
register, indicating the desired base address for the stack segment.mov ss, ax
then transfers the value fromAX
(containing0x9000
) into theSS
register, setting up the stack segment to start at memory address0x9000
.
2. Setting Up the Stack Pointer (SP
):
- The stack pointer
SP
points to the top of the stack. - You need to initialize
SP
to point to a suitable location in memory. - Often, you set it to the highest address in your chosen stack segment.
- This is typically done by loading a value into
SP
directly using theMOV
instruction.
mov ax, 0x9000 ; Set up stack segment at 0x9000
mov ss, ax ; Load the stack segment into the SS register
mov sp, 0xFFFF ; Initialize stack pointer to the top of the stack
The reason for initializing SP
to 0xFFFF
is twofold:
- Stack Growth Direction:
- In x86 architecture, the stack grows downwards in memory. This means that as items are pushed onto the stack, the stack pointer (
SP
) decrements towards lower memory addresses. By initializingSP
to0xFFFF
, you're setting it to the highest address within the stack segment, allowing the stack to grow downwards without overlapping with other memory regions.
- In x86 architecture, the stack grows downwards in memory. This means that as items are pushed onto the stack, the stack pointer (
- Stack Size:
- Real mode x86 processors have a 16-bit address space, meaning they can address up to 64KB of memory. By setting
SP
to0xFFFF
, you're effectively allocating the entire stack segment (from0x9000
to0xFFFF
) for use as the stack. This provides a reasonably large stack space of 32KB (since the stack grows downwards, it starts from the highest address). - So, initializing
SP
to0xFFFF
ensures that the stack has ample space to accommodate stack operations without colliding with other parts of memory and also ensures that it starts at the highest address within the stack segment, allowing it to grow downwards as items are pushed onto it.
- Real mode x86 processors have a 16-bit address space, meaning they can address up to 64KB of memory. By setting
3. Pushing and Popping Data:
- Once the stack is set up, you can use the
PUSH
instruction to push data onto the stack and thePOP
instruction to remove data from the stack. - Example:
mov ax, 1234 ; Example value
push ax ; Push value onto the stack
pop bx ; Pop value from the stack into BX register
4. Stack Frame:
- In more complex bootloader or program code, you might implement a stack frame for each function call. This involves using the base pointer
BP
to reference function arguments and local variables within the stack frame.
5. Stack Growth Direction:
- In x86 architecture, the stack typically grows downwards (towards lower memory addresses).
- Therefore, when pushing data onto the stack, the stack pointer
SP
is decremented, and when popping data off the stack,SP
is incremented.
Stack Safety and Management
1. Stack Overflow:
- A stack overflow occurs when too much data is pushed onto the stack, causing it to exceed its allocated size.
- Care must be taken to ensure that the stack does not overflow, especially in bootloader development where memory resources are limited.
2. Stack Underflow:
- A stack underflow occurs when data is popped from an empty stack.
- Proper stack management must prevent underflow situations.
3. Stack Corruption:
- Incorrect manipulation of the stack can lead to stack corruption, where data is overwritten or misaligned.
- This can cause program crashes or unexpected behavior.
4. Stack Cleanup:
- It's essential to properly clean up the stack after use, especially in bootloader code, to avoid leaving residual data that could interfere with subsequent operations or program execution.
stack.asm
:
mov ah, 0x0e
; Allow us to print to screen, enable scrolling teletype BIOS routine
; Ok so I just learnt the stack. Let's push, pop and print from stack
; Create base and top of stack at memory location 0x8000
mov bp, 0x8000
mov sp, bp
; Note how this is considerably far from our boot sector load point (0x7c00)
; so that it won't overwrite our boot sector
; Push some data to stack. Note that the stack is 2 bytes big,
; so the 1 byte big chars are padded with zeroes at the most significant digit end
push 'A'
push 'B'
push 'C'
; Pop a value into the register b, and copy the lower end (the actual text byte)
; to al and print it using the interrupt
pop bx
mov al, bl
int 0x10
; Print another byte from stack
pop bx
mov al, bl
int 0x10
; Lets try and prove that the stack grows down in memory value from base.
; Lets print the last thing on our stack. This is 2bytes below base. So, 0x8000 - 2 bytes = 0x7ffe
mov al, [0x7ffe]
int 0x10
; Loop infinitely after finishing
jmp $
; Pad rest of boot sector with zeroes
times 510-($-$$) db 0
; Write magic number at end of boot sector
dw 0xaa55