Given main.c:
#include <stdio.h>
void (*fn_ptr)(void);
void foo(void);
void bar(void) {
printf("bar\n");
}
int main(void) {
fn_ptr = bar;
foo();
}
and foo.s:
extern fn_ptr
global foo
foo:
mov rax, fn_ptr
call [rax]
ret
we can run and compile it like so:
clear &&
nasm foo.s -f elf64 -O0 &&
clang main.c foo.o -z noexecstack &&
./a.out
which successfully prints bar, but also prints a warning:
/usr/bin/ld: foo.o: warning: relocation against `fn_ptr' in read-only section `.text'
/usr/bin/ld: warning: creating DT_TEXTREL in a PIE
bar
I couldn't find any online examples of calling global function pointers in NASM, and changing it to mov rax, fn_ptr wrt ..plt or call [fn_ptr wrt ..plt] prints this error:
foo.s:7: error: ELF format cannot produce non-PC-relative PLT references
(For anyone wondering what warning the -z noexecstack gets rid of):
/usr/bin/ld: warning: foo.o: missing .note.GNU-stack section implies executable stack
/usr/bin/ld: NOTE: This behaviour is deprecated and will be removed in a future version of the linker
Same as any other case of accessing static data from things you're linking into the same ELF object (PIE executable): directly with a RIP-relative addressing mode.
You don't need and don't want to go through the PLT unless you want to do symbol-interposition tricks with LD_PRELOAD with a library that defines a different global variable. But I'm not sure that can do anything for an executable (not another shared library), and since this is a variable not a function, at most it could change the initial value to be something other than 0.
extern fn_ptr
default rel ;;;; Added this
global foo
foo:
call [fn_ptr] ;;;; equivalent to call qword [rel fn_ptr]
ret
I could also have done mov rbx, [fn_ptr] / call rbx / call rbx if I wanted to make multiple calls with the same function pointer without reloading the function pointer every time, also saving code-size. Or lea rbx, [fn_ptr] / call [rbx] / call [rbx] if I wanted to just save a bit of code-size but still reload the function pointer for every call.
This is a variable so it doesn't have a PLT entry; those are for functions. It will have a GOT entry if one is required, like if you were making a shared library and you want fn_ptr to be ELF visibility global instead of hidden. (global is the default unless you use compiler options to change that.)
mov rax, [rel fn_ptr wrt ..got] ; load a pointer to the variable from the GOT
call [rax] ; memory-indirect call, loading a new RIP from memory
This is similar to how you use GOT entries to call library functions the way gcc/clang -fno-plt does: Can't call C standard library function on 64-bit Linux from assembly (yasm) code
If you don't need code outside your shared library to be able to read and modify this function pointer, make it __attribute__((hidden)) so you can access it directly with [rel fn_ptr], without the unnecessary level of indirection.
As Joshua's answer shows, you actually can use wrt ..plt, which makes an R_X86_64_PLT32 relocation instead of R_X86_64_PC32, but it does still resolve to addressing the global variable directly, not a GOT or PLT entry for it. I don't know what PLT32 relocations are for. There isn't a different machine instruction, it's still call qword [rel32] with a RIP-relative addressing mode, just I guess a different way of getting the linker to calculate the right offset from the end of this instruction to the global variable. Maybe the distinction mattered for 32-bit code which didn't have RIP-relative addressing so would want to manually add an offset from the PLT instead of from itself.
0: ff 15 00 00 00 00 call QWORD PTR [rip+0x0] # 6 <foo+0x6> 2: R_X86_64_PC32 fn_ptr-0x4
6: ff 15 00 00 00 00 call QWORD PTR [rip+0x0] # c <foo+0xc> 8: R_X86_64_PLT32 fn_ptr-0x4
is disassembly from
call [fn_ptr] ; (with default rel so this is [rel fn_ptr]
call [rel fn_ptr wrt ..plt]
Without default rel and/or a [rel in the addressing mode, see
mov reg, imm64 with an absolute address needs a text-relocation in a PIE executable or shared object, hence the warning.From comments; I can now type answer.
The following assembly instructions don't work:
mov rax, fn_ptr wrt ..plt
call fn_ptr wrt ..plt
call [fn_ptr wrt ..plt]
But these do:
mov rax, [rel fn_ptr wrt ..plt]
call [rel fn_ptr wrt ..plt]
The ELF format error is true. I'm surprised it can't handle mov rax, fn_ptr wrt ..plt but call [fn_ptr wrt ..plt] definitely cannot work. There's no such instruction as call [qword] but only call rel dword and call [rel dword].
The mov instruction that surprises me that it doesn't work wouldn't get you out of the problem anyway; that's asking for an absolute fixup in the text segment to refer to the plt by absolute address. So if it did work that's still a warning.
What you want to access functions elsewhere is always something with rel so you get a PIC-relative reference to the fixup area. Basic forms:
lea rax, [rel fn_ptr wrt ..plt] ; gets the absolute address of the address of fn_ptr
mov rax, [rel fn_ptr wrt ..plt] ; gets the absolute address of fn_ptr
call [rel fn_ptr wrt ..plt] ; calls fn_ptr
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With