Search code examples
assemblybootloadergnu-assemblerattx86-16

INT 16h/AH=0h doesn't wait for keystroke in my bootloader


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  

Solution

  • 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