assemblyx86-64nasmaddressing-moderelocation

Why is a RIP-relative LEA instruction producing a PIC-incompatible R_X86_64_32S relocation?


I'm going through the x86-64 tutorial on exercism.org. I'm using NASM on Linux, producing an ELF binary. There is just a bit of C code that invokes my assembly code in a test harness. Their build system specifies -pie in the LDFLAGS and -fPIE in the CFLAGS (among others, but those are the most relevant here I think). Therefore, I need (and would like to understand) a solution that uses PIC, which requires RIP-relative addressing.

I have an index (in rdi) into an array of 8-byte (qword) values called values. I just want to get the address at the offset in order to mov the value it points to into a register. Or I would accept moving the value directly.

I tried this:

lea rbx, [rel values + rdi * 8]

My understanding is that this will look at the address (relative to rip) of values, which is in the data section, then it will add to that the correct offset (rdi * 8) and put that in rbx.

But this produces next error:

/usr/bin/ld: space_age.o: relocation R_X86_64_32S against `.data' can not be used when making a PIE object; recompile with -fPIE

I understand that recompile with -fPIE is the result of the linker thinking that the code was compiled rather than assembled from handwritten assembly. So it seems like NASM is producing a relocation type that is not okay with what it's being linked against, and the linker sees it as not PIE, right?

So I then tried this:

lea rbx, [rdi * 8]    ; first compute the offset: index * sizeof(qword value)
lea r8, [rel values]  ; then find the address of the values
add rbx, r8           ; rbx = offset of the array + address of base of the array

To me, this is the exact same thing as the first instruction. I don't understand how these differ, but something about the fact that the relocation in the first is R_X86_64_32S seems to suggest that the first instruction only has a signed 32 bit offset, and maybe lea r8, [rel values] creates a different relocation type that's 64 bits, but I'm just guessing there.

Is this possible to do in one instruction?


Edit: this is not a duplicate of this question about addressing modes because it did not explain why NASM is accepting my code but then it fails to link. I now understand thanks to the accepted answer that this addressing mode does not exist, but NASM silently ignores the rel part.


Solution

  • There is no such instruction as lea rbx, [rel values + rdi * 8], or in other words lea rbx, [values + rip + rdi * 8]. The rip-relative addressing mode cannot be combined with an index register (with or without scaling). See Referencing the contents of a memory location. (x86 addressing modes)

    Unfortunately, it looks like nasm handles this by just ignoring the rel and assembling lea rbx, [values + rdi * 8], which would require the problematic relocation. The address of values would have to go in the 32-bit displacement field of the instruction's memory operand, but that is impossible since values need not be located in the lowest or highest 2 GB of memory; hence the confusing error message.

    But the correct solution is that you just have to write more instructions. You can get the desired effect in two instructions with

    lea rbx, [rel values]
    lea rbx, [rbx + rdi * 8]
    

    Or if you want to actually do the load:

    lea rbx, [rel values]
    mov rcx, [rbx + rdi * 8]
    

    (The choices of registers are arbitrary.)