2

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?

Sep Roland
  • 33,889
  • 7
  • 43
  • 76
  • 1
    What `ld` command did you use to link? Did you have `-Ttext=0x7C00` or something that would tell the linker calculate useful absolute addresses to fill in? If no, then it was probably using the default of `0x401000` or something, which of course doesn't fit in 16 bits. Oh, also you were trying to link it as a PIE executable that expects to be relocatable anywhere in 64-bit address-space!! Don't do that; if you're not using `ld` directly, you should be using `gcc -m32 -static -no-pie -nostdlib` like for making a static executable t run under Linux (rather than a flat binary MBR.) – Peter Cordes May 27 '23 at 11:58
  • 1
    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`. – Peter Cordes May 27 '23 at 12:02
  • Oh! I was wondering why the message about PIE came up because quite obviously this is NOT PIC and I had never explicitly requested that. But you're entirely correct, PIE is the default (and has been for ages) in gcc, that messed me up. Your solution works perfectly and you're also right about the literal prefix for the symbol. Thanks a bunch! – performancematters May 27 '23 at 16:06

1 Answers1

2

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.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847