I am trying to write a simple bootloader in assembly. What I am trying to do is print a few strings, then wait for a keypress. This is my code:
[org 0x7c00]
mov al, 0
mov ah, 0
mov ax, 0
mov bx, 0
mov bp , 0x8000 ; Set the base of the stack a little above where BIOS
mov sp , bp ; loads our boot sector - so it won ’t overwrite us.
jmp main
message:
db 0x0a,0x0d,'Enderbyte Programs',0x0a,0x0d,0
bmessage:
db 0x0a,0x0d,'Booting...',0x0a,0x0d,0
cmessage:
db 0x0a,0x0d,'Just kidding this does nothing ',0
kmessage:
db 'Or does it?',0
print_string:
mov al, [bx]
inc bx
cmp al, 0
jne print_char
mov bx, 0
ret
print_char:
int 0x10; Print AL register
jmp print_string
main:
mov ah, 0x0e ; int 10/ ah = 0 eh -> scrolling teletype BIOS routine
mov bx, message
call print_string
mov bx, bmessage
call print_string
mov bx, cmessage,
call print_string
mov ah, 00h
int 16h; Wait for keypress?
mov ah, 0x0e
mov bx, kmessage
call print_string
;jmp $
This code works perfectly on VirtualBox, but when I try to run it on my PC (by copying it into a flash drive and booting from it), something very strange happens. The cursor is moved to the bottom-right area of the screen and prints off a strange character (An O but with an arrow coming off at the NE corner). Pressing a key causes it to clear the screen, the cursor jumps around before printing off another one of these characters. What is going on?
I have tried to follow the advice in Assembly boot loader working on virtual PC, not on real PC (setting all registers to zero, validating the stack) but it still did not work on the real hardware. (Still works fine on Virtualbox).
B0 00 B4 00 B8 00 00 BB 00 00 BD 00 80 89 EC EB
63 0A 0D 45 6E 64 65 72 62 79 74 65 20 50 72 6F
67 72 61 6D 73 0A 0D 00 0A 0D 42 6F 6F 74 69 6E
67 2E 2E 2E 0A 0D 00 0A 0D 4A 75 73 74 20 6B 69
64 64 69 6E 67 20 74 68 69 73 20 64 6F 65 73 20
6E 6F 74 68 69 6E 67 20 00 4F 72 20 64 6F 65 73
20 69 74 3F 00 8A 07 43 3C 00 75 04 BB 00 00 C3
CD 10 EB F1 B4 0E BB 11 7C E8 E9 FF BB 28 7C E8
E3 FF BB 37 7C E8 DD FF B4 00 CD 16 B4 0E BB 59
7C E8 D1 FF 25 00 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
You should do a better setup and probably a somehow valid MBR. You cannot assume that BIOS set up the segments and registers for you. You have to initialize the most important things from the beginning. Maybe try something like:
This code assumes that your print_string
is properly functional.
[ORG 0x7c00]
[BITS 16]
global boot
boot:
jmp _start
nop
; Here is a example BPB
OEMname: db "mkfs.fat"
bytesPerSector: dw 512
sectPerCluster: db 0
reservedSectors: dw 0
numFAT: db 0
numRootDirEntries: dw 0
numSectors: dw 0
mediaType: db 0
numFATsectors: dw 0
sectorsPerTrack: dw 0
numHeads: dw 0
numHiddenSectors: dd 0
numSectorsHuge: dd 0
driveNum: db 0
reserved: db 0
signature: db 0
volumeID: dd 0
volumeLabel: db "NO NAME "
fileSysType: db "FAT12 "
_start:
cli
xor ax, ax ; clear your segments
mov es, ax
mov ds, ax
jmp 0x00:main ; clear code segment
main:
sti
mov bp , 0x8000 ; Set the base of the stack a little above where BIOS
mov ss, ax ; Setup Stack SS (AX=0) followed immediately by SP
mov sp , bp
mov ah, 0x0e ; int 10/ ah = 0 eh -> scrolling teletype BIOS routine
mov bx, message
call print_string
mov bx, bmessage
call print_string
mov bx, cmessage,
call print_string
mov ah, 00h
int 16h; Wait for keypress?
mov ah, 0x0e
jmp $
print_string:
mov al, [bx]
inc bx
cmp al, 0
jne print_char
mov bx, 0
ret
print_char:
int 0x10; Print AL register
jmp print_string
message:
db 0x0a,0x0d,"Enderbyte Programs",0x0a,0x0d,0
bmessage:
db 0x0a,0x0d,"Booting...",0x0a,0x0d,0
cmessage:
db 0x0a,0x0d,"Just kidding this does nothing ",0
kmessage:
db "Or does it?",0
times 446 - ($ - $$) db 0x00 ; Padding to 512 bytes
; Partitions table for a MBR
partition_table_1:
db 0x80 ; Status, 0x80 means active
db 0x00 ; First Absolute Sector CHS
db 0x00 ;
db 0x00 ;
db 0x00 ; Partition Type
db 0x00 ; Last Absolute Sector CHS
db 0x00 ;
db 0x00 ;
dd 0x00000001 ; First Absolute Sector LBA
dd 0x00000200 ; Number of Sectors
partition_table_2:
db 0x00 ; Status, 0x80 means active
db 0x00 ; First Absolute Sector CHS
db 0x00 ;
db 0x00 ;
db 0x00 ; Partition Type
db 0x00 ; Last Absolute Sector CHS
db 0x00 ;
db 0x00 ;
dd 0x00000000 ; First Absolute Sector LBA
dd 0x00000000 ; Number of Sectors
partition_table_3:
db 0x00 ; Status, 0x80 means active
db 0x00 ; First Absolute Sector CHS
db 0x00 ;
db 0x00 ;
db 0x00 ; Partition Type
db 0x00 ; Last Absolute Sector CHS
db 0x00 ;
db 0x00 ;
dd 0x00000000 ; First Absolute Sector LBA
dd 0x00000000 ; Number of Sectors
partition_table_4:
db 0x00 ; Status, 0x80 means active
db 0x00 ; First Absolute Sector CHS
db 0x00 ;
db 0x00 ;
db 0x00 ; Partition Type
db 0x00 ; Last Absolute Sector CHS
db 0x00 ;
db 0x00 ;
dd 0x00000000 ; First Absolute Sector LBA
dd 0x00000000 ; Number of Sectors
dw 0xAA55 ; Boot signature required to boot
First, we cleared the segments to ensure that BIOS didn't give us junk values that will cause trouble later. We also cleared the code segments to make sure that we are in address 0x0000:0x7c00 instead of its equivalents such as 0x07c0:0x0000. I formatted the code, and move the data down so you can manipulate them easier. Your code also lacked padding and signature which were added. I also added some MBR partition tables in case you are booting from harddrive or equivalent system. You could also add a BPB, you can find the structure to that here:
https://wiki.osdev.org/FAT#BPB_.28BIOS_Parameter_Block.29
The above code can be compiled with
nasm -fbin bootloader.asm -o bootloader.bin
Write this to the first sector of a harddrive/USB/CD or equivalent.
Also check these out:
https://wiki.osdev.org/Bootloader
https://wiki.osdev.org/Rolling_Your_Own_Bootloader
https://wiki.osdev.org/MBR_(x86)
Edit:
The above code is compiled without errors and runs on QEMU.
Edit:
Also added a BPB