insanikov
insanikov

Reputation: 233

Issues with bootloader

I am in the process of learning Assembly, and I tried writing my own bootloader. It works fine on VirtualBox , but it doesn't work on a actual PC. On pc 'Hello World!' doesn't get printed.

This is the code is use:

BITS 16
ORG 0x7C00

jmp boot_sector

;------------------------------

OEMLabel            db "FLOPPYDR"
BytesPerSector      dw 512
SectorsPerCluster   db 1
ReservedForBoot     dw 1
NumberOfFats        db 2
RootDirEntries      dw 224
LogicalSectors      dw 2880
MediumByte          db 0xF0
SectorsPerFat       dw 9
SectorsPerTrack     dw 18
Sides               dw 2
HiddenSectors       dd 0
LargeSectors        dd 0
DriveNo             dw 0
Signature           db 41
VolumeID            dd 0x00
VolumeLabel         db "FLOPPYDRIVE"
FileSystem          db "FAT12"

;##############################
          boot_sector:
;##############################

  mov ax, 0x0000            ; Set up the stack
  mov ss, ax                ; Is this done correctly?
  mov sp, 0x7C00            ; (I dont quite understand)

  int 0x10                  ; Set video mode

  int 0x13                  ; Reset the drive

  mov ah, 0x02              ; Read more sectors
  mov al, 2                 ; Read two extra sectors,
  mov bx, main_sector       ; starting from the second.
  mov ch, 0                 ;
  mov cl, 2                 ; dl has been set already (?)
  mov dh, 0                 ;
  int 0x13                  ;

  mov [bootdev], dl         ; Store original dl in bootdev

  jmp main_sector           ; Go to the main sector (0x200 I think)

  times 510 - ($ - $$) db 0 ; Fill in the rest of the sector with 0s
  dw 0xAA55                 ; and 0xAA55 at the end for signature

;##############################
          main_sector:
;##############################

jmp Start

;------------------------------

bootdev db 0
msg db 'Hello World!', 10, 13, 0

;------------------------------

print_string:
  mov ah, 0x0E
  mov bh, 0
  cmp al, 0
  jne .loop
  mov bl, 0x0F
  .loop:
    lodsb
    cmp al, 0
    je .end
    int 0x10
    jmp .loop
  .end:
    ret

;------------------------------

  Start:
    mov si, msg
    call print_string
    hlt

    times 512 - ($ - main_sector) db 0

I've also commented in some questions, but these are not my main question (well, maybe me not knowing the answer causes the problem). Why doesn't this work on a real PC?

To compile I use nasm -f bin boot.asm -o boot.bin and to create a virtual floppydisk file I use mkfile 1474560 floppy.flp

Then I open up floppy.flp using HexEdit and replace the first 64 lines (0x00 - 0x3F) with the content of the boot.bin file (opened using HexEdit).

