64 bit Linux uses the small memory model by default, which puts all code and static data below the 2GB address limit. This makes sure that you can use 32-bit absolute addresses. Older versions of gcc use 32-bit absolute addresses for static arrays in order to save an extra instruction for relative address calculation. However, this no longer works. If I try to make a 32-bit absolute address in assembly, I get the linker error: "relocation R_X86_64_32S against `.data' can not be used when making a shared object; recompile with -fPIC". This error message is misleading, of course, because I am not making a shared object and -fPIC doesn't help. What I have found out so far is this: gcc version 4.8.5 uses 32-bit absolute addresses for static arrays, gcc version 6.3.0 doesn't. version 5 probably doesn't either. The linker in binutils 2.24 allows 32-bit absolute addresses, verson 2.28 does not.
The consequence of this change is that old libraries have to be recompiled and legacy assembly code is broken.
Now I want to ask: When was this change made? Is it documented somewhere? And is there a linker option that makes it accept 32-bit absolute addresses?
Your distro configured gcc with
--enable-default-pie
, so it's making position-independent executables by default, (allowing for ASLR of the executable as well as libraries). Most distros are doing that, these days.You actually are making a shared object: PIE executables are sort of a hack using a shared object with an entry-point. The dynamic linker already supported this, and ASLR is nice for security, so this was the easiest way to implement ASLR for executables.
32-bit absolute relocation aren't allowed in an ELF shared object; that would stop them from being loaded outside the low 2GiB (for sign-extended 32-bit addresses). 64-bit absolute addresses are allowed, but generally you only want that for jump tables or other static data, not as part of instructions.1
The
recompile with -fPIC
part of the error message is bogus for hand-written asm; it's written for the case of people compiling withgcc -c
and then trying to link withgcc -shared -o foo.so *.o
, with a gcc where-fPIE
is not the default. The error message should probably change because many people are running into this error when linking hand-written asm.Use
gcc -fno-pie -no-pie
to override this back to the old behaviour.-no-pie
is the linker option,-fno-pie
is the code-gen option. With only-fno-pie
, gcc will make code likemov eax, offset .LC0
that doesn't link with the still-enabled-pie
.(clang can have PIE enabled by default, too: use
clang -fno-pie -nopie
. A July 2017 patch made-no-pie
an alias for-nopie
, for compat with gcc, but clang4.0.1 doesn't have it.)With only
-no-pie
, (but still-fpie
) compiler-generated code (from C or C++ sources) will be slightly slower and larger than necessary, but will still be linked into a position-dependent executable which won't benefit from ASLR. "Too much PIE is bad for performance" reports an average slowdown of 3% for x86-64 on SPEC CPU2006 (I don't have a copy of the paper so IDK what hardware that was on :/). But in 32-bit code, the average slowdown is 10%, worst-case 25% (on SPEC CPU2006).The penalty for PIE executables is mostly for stuff like indexing static arrays, as Agner describes in the question, where using a static address as a 32-bit immediate or as part of a
[disp32 + index*4]
addressing mode saves instructions and registers vs. a RIP-relative LEA to get an address into a register. Also 5-bytemov r32, imm32
instead of 7-bytelea r64, [rel symbol]
for getting a static address into a register is nice for passing the address of a string literal or other static data to a function.-fPIE
still assumes no symbol-interposition for global variables / functions, unlike-fPIC
for shared libraries which have to go through the GOT to access globals (which is yet another reason to usestatic
for any variables that can be limited to file scope instead of global). See The sorry state of dynamic libraries on Linux.Thus
-fPIE
is much less bad than-fPIC
for 64-bit code, but still bad for 32-bit because RIP-relative addressing isn't available. See some examples on the Godbolt compiler explorer. On average,-fPIE
has a very small performance / code-size downside in 64-bit code. The worst case for a specific loop might only be a few %. But 32-bit PIE can be much worse.None of these
-f
code-gen options make any difference when just linking, or when assembling.S
hand-written asm.gcc -fno-pie -no-pie -O3 main.c nasm_output.o
is a case where you want both options.If your GCC was configured this way,
gcc -v |& grep -o -e '[^ ]*pie'
prints--enable-default-pie
. Support for this config option was added to gcc in early 2015. Ubuntu enabled it in 16.10, and Debian around the same time in gcc6.2.0-7
(leading to kernel build errors: https://lkml.org/lkml/2016/10/21/904).Related: Build compressed x86 kernels as PIE was also affected by the changed default.
Why doesn't Linux randomize the address of the executable code segment? is an older question about why it wasn't the default earlier, or was only enabled for a few packages on older Ubuntu before it was enabled across the board.
Note that
ld
itself didn't change its default. It still works normally (at least on Arch Linux with binutils 2.28). The change is thatgcc
defaults to passing-pie
as a linker option, unless you explicitly use-static
or-no-pie
.In a NASM source file, I used
a32 mov eax, [abs buf]
to get an absolute address. (I was testing if the 6-byte way to encode small absolute addresses (address-size + mov eax,moffs:67 a1 40 f1 60 00
) has an LCP stall on Intel CPUs. It does.)related: building static / dynamic executables with/without libc, defining
_start
ormain
.Checking if an existing executable is PIE or not
file
andreadelf
say that PIEs are "shared objects", not ELF executables. Static executables can't be PIE.This has also been asked at: How to test whether a Linux binary was compiled as position independent code?
Semi-related (but not really): another recent gcc feature is
gcc -fno-plt
. Finally calls into shared libraries can be justcall [rip + symbol@GOTPCREL]
(AT&Tcall *puts@GOTPCREL(%rip)
), with no PLT trampoline.Distros will hopefully start enabling it soon, because it also avoids needing writeable + executable memory pages. It's a significant speedup for programs that make a lot of shared-library calls, e.g. x86-64
clang -O2 -g
compiling tramp3d goes from 41.6s to 36.8s on whatever hardware the patch author tested on. (clang is maybe a worst-case scenario for share library calls.)It does require early binding instead of lazy dynamic linking, so it's slower for big programs that exit right away. (e.g.
clang --version
or compilinghello.c
). This slowdown could be reduced with prelink, apparently.This doesn't remove the GOT overhead for external variables in shared library PIC code, though. (See the godbolt link above).
Footnotes 1
64-bit absolute addresses actually are allowed in Linux ELF shared objects, with text relocations to allow loading at different addresses (ASLR and shared libraries). This allows you to have jump tables in
section .rodata
, orstatic const int *foo = &bar;
without a runtime initializer.So
mov rdi, qword msg
works (NASM/YASM syntax for 10-bytemov r64, imm64
, aka AT&T syntaxmovabs
, the only instruction which can use a 64-bit immediate). But that's larger and usually slower thanlea rdi, [rel msg]
, which is what you should use if you decide not to disable-pie
. A 64-bit immediate is slower to fetch from the uop cache on Sandybridge-family CPUs, according to Agner Fog's microarch pdf. (Yes, the same person who asked this question. :)You can use NASM's
default rel
instead of specifying it in every[rel symbol]
addressing mode. See also Mach-O 64-bit format does not support 32-bit absolute addresses. NASM Accessing Array for some more description of avoiding 32-bit absolute addressing. OS X can't use 32-bit addresses at all, so RIP-relative addressing is the best way there, too.In position-dependent code (
-no-pie
), you should usemov edi, msg
when you want an address in a register; 5-bytemov r32, imm32
is even smaller than RIP-relative LEA, and more execution ports can run it.