Search code examples
assemblyx86osdevgrub2multiboot

GNU GRUB Gives "error: unsupported tag: 0xc" for Multiboot2


I tried to use the following code for the header of a Multiboot2 compliant kernel, but when I tried the multiboot2 command in grub, it gave the following error message:

error: unsupported tag: 0xc

My Multiboot2 header is defined as:

section .multiboot align=4096
mbhead: dd 0xe85250d6
        dd 0
        dd 76
        dd 0 - 76 - 0xe85250d6                  ; TODO the linker and assembler get angry if we calculate this with symbols, we need to do this by hand
        dw 1                                    ; multiboot information request
        dw 0
        dd 20
        dd 1
        dd 2
        dd 6
        dw 4                                    ; console flags
        dw 0
        dd 12
        dd 0x3
        dw 5                                    ; framebuffer settings
        dw 1
        dd 12
        dd 80
        dd 25
        dd 0
        dw 0                                    ; last tag
        dw 0
        dd 8

My project repository provides complete source code. I generate an ISO with make test_longmode.iso. I test with QEMU.

What is the problem causing the tag error, and how I can I fix it?


Solution

  • Primary Problems

    The GRUB error:

    error: unsupported tag: 0xc

    Is because you haven't 8 byte aligned each of the tag as per the Multiboot2 specification:

    3.1.3 General tag structure

    Tags constitutes a buffer of structures following each other padded when necessary in order for each tag to start at 8-bytes aligned address. Tags are terminated by a tag of type ‘0’ and size ‘8’. Every structure has following format

    To easily fix this you can use align 8 before each tag and you can let the assembler handle the alignment. This alignment doesn't get computed into the length of the tag.

    Your checksum could better be computed so as to not cause a warning by having NASM use truncation. This line:

    dd 0 - 76 - 0xe85250d6
    

    Could have been better expressed as:

    dd 0x100000000 - 76 - 0xe85250d6
    

    Code Readability

    I think the first thing about this code snippet is how unreadable it is. What caught my attention is this comment:

    TODO the linker and assembler get angry if we calculate this with symbols, we need to do this by hand

    It seems you may have computed values by hand because you had a problem in attempting to do it in some other fashion. A NASM directive that helps here is the EQU directive:

    3.2.4 EQU: Defining Constants

    EQU defines a symbol to a given constant value: when EQU is used, the source line must contain a label. The action of EQU is to define the given label name to the value of its (only) operand. This definition is absolute, and cannot change later. So, for example,

    message         db      'hello, world' 
    msglen          equ     $-message
    

    defines msglen to be the constant 12. msglen may not then be redefined later. This is not a preprocessor definition either: the value of msglen is evaluated once, using the value of $ (see section 3.5 for an explanation of $) at the point of definition, rather than being evaluated wherever it is referenced and using the value of $ at the point of reference.

    By using EQU we can make the code more readable.

    Secondly you can add labels to the begin and end of each tag and then have the assembler calculate the length of each tag for you. The length is the difference between an end label and start label for each tag.


    Improved Code

    The NASM assembly code with the changes suggested above could look like:

    MB2_ARCH  EQU 0                                 ; 0 = x86/x86-64
    MB2_LEN   EQU (mbend-mbhead)
    MB2_MAGIC EQU 0xe85250d6
    
    section .multiboot align=4096
    mbhead:
            dd MB2_MAGIC                            ; Multiboot2 magic number
            dd MB2_ARCH                             ; Architecture
            dd MB2_LEN                              ; Multiboot header length
            dd 0x100000000 - MB2_LEN - MB2_ARCH - MB2_MAGIC
                                                    ; Checksum
    
    mb2_tag_info_start:
            dw 1                                    ; multiboot information request
            dw 0
            dd mb2_tag_info_end - mb2_tag_info_start
            dd 1
            dd 2
            dd 6
    mb2_tag_info_end:
    
            align 8
    mb2_tag_console_start:
            dw 4                                    ; console flags
            dw 0
            dd mb2_tag_console_end - mb2_tag_console_start
            dd 0x3
    mb2_tag_console_end:
    
            align 8
    mb2_tag_fb_start:
            dw 5                                    ; framebuffer settings
            dw 1
            dd mb2_tag_fb_end - mb2_tag_fb_start
            dd 80
            dd 25
            dd 0
    mb2_tag_fb_end:
    
            align 8
    mb2_tag_end_start:
            dw 0                                    ; last tag
            dw 0
            dd mb2_tag_end_end - mb2_tag_end_start
    mb2_tag_end_end:
    
    mbend:
    

    This can be improved further but even this will seem more readable and self documenting than the original code snippet. If you change the size of a tag or the header, all the lengths will be computed for you. If the size changes the align 8 directive will compute proper alignment for you. This makes the code more maintainable.