E9 38 00 46 4C 4F 50 50 59 44 52 00 02 01 01 00
02 E0 00 40 0B F0 09 00 12 00 02 00 00 00 00 00
00 00 00 00 00 00 29 00 00 00 00 46 4C 4F 50 50
59 44 52 49 56 45 46 41 54 31 32 B8 00 00 8E D0
BC 00 7C CD 10 CD 13 B4 02 B0 02 BB 00 7E B5 00
B1 02 B6 00 CD 13 88 16 03 7E E9 A3 01 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 AA <-- End of first sector
E9 24 00 00 48 65 6C 6C 6F 20 57 6F 72 6C 64 21
0A 0D 00 B4 0E B7 00 3C 00 75 02 B3 0F AC 3C 00
74 04 CD 10 EB F7 C3 BE 04 7E E8 E6 FF F4 00 00 (The rest is just 0's).

This is the Terminal when I burn floppy.flp to the USB drive:

Last login: Wed Sep 23 12:10:48 on ttys000
MacBook-Air:~ sasha$ cd ~/Desktop
MacBook-Air:Desktop sasha$ diskutil list
/dev/disk0
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        *121.3 GB   disk0
   1:                  Apple_HFS                         209.7 MB   disk0s1
   2:          Apple_CoreStorage                         120.5 GB   disk0s2
   3:                 Apple_Boot Recovery HD             650.0 MB   disk0s3
/dev/disk1
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:                  Apple_HFS Macintosh HD           *120.1 GB   disk1
                                 Logical Volume on disk0s2
                                 8CD6A846-395D-4C97-A5DE-0A7ABA9F1C99
                                 Unencrypted
/dev/disk2
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     Apple_partition_scheme                        *17.1 MB    disk2
   1:        Apple_partition_map                         32.3 KB    disk2s1
   2:                  Apple_HFS Flash Player            17.1 MB    disk2s2
/dev/disk3
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:                            FLOPPYDRIVE            *1.0 GB     disk3
MacBook-Air:Desktop sasha$ diskutil unmountdisk /dev/disk3
Unmount of all volumes on disk3 was successful
MacBook-Air:Desktop sasha$ sudo dd bs=512 if=floppy.flp of=/dev/disk3
Password:
2880+0 records in
2880+0 records out
1474560 bytes transferred in 0.843982 secs (1747146 bytes/sec)
MacBook-Air:Desktop sasha$ 

Upvotes: 2

Views: 221

Answers (2)

Michael Petch
Michael Petch

Reputation: 47573

I've written about bootloaders at some length recently on Stackoverflow. Most issues that involve the situation where it works on one emulator or VM but not on another (or physical hardware) usually come down to making false assumptions about the state of the segment registers when the BIOS jumps to your code. Under some emulators the segment registers may have more sane values in them, but that usually isn't the case. From my previous answer I had these two tips that seem to apply here:

  1. When the BIOS jumps to your code you can't rely on DS,ES,SS,SP registers having valid or expected values. They should be set up appropriately when your bootloader starts.
  2. The direction flag used by lodsb, movsb etc could be either set or cleared. If the direction flag is set improperly SI/DI registers may be adjusted in the wrong direction. Use STD/CLD to set it to the direction you wish (CLD=forward/STD=backwards). In this case the code assumes forward movement so one should use CLD. More on this can be found in an instruction set reference

Your assembly code is set to compile and link assuming an origin point of 0x7C00 (via ORG 0x7C00). Your code that accesses variables like msg and bootdev will be made with the assumption that their memory address will be absolute within the segment (DS). This means that if you have an invalid DS segment then you could be addressing variables, data, and labels at the wrong locations. As an example:

mov [bootdev], dl 

has an implicit reference to DS and is equivalent to addressing it with an explicit DS segment:

mov [ds:bootdev], dl

If DS has some random value in it then you'll likely be accessing memory in places you don't expect. For some environments DS might just be zero so your code will work.

How do you know which segment to use? The bootloader is loaded by the BIOS at physical memory 0x0000:0x7C00(segment:offset) . Your origin point (Set with ORG directive) matches the offset so that means in your case DS should be set to zero.

In your code ES should also be set to zero. The reason is that INT 0x13 AH=0x02 (disk read) says:

ES:BX Buffer Address Pointer

Imagine if ES is set to random garbage, the disk read will likely read into memory you didn't intend. So just like DS, ES must also be set. You've written your bootloader and kernel in the same file with the origin point of 0x7C00 so again you just need to use an ES segment set to zero.

When setting up your stack you can set ES and DS appropriately.

mov ax, 0x0000            ; Set up the stack
mov ss, ax                ; Is this done correctly?
mov sp, 0x7C00            ; (I dont quite understand)
mov ds, ax                ; Set DS to 0 because that is what your code needs
mov es, ax                ;     ES the same as DS.
cld                       ; Read my tip #2

You did ask if you set your stack properly. There is nothing wrong with it. Your instructions effectively set up a a stack that grows downward from 0x0000:0x7C00 just below the area occupied by your bootloader. That leaves about 27kb (0x7C00-0x1000) of stack space. 4k is plenty for BIOS calls and your current code. The first 0x1000 of memory are generally for interrupt table/BIOS data area etc.

One other bug I noticed in your code was when you try to reset the disk drive:

int 0x10                  ; Set video mode
int 0x13                  ; Reset the drive

You set AX to zero above these 2 lines. INT 0x10 AH=0x00 (set video mode) has the side effect of returning information in AX. Since AX can be clobbered your call to INT 0x13 AH=0x00 will likely be wrong. You need to clear AH (or all of AX) prior to calling int 0x13 to reset the drive. The code should look like:

int 0x10                  ; Set video mode
xor ax,ax                 ; clear AX (AH=0)
int 0x13                  ; Reset the drive

There is a minor issue at the top of your program and would likely only be a problem if you put this bootloader on a properly formatted FAT12 disk image and tried to mount it in your OS. You have:

jmp boot_sector
;------------------------------    
OEMLabel            db "FLOPPYDR"

The disk structure you have within your bootloader should have OEMLabel starting from the 4th byte. jmp boot_sector could be encoded as a 2 or 3 byte instruction by NASM. Use short to force a 2 byte encoding followed by a NOP (1 byte instruction). This will place OEMLabel at the 4th byte in the file. It could look like this:

jmp short boot_sector
nop
;------------------------------    
OEMLabel            db "FLOPPYDR"

Alternatively you can encode a JMP that may be 2 or 3 bytes when encoded and pad it with NOP if necessary using NASM's TIMES directive so that OEMLabel always starts at the 4th byte:

jmp boot_sector
times 3-($-$$) nop
;------------------------------    
OEMLabel            db "FLOPPYDR"

A trick to avoid using hexedit to manually insert the bootloader code at the beginning of a disk image is to use dd. You can use dd to overwrite the first 1024 bytes and keep the rest intact. Try dd if=boot.bin of=floppy.flp bs=512 count=2 conv=notrunc . This should open up floppy.flp write 2 512 byte sectors containing the 1024 bytes from boot.bin without truncating the file (conv=notrunc)

Upvotes: 3

Brendan
Brendan

Reputation: 37222

When a bootloader is started the contents of almost all registers (including segment registers) is "undefined". The only register that actually does have a known value is DL (which contains the BIOS' drive number for the disk).

All instructions that refer to memory use an implied or explicit segment register. For example, both mov [bootdev], dl and lodsb rely on the (implied) DS segment register, which is never set and is still undefined.

Like all undefined values, it's possible (due to pure luck) for them to be a value that makes things work by accident. Your code would work if the BIOS happened to leave the value 0x0000 in DS.

Upvotes: 2

Related Questions