The other review hit the most important parts, but there are a few more things to consider.
Consider using the standard C interface
If the code is instead written like this:
; IN: rdi points to NUL-terminated string
; OUT: rax contains string length
strlen:
xor rax, rax
dec rax
.top:
inc rax
cmp byte [rdi + rax], 0
jne .top
ret
This would have the advantage of being callable from C.
Use named constants
Instead of having "magic numbers" littering the code, it's better to define named constants. For example the number 1
is used in two different ways; once for the WRITE
syscall, and once for the stdout
file handle. I'd recommend defining and using one named constant for each.
Consider more general usage
As you mention in a comment, the only difference between puts
and fputs
is the file handle. In this case, one could get both puts
and fputs
very cheaply like this:
puts:
mov rdi, 1 ; fd for stdout
fputs:
call strlen
mov rax, 1 ; WRITE syscall
syscall
ret
Note that this uses your existing calling convention rather than the C calling convention.
Consider using macros
You may find it useful to define some macros for common things like this:
%macro SYSTEM 1
mov rax, %1
syscall
%endmacro
WRITE: equ 1
SYSTEM WRITE