10

I have written simple first stage bootloader which displays "Hello world" using interrupt to bios. Now as a next obvious step to write a second stage, but where code for that should exist and how to load it from first stage ?

Here is a program for first stage

[BITS 16]   ;Tells the assembler that its a 16 bit code
[ORG 0x7C00]    ;Origin, tell the assembler that where the code will
                ;be in memory after it is been loaded

MOV SI, HelloString ;Store string pointer to SI
CALL PrintString    ;Call print string procedure
JMP $       ;Infinite loop, hang it here.


PrintCharacter: ;Procedure to print character on screen
    ;Assume that ASCII value is in register AL
MOV AH, 0x0E    ;Tell BIOS that we need to print one charater on screen.
MOV BH, 0x00    ;Page no.
MOV BL, 0x07    ;Text attribute 0x07 is lightgrey font on black background

INT 0x10    ;Call video interrupt
RET     ;Return to calling procedure



PrintString:    ;Procedure to print string on screen
    ;Assume that string starting pointer is in register SI

next_character: ;Lable to fetch next character from string
MOV AL, [SI]    ;Get a byte from string and store in AL register
INC SI      ;Increment SI pointer
OR AL, AL   ;Check if value in AL is zero (end of string)
JZ exit_function ;If end then return
CALL PrintCharacter ;Else print the character which is in AL register
JMP next_character  ;Fetch next character from string
exit_function:  ;End label
RET     ;Return from procedure


;Data
HelloString db 'Hello World', 0 ;HelloWorld string ending with 0

