Search code examples
assemblyx86-64nasm

Print return value from a syscall in assembly


The goal is to print a time and date but so far I'm not able to even print a timestamp (epoch time).

Details:

System: Linux
Assembler: NASM (Intel syntax)
Arch: x86_64

Version 1:

time_t t = time(NULL);
printf("%ld\n");

time.nasm:

global _start

section .text

_start:
    mov rax, 201    ; sys_time
    xor rdi, rdi
    syscall

    mov rsi, rax    ; store return value in rsi (arg2) for sys_write
    mov rax, 1      ; sys_write
    mov rdi, 1
    mov rdx, 64     ; How to get the proper size here?
    syscall
    
    jmp exit

exit:
    mov rax, 60
    xor rdi, rdi
    syscall

Version 2:

time_t t;
time(&t);
printf("%ld\n");

time.nasm:

global _start

section .text

_start:
    mov rax, 201     ; sys_time
    mov rdi, time
    syscall

    mov rax, 1
    mov rdi, 1
    mov rsi, time    
    mov rdx, 64     ; How to get the proper size here?
    syscall
    
    jmp exit

exit:
    mov rax, 60
    xor rdi, rdi
    syscall


section .data
    time: dq 0x0

Compilation and linking:

nasm -g -f elf64 time.nasm -o time.o && ld time.o -o time && ./time

Question:

  1. How do I get the return value in both solutions?
  2. Is there a way to get a date and time already formatted?

