On Sat, Dec 26, 2020 at 5:41 PM Rhys Rustad-Elliott <m...@rhysre.net> wrote: > > Hi all, > > I've encountered a strange issue when compiling C to a flat binary with GCC. > It's questionably a bug, but I hesitate to strongly say that due to my lack of > familiarity with the GCC codebase and the rather obscure nature of what I'm > trying to do. Posting this here in the hopes that someone can either a) > confirm > this is in fact a GCC bug (in which case I'll file a bug report) or b) tell me > what I'm doing wrong. > > First off, system information: > > $ head -1 /etc/os-release > PRETTY_NAME="Debian GNU/Linux 10 (buster)" > > $ uname -a > Linux desktop 5.9.0-0.bpo.2-amd64 #1 SMP Debian 5.9.6-1~bpo10+1 (2020-11-19) > x86_64 GNU/Linux > > $ gcc -v > Using built-in specs. > COLLECT_GCC=gcc > COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/8/lto-wrapper > OFFLOAD_TARGET_NAMES=nvptx-none > OFFLOAD_TARGET_DEFAULT=1 > Target: x86_64-linux-gnu > Configured with: ../src/configure -v --with-pkgversion='Debian 8.3.0-6' > --with-bugurl=file:///usr/share/doc/gcc-8/README.Bugs > --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++ --prefix=/usr > --with-gcc-major-version-only --program-suffix=-8 > --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id > --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix > --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu > --enable-libstdcxx-debug --enable-libstdcxx-time=yes > --with-default-libstdcxx-abi=new --enable-gnu-unique-object > --disable-vtable-verify --enable-libmpx --enable-plugin --enable-default-pie > --with-system-zlib --with-target-system-zlib --enable-objc-gc=auto > --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 > --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic > --enable-offload-targets=nvptx-none --without-cuda-driver > --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu > --target=x86_64-linux-gnu > Thread model: posix > gcc version 8.3.0 (Debian 8.3.0-6) > > I'm currently in the process of writing a packer for ELF binaries (similar to > UPX). To do this, I compile my loader code into a position-independent flat > binary with no dependency on glibc using a custom linker script and then > inject > it into an ELF binary, where it as well as the packed binary get loaded on > exec. The loader code is passed initial control, decrypts/decompresses the > packed binary, and hands control to the unpacked binary. > > The problem comes when I try to take the address of a function that resides > outside the current translation unit in the loader code. Instead of &func > evaluating to the address of the function func, it evaluates to the first > sizeof(void *) bytes of the _code_ of the function func. I've created a > minimal > reproduction here: > > $ ls > func.c link.lds Makefile prog.c > > $ cat prog.c > extern void func(); > > void _start() > { > void *ptr = (void *) func; > } > > $ cat func.c > void func() > { > > } > > $ cat link.lds > OUTPUT_FORMAT("binary") > OUTPUT_ARCH(i386:x86-64) > ENTRY(_start) > > SECTIONS > { > . = 0x0000000001000000; > .text : { > *(.text) > } > .data : { > *(.data) > } > } > > rhys@desktop:~/repro$ cat Makefile > CFLAGS = -fpie -nostdlib -nostartfiles -nodefaultlibs -fno-builtin -c -I .. > LDFLAGS = -pie > > OBJS = prog.o func.o > BIN = prog > > all: $(OBJS) > $(LD) $(LDFLAGS) $(OBJS) -T link.lds -o $(BIN) > > %.o: %.c > $(CC) $(CFLAGS) $< -o $@ > > %.o: %.S > $(AS) $< -o $@ > > clean: > rm -f $(OBJS) $(BIN) > > As can be seen, this setup is pretty simple, _start gets control, and does > nothing other than load the address of func (which is located in another > translation unit) into ptr. Disassembling the output binary with radare2, I > see > the following: > > ┌ 18: fcn.00000000 (); > │ ; var int64_t var_8h @ rbp-0x8 > │ 0x00000000 55 push rbp > │ 0x00000001 4889e5 mov rbp, rsp > │ 0x00000004 488b05070000. mov rax, qword [0x00000012] ; > fcn.00000012 > │ ; > [0x12:8]=0xc35d90e5894855 > │ 0x0000000b 488945f8 mov qword [var_8h], rax > │ 0x0000000f 90 nop > │ 0x00000010 5d pop rbp > └ 0x00000011 c3 ret > > ┌ 7: fcn.00000012 (); > │ 0x00000012 55 push rbp > │ 0x00000013 4889e5 mov rbp, rsp > │ 0x00000016 90 nop > │ 0x00000017 5d pop rbp > └ 0x00000018 c3 ret > > Note how fcn.00000000 corresponds to _start and fcn.00000012 corresponds to > func. Now note the instructions at address 0x00000004 and 0x0000000b. They > should be loading the _address_ 0x00000012 into rax and then loading rax into > var_8h on the stack but instead load the eightbyte value _at_ address > 0x00000012 int rax (ie. the first eightbytes of func's code). > > Removing the OUTPUT_FORMAT("binary") line from link.lds so that the linker > instead creates an ELF binary, the problem is not present: > > ┌ 18: entry0 (); > │ ; var int64_t var_8h @ rbp-0x8 > │ 0x01000000 55 push rbp ; [01] > -r-x section size 25 named .text > │ 0x01000001 4889e5 mov rbp, rsp > │ 0x01000004 488d05070000. lea rax, [sym.func] ; > 0x1000012 > │ 0x0100000b 488945f8 mov qword [var_8h], rax > │ 0x0100000f 90 nop > │ 0x01000010 5d pop rbp > └ 0x01000011 c3 ret > ; DATA XREF from entry0 @ 0x1000004 > ┌ 7: sym.func (); > │ 0x01000012 55 push rbp > │ 0x01000013 4889e5 mov rbp, rsp > │ 0x01000016 90 nop > │ 0x01000017 5d pop rbp > └ 0x01000018 c3 ret > > Note how the mov has been replaced with an lea, ensuring that the address and > not the data is loaded. > > In as short of an example as I could come up with, this is the problem I'm > encountering. I did a bit of digging in the source of gnu ld and gcc and made > a bit of headway, but didn't manage to root-cause this. Taking a look at the > relocations in prog.o in this example, we can see that there is a relocation > of type R_X86_64_GOTPCRELX for func. > > $ readelf --relocs prog.o | grep func > 000000000007 000a0000002a R_X86_64_REX_GOTP 0000000000000000 func - 4 > > Looking through the code for GNU ld, it seems this type of relocation, when > occurring in a mov instruction is sometimes changed into an lea (indeed, this > seems to be the reason for this type of relocation's existence: > https://groups.google.com/g/x86-64-abi/c/n9AWHogmVY0), but for whatever > reason, > that's not happening when I link into a flat binary as above. > > If anyone could confirm this is a bug or tell me what I'm doing wrong, that > would be greatly appreciated. Thanks in advance for any help provided!
Two things, this should really be on the binutils mailing list rather than the GCC mailing list. Second you can't generate a flat binary which has a GOT as it requires relocations and there is no way to represent relocations in flat binary. So you need to use non PIE or still use elf format. PIE requires the use of the GOT. Thanks, Andrew Pinski > > Cheers, > Rhys >