TIMES 510 - ($ - $$) db 0   ;Fill the rest of sector with 0
DW 0xAA55           ;Add boot signature at the end of bootloader
Xinus
  • 29,617
  • 32
  • 119
  • 165
  • Are you using C language? Any other info of importance that you can share? – Hamish Grubijan Jan 14 '10 at 15:53
  • we have used assembly x86 instructions for first stage but we r planning to write second stage in higher level language like c .. where do I store that second stage binary and how to load it from first stage bootloader – Xinus Jan 14 '10 at 16:15
  • Possible duplicate of [Loading kernel from assembly (NASM)](http://stackoverflow.com/questions/1551240/loading-kernel-from-assembly-nasm) – Ciro Santilli OurBigBook.com Oct 28 '15 at 18:14

3 Answers3

8

On x86 you would do the following (simplified):

  • Have the bootloader load the n-th sector of the disk/floppy (wherever you're booting from) into memory and execute it (i.e. load segment/offset and do retf). A better alternative is to search the filesystem for a certain filename (e.g. KERNEL.BIN) -- but you'd need to know the file system type (e.g. FAT12 if you're testing from a floppy image).
  • The kernel would then start in real mode. It sets up code descriptors, GDT, and so on, activates 32-bit addressing (you should have heard of "A20") and finally enters protected mode. Then you need a far jump to a 32-bit code segment (kernel file must be linked together in a way that the 32-bit code is at an absolute position, e.g. at offset 512, right after the 16-bit real mode stuff).
  • The 32-bit kernel assembly, then, just defines EXTERN _mykernel (for example) and calls that symbol.
  • Then you can begin writing your kernel as C function mykernel.

Okay that was a short overview of what I did a few years ago (with lots of copy&paste from the Internet ;). If that isn't helpful, here are some good web resources on OS development:

Hope that helps ^^

AndiDog
  • 68,631
  • 21
  • 159
  • 205
1

Minimal runnable NASM BIOS example that loads stage 2 and jumps to it

use16
org 0x7C00

    ; You should do further initializations here
    ; like setup the stack and segment registers.

    ; Load stage 2 to memory.
    mov ah, 0x02
    ; Number of sectors to read.
    mov al, 1
    ; This may not be necessary as many BIOS set it up as an initial state.
    mov dl, 0x80
    ; Cylinder number.
    mov ch, 0
    ; Head number.
    mov dh, 0
    ; Starting sector number. 2 because 1 was already loaded.
    mov cl, 2
    ; Where to load to.
    mov bx, stage2
    int 0x13

    jmp stage2

    ; Magic bytes.    
    times ((0x200 - 2) - ($ - $$)) db 0x00
    dw 0xAA55

stage2:

    ; Print 'a'.
    mov ax, 0x0E61
    int 0x10

    cli
    hlt

    ; Pad image to multiple of 512 bytes.
    times ((0x400) - ($ - $$)) db 0x00

Compile and run:

nasm -f bin -o main.img main.asm
qemu-system-i386 main.img

Expected outcome: a gets printed to the screen, and then the program halts.

Tested on Ubuntu 14.04.

Saner GAS example using a linker script and more correct initialization (segment registers, stack) on my GitHub.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
  • If anyone can guess why the downvote, please do so I can learn and improve information. I never retaliate. – Ciro Santilli OurBigBook.com Oct 05 '15 at 11:54
  • 1
    Ah, I'm looking through the `bootloader` tag active list and I see we meet again. One observation i'll make is that i would have probably closed this question as too broad. Although you provide a solution you assert that the OP would just load from the next sector after the MBR. Although that is a solution, i think the upvoted one is closer the mark in discussing other ideas like doing a file lookup for the file system. That answer assumes a great many things too - is there a file system containing the 2nd stage? What file system is it? etc... – Michael Petch Oct 05 '15 at 19:34
  • @MichaelPetch hey there again :-) Agreed that that is a good answer. It is just that I usually like to first get some stuff running to see the beauty of it: and it then makes it easier to understand the deeper parts later. – Ciro Santilli OurBigBook.com Oct 05 '15 at 19:44
  • 1
    You should be ensuring the _ES_ register is zero since the Int13h/AH=2h is used for the buffer address. I'd probably also take the opprtunity to use the JMP stage2 to set _CS_ to zero so I'd do a `jmp 0x0000:stage2`. I think for completeness by the time you are in stage2 you should have them making sure _DS_ is also zero since any code that relies on _DS_ being a specific value may fail to work properly in environments where the BIOS may have set _DS_ to something else. – Michael Petch Nov 28 '17 at 23:23
  • 1
    As well I don't recommend you set _DL_ to a fixed drive number. You should just use the one passed in from the BIOS when transfer was controlled to the bootloader. – Michael Petch Nov 28 '17 at 23:27
  • And since you are reading sectors into RAM you should set up SS:SP in a location that you specifically know won't be clobbered by the disk read. If the stack happens to be growing down 0x8000 towards 0x7e00 your stack may end up colliding with your second stage. It is simplest to move the stack where you know there is no interference. – Michael Petch Nov 29 '17 at 00:02
1

Look at the GRUB implementation here (stage 1):

http://src.illumos.org/source/xref/illumos-gate/usr/src/grub/grub-0.97/stage1/stage1.S

First noticed the starting point at 0x7c00 and the end signature of 0xaa55 for this first sector. From within the disassembly, u can see this:

349 copy_buffer:
350   movw    ABS(stage2_segment), %es
351 
352   /*
353    * We need to save %cx and %si because the startup code in
354    * stage2 uses them without initializing them.
355    */
356   pusha
357   pushw   %ds
358 
359   movw    $0x100, %cx
360   movw    %bx, %ds
361   xorw    %si, %si
362   xorw    %di, %di
363 
364   cld
365 
366   rep
367   movsw
368 
369   popw    %ds
370   popa
371 
372   /* boot stage2 */
373   jmp *(stage2_address)
374 
375 /* END OF MAIN LOOP */
376

Essentially the logic is to copy the stage 2 code into another part of memory, and after that jump directly there, and that is "boot stage2". In other words, "boot stage1" is effectively triggered from BIOS after it has loaded the sector into memory, whereas stage2 is where you jump there - it can be anywhere.

Peter Teoh
  • 6,337
  • 4
  • 42
  • 58
  • How does the assembler know that all the labels for the code loaded from the disk are now offset by wherever you loaded into RAM? – James M. Lay May 17 '15 at 21:21