Skip to content

Instantly share code, notes, and snippets.

@jart
Last active Mar 8, 2021
Embed
What would you like to do?
Go-like defer for C that works with most optimization flag combinations under GCC/Clang
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2020 Justine Alexandra Roberts Tunney │
│ │
│ Permission to use, copy, modify, and/or distribute this software for │
│ any purpose with or without fee is hereby granted, provided that the │
│ above copyright notice and this permission notice appear in all copies. │
│ │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "gc.h"
/**
* Adds destructor to garbage shadow stack.
*
* @param frame is passed automatically by wrapper macro
* @param fn takes one argument
* @param arg is passed to fn(arg)
* @return arg
* @note maybe add __attribute__((__noipa__)) here if you use it
*/
void __defer(struct StackFrame *frame, void *fn, void *arg) {
if (!arg) return;
append(&__garbage, /* note: append() not included */
(&(const struct Garbage){frame->next, (intptr_t)fn, (intptr_t)arg,
frame->addr})) != -1) {
frame->addr = (intptr_t)&__gc;
}
#ifndef GC_H_
#define GC_H_
#define gc(THING) defer(free, (THING))
/**
* Calls FN(ARG) when function returns.
*/
#define defer(FN, ARG) \
({ \
__typeof(ARG) Arg = (ARG); \
/* force -fno-omit-frame-pointer and */ \
/* prevent weird opts like tail call */ \
asm("" : "+g"(Arg)); \
__defer(__builtin_frame_address(0), FN, Arg); \
asm("" : "+g"(Arg)); \
Arg; \
})
struct StackFrame {
struct StackFrame *next;
intptr_t addr;
};
struct Garbages {
size_t i, n;
struct Garbage {
struct StackFrame *frame;
intptr_t fn;
intptr_t arg;
intptr_t ret;
} * p;
};
extern struct Garbages __garbage;
int64_t __gc(void);
void __defer(struct StackFrame *, void *, void *);
#endif /* GC_H_ */
/*-*- 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 │
│ │
│ Permission to use, copy, modify, and/or distribute this software for │
│ any purpose with or without fee is hereby granted, provided that the │
│ above copyright notice and this permission notice appear in all copies. │
│ │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
/ Invokes deferred function calls.
/
/ This offers behavior similar to std::unique_ptr. Functions
/ overwrite their return addresses jumping here, and pushing
/ exactly one entry on the shadow stack below. Functions may
/ repeat that process multiple times, in which case the body
/ of this gadget loops and unwinds as a natural consequence.
/
/ @param rax,rdx,xmm0,xmm1,st0,st1 is return value
/ @assume system five nexgen32e abi conformant
/ <LIMBO>
__gc: decq __garbage(%rip)
mov __garbage(%rip),%r8
mov __garbage+16(%rip),%r9
js 9f
shl $5,%r8
lea (%r9,%r8),%r8
mov 8(%r8),%r9
mov 16(%r8),%rdi
push 24(%r8)
/ </LIMBO>
push %rbp
mov %rsp,%rbp
sub $0x20,%rsp
push %rax
push %rdx
movdqa %xmm0,-0x20(%rbp)
movdqa %xmm1,-0x10(%rbp)
call *%r9
movdqa -0x10(%rbp),%xmm1
movdqa -0x20(%rbp),%xmm0
pop %rdx
pop %rax
leave
ret
9: call abort
.type __gc,@function
.size __gc,.-__gc
.globl __gc
.bss
.align 8
__garbage:
.quad 0 # garbage.i
.quad 0 # garbage.n
.quad 0 # garbage.p
.rept INITIAL_CAPACITY
.quad 0 # garbage.p[𝑖].frame
.quad 0 # garbage.p[𝑖].fn
.quad 0 # garbage.p[𝑖].arg
.quad 0 # garbage.p[𝑖].ret
.endr
.size __garbage,.-__garbage
.type __gc,@object
.globl __garbage
.previous
.section .init
movq $INITIAL_CAPACITY,__garbage+8
movq $__garbage+24,__garbage+16
/*-*- 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 │
│ │
│ Permission to use, copy, modify, and/or distribute this software for │
│ any purpose with or without fee is hereby granted, provided that the │
│ above copyright notice and this permission notice appear in all copies. │
│ │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
/ Jumps up stack to previous setjmp() invocation.
/
/ This is the same as longjmp() but also unwinds the stack to free
/ memory, etc. that was registered using gc() or defer(). If GC
/ isn't linked, this behaves the same as longjmp().
/
/ @param rdi points to the jmp_buf which must be the same stack
/ @param esi is returned by setjmp() invocation (coerced nonzero)
/ @assume system five nexgen32e abi conformant
/ @noreturn
gclongjmp:
push %rbp
mov %rsp,%rbp
.weak __garbage
lea __garbage(%rip),%r12
test %r12,%r12
jnz .L.unwind.destructors
0: jmp longjmp
.L.unwind.destructors:
push %rdi
push %rsi
mov (%r12),%r13 # garbage.i
mov 16(%r12),%r14 # garbage.p
mov (%rdi),%r15 # jmp_buf[0] is new %rsp
shl $5,%r13
1: test %r13,%r13
jz 2f
sub $32,%r13
cmp (%r14,%r13),%r15
ja 2f
mov 8(%r14,%r13),%rax # garbage.p[𝑖].fn
mov 16(%r14,%r13),%rdi # garbage.p[𝑖].arg
call *%rax
decq (%r12)
jmp 1b
2: pop %rsi
pop %rdi
jmp 0b
.size gclongjmp,.-gclongjmp
.type gclongjmp,@function
.globl gclongjmp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment