182 lines
5.6 KiB
ArmAsm
182 lines
5.6 KiB
ArmAsm
/*-*- 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
|