how to correctly store the address of a procedure in a register x86_64 nasm?

2

I want to store the address of a procedure in a register as follows :

extern  _printf

section .text

global _foo

_foo :
    mov     rax, [rel _printf]
    call    rax
    ret

but i get this error when compiling with a main written in C :

ld: warning: PIE disabled. Absolute addressing (perhaps -mdynamic-no-pic) not allowed in code signed PIE, but used in _foo from procedure.o. To fix this warning, don't compile with -mdynamic-no-pic or link with -Wl,-no_pie
final section layout:
    __TEXT/__text addr=0x100000E70, size=0x000000AD, fileOffset=0x00000E70, type=1
    __TEXT/__stubs addr=0x100000F1E, size=0x00000018, fileOffset=0x00000F1E, type=28
    __TEXT/__stub_helper addr=0x100000F38, size=0x00000038, fileOffset=0x00000F38, type=32
    __TEXT/__cstring addr=0x100000F70, size=0x00000036, fileOffset=0x00000F70, type=13
    __TEXT/__unwind_info addr=0x100000FA8, size=0x00000050, fileOffset=0x00000FA8, type=22
    __DATA/__nl_symbol_ptr addr=0x100001000, size=0x00000008, fileOffset=0x00001000, type=29
    __DATA/__got addr=0x100001008, size=0x00000010, fileOffset=0x00001008, type=29
    __DATA/__la_symbol_ptr addr=0x100001018, size=0x00000020, fileOffset=0x00001018, type=27
ld: 32-bit RIP relative reference out of range (-4294971162 max is +/-2GB): from _foo (0x100000F13) to _printf@0x00000000 (0x00000000) in '_foo' from procedure.o for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

i tried compiling with -Wl,-no_pie and i got this :

ld: illegal text-relocation to '_printf' in /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib/libSystem.tbd from '_foo' in procedure.o for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

so how could i resolve this problem ??

nasm
linker-errors

1 Answer

1

Two possible ways to do this: static linking and dynamic linking. I'll show both. I suspect (based on your code) you are statically linking.

The first two sections are for Linux. The last section is for Macs.

Static Linking

The following works the way you want, I think. I don't use rel because I don't need to; since printf is statically linked, I can just get its address directly.

The push rbp (8 bytes) is necessary; it makes sure the stack is 16-byte aligned after the return address (8 more bytes) is pushed by the call to printf. Calling functions that may use the xmm registers requires that you have the stack aligned on a 16-byte boundary, or you can get a segfault, and printf uses those registers.

Also, printf expects the number of fp arguments to be in rax, so I set it to zero and use rdx instead to hold the address.

; hello-static.asm
; nasm -felf64 hello-static.asm && gcc -static -o hello-static hello-static.o

        extern  printf

        section .text

        global main

main:   push    rbp
        mov     rdx, printf
        mov     rdi, fmt
        xor     eax, eax
        call    rdx
        pop     rbp
        ret

fmt:    db "Hello", 10, 0

Dynamic Linking

The traditional way to do this in a dynamically linked executable is via the PLT. The following does it that way.

; hello.asm
; nasm -felf64 hello.asm && gcc -o hello hello.o

        extern  printf

        section .text

        global main

main:   push    rbp
        lea     rdx, [rel printf wrt ..plt]
        mov     rdi, fmt
        xor     eax, eax
        call    rdx
        pop     rbp
        ret

fmt:    db "Hello", 10, 0

A few words of explanation. First, pushing rbp makes sure the stack is aligned on a 16-byte boundary by the call to printf. Second, I used rel to get the address of the jump in the PLT with respect to the value of rip. Third, I used rdx instead of rax; the latter register is used by printf for the number of floating point arguments.

The address in rdx will not contain the actual address of printf. For that you would need to go to the GOT and fetch it from there.

The following code fetches the address from the GOT.

; hello-got.asm
; nasm -felf64 hello-got.asm && gcc -o hello-got hello-got.o

        extern  printf
        extern  _GLOBAL_OFFSET_TABLE_

        section .text

        global main

main:   push    rbx
        lea     rbx, [rel _GLOBAL_OFFSET_TABLE_]
        lea     rdx, [printf wrt ..got]
        add     rdx, rbx
        mov     rdx, [rdx]
        mov     rdi, fmt
        xor     eax, eax
        call    rdx
        pop     rbx
        ret

fmt:    db "Hello", 10, 0

Here I first get the address of the GOT using rip-relative addressing. Then I get the offset into the GOT of the printf function's vector. Then I add the offset to the base address of the GOT to get the absolute address of the printf vector. Finally, I move the vector into rdx so I can call it later.

I don't push rbp because I don't need to save the stack frame here; I have to preserve rbx (it is traditionally used to hold the base address of the GOT) because it's a callee-saved register, and that takes care of stack alignment.

Mac (macho64)

The procedure for the Mac is nearly the same: look up the symbol in the global offset table to find the vector, and then de-reference it to obtain the actual address of the procedure. Use wrt ..gotpcrel instead of finding the GOT and then using wrt ..got.

; hello-mac.asm
; nasm -fmacho64 hello-mac.asm && ld -o hello-mac hello-mac.o -lc

        extern _printf
        global _main

        section .text

_main:  push    rbp
        mov     rbp, rsp
        mov     rdi, msg
        mov     rsi, 17
        lea     rsi, [rel _printf wrt ..gotpcrel]
        xor     eax, eax
        call    [rsi]
        xor     eax, eax
        leave
        ret

        section .data

msg:    db "printf: 0x%lx", 10, 0
answered on Stack Overflow Feb 24, 2021 by Stacy J • edited Feb 25, 2021 by Stacy J

User contributions licensed under CC BY-SA 3.0