A little question about function address with no-pie on RISCV

ywgrit wangxin03@loongson.cn
Thu Nov 28 07:15:02 GMT 2024


在 2024/11/28 下午3:00, Xi Ruoyao 写道:
> On Thu, 2024-11-28 at 14:38 +0800, ywgrit wrote:
>> 在 2024/11/28 上午11:13, Xi Ruoyao 写道:
>>> On Thu, 2024-11-28 at 10:18 +0800, ywgrit wrote:
>>>> Thank you very much.
>>>>
>>>> 在 2024/11/27 下午7:21, Xi Ruoyao 写道:
>>>>> 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.
>>>> Is it reasonable to access the address of a function and get the address
>>>> of the plt of the function?
>>> As long as the result is same when the function pointer is same when you
>>> take it from the shared library and the PDE, it's fine.  C only requires
>>> a consistent result of function pointer [in]equality test (== and !=),
>>> but the result from casting the pointer to an integer (like what printf
>>> does for %p) or other comparison (<. <=, >, >=) are all implementation-
>>> defined.
>> If the correctness requirement means that the function pointers obtained
>> from the shared library and pde should be same, then
>>
>> x86_64, riscv64, and loongarch64 all satisfy this requirement and the
>> function pointers obtained all point to plt entry.
> It's all the correctness requirement for C.  For other languages the
> situation may be different, and the compiler must generate code
> satisfying the language spec.  If manually writing assembly the
> programmer has to do all the things correct.

Thanks, I understand now.

> Also I don't know if we are encountering the same bug despite we've both
> found some bug in this area...
>
The second half of my last email describes in detail the bug I 
encountered, and I'm actually not sure if we ran into the same bug.




More information about the Binutils mailing list