Search code examples
assemblynasmx86-64puts

puts implementation in assembly with nasm x86-64


I'm new to assembly and I'm trying to work on an implementation of the C function puts in assembly with nasm x86-64 on my Ubuntu.

Everything was fine until I write the tests for the function: there is a behavior that I can not reproduce: When I send char *str = strdup(""); to my puts function (alias ft_puts), my function return an error instead of printing a newline like the original puts function.

Here is my puts implementation (ft_puts.s):

section .data
new_line        db      10      ; new line

section     .text
global      ft_puts
extern      strlen

ft_puts:                        ; Alias int puts(const char *s)
    call    strlen
    push    rax                 ; Number of printed chars have to be returned by ft_puts

    cmp     rax, 0
    jg      print_string        ; if length > 0, print string
    cmp     rax, 0
    jz      print_newline       ; else if length == 0 (jle or <= 0, give the same result to my problem), print new line char
    jmp     error               ; else go to error

print_string:
    mov     rsi, rdi            ; string arg for write
    mov     rdi, 1              ; file_descriptor arg for write
    mov     rdx, rax            ; length arg returned by ft_strlen for write
    mov     rax, 1              ; write
    syscall

    test    rax, rax
    jle     error               ; if write failed, go to error
    jmp     print_newline       ; else print new line char

print_newline:
    mov     rsi, new_line       ; new line as string arg for write
    mov     rdx, 1              ; new line string length
    mov     rax, 1              ; write
    syscall

    test    rax, rax
    jle     error               ; if write failed, go to error
    jmp     success             ; else go to success

success:
    pop     rax                 ; Get number of chars printed by print_string
    inc     rax                 ; Add new line printed by print_newline to this number
    jmp     end

error:
    pop     rax
    mov     rax, -1             ; Return EOF (alias -1) on error
    jmp     end

end:
    ret

Sorry if my code looks terrible, I started assembly a week ago. I'm new to Stack Overflow and the help center told me to give a code for testing my code. So, here is a main.c:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int ft_puts(const char *s);

int main(void)
{
    char    *str;
    int     ret[2];

    str = strdup(".");

    write(1, "[", 1);
    ret[0] = puts(str);
    write(1, "]", 1);
    write(1, "\n", 1);
    write(1, "[", 1);
    ret[1] = ft_puts(str);
    write(1, "]\n", 2);

    printf("puts return value : %d | ft_puts return value : %d\n\n", ret[0], ret[1]);
    free(str);


    str = strdup("");

    write(1, "[", 1);
    ret[0] = puts(str);
    write(1, "]", 1);
    write(1, "\n", 1);
    write(1, "[", 1);
    ret[1] = ft_puts(str);
    write(1, "]\n", 2);

    printf("puts return value : %d | ft_puts return value : %d\n", ret[0], ret[1]);
    free(str);

    return (0);
}

And to compile and run this code :)

nasm -f elf64 ft_puts.s -o ft_puts.o
gcc -c main.c
gcc main.o ft_puts.o -o test
./test

The problem seems to be in my print_newline label but I can't find it. If someone can help me ? (It's hard to get some help on assembly language around me irl) (I don't know if should include C tag to my question too haha, so much questions)


Solution

  • Two problems. One, the call strlen is allowed to clobber some registers which include rdi and you need that later. So, surround the call strlen with push rdi and pop rdi to save and restore it.

    Second, you do not initialize rdi in the print_newline block. You need to set it to 1 for stdout just like you did in the print_string block.

    PS: You should learn to use a debugger.