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!
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.
lodsb
move through strings in the forward direction. Use the CLD
instruction to clear the Direction Flag (DF=0).jmp .
--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: