/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ │vi: set et ft=asm ts=8 sw=8 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ │ Copyright 2020 Justine Alexandra Roberts Tunney │ │ │ │ This program is free software; you can redistribute it and/or modify │ │ it under the terms of the GNU General Public License as published by │ │ the Free Software Foundation; version 2 of the License. │ │ │ │ This program is distributed in the hope that it will be useful, but │ │ WITHOUT ANY WARRANTY; without even the implied warranty of │ │ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ │ General Public License for more details. │ │ │ │ You should have received a copy of the GNU General Public License │ │ along with this program; if not, write to the Free Software │ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "ape/relocations.h" #include "libc/macros.h" .section .text.exit,"ax",@progbits .source __FILE__ / Delegates to __cxa_atexit(). / / @param rdi callback typed void(*)(void) (nullable) / @return 0 on success or nonzero if out of space atexit: xor %esi,%esi xor %edx,%edx / 𝑠𝑙𝑖𝑑𝑒 / Registers destructor to be called upon exit(). / / Destructors are called in reverse order. They won't be called / if the program aborts or _exit() is called. Invocations of this / function are usually generated by the C++ compiler. / / @param rdi callback typed void(*)(T) (nullable) / @param rsi callback arg typed T (nullable) / @param rdx dso handle (nullable) / @return 0 on success or nonzero if out of space / @note folks have forked libc in past just to unbloat atexit() / @see news.ycombinator.com/item?id=20228082 / @preinitsafe __cxa_atexit: push %rbp mov %rsp,%rbp .profilable push %r15 ezlea g_cxa,cx mov %rcx,%r15 mov (%r15),%rax # get state->block mov 8(%r15),%rcx # get state->offset test %rcx,%rcx jz 2f 0: sub $24,%rcx mov %rdx,(%rax,%rcx) # set cb->dso mov %rsi,8(%rax,%rcx) # set cb->arg mov %rdi,16(%rax,%rcx) # set cb->fn mov %rcx,8(%r15) # set state->offset xor %eax,%eax # success 1: pop %r15 pop %rbp ret 2: .weak calloc ezlea calloc,cx test %rcx,%rcx jz 1b # fail (no malloc) push %rax push %rdi push %rsi pushpop ATEXIT_MAX+1,%rdi pushpop 16,%rsi call *%rcx pop %rsi pop %rdi pop %rcx # rax=new rcx=old test %rax,%rax jz 1b # fail (no memory) mov $ATEXIT_MAX*8*3,%r8 mov %rax,(%r15) # set state->block mov %rcx,(%rax,%r8) # set block->next mov %r8,%rcx jmp 0b .endfn __cxa_atexit,globl .endfn atexit,globl / Triggers destructors. / / This implementation supports DSO handles, but is optimized for / cases when it's called only once by exit(). / / @param rdi is dso predicate or null to destroy all / @see libc/exit.c __cxa_finalize: push %rbp mov %rsp,%rbp .profilable push %r14 push %r13 push %r12 mov g_cxa(%rip),%rsi mov %rdi,%r14 0: mov %rsi,%r12 # loop through blocks pushpop ATEXIT_MAX,%rcx 1: lodsq #→ dso # loop through callbacks xchg %rax,%rdx lodsq #→ arg xchg %rax,%rdi lodsq #→ fn test %rax,%rax jz 2f # ignore empty slots test %r14,%r14 jmp 5f # null predicate match all cmp %r14,%rdx jne 2f # predicate mismatch 5: push %rsi push %rcx push %rcx call *%rax pop %rcx pop %rcx pop %rsi xor %eax,%eax # clear slot (never reused) mov %rax,-8(%rsi) 2: loop 1b lodsq # get next block ptr test %rax,%rax # don't free static block no. 1 jz 3f # which always has next == NULL test %r14,%r14 jz 1f # don't free anything if just one dso push %rax mov %r12,%rdi .weak free call free # can't panic due to earlier test 1: pop %rsi jmp 0b 3: pop %r12 # align stack for next call test %r14,%r14 # no static dtor for dso exit jnz 9f ezlea __fini_array_end,ax # static dtors in reverse order .weak __fini_array_end # could be called multiple times ezlea __fini_array_start,cx # idempotency recommended .weak __fini_array_start # or consider atexit() 8: sub $8,%rax # @see ape/ape.lds cmp %rcx,%rax jl 9f push %rax push %rcx call *(%rax) pop %rcx pop %rax jmp 8b 9: pop %r13 pop %r14 pop %rbp ret .endfn __cxa_finalize,globl,hidden .bss .align 16 # static/dynamic hybrid linked list g_cxa: .quad 0 # last block ptr: (long (*)[32][3]) .quad 0 # block byte offset moves backwards .endobj g_cxa g_cxa_static: .rept ATEXIT_MAX .quad 0 # dso .quad 0 # arg .quad 0 # fn (or NULL for empty) .endr .quad 0 # next (always NULL in static block) .endobj g_cxa_static .previous .init.start 300,_init_g_cxa ezlea g_cxa,cx lea g_cxa_static-g_cxa(%rcx),%rax mov %rax,(%rcx) movl $ATEXIT_MAX*8*3,8(%rcx) .init.end 300,_init_g_cxa