-1

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?
HTF
  • 6,632
  • 6
  • 30
  • 49
  • 4
    You need to convert from binary to text. You are not doing what `printf("%ld")` is doing, you are doing `write(&t)`. – Jester Dec 17 '20 at 11:09
  • 1
    I can't close this because there is a bounty but version 1 is pretty much a duplicate of this question which has a very good answer with code (you just need to convert from GAS AT&T syntax to NASM) to write a 64-bit value in decimal: https://stackoverflow.com/questions/45835456/printing-an-integer-as-a-string-with-att-syntax-with-linux-system-calls-instea/45851398#45851398 – Michael Petch Dec 19 '20 at 15:48
  • 1
    @MichaelPetch: I have a NASM version of basically the same answer at [How do I print an integer in Assembly Level Programming without printf from the c library?](https://stackoverflow.com/a/46301894), also x86-64 Linux. (It uses 32-bit operand-size, but that's easier to change than porting AT&T to NASM). – Peter Cordes Dec 19 '20 at 18:33
  • 2
    The main difficulty is converting an integer to a string. Either use an existing routine (e.g. `printf`) to do this part for you or implement a conversion algorithm manually. The latter is already described well in the answer Michael Petch linked, so I'm not sure what more you expect by posting a bounty. – fuz Dec 19 '20 at 19:53

1 Answers1

3

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

RabidBear
  • 182
  • 1
  • 6
  • Your `print_num` has `0` as a special case, but another way of doing the loop can avoid that: if you use a `do{digit = x % 10; x/=10; ... }while(x!=0);` loop structure, you store a single zero, just like for an input of 1..9, because `0 % 10` is 0. My answer on [How do I print an integer in Assembly Level Programming without printf?](//stackoverflow.com/a/46301894) does it that way. Also, storing a length byte for your syscall wrapper to reload seems over-complicated. I like that you store backwards from the end of a short stack buffer; often people over-complicate that with push/pop loops. – Peter Cordes Dec 21 '20 at 03:00
  • 2
    However, off-site links to the code are frowned upon on Stack Overflow. I'd suggest putting at least key parts of it into the question directly. You can keep the link, but the answer still has to work / be somewhat useful if the link dies. Perhaps your text description of an algorithm for formatting times is useful without code. – Peter Cordes Dec 21 '20 at 03:04
  • @PeterCordes Thanks for reviewing the code! Yes do while loop looks good, you're genius. I should have done this LOL. About off-site links: actually I can put everything here, but wouldn't that make this answer super long? – RabidBear Dec 21 '20 at 03:33
  • 1
    You [always want](https://stackoverflow.com/questions/47783926/why-are-loops-always-compiled-into-do-while-style-tail-jump) your loops to be do{}while in asm whenever possible; normally easy for loops that can always run at least 1 iteration. This is one of the somewhat rare cases where a condition at the top is not just less efficient but actually creates problems to work around. And thanks, spotting nice ways to write code is something that comes with optimization experience (including looking at compiler output which is sometimes good), but I wouldn't say no to being called a genius :P – Peter Cordes Dec 21 '20 at 03:47
  • re: length of answers: that's why I suggested pulling out some key parts of the code as examples, while you keep the text explanations of the big picture, and the link. But if you did put in everything in one giant code block, Stack Overflow would give the code block its own scroll bar. It would not be great for readability, though, so at least the print-integer code in its own block because it answers a separate part of the question. (Although that part is a duplicate of existing print-integer Q&As so you could just link other SO answers.) – Peter Cordes Dec 21 '20 at 03:50
  • @PeterCordes Ahh yes, you are right. I edited the answer and added your answer. – RabidBear Dec 21 '20 at 04:19
  • @SepRoland Okay I pasted the code to this answer. But I don't know why you and other people wanted me to do so. Is there some reason behind it? – RabidBear Dec 22 '20 at 03:00
  • 1
    If Github goes down; goes out of business; or you delete the project then the code within the answer is still useful. – Michael Petch Dec 22 '20 at 03:02
  • @MichaelPetch Ahh I see. Thanks. I have never thought about the possibility that Github may go out of business ... then in my future answers I will not provide external links whenever possible. – RabidBear Dec 22 '20 at 03:09