x86x86-64nasmgrubmultiboot

multiboot2 header to properly go into "EFI amd64 machine state with boot services enabled" - hlt instruction not working?


I have a simple setup of a basic start.asm (nasm assembler) and a multiboot2_header.asm that get linked together once compiled. The multiboot2 header is at the beginning of the final ELF64-x86_64-file. To run it, I use QEMU -> OVMF (UEFI) -> GRUB -> <my-binarỳ>. When I use a minimal multiboot2 header, everything works: I'm in 32-bit mode and I can set up my own stack and call functions. To verify this, I check the registers in QEMU. But now I want to boot into EFI amd64 machine state with boot services enabled, which is defined in section 3.5 multiboot2 spec [1], but this causes problems.

How I try to accomplish my goal: The spec tells, that the multiboot2 header must contain the two tags EFI boot services tag: leaves UEFI boot services enabled and EFI amd64 entry address tag of Multiboot2 header tag. I'm confident I have done this right (code below).

Problem (UPDATE 2021-06-17) After some more investigating, it looks like my approach is mostly(?) correct. The problem is, that my hlt instruction is ignored. This way, more code got executed than expected and some execution poisoned eax. If I change the hlt in my start-symbol to an infinite unconditional jmp to the address of this jmp, the value in eax is correct! (update end)

multiboot2-header.asm:

; This file uses "Netwide Assembler Syntax" and can be compiled by running
; `nasm -f elf64 multiboot2_header.asm -o multiboot2_header.o`
;
; External symbol, that comes "start.asm"
EXTERN start

ALIGN   8  ; according to spec, the header must be 64-bit (8 byte) aligned
section .multiboot_header

    header_start:
        ;   dd => int 32, see https://www.cs.uaf.edu/2017/fall/cs301/reference/x86_64.html
        dd  0xe85250d6                ; magic number (multiboot 2 spec)
        dd  0                         ; architecture 0 (protected mode i386; spec doesn't specify many options)
        dd  header_end - header_start ; header length
        ;   checksum
        dd  0x100000000 - (0xe85250d6 + 0 + (header_end - header_start))

        ; OPTIONAL MULTIBOOT2 TAGS (additional to required END TAG)
        ; In order to boot into "EFI amd64 machine state with boot services enabled" (3.5 in Spec, 2021-06)
        ; machine state, we must specify a few additional tags:
        ;
        ; ------------------------------------------------------------------------------------
        ; "EFI boot services tag": leaves UEFI boot services enabled: its our task to exit them
        ALIGN   8       ; alignment in bits, according to multiboot2 spec, tags are 8-byte (64bit) aligned
        dw      7       ; type  (16bit)
        dw      0       ; flags (16bit)
        dd      8       ; size  (32bit)
        ; ------------------------------------------------------------------------------------
        ; "EFI amd64 entry address tag of Multiboot2 header tag"
        ALIGN   8
        dw      9       ; type  (16bit)
        dw      0       ; flags (16bit)
        dd      12      ; size  (32bit)
        ; TODO I'm not entirely sure how this works together with the "start" symbol from the linker script:
        ;  perhaps the start symbol in the linker script is a fallback, if this is not found
        dd      start   ; entry_addr (32bit)
        ; ------------------------------------------------------------------------------------
        ; REQUIRED END TAG
        ALIGN   8
        dw      0       ; type  (16bit)
        dw      0       ; flags (16bit)
        dd      8       ; size  (32bit)
    header_end:

PS: The Rust tool bootinfo [2] correctly recognizes the multiboot2 header and the specified tags in my final ELF.


Solution

  • My initial approach was right and actually correct. I just couldn't properly check this because of two misunderstandings:

    You can verify the registers in QEMU easily during startup/after handoff if the start-symbol looks like this:

    GLOBAL start
    
    SECTION .text
    
    ; produce x-bit x86 code (even if this will be an ELF-64 file)
    [BITS 64]
    
        start:
            mov r12, 0xffffeeeeddddcccc   ; verify that we are in 64-bit mode (otherwise this fails)
            cli ; without clear interrupts, the hlt is ignored (I don't know what interrupt comes.. maybe the key input in qemu)
            hlt