从汇编中的系统调用打印 return 值

Print return value from a syscall in assembly

目标是打印时间和日期,但到目前为止我什至无法打印时间戳(纪元时间)。

详情:

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

版本 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

版本 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

编译链接:

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

问题:

  1. 如何在两种解决方案中获取 return 值?
  2. 有没有办法获取已经格式化的日期和时间?

为此编写了一个 200 多行的汇编程序。它首先打印时间戳,然后打印格式化的日期和时间。它的所有功能都遵循 System-V x64 调用约定。

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 函数打印寄存器 rdi 中的任意数字。如果你想知道我如何打印一个数字你可以看看那个函数。

print_time 是计算和打印日期和时间的地方。

这是输出,以及使用 asctime(gmtime(time_t t))

打印格式化日期和时间的 C 程序的输出
$ ./time && ./ct
1608515228
1:47:8 Mon 12/21/2020
Unix time: 1608515228
C library returns: Mon Dec 21 01:47:08 2020

(最后两行来自C程序)

您也可以在第 34 行中放置任何时间戳来进行测试。

我的解决方案很幼稚:

  • 先算出总天数,可以使用 time/60/60/24 找到(然后您会在这一步得到 hour/min/sec)。
  • 然后算出年份。我通过逐年减去一年中的天数来做到这一点。我设计了一个函数来计算任何一年的天数作为我的辅助函数。
  • 查找年月日。和步骤2差不多,我设计了一个计算任意一年任意月份天数的函数作为我的辅助函数

编辑:

按照几个人的要求,将整个程序粘贴到这个答案中。

为了打印整数部分,我在这里使用了@PeterCordes 的实现: