0

Firstly, my native system is amd64, Windows, using cygwin, and the GNU toolchain and binutils.

I am writing an x86 bootloader but can't get ld to generate correct relative addresses. I've prepared this minimum reproducible example:

main.s

.code16
.global _start
.section .text
_start:
    call other
    hlt

other.s

.code16
.global other
.section .text
other:
    mov $0xFFFF, %ax
    ret

script.ld

ENTRY(_start);

SECTIONS {
    .text : {
        *(.text);
    }
}

To reproduce, execute:

$ as main.s -o a.o
$ as other.s -o b.o
$ ld -T script.ld *.o -o c.o

Then when you examine c.o using:

$ objdump -sD -m i8086 c.o

c.o:     file format pei-x86-64

Contents of section .text:
 200000000 e80b00f4 90909090 90909090 90909090  ................
 200000010 b8ffffc3 90909090 90909090 90909090  ................

Disassembly of section .text:

00000000 <_start>:
   0:   e8 0b 00                call   e <__major_subsystem_version__+0x9>
   3:   f4                      hlt
   4:   90                      nop
   5:   90                      nop
   6:   90                      nop
   7:   90                      nop
   8:   90                      nop
   9:   90                      nop
   a:   90                      nop
   b:   90                      nop
   c:   90                      nop
   d:   90                      nop
   e:   90                      nop
   f:   90                      nop

00000010 <other>:
  10:   b8 ff ff                mov    $0xffff,%ax
  13:   c3                      ret
  14:   90                      nop
  15:   90                      nop
  16:   90                      nop
  17:   90                      nop
  18:   90                      nop
  19:   90                      nop
  1a:   90                      nop
  1b:   90                      nop
  1c:   90                      nop
  1d:   90                      nop
  1e:   90                      nop
  1f:   90                      nop

Notice how the relative address for the call instruction at address 0 points to 0xE instead of 0x10 where it should.

While the object files are in pe[i]-x86-64 format the instructions are still 16-bit (hence the -m i8086 option for proper disassembly).

The reason why I think the address is wrong is because ld believes the code is 64-bit trusting the file format and resolves wrong addresses. This theory is on thin ice however because the relocation information in a.o says:

$ objdump -sDr -m i8086 a.o

a.o:     file format pe-x86-64

Contents of section .text:
 0000 e80000f4 90909090 90909090 90909090  ................

Disassembly of section .text:

00000000 <_start>:
   0:   e8 00 00                call   3 <_start+0x3>
                        1: R_X86_64_PC16        other
   3:   f4                      hlt
[...]

where the relocation type is R_X86_64_PC16 which truncates the address down to 16-bits as far as I can tell, and should work.

In my actual project I use ld to combine object files just like above then use objcopy to convert it into a flat binary image in order to boot as a floppy disk using an emulator. I do it this way because ld simply cannot convert object files into flat binaries.

I've tried to change the object formats of a.o and b.o before linking but my system does not support anything other than 32 and 64 bit object formats i.e. I can't (or don't think I can) use objcopy to do it.

Lolechi
  • 151
  • 1
  • 3
  • 20
  • I think PC16 is a program-counter relative relocation, which is usual for x86-64 position independent executables. But overall, I wouldn't have much hope for being able to build 16-bit binaries with a 32/64-bit ELF toolchain. I'd switch to something better suited. – Nate Eldredge Apr 26 '20 at 19:16
  • @NateEldredge The best choice of assembler seems to be NASM, but I don't know which linker to use. Any suggestions? – Lolechi Apr 26 '20 at 20:56
  • 1
    I recommend if you use cygwin, use an i686-elf cross compiler. I recall there are issue with the Windows specific toolchain and LD processing 16-bit relocations and attempting to treat them as 32-bit. An i686 cross compiler would include a binutils with a version of AS and LD suitable for OS development. You can find more on building cross compilers here: https://wiki.osdev.org/GCC_Cross-Compiler. This would keep you from using the Windows tool chain for OS development which can be problematic for other reasons. Such a tool chain would include AS, LD, C/C++. – Michael Petch Apr 26 '20 at 21:56
  • @NateEldredge: You're thinking of RIP-relative data addressing modes which are x86-64 specific. Remember that all modes have relative branches, like `call rel16` and `jmp rel8/rel16`. Assembling `.code16` code into a 64-bit `.o` explains the `R_X86_64_PC16` relocation. – Peter Cordes Apr 26 '20 at 22:02
  • Did you try using `as --32` to at least put this 16-bit machine code into 32-bit object files? It's still not 16-bit but it might possibly help. At least 32-bit mode code *could* encode `call rel16` with a prefix, IIRC. – Peter Cordes Apr 26 '20 at 22:05
  • 1
    @PeterCordes : It doesn't matter, the issue applies for 32 bit and 64-bit. I happened to test it here. The problem is the linker. There are other issues that don't make the Windows tool chain inviting for OS development. It is far easier to build a cross compiler toolchain. That also covers them when they move to dealing with their kernel if they happen to develop in C/C++. – Michael Petch Apr 26 '20 at 22:09
  • Which `ld` version do you use? The problem you describe (relative 16-bit relocations are wrong) is a **known** problem in some `ld` versions. – Martin Rosenau Apr 27 '20 at 18:53
  • @MartinRosenau GNU ld (GNU Binutils) 2.34.0.20200307 - this is irrelevant now though, see below for the self-answer. – Lolechi Apr 27 '20 at 20:38

1 Answers1

2

As @NateEldredge and @MichaelPetch have pointed out this is not a problem with any of the tools or the code but with my toolchain. I have since compiled a GCC cross-compiler and binutils for an OS-agnostic generic x86-32 (i686) target platform.

For others who find this answer while searching the net: https://wiki.osdev.org/GCC_Cross-Compiler

Lolechi
  • 151
  • 1
  • 3
  • 20
  • I believe in the Cygwin native toolchain, it is a bug in LD and the linker is treating all 16 bit PC relative relocations as having 4 byte displacement (instead of 2) when computing the distance from the program counter to the target. This would explain why such offsets are always off by 2. The same thing occurs with 32-bit objects as well. This isn't an issue on ELF targets. – Michael Petch Apr 28 '20 at 00:50