Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I call a global C function pointer in NASM without warnings?

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
like image 692
MyNameIsTrez Avatar asked Sep 01 '25 03:09

MyNameIsTrez


2 Answers

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

  • 32-bit absolute addresses no longer allowed in x86-64 Linux?
  • How to load address of function or label into register - mov reg, imm64 with an absolute address needs a text-relocation in a PIE executable or shared object, hence the warning.
like image 66
Peter Cordes Avatar answered Sep 02 '25 15:09

Peter Cordes


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
like image 39
Joshua Avatar answered Sep 02 '25 15:09

Joshua