Search code examples
cassemblyx86-64nasmlibc

How can I test my own implementation of GLIBC in NASM x64?


I'm currently developing my NASM x64 version of GLIBC, and I'm having a problem testing it. The ASM code is first compiled in .so, next my test code (in C language) is compiled via GCC, then I load my shared library via LD_PRELOAD and execute my binary.

Here's my ASM implementation of putchar() :

bits 64

STDOUT equ 1

section .text
    global putchar
    putchar:
        push rdi
        mov rax, 1
        mov rdi, STDOUT
        mov rsi, rsp
        mov rdx, 1
        syscall
        pop rdi
        ret

Here's the test main:

#include <stddef.h>

extern size_t putchar(const char str);

int main(void)
{
    putchar('z');
    return (0);
}

And here's 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 -g3
    LD_PRELOAD=./minilibc.so ./test/test

.PHONY: all clean fclean re

The problem is that GDB continues to use the glibc function when I try to set a breakpoint:

(gdb) b putchar
Breakpoint 1 at 0x1030
...
Breakpoint 1, 0x00007ffff7c7bb20 in putchar () from /usr/lib/libc.so.6

I've also tried compiling with -nostlib (in make test) but it doesn't work:

ld: warning: cannot find entry symbol _start; defaulting to 0000000000001020
...
/home/mimahk/Documents/Workspace C/minilibc/test/main.c:7:(.text+0xa): undefined reference to `putchar'

I know that in reality the binary needs a _start label to run but despite several searches I can't code it.

I tested this but it doesn't work:

asm(".global _start; _start: call main; movq %rax, %rdi; movq $60, %rax;");

I get : main.c:9:(.text+0x19): undefined reference to putchar

Can you tell me how I can test my shared library?


Solution

  • Thanks to comments from @PeterCordes and @Jester I was able to correct the problem.

    First, I had to modify the code to precede main() with an ASM label named _start.

    Then I modified a few options in my Makefile to link the .so directly at runtime (and added the basic debug options):

    -ggdb3: Enables in-depth debugging with GDB.

    -fno-builtin: Removes GCC's automatic code replacements (e.g. printf("%s\n", str) by puts(str).

    -nostdlib: Do not include GLIBC.

    -fno-plt: Use GOT directly without PLT.

    -L./ -lminilibc: Instructs the linker to link my executable to the minilibc library (it should therefore be called libminilibc.so).

    -Wl,-rpath=./: Tells the linker where my library is at runtime (because it's not in /usr/lib/) and saves me the trouble of using LD_PRELOAD.

    Here's the complete code:

    main.c

    extern int putchar(const char str);
    
    asm(".global _start; _start: call main; movq %rax, %rdi; movq $60, %rax; syscall"); // Call the main() function and exit with the code corresponding to the return of main().
    
    int main(void)
    {
        putchar('H');
        return (0);
    }
    
    

    Makefile

    CC      =   nasm
    CFLAGS  =   -f elf64
    SRC     =   $(wildcard src/*.asm)
    OBJ     =   $(SRC:.asm=.o)
    NAME    =   libminilibc.so
    
    all: $(NAME)
    
    $(NAME): $(OBJ)
        ld -shared -o $(NAME) $(OBJ)
    
    %.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 -ggdb3 -fno-builtin -nostdlib -fno-plt -L./ -lminilibc -Wl,-rpath=./
    
    .PHONY: all clean fclean re