Skip to content
Advertisement

GCC promotes weird relocation R_X86_64_32S error but not in manually linking

I’m trying to invoke sys_write via the syscall instruction under Linux environment on an x86_64 machine with inline assembly in C files. To achieve this, I wrote the following code:

[tjm@ArchPad poc]$ cat poc.c 
const char S[] = "abcdn";

int main() {
    __asm __volatile(" 
            movq $1, %%rax; 
            movq $1, %%rdi; 
            movq %0, %%rsi; 
            movq $5, %%rdx; 
            syscall;"
            :
            : "i"(S)
            );
    return 0;
}

While my compiling it with GCC, the following error promoted:

[tjm@ArchPad poc]$ gcc -O0 poc.c -o poc
/usr/bin/ld: /tmp/ccWYxmSb.o: relocation R_X86_64_32S against symbol `S' can not be used when making a PIE object; recompile with -fPIE
collect2: error: ld returned 1 exit status

Then I added -fPIE to the compiler command. Unfortunately, nothing changed:

[tjm@ArchPad poc]$ gcc -fPIE -O0 poc.c -o poc 
/usr/bin/ld: /tmp/ccdK36lx.o: relocation R_X86_64_32S against symbol `S' can not be used when making a PIE object; recompile with -fPIE
collect2: error: ld returned 1 exit status

This is so weird that it made me curious to compile and link it manually:

[tjm@ArchPad poc]$ gcc -fPIE -O0 poc.c -S
[tjm@ArchPad poc]$ as -O0 poc.s -o poc.o
[tjm@ArchPad poc]$ ld -O0 -melf_x86_64 --dynamic-linker /usr/lib/ld-linux-x86-64.so.2 /usr/lib/crt{1,i,n}.o -lc poc.o -o poc

Surprisingly, no error occurred during the attempt above. I then tested the executable’s functionality, which seems working:

[tjm@ArchPad poc]$ ./poc 
abcd

Any ideas of why this happens and how to prevent GCC from promoting the error above?

Advertisement

Answer

The immediate move instruction movq $S, %rsi, despite taking a 64-bit register, takes only a 32-bit signed immediate. When building a position-independent executable which is to run with ASLR, as is the default on most Linux systems, your program is typically not located in the low or high 31 bits of virtual memory, so the address of a global or static object like S won’t fit in a signed 32-bit immediate.

One fix is to use movabs instead, which does take a full 64-bit immediate. Replacing movq %0, %%rsi with movabs %0, %%rsi lets the code build. However, the better approach is to use RIP-relative addressing lea S(%rip), %rsi, which is a shorter instruction and avoids the need for a relocation at load time. This is a little awkward to do inside inline asm, so you can let the compiler load the address into the register for you, with an input operand like "S" (S) (confusingly the constraint for the rsi register is S, which happens to coincide with the name you chose for your array variable).

When you linked manually, you built a non-position-independent executable, which meant that treating the address as a constant worked. You can get the same effect by passing -no-pie to gcc.

There is another serious problem with your code, in that it doesn’t declare the registers it clobbers – not only those which you explicitly mov into, but also rcx and r11 which syscall modifies implicitly. You should take a look at How to invoke a system call via syscall or sysenter in inline assembly? which has a correct example. It would be wise to study those examples carefully – GCC inline asm is powerful, but also very easy to get wrong in subtle ways that the compiler will not help you detect, and which may appear to work in a simple program but fail unpredictably in a more complex one.

User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement