cassemblyshared-librariesx86-64nasm

Error linking a Linux shared library in NASM - 'readdressing R_X86_64_PC32 to symbol "foo"'


I am currently re-coding some functions from the standard C library in NASM x64. At the moment I only have two functions:

strlen:

bits 64

section .text
    global  strlen
        strlen:
            xor rbx, rbx
            jmp strlen_loop

        strlen_loop:
            cmp byte [rdi + rbx], 0
            je strlen_ret
            inc rbx
            jmp strlen_loop

        strlen_ret:
            mov rax, rbx
            ret

putstr:

bits 64

extern strlen

STDOUT equ 1

section .text
    global putstr
        putstr:
            call strlen
            mov rdx, rax
            xor rax, rax
            mov rdi, STDOUT
            syscall
            ret

Here is the makefile:

CC      =   nasm
CFLAGS  =   -f elf64
SRC     =   $(wildcard src/*.asm)
OBJ     =   $(SRC:.asm=.o)
NAME    =   minilibc.so

all: $(NAME)

$(NAME): $(OBJ)
    ld -fPIC -shared -o $(NAME) $(OBJ) -nostdlib

%.o: %.asm
    $(CC) $(CFLAGS) $< -o $@

clean:
    rm -f $(OBJ)

fclean: clean
    rm -f $(NAME)

re: fclean all

test: re
    gcc -o test/test test/main.c
    LD_PRELOAD=./minilibc.so ./test/test

.PHONY: all clean fclean re

and the test code:

#include <stdio.h>

extern size_t putstr(const char *str);

int main(void)
{
    const char *str = "Hello, World!\n";
    putstr(str);
    return (0);
}

Here's the error I get:

rm -f src/putstr.o src/strlen.o
rm -f minilibc.so
nasm -f elf64 src/putstr.asm -o src/putstr.o
nasm -f elf64 src/strlen.asm -o src/strlen.o
ld -fPIC -shared -o minilibc.so src/putstr.o src/strlen.o -nostdlib
ld: src/putstr.o: attention: readdress on "strlen" in read-only ".text" section
ld: src/putstr.o: readdressing R_X86_64_PC32 to symbol "strlen" cannot be used when creating a shared object; recompile with -fPIC
ld: final link edit failed: wrong value
make: *** [Makefile:10: minilibc.so] Error 1

LD seems to be telling me to recompile in PIC mode, which I'm already doing, so is the problem that my functions have the same name as the GLIBC functions? However, I'm compiling without the standard library.


Solution

  • Use global strlen:function hidden instead of global strlen to make the symbol visible to the linker so other object files can reference it, but not exported from the .so shared object we're making so it doesn't participate in symbol interposition.

    Or to allow efficient calls inside the library but still export the symbol outside, define a second label at the same place, like strlen_internal: strlen: and make the internal one hidden but the other one just normal global strlen:function non-hidden. I don't know of a better way to do this, to get ld to make calls to a visibility=default symbol only go to the definition inside this shared object.


    Your global strlen makes the symbol fully global, participating in symbol interposition. The linker won't let you reference that symbol with a call rel32 since the actual definition that gets used at dynamic link time might be in a different shared library (for example libc.so since you used the name of a standard C function), and it might be loaded more than +- 2GiB away from this shared library.

    In 32-bit code, a rel32 can reach anywhere in virtual address-space, so "re-addressing" (like a text relocation) could handle it with just the warning about needing to rewrite the machine code in a read-only page (in .text).


    In this case you don't want to let your library call a faster version of strlen if the dynamic linker has already seen one in a different library. Your putstr depends on custom behaviour of your strlen that the ABI doesn't require (e.g. not modifying the RDI pointer arg so you have it available after finding the length, without saving/restoring anything on the stack).

    Your strlen is also not ABI-compatible with compiler-generated code; it modifies the caller's RBX, which is a call-preserved register in standard x86-64 calling conventions. But that's a problem even when it's called from putstr since that eventually returns to a C caller. Just use RAX in the first place, instead of a separate register that you later copy to the return value. Or count into RDX since this private version of strlen only exists as a helper for putstr. (Maybe call it a different name, too.)

    Your putstr is also just plain broken, BTW; it doesn't copy RDI to RSI for the void* arg to write, and actually makes a read system call not write. (The system call numbers match the standard file descriptor, e.g. __NR_write = STDOUT_FD = 1 in unistd_64.h for x86-64 Linux, this is a helpful choice for remembering them.) Presumably you'd notice that if you tried this not in a shared library so you could get it working linked into an executable. Or if you put your strlen in the same .asm as putstr and made it not global.


    Related: