Search code examples
assemblylinkerx86-16bootloadergnu-assembler

GAS x86 16-bit assembler does not resolve addresses


I'm trying to write a small bootloader for x86 using GAS, i.e., 16-bit assembly. This, located at 0x7C00, works perfectly:

 .code16   
 .text

  .globl main
main:
    mov $0xb800, %ax
    mov %ax, %es
    
    mov $0x7c0f, %si
    movb (%si), %al
    mov %al, %es:0
hlt

string:
    .byte 'A'

    .section    .note.GNU-stack,"",@progbits
    .section    .note.gnu.property,"x"

However, as you can see, the address of "string" is hardcoded to be $0x7C0F. As soon as I try to replace that hardcoded line by:

    #mov $0x7c0f, %si
    mov string, %si

compilation fails:

/usr/bin/ld: /tmp/cctVBx2h.o: relocation R_X86_64_16 against `.text' can not be used when making a PIE object; recompile with -fPIE  
/usr/bin/ld: failed to set dynamic section sizes: bad value

How can I make GAS resolve the addresses automatically, as it would also do in 32-bit or 64-bit mode?

Edit
Yikes, I just found this comment in the Linux source code, arch/x86/realmode/rm/realmode.h where they hardcode the longjump required to enter protected mode:

This must be open-coded since gas will choke on using a relocatable symbol for the segment portion.

Does this mean GAS just is unable to do this properly?


Solution

  • GCC's default on most modern distros is to make a PIE executable, and the error message suggests you just used gcc -nostdlib foo.s or something to ask GCC to assemble + link this into a normal 64-bit PIE executable! Which of course won't work; a 16-bit absolute address doesn't have room to be relocated anywhere in 64-bit virtual address-space.

    Normally you'd link with ld with -Ttext=0x7C00 or something to tell it to calculate useful absolute addresses to fill in, ones that will fit in a 16-bit absolute.

    Or if you want to use gcc instead of ld, perhaps gcc -m32 -static -no-pie -nostdlib, the same command you'd use to make a static executable intended to run under Linux. But then you need objcopy or something to get a flat binary MBR out of it.

    (Don't forget the .word 0xaa55 boot signature. There are various Q&As about using the GNU assembler and other tools in the GNU toolchain to make legacy BIOS MBR boot sectors, mostly tagged and mentioning GAS or tagged with it.)


    Also, you want mov $string, %si to use the address as an immediate, not load 2 bytes from it as a [disp16] addressing mode. For the same reason you used $0x7c0f instead of 0x7c0f.