Reputation: 813
I have made a basic bootloader in assembly, but it doesn't actually jump to the kernel. It just says "Booting...". I'm sure it's just some silly mistake I made, like jumping to the wrong place. It should show an output like "Booting... Loaded!". I've also tried setting es to 0 before loading it, but even that doesn't work. Here's my code:
mov ax, 9ch
mov ss, ax
mov sp, 4096d
mov ax, 7c0h
mov ds, ax
mov es, ax
xor ah, ah
int 13h
clc
mov si, msg2
call print
mov ah, 02h
xor ax, ax
mov es, ax
mov bx, 0x7E00
mov al, 1h
mov ch, 0
mov cl, 2h
mov dh, 0
int 13h
jc error
jmp 0x7E00
mov si, msg3
call print
error:
mov si, msg
call print
hlt
print:
lodsb
cmp al, 0
jz done
mov ah, 0eh
int 10h
jmp print
done:
ret
msg db "An error occured!!", 0
msg2 db "Booting...", 0
msg3 db "Did not jump to kernel correctly!"
times 510-($-$$) db 0
dw 0xAA55
mov si, msgloaded
call printl
jmp $
printl:
lodsb
cmp al, 0
jz donel
mov ah, 0eh
int 10h
jmp print
donel:
ret
msgloaded db "Loaded!", 0
times 0x400-($-$$) db 0
All help is appreciated. I will credit anyone who can help me. Thanks!
Upvotes: 1
Views: 689
Reputation: 37262
For high level languages there's lots of clues about what the programmer intended contained in the structure loops, how variable names were chosen, defines/enums, etc; and it's easy to write maintainable code without comments.
For assembly language there's no well chosen variable names and no variable types (e.g. ax
doesn't tell the reader if it's a pointer to a string or a giraffe's height or ...), instructions often don't show the intent (e.g. lea
might be used to multiply by a constant and might not be used to load an effective address), control flow is far more flexible (e.g. something like a do(condition1) { } while(condition2)
is perfectly fine) and goto
(both jmp
and conditional branches like jc
) are used a lot.
For this reason, well written/maintainable assembly language uses lots of comments. More specifically, you'd use comments on the right hand side to describe your intentions. This allows you to check if the intentions are correct by reading comments, and then check if the intention is implemented correctly by comparing the instruction on each line with it's comment. It makes it much easier to avoid bugs, and much easier to find bugs.
Here's the first half of your code with comments:
;Memory Layout
;
; 0x009C:0x1000 = 0x000019C0 = stack top
; 0x07C0:0x0000 = 0x00007C00 = load address
; 0x0000:0x7E00 = 0x00007E00 = kernel address
%define STACK_SEGMENT 0x009C
%define STACK_TOP_OFFSET 0x1000
%define LOAD_SEGMENT 0x07C0
%define KERNEL_SEGMENT 0x0000
%define KERNEL_OFFSET 0x7E00
;_______________________________________________
;Entry point
;
;Input
; dl = BIOS boot device number
mov ax, STACK_SEGMENT
mov ss, ax
mov sp, STACK_TOP_OFFSET
mov ax, LOAD_SEGMENT
mov ds, ax
mov es, ax
;Reset disk system
;
;Note: This should be completely unnecessary. We know the BIOS
; disk services are working correctly and don't need
; to be reset because the BIOS just used it successfully
; to load this code into memory.
xor ah, ah ;ah = BIOS "reset disk system" function number
int 13h ;Call BIOS disk services
clc ;Unnecessary
;Display welcome message
mov si, msg2
call print
;Load kernel from disk
; dl = BIOS boot device number
mov ah, 02h ;ah = BIOS "read sectors" function number
xor ax, ax ;ax = KERNEL_SEGMENT
mov es, ax
mov bx, KERNEL_OFFSET ;es:bx = address to load kernel
mov al, 1h ;al = number of sectors to read
mov ch, 0 ;ch = cylinder number for first sector
mov cl, 2h ;cl = sector number for first sector
mov dh, 0 ;dh = head number for first sector
int 13h ;Call BIOS disk services
jc error ;Handle error if there was one
;Pass control to "kernel"
jmp KERNEL_SEGMENT:KERNEL_OFFSET
Here's the part that makes your bug obvious:
;ah = BIOS "read sectors" function number
;ax = KERNEL_SEGMENT
Essentially, if you commented your code properly, you would've noticed that loading KERNEL_SEGMENT
into ax
overwrites the BIOS function number (which was in the highest 8 bits of ax
). This causes this piece of code to call the BIOS "reset disk system" function and not load anything from disk at all. When it jumps to where the kernel should've been loaded (but wasn't) later, that memory is probably still full of zeros because it hasn't been used, but memory full of zeros are decoded as add
instructions by the CPU, so the CPU happily executes the add
instructions for ages.
Note: There is another (unrelated) bug - your code to print strings uses lodsb
which depends on the direction flag; but you don't do a cld
instruction to set the direction flag, so depending on the (undefined) state the BIOS left this flag in, it could print garbage instead.
Upvotes: 3