Search code examples
assemblyldx86-16bootloadergnu-assembler

relocated truncation to fit: 16 against `.text'


I hope you guys are having a great day. I have a question about compiling assembly into a .bin. I am trying to use This article to fix it, but even then I get `relocated truncation to fit: 16 against '.text'.

bootReal.s:

#generate 16-bit code
.code16

#hint the assembler that here is the executable code located
.text
.globl _start;
#boot code entry
_start:
      jmp _boot                           #jump to boot code
      welcome: .asciz "Hello, World\n\r"  #here we define the string

     .macro mWriteString str              #macro which calls a function to print a string
          leaw  \str, %si
          call .writeStringIn
     .endm

     #function to print the string
     .writeStringIn:
          lodsb
          orb  %al, %al
          jz   .writeStringOut
          movb $0x0e, %ah
          int  $0x10
          jmp  .writeStringIn
     .writeStringOut:
     ret

_boot:
     mWriteString welcome

     #move to 510th byte from the start and append boot signature
     . = _start + 510
     .byte 0x55
     .byte 0xaa

Commands I used:

as bootReal.s -o bootReal.s

ld.exe -o file.tmp bootReal.o <- this one throws the error

Any help would be appreciated!


Solution

  • As @jester suggested you will want to set a Virtual Memory Address(VMA) starting point. The linker you are using on Windows has used an internal linker script that sets a VMA >= 0x10000. So any references to absolute addresses that can't fit in 16-bits will generate a relocation error. You can't fit an address >= 0x10000 into a 16-bit register so the linker aborts with an error similar to:

    (.text+0x1f): relocation truncated to fit: R_386_16 against `.text'

    The error may be slightly different if you are using a 64-bit tool chain but it will be something like R_???????_16 against '.text'

    To fix this you can either create your own linker script or set the base VMA to the appropriate value on the LD command line. I would recommend using -Ttext=0x7c00 and setting DS to 0x0000 in your bootloader.

    I have General Bootloader tips that discuss many of the problems writing a bootloader. You shouldn't assume that when your bootloader is running that the segment registers have any particular value. If you use -Ttext=0x7c00 as a VMA (ORG) then you need to set DS to zero. The segment:offset pair 0x0000:0x7c00 = physical address 0x07c00 (0x0000<<4+0x7c00). 0x07c00 is where the legacy BIOS will load your boot sector into memory.

    If you ever get a relocation error like:

    (.text+0x1f): relocation truncated to fit: R_386_16 against `.text'

    You can always use OBJDUMP to show the relocation entries in the object file. In this case since you are writing 16-bit code you'll want to have OBJDUMP dump the code (-D); decode as 16-bit instructions (-Mi8086) and output the relocation entries (-r). The output of objdump -Mi8086 -Dr bootReal.o would appear similar to this (it may vary based on the linker being used):

    00000000 <_start>:
       0:   eb 1b                   jmp    1d <_boot>
    
    00000002 <welcome>:
       2:   48                      dec    %ax
       3:   65                      gs
       4:   6c                      insb   (%dx),%es:(%di)
       5:   6c                      insb   (%dx),%es:(%di)
       6:   6f                      outsw  %ds:(%si),(%dx)
       7:   2c 20                   sub    $0x20,%al
       9:   57                      push   %di
       a:   6f                      outsw  %ds:(%si),(%dx)
       b:   72 6c                   jb     79 <_boot+0x5c>
       d:   64 0a 0d                or     %fs:(%di),%cl
            ...
    
    00000011 <.writeStringIn>:
      11:   ac                      lods   %ds:(%si),%al
      12:   08 c0                   or     %al,%al
      14:   74 06                   je     1c <.writeStringOut>
      16:   b4 0e                   mov    $0xe,%ah
      18:   cd 10                   int    $0x10
      1a:   eb f5                   jmp    11 <.writeStringIn>
    
    0000001c <.writeStringOut>:
      1c:   c3                      ret
    
    0000001d <_boot>:
      1d:   8d 36 02 00             lea    0x2,%si
                            1f: R_386_16    .text
      21:   e8 ed ff                call   11 <.writeStringIn>
            ...
     1fc:   00 00                   add    %al,(%bx,%si)
     1fe:   55                      push   %bp
     1ff:   aa                      stos   %al,%es:(%di)
    

    In the relocation error .text+0x1f was referenced as the source of the problem. If you look in the OBJDUMP output there is a relocation entry:

      1d:   8d 36 02 00             lea    0x2,%si
                            1f: R_386_16    .text
    

    This relocation is associated with the instruction above it. Which means essentially that the linker tried to generate an offset for the LEA instruction but the value couldn't be represented in a 16-bit value. SI is 16-bit register and can't have a value placed in it >= 0x10000.


    Problems with the Code

    • You want to properly set DS to 0 if using a VMA (ORG) of 0x7c00
    • Ensure string instructions like lodsb move through strings in the forward direction. Use the CLD instruction to clear the Direction Flag (DF=0).
    • It is usually a good idea to set your own stack pointer SS:SP. This is important if you ever read more data off the disk into memory. You don't know where the BIOS set SS:SP and you don't want to clobber it. A convenient place to set the the stack is just below the bootloader at 0x0000:0x7c00.
    • To prevent your bootloader from running semi random code after the last instruction you will want to put the processor in some kind of infinite loop. An easy way would be jmp .
    • LD will generate an executable file in a format that is not a binary file and can't be run as a bootloader. You can have the linker write a binary file with the --oformat=binary option.

    The revised code could look like:

    #generate 16-bit code
    .code16
    
    #hint the assembler that here is the executable code located
    .text
    .globl _start;
    #boot code entry
    _start:
          jmp _boot                           #jump to boot code
          welcome: .asciz "Hello, World\n\r"  #here we define the string
    
         .macro mWriteString str              #macro which calls a function to print a string
              leaw  \str, %si
              call .writeStringIn
         .endm
    
         #function to print the string
         .writeStringIn:
              lodsb
              orb  %al, %al
              jz   .writeStringOut
              movb $0x0e, %ah
              int  $0x10
              jmp  .writeStringIn
         .writeStringOut:
         ret
    
    _boot:
         xor %ax, %ax
         mov %ax, %ds      # Initialize the DS segment to zero
         mov %ax, %ss      # Set the stack pointer below bootloader at 0x0000:0x7c00
         mov $0x7c00, %sp
         cld               # Clear Direction Flag (DF=0) to set forward movement on string
                           # instructions
    
         mWriteString welcome
         jmp .             # Enter an infinite loop to stop executing code beyond this point
    
         #move to 510th byte from the start and append boot signature
         . = _start + 510
         .byte 0x55
         .byte 0xaa
    

    To assemble and run you could use something like:

    as bootReal.s -o bootReal.o
    ld -Ttext=0x7c00 --oformat=binary -o boot.bin bootReal.o
    

    An alternative is to generate an executable with LD and use OBJCOPY to convert the executable to a binary file:

    as bootReal.s -o bootReal.o
    ld -Ttext=0x7c00 -o file.tmp bootReal.o
    objcopy -O binary file.tmp boot.bin
    

    Either way should generate a 512 byte binary file (bootloader) called boot.bin. If you ran it with QEMU with qemu-system-i386 -fda boot.bin the output would look similar to:

    enter image description here