I have written my first bootloader using GNU Assembler with AT&T syntax. It is suppose to print hello world
to the screen then inform user that pressing any key will cause a reboot. Only after a key is pressed is a reboot suppose to be initiated. My bootloader code does not wait for a key and automatically reboot after printing information. Why isn't this code waiting for a keystroke, and how can I fix it?
My boot sector code:
#generate 16-bit code
.code16
#hint the assembler that here is the executable code located
.text
.globl _start;
#boot code entry
_start:
jmp _boot #jump to boot code
welcome: .asciz "Hello, World\n\r" #here we define the string
AnyKey: .asciz "Press any key to reboot...\n\r"
.macro mWriteString str #macro which calls a function to print a string
leaw \str, %si
call .writeStringIn
.endm
#function to print the string
.writeStringIn:
lodsb
orb %al, %al
jz .writeStringOut
movb $0x0e, %ah
int $0x10
jmp .writeStringIn
.writeStringOut:
ret
#Gets the pressed key
.GetPressedKey:
mov 0, %ah
int $0x16 #BIOS Keyboard Service
ret
.Reboot:
mWriteString AnyKey
call .GetPressedKey
#Sends us to the end of the memory
#causing reboot
.byte 0x0ea
.word 0x0000
.word 0xffff
_boot:
mWriteString welcome
call .Reboot
#move to 510th byte from the start and append boot signature
. = _start + 510
.byte 0x55
.byte 0xaa
Int 0x16 AH=0 will wait for a keystroke:
KEYBOARD - GET KEYSTROKE
AH = 00h Return: AH = BIOS scan code AL = ASCII character
You had the right idea with this:
mov 0, %ah
int $0x16 #BIOS Keyboard Service
ret
The problem is that mov 0, %ah
treats 0 as a memory operand. In this case it would be the same as moving the byte value at DS:[0] into AH. You just want to move the immediate (constant) value 0 to AH. In AT&T syntax immediate values are prepended with a dollar sign $
. If a value is not prefixed with a $
GNU assembler assumes it is a memory reference. The code should have been:
mov $0, %ah
int $0x16 #BIOS Keyboard Service
ret
Alternatively you could have zeroed out AH with:
xor %ah, %ah
Your code make some assumptions about the values of the DS register when your bootloader loads, and it doesn't set up its own stack. For some good practices about writing a bootloader that will work a wider variety of emulators and real hardware you might wish to have a look at some of my General Bootloader Tips given in a previous Stackoverflow answer.
Additionally, in your case I'd probably move your data after the code and remove the JMP at the beginning since you aren't using a BIOS Parameter Block . Some BIOSes will look for a JMP at the start of the bootsector and assume you have a BPB and may actually overwrite your code thinking it is filling in values of a data structure. The best way to avoid that situation is to simply avoid having a JMP as a first instruction IF you don't have a BPB.
You are likely running under an emulator that exhibits the behavior that you need, but if you were to move this bootloader to other environments (and even other emulators) you may find it doesn't work as expected.
At the end you manually encode a long jump to do the reboot:
.byte 0x0ea
.word 0x0000
.word 0xffff
Although you may have had to do this in some older versions of MASM or TASM, GNU assembler with AT&T syntax supports doing it this way:
jmp $0xffff,$0x0000