csx64 0.1.0

An Intel-style x64 assembler and executor.
Documentation
global atexit, exit, abort

segment text

; int atexit(void(*func)())
; adds func to the stack of functions to call in exit() before termination.
; the same function can be registered more than once.
; calls to atexit() during exit() will always fail.
; returns zero on success.
atexit:
    cmp byte ptr [exit_flag], 0
    jnz .fail ; calling atexit during exit always fails

    mov eax, [atexit_len]
    cmp eax, ATEXIT_CAP
    jae .fail ; check failure case (arr full)

    mov [atexit_arr + 8 * rax], rdi
    inc dword ptr [atexit_len]
    xor eax, eax ; return 0 (success)
    ret

    .fail:
    mov eax, 1
    ret

; [[noreturn]] void exit(int status)
; terminates execution with the specified status (return value).
; before termination, invokes all the functions set by (successful) calls to atexit() in reverse order.
; calling exit() from an atexit hook during exit() triggers an abort().
; it is recommended not to directly call sys_exit, as some of the atexit() cleanup may be important.
exit:
    cmp byte ptr [exit_flag], 0
    jnz abort ; calling exit during exit immediately kills with -1 result
    mov byte ptr [exit_flag], 1 ; set the exit flag

    ; we'll use call-safe registers - no need to preserve them since we won't be returning
    mov r15d, edi          ; status in r15d
    mov r14d, [atexit_len] ; len in r14
    
    jmp .aft
    .loop:
        call [atexit_arr + 8 * r14]
    .aft:
        dec r14
        cmp r14, 0
        jge .loop

    .done:
    mov eax, sys_exit
    mov ebx, r15d
    syscall

; void abort(void);
; immediately terminates execution with error code -1.
; it is recommended to use exit() instead where possible, as abort performs no cleanup.
abort:
    mov eax, sys_exit
    mov ebx, -1
    syscall

segment bss

ATEXIT_CAP: equ 32 ; atexit static capacity - must be at least 32

align 8
atexit_arr: resq ATEXIT_CAP
atexit_len: resd 1
exit_flag: resb 1