2
votes

I was trying to write the bootloader from Bootloader. The code written is

BITS 16

start:
    mov ax, 07C0h       ; Set up 4K stack space after this bootloader
    add ax, 288     ; (4096 + 512) / 16 bytes per paragraph
    mov ss, ax
    mov sp, 4096

    mov ax, 07C0h       ; Set data segment to where we're loaded
    mov ds, ax


    mov si, text_string ; Put string position into SI
    call print_string   ; Call our string-printing routine

    jmp $           ; Jump here - infinite loop!


    text_string db 'This is my cool new OS!', 0


print_string:           ; Routine: output string in SI to screen
    mov ah, 0Eh     ; int 10h 'print char' function

.repeat:
    lodsb           ; Get character from string
    cmp al, 0
    je .done        ; If char is zero, end of string
    int 10h         ; Otherwise, print it
    jmp .repeat

.done:
    ret


    times 510-($-$$) db 0   ; Pad remainder of boot sector with 0s
    dw 0xAA55       ; The standard PC boot signature

What I couldn't understand is why are we writing jmp$. By writing jmp$ it enters an infinite loop. So, after entering an infinite loop, the last two lines

times 510-($-$$) db 0   ; Pad remainder of boot sector with 0s
    dw 0xAA55       ; The standard PC boot signature

are never executed.

Moreover why are we adding 288 to ax?

2
Well it doesn't have anything to do after printing, so infinite loop is the best it can do to avoid executing your text string and anything after it. - Ruslan
Adding 288 to 0x7c0 and storing it in the stack segment simply puts space between the stack and the bootloader. The memory area in between could be used as disk buffer etc. - Michael Petch
One other variation of jmp $ that you may also come across is the sequence cli endloop: hlt jmp endloop. - Michael Petch

2 Answers

4
votes

$ is the address of the current instruction, so jmp $ is looping to itself. This is usually done for a fatal error.

Here, the loader is incomplete, so it's outputting a message and then looping. The "loop" instruction [hopefully] would be replaced by real code [to be added].

Things defined with the db or dw pseudo-ops are definitions for data and are not executable instructions [usually--unless you need a special instruction not known to the assembler].

So, without the infinite loop, you'd try to execute the data at text_string:, which would produce undefined/unexpected results, let alone trying to execute the last part of the boot block.


The 288 offset ...

The boot is loaded at address 0x07C00. It is trying to set up its stack segment at location (0x07C00 + 4096 + 512) --> 0x8E00. But, it's trying to put this into a segment register, so the value must be shifted right by 4 bits. 0x07C0 is already shifted and 288 is (4096 + 512) >> 4 or 0x0120. The final value for SS is 0x07C0 + 0x0120 --> 0x08E0 [at address 0x8E00]

This seems wrong (i.e. the arithmetic doesn't match), but the sp register is set to 4096, so the final resting place for ss:sp is address 0x9E00.

In 8086 real mode addressing, all addresses use a segment register and some offset. The final address is: address = (segreg << 4) + offset. This gets done by the hardware on each instruction that accesses memory in some way.

When you jump around in code, you use the CS [code segment] register. Data access uses the DS [data segment] register. And stack access (e.g. push/pop or %sp relative, use the SS [stack segment] register. There is also an ES [extra segment] register that is used in string instructions.

0
votes

The last two statements are not executables statements, they are kind of pseudo-instructions or assembler directives. Here, they are used to assemble the object code and not some executable instructions, and to make sure that boot sector is of 512 bytes.

jmp $ is however an executable instruction, and will be run after the loader has loaded it. While the last two lines are used (and not executed) by the assembler when it assembles the assembly code into an object file.