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 mov
ing 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.
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.)