Solution

  • Wrote a 200+ line assembly program to do this. It prints the time stamp first and then prints the formatted date and time. All its functions follow System-V x64 calling convention.

    global _start
    
    section .rodata
    
    strings:            ; '\n', ' ', '/', ':'
        db 0x1, 0xa, 0x1, 0x20
        db 0x1, 0x2f, 0x1, 0x3a
    weekdays:           ; weekday strings
        db 0x3, 0x54, 0x68, 0x75
        db 0x3, 0x46, 0x72, 0x69
        db 0x3, 0x53, 0x61, 0x74
        db 0x3, 0x53, 0x75, 0x6e
        db 0x3, 0x4d, 0x6f, 0x6e
        db 0x3, 0x54, 0x75, 0x65
        db 0x3, 0x57, 0x65, 0x64
    months:             ; length of months
        db 0x1f, 0x1c, 0x1f, 0x1e
        db 0x1f, 0x1e, 0x1f, 0x1f
        db 0x1e, 0x1f, 0x1e, 0x1f
    
    section .text
    
    _start:
        push rbx        ; align stack
    
        mov rax, 201    ; sys_time
        xor rdi, rdi
        syscall
    
        mov rbx, rax
    
        ; you may uncomment the following line and put an arbitary timestamp to test it out
        ; mov rbx, 0
    
        mov rdi, rbx
        call print_num  ; print unix timestamp
    
        mov rdi, strings
        call sys_print  ; new line
    
        mov rdi, rbx
        call print_time ; print formatted date
    
        pop rbx         ; since we are exiting, we don't need this pop actually
    
        mov rax, 60     ; sys_exit
        xor rdi, rdi
        syscall
    
    leap_year:          ; rsi + (year in rdi is leap)
        mov rax, rdi
        mov rcx, 4
        xor rdx, rdx
        div rcx
        test rdx, rdx   ; return 0 if year % 4
        jnz func_leap_year_ret_0
        mov rax, rdi
        mov rcx, 100
        xor rdx, rdx
        div rcx
        test rdx, rdx   ; return 1 if year % 100
        jnz func_leap_year_ret_1
        mov rax, rdi
        mov rcx, 400
        xor rdx, rdx
        div rcx
        test rdx, rdx   ; return 0 if year % 400
        jnz func_leap_year_ret_0
    func_leap_year_ret_1:
        lea rax, [rsi + 1]
        ret
    func_leap_year_ret_0:
        mov rax, rsi
        ret
    
    year_length:        ; length of year in rdi
        mov rsi, 365
        jmp leap_year
    
    month_length:       ; length of month (year in rdi, month in rsi)
        push r15
        push r14
        push r13
    
        mov r14, rsi    ; back up month in r14, will be used as index
        cmp rsi, 1
        setz r15b
        movzx r13, r15b
        xor rsi, rsi
        call leap_year
        and r13, rax
        movzx rax, byte [r14 + months]
        add rax, r13
    
        pop r13
        pop r14
        pop r15
        ret
    
    print_time:         ; print time_t in rdi
        push r15
        push r14
        push r13
        push r12
        mov r14, 1970   ; 1970-01-01T00:00:00Z
        xor r15, r15
    
        mov rcx, 60
        mov rax, rdi
        xor rdx, rdx
        div rcx
        push rdx        ; push #5
        xor rdx, rdx
        div rcx
        push rdx        ; push #6
        mov rcx, 24
        xor rdx, rdx
        div rcx
        push rdx        ; push #7, the last one
        mov r12, rax
        mov r13, rax
    func_print_time_loop_1_start:
        mov rdi, r14
        call year_length
        cmp r13, rax
        jb func_print_time_loop_2_start
        sub r13, rax
        inc r14
        jmp func_print_time_loop_1_start
    func_print_time_loop_2_start:
        mov rdi, r14
        mov rsi, r15
        call month_length
        cmp r13, rax
        jb func_print_time_loop_end
        sub r13, rax
        inc r15
        jmp func_print_time_loop_2_start
    func_print_time_loop_end:
        ; print time
        mov rdi, [rsp]
        call print_num
        mov rdi, strings + 6
        call sys_print
        mov rdi, [rsp + 8]
        call print_num
        mov rdi, strings + 6
        call sys_print
        mov rdi, [rsp + 16]
        call print_num
    
        ; print " "
        mov rdi, strings + 2
        call sys_print
    
        ; print weekday
        mov rax, r12
        mov rcx, 7
        xor rdx, rdx
        div rcx
        lea rdi, [rdx * 4 + weekdays]
        call sys_print
    
        ; print " "
        mov rdi, strings + 2
        call sys_print
    
        ; print date
        mov rdi, r15
        inc rdi
        call print_num
        mov rdi, strings + 4
        call sys_print
        mov rdi, r13
        inc rdi
        call print_num
        mov rdi, strings + 4
        call sys_print
        mov rdi, r14
        call print_num
    
        ; print new line
        mov rdi, strings
        call sys_print
    
        add rsp, 24
        pop r12
        pop r13
        pop r14
        pop r15
        ret
    
    print_num:          ; print number in rdi
        mov r8, rsp
        sub rsp, 24     ; 21 bytes for local storage, with extra 3 bytes to keep stack aligned
        xor r9, r9
        mov rax, rdi
        mov rcx, 10
    func_print_num_loop_start:
        dec r8
        xor rdx, rdx
        div rcx
        add dl, 48
        mov [r8], dl
        inc r9b
        test rax, rax
        jnz func_print_num_loop_start
    func_print_num_loop_end:
        dec r8
        mov [r8], r9b
        mov rdi, r8
        call sys_print
        add rsp, 24     ; deallocate local storage, restore rsp
        ret
    
    sys_print:          ; print a string pointed by rdi
        movzx rdx, byte [rdi]
        lea rsi, [rdi + 1]
        mov rdi, 1      ; stdout
        mov rax, 1      ; write
        syscall
        ret
    

    print_num function prints any number in register rdi. If you want to know how I print a number you can look at that function.

    print_time is the where the date and time are calculated and printed.

    Here is the output, along with output from a C program that prints formatted date & time using asctime(gmtime(time_t t))

    $ ./time && ./ct
    1608515228
    1:47:8 Mon 12/21/2020
    Unix time: 1608515228
    C library returns: Mon Dec 21 01:47:08 2020
    

    (The last two lines are from the C program)

    You can also put any timestamp in line 34 to test it out.

    My solution is very naive:

    • Figure out the total days first, it can be found using time/60/60/24 (and you get your hour/min/sec in this step).
    • Then figure out the year. I did this by subtracting number of days in a year, year by year. I designed a function to figure out the number of days in any year as my helper function.
    • Find month of year and day of month. It's almost the same with step 2. I designed a function to figure out the number of days in any month of any year as my helper function.

    Edit:

    Pasted the whole program to this answer, as several people asked.

    For printing integer part, I used implementation from @PeterCordes here:

    https://stackoverflow.com/a/46301894