简体   繁体   中英

Incorrect relative address using GNU LD w/ 16-bit x86

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 ao 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 ao and bo before linking but my system does not support anything other than 32 and 64 bit object formats ie I can't (or don't think I can) use objcopy to do it.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM