0

Consider this program on godbolt:

#include <cassert>
#include <cstdint>

int64_t const x[] = { 42, 2, 3, 4 };

int64_t f() {
  int64_t i;
  asm volatile (
    "xor %[i], %[i]\n\t"
    "movq _ZL1x(,%[i],8), %[i]"
  : [i]"=r"(i));
  return i;
}

int64_t g() {
  int64_t i;
  asm volatile (
    "xor %[i], %[i]"
  : [i]"=r"(i));
  i = x[i];
  return i;
}

int main() {
  assert(f() == 42);
}

It compiles, links and runs fine for gcc 13.1 but clang 16.0 gives a linker error:

[...]: relocation R_X86_64_32S against `.rodata.cst32' can not be used when making a PIE object; recompile with -fPIE

If I try the same code locally and compile it with g++ -O3 main.cpp (same gcc version) I get the same error above. (Recompiling with -fPIE doesn't fix.)

It's worth noticing that the code generated by gcc for f() and g() are identical:

_Z1fv:
  xor %rax, %rax
  movq _ZL1x(,%rax,8), %rax
  ret
_Z1gv:
  xor %rax, %rax
  movq _ZL1x(,%rax,8), %rax
  ret

and if I remove f from the source file and use g in main, then all compilers are happy, locally and on godbolt.

I understand that directly writing _ZL1x in my inline assembly is weird and very likely to be a bad practice but that's not the point here. I would like to know why gcc is happy on godbolt and not locally and, more importantly, how could I make it to work locally and (if possible) for clang as well.

Cassio Neri
  • 19,583
  • 7
  • 46
  • 68
  • 1
    Add `-no-pie`. Presumably godbolt does not default to PIE. – Jester Jun 17 '23 at 19:10
  • If you change the godbolt compiler to `x86-64 clang 16.0.0` you get the same error. By turning off the setting for `Execute the code`, you can see the generated asm. No doubt the problem stems from `leaq _ZL1x(%rip), %rcx`. – David Wohlferd Jun 17 '23 at 19:22
  • @DavidWohlferd: Godbolt's install of clang 16 is configured with `-fPIE -pie` as the default, for some reason, unlike all its other x86 compilers. But that is how Linux distros have configured gcc/clang for several years now: See [32-bit absolute addresses no longer allowed in x86-64 Linux?](https://stackoverflow.com/q/43367427) – Peter Cordes Jun 17 '23 at 19:49
  • If you locally did `gcc -O3 -S`, you could see the PIE-compatible asm it made for your `g()` function, using a RIP-relative LEA first. Also, you could use `extern "C"` to have it not name-mangle your `const int64_t x[]` array, so the asm symbol name would just be `x`. – Peter Cordes Jun 17 '23 at 19:55
  • @PeterCordes Thanks for the link. It's very useful. – Cassio Neri Jun 18 '23 at 21:57
  • I'm considering reopening this to answer about how GCC/Clang are configured on the Godbolt Compiler Explorer vs. in typical distros (`-fPIE -pie -fstack-protector-strong` on by default, along with perhaps `-fno-plt`.) As someone who just ran into this issue, would the duplicate alone have answered it, without the comments here? – Peter Cordes Jun 19 '23 at 05:14
  • @PeterCordes I think this would be useful. I only felt that my question was answered by the collection of comments and the answers to other posts. I'll also be happy to accept your answer. – Cassio Neri Jun 19 '23 at 12:41

1 Answers1

1

Most Linux distros configure GCC and Clang with -fPIE -pie -fstack-protector-strong on by default, and perhaps -fno-plt. So they're making Position-Independent Executables that have to be relocatable to anywhere in 64-bit address-space.

Matt Godbolt's Compiler Explorer has compilers installed with their vanilla configuration, with none of those options on by default, so they're making traditional executables that get linked to a specific address in the low 31 bits of virtual address space. (Except x86-64 Clang 16.0 which has at least -fPIE enabled. This is purely up to the sysadmins of https://godbolt.org/ using the default config options when building compilers from source.)


Any addressing mode other than symbol(%rip) uses at most a 32-bit sign-extended displacement1, so an absolute address has to fit into that. That only works in a Linux non-PIE executable, and I think a Windows non-LargeAddressAware executable or dll. Not in MachO64 no matter how you link it.

If you locally did gcc -O3 -S, you could see the PIE-compatible asm it made for your g() function, using a RIP-relative LEA first.

See


(Recompiling with -fPIE doesn't fix.)

That error message is assuming the machine code was generated by a compiler from a .c, not just assembled from hand-written asm. With hand-written asm, there is no compilation step, only assembling. Or, you the human are the "compiler", so you need to position-independent asm to implement the algorithm that exists in your brain. (64-bit absolute addresses are also allowed, with the dynamic linker applying a text-relocation fixup on load, but RIP-relative addressing is efficient.)


Footnote 1: Except the special 64-bit absolute addressing mode for load/store of the accumulator, like movabs foo, %eax, which is less compact than RIP-relative so you don't want it if you're building normal code where static data is within +-2GiB of code. There's a reason compilers don't use it.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • Regarding the name mangling, to my utmost surprise, your suggestion does't work. The rules on whether to mangle or not are different for variables and functions. For variables, `const`, `static`, `extern` seem more relevant than `extern "C"`. See [this](https://godbolt.org/z/YxzKGGcrb). – Cassio Neri Jun 19 '23 at 16:29
  • 1
    @CassioNeri: Oh, it's the `extern`, not the `"C"`, that disabled name-mangling when I tested with `extern "C" int const x[] = {...}`. https://godbolt.org/z/qaM6njsYz . I knew global variable names weren't normally mangled in the C++ ABI used on Linux, but didn't realize that didn't apply to non-const. I'll just remove that from the answer, then. – Peter Cordes Jun 19 '23 at 17:37