A little question about function address with no-pie on RISCV
Xi Ruoyao
xry111@xry111.site
Wed Nov 27 11:21:38 GMT 2024
Fangrui has left Google.
On Wed, 2024-11-27 at 18:00 +0800, ywgrit wrote:
> 1) The program tested on both riscv and x86_64.
>
> // foo.c
>
> void foo() {}
>
>
> // main.c
>
> #include <stdio.h>
> void foo();
>
> int main() {
> foo();
> printf("%p\n", foo);
> return 0;
> }
>
> 2) The commands executed.
>
> gcc -fpic -shared -g -o libfoo.so foo.c
>
> gcc -c main.c -o main.o
>
> gcc main.o -o main -lfoo -L. -no-pie
>
> 3) The result.
>
> // riscv
>
> ./main
>
> 0x120000530
>
> // x86_64
>
> ./main
>
> 0x7ffff3ea05a0
>
> 4) The question
>
> With no-pie, riscv print the address of foo@plt which is in main, x86_64
> print the address of foo which is in libfoo.so.
On x86_64 if you use
gcc main.o -o main -lfoo -L. -no-pie -fno-pie
you get the address of the PLT entry too. -fno-pie controls GCC code
generation, while -no-pie is directly passed to the linker and mostly
ignored by GCC.
Traditionally, in a PDE the relocation for addressing *external* symbols
was resolved at link time. For example, when t.so provides data symbol
dat and function symbol func, and pde.c is:
int printf(const char *, ...);
extern int dat;
extern int func();
int main() {
printf("%p\n", &dat);
printf("%p\n", &func);
}
On x86_64 "gcc pde.c -fno-pie -no-pie t.so -O2" gives;
0000000000401060 <main>:
401060: 48 83 ec 08 sub $0x8,%rsp
401064: be 20 40 40 00 mov $0x404020,%esi
401069: bf 04 20 40 00 mov $0x402004,%edi
40106e: 31 c0 xor %eax,%eax
401070: e8 bb ff ff ff call 401030 <printf@plt>
401075: be 40 10 40 00 mov $0x401040,%esi
40107a: bf 04 20 40 00 mov $0x402004,%edi
40107f: 31 c0 xor %eax,%eax
401081: e8 aa ff ff ff call 401030 <printf@plt>
401086: 31 c0 xor %eax,%eax
401088: 48 83 c4 08 add $0x8,%rsp
40108c: c3 ret
40108d: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1)
401094: 00 00 00
401097: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
40109e: 00 00
So the address of dat is "fixed" at 0x404020, the address of func is
"fixed" at 0x401040, etc. But dat and func are actually in t.so which
is loaded elsewhere, so for the data symbol dat, there's a "copy
relocation" in the PDE:
000000404020 000400000005 R_X86_64_COPY 0000000000404020 dat + 0
which instructs ld.so to copy dat to 0x404020 before running the code in
the PDE.
For the function symbol func, the solution should be obvious: the
"fixed" address 0x401040 is just the address of the PLT entry for func.
This scheme had seemed useful even for PIE so it had been used for PIE
for a while. But it has some drawbacks, read
https://maskray.me/blog/2021-01-09-copy-relocations-canonical-plt-entries-and-protected
for details. So nowadays (with a recent toolchain) this scheme is no
longer used for PIE by default.
When LoongArch supported was added, due to all the drawbacks
"R_LARCH_COPY" was NACK'ed very early, thus on LoongArch this scheme
isn't used at all, even for PDE.
So both x86_64 and RISC-V still use this scheme for PDE but not PIE, the
difference seems for x86_64 it's decided at the compile time (i.e.
depending on the GCC option -fno-pie), and for RISC-V it's decided at
the link time (i.e depending on the ld option -no-pie).
--
Xi Ruoyao <xry111@xry111.site>
School of Aerospace Science and Technology, Xidian University
More information about the Binutils
mailing list