1

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!

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
programmer
  • 743
  • 4
  • 10
  • Maybe because you don't even HAVE a jump to the kernel? – Jester Nov 17 '17 at 18:39
  • Oops. Must have accidentally deleted that when copying it into the box. Sorry. – programmer Nov 17 '17 at 18:41
  • Also, your `int 13h` call is obvously in the wrong place (before even setting up the arguments???) **and** it's loading the data to the wrong place. – Jester Nov 17 '17 at 18:42
  • int 13h should actually be called twice. Once to reset the drive, and one to load it. I fixed it and even now it still doesn't work. – programmer Nov 17 '17 at 18:43
  • You have the segments set to `7c0h` so the offset to load and jump to is of course `200h` not `7e00h`. – Jester Nov 17 '17 at 18:45
  • I thought about that, but like I said I also tried manually setting es to 0. – programmer Nov 17 '17 at 18:47
  • Setting `es` to `0` will fix the loading but not the jump. It's also a bad idea to create difference between `ds` and `es` (and `cs`, which you forgot to set). – Jester Nov 17 '17 at 18:48
  • Setting es to 0 and bx to 0x7E00 will make it 0x0000:0x7E00 which is the same as 0x7E00, so jumping there should jump to the kernel. – programmer Nov 17 '17 at 18:49
  • That's not how it works :) `jmp 0x7E00` is not an absolute jump. You could probably do `jmp 0:0x7e00` though. – Jester Nov 17 '17 at 18:50
  • I'm sure it's not, but I'm new to segment:offset values. Could you tell me how to do it? – programmer Nov 17 '17 at 18:51
  • Ok , I tried to do jmp 0:0x7E00 and it still doesnt work. – programmer Nov 17 '17 at 18:53
  • Possible duplicate of [Boot loader doesn't jump to kernel code](https://stackoverflow.com/questions/32701854/boot-loader-doesnt-jump-to-kernel-code) – Ped7g Nov 17 '17 at 20:00

1 Answers1

3

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.

Brendan
  • 35,656
  • 2
  • 39
  • 66
  • `0x07C0:0x0000 = 0x00007C00 = load address` -> nope, depends on BIOS. And the `0000:7C00` is far more common, but still a robust enough bootloader always starts with far jump to "normalize" `cs:ip` to the expected state. – Ped7g Nov 17 '17 at 20:02
  • So then, what is it I need to do? I tried setting ah directly before the int 13h call, but even that doesn't work. – programmer Nov 17 '17 at 20:05
  • @Ped7g: Yes, and no. Nothing in this code actually depends on `CS` (and it should still work fine if BIOS used `0x0000:0x7C00` instead); and that comment (and the `%define`) document the intention that was/is used for data (e.g. `DS`, which is loaded and is used for printing strings). A `jmp LOAD_SEGMENT: start` would be nice (and could avoid future bugs) though. – Brendan Nov 17 '17 at 20:09
  • @programmer: Loading the "read sectors" function number into `ah` immediately before the `int 0x13` should've worked, but I think you had problems with the `jmp KERNEL_SEGMENT:KERNEL_OFFSET` before - multiple bugs with similar symptoms makes it look like nothing changed if you only fix one of the bugs. – Brendan Nov 17 '17 at 20:11
  • I just need a bootloader to launch my kernel. Could someone tell me where to find some working bootloader code. – programmer Nov 17 '17 at 20:15
  • @programmer: While I'm here; I'd also recommend using `0x0000` for all segments (and assuming the assembler is something like NASM, putting an `org 0x7C00` at the top of the code). This makes it a lot easier to avoid confusion caused by segmentation (especially later on, when you start using protected mode). – Brendan Nov 17 '17 at 20:15
  • @programmer: If you want a third-party boot loader that does the work for you, most people just use GRUB and multi-boot. – Brendan Nov 17 '17 at 20:16
  • But I need it to be open-source and made in assembly (because I actually need to load some other sectors in my kernel). So if someone just had a very basic assembly bootloader that would be great. – programmer Nov 17 '17 at 20:18
  • @programmer: For multi-boot; the boot loader loads an entire file into memory. It also gets a memory map from the firmware, can (optionally) setup a video mode, and switches to protected mode for you. GRUB is open source (GPL licence I think). – Brendan Nov 17 '17 at 20:21
  • That's the problem: protected mode. I need my kernel to run in real mode, and I also need to have the code for the bootloader (which must be assembly) to use in parts of my kernel that have to load other sectors. – programmer Nov 17 '17 at 20:22
  • @programmer: ..however, if it must be in assembly, then GRUB won't be suitable (it mostly in C). In that case it's probably better to continue working on your own (most open source boot loaders written in assembly aren't much better than what you already have) – Brendan Nov 17 '17 at 20:23
  • I don't care how messy the code is as long as it's in assembly and it works. – programmer Nov 17 '17 at 20:24
  • @programmer: Can I ask why it "must" use a crippled 16-bit mode that has been obsolete for longer than most people have been alive? – Brendan Nov 17 '17 at 20:24
  • Because my kernel is in assembly (which I hope to get away from here soon) and I need to use some of the bootloader code in my kernel to load additional sectors (which contain apps, libraries, and a bunch of other stuff that won't fit in the kernel) – programmer Nov 17 '17 at 20:26
  • @programmer: Erm. My kernel is assembly too, but I switch to protected mode (and enable paging) in the boot loader (which loads a kind of "initial RAM disk" containing multiple kernels). It stops using real mode long before it decides which kernel it should boot. – Brendan Nov 17 '17 at 20:29
  • @Ped7g : regarding forcing _CS_, it isn't necessary if you code the original bootloader to avoid requiring an absolute value relative to a particular segment. Doing such a FAR JMP at the beginning doesn't hurt anything but takes up bytes. Examples where _CS_ matters is especially evident with near jump tables. This [SO Question/Answer](https://stackoverflow.com/questions/34548325/near-call-jump-tables-dont-always-work-in-a-bootloader) gives some examples where not setting _CS_ would fail. – Michael Petch Nov 17 '17 at 22:45