Search code examples
assemblyx86masmbootloaderosdev

Masm string variable inside bootloader causes: Undefined symbol error


For reasons I can not put my finger on, using a variable inside my bootloader causes it to be marked as an undefined symbol.

.386
option segment:use16
.model tiny, stdcall

;------------------------------
; CODE
;------------------------------
.code
org 07c00h

Print PROTO lpStr:WORD

_start:
    INVOKE Print, OFFSET bootInfo
    cli
    hlt

error:
    cli
    hlt

Print PROC USES ax si lpStr:WORD
    mov si, lpStr
    mov ah, 0Eh
    @@loop:
    lodsb
    cmp al, 0
    je @@done
    int 10h
    jmp @@loop
    @@done:
    ret
Print ENDP

;------------------------------
; DATA
;------------------------------
bootInfo db "Booting CompatOS...", 0

;------------------------------
; PADDING
;------------------------------
byte 510-($-_start) dup (0)
dw 0AA55h

END _start

I've searched and looked if there is something wrong with the way I declare my variable, but apparently, it should work like this. For context, I oriented myself on this.

Any help is much appreciated. Thanks.

Edit:

MASM and linker used to compile the sources. Makefile:

.PHONY: clear
clear:
    rm -f -r -d ./tmp/
    rm -f -r -d ./bin/

.PHONY: build_debug
build_debug:
    mkdir -p tmp
    mkdir -p bin
    masm_615/bin/ml /nologo /AT /c /Febin\\CompatOS.img /Fotmp\\boot.obj src\\boot\\boot.asm
    masm_615/bin/link /nologo /TINY /NOD tmp\\boot.obj, bin\\CompatOS.img, NUL, NUL, NUL

Errors:

1>src\boot\boot.asm(14): error A2006: undefined symbol : bootInfo
1>src\boot\boot.asm(14): error A2114: INVOKE argument type mismatch : argument : 1

Solution

  • The errors are because you're using the invoke directive with a forward reference. bootInfo is declared after you use invoke. The issue has been discussed on the MASM32 forum. One way to resolve this is not use invoke and do a call. This would require you having to push the address of bootInfo on the stack yourself like this:

    ;   INVOKE Print, OFFSET bootInfo
        mov ax, OFFSET bootInfo
        push ax                       ; Push register since original 8086
                                      ; didn't have a push IMMediate instruction
        call Print
    

    Another coding option is to move the data to near the beginning of your bootloader and the first instruction of the bootloader jumps over the data to the code. This would put bootInfo before the invoke and you'd no longer have a forward reference. Something like this:

    ;------------------------------
    ; CODE
    ;------------------------------
    .code
    org 07c00h
    
    Print PROTO lpStr:WORD
    
    _start:
        jmp skipdata
        ;------------------------------
        ; DATA
        ;------------------------------
        bootInfo db "Booting CompatOS...", 0
    
    skipdata:
        INVOKE Print, OFFSET bootInfo
        cli
        hlt
    

    Another way to solve this is to use JWASM (an open source MASM compatible assembler). I noticed JWASM has no problem with forward references when using invoke.


    Notes

    • I realize you are somewhat constrained because this is a bootloader and you can't just put bootInfo in a .data section and place it before the .code section, which would have been a solution that would work outside a bootloader. You need a solution that would properly place code before data (or data surrounded by code), followed by the boot signature 0xaa55 at the end of a 512 byte sector.

    • I recommend taking a look at my bootloader tips for common problems when developing a bootloader, including suggestions when testing on real hardware.

    • You may wish to use the .8086 directive rather than .386 if you intended to run on real hardware that may predate the 386. Legacy bootloaders are generally developed with the lowest common denominator in mind - the 8086 processor. If you don't intend to run on ancient hardware then this may not be an issue.