feat(setjmp): x86_64 setjmp/longjmp/sigsetjmp

This commit is contained in:
2026-09-03 17:39:20 -04:00
parent dec1017527
commit b2d588885e
6 changed files with 684 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
/*
* vlibc — x86_64 longjmp (todo 4).
*
* longjmp(env, val) / _longjmp(env, val): restore the environment saved by
* setjmp and make that setjmp return val — with 0 coerced to 1 (POSIX:
* longjmp(env, 0) must return 1 so it is distinguishable from the initial
* setjmp return of 0). Never returns.
*
* Restores the FP control state (MXCSR and x87 control word), the
* callee-saved registers, then jumps to the saved rip with the saved rsp,
* exactly as if setjmp had returned val.
*
* Registers: env arrives in rdi and is moved to r12, a callee-saved scratch
* register that is itself restored from the buffer, so it can serve as the
* base for every load. val rides in rax, which is never restored from the
* buffer, and is therefore the value setjmp observes. The saved rsp and rip
* travel through rdx and rcx (both caller-saved, safe to clobber) until the
* final `mov %rdx,%rsp; jmp *%rcx`.
*/
.global longjmp
.global _longjmp
.type longjmp,@function
.type _longjmp,@function
longjmp:
_longjmp:
mov %rsi,%rax /* val; coerce 0 to 1 below */
test %rax,%rax
jnz 1f
inc %rax
1:
mov %rdi,%r12 /* env base (r12 is restored from the buffer later) */
ldmxcsr 64(%r12)
fldcw 72(%r12)
mov 0(%r12),%rbx
mov 8(%r12),%rbp
mov 32(%r12),%r14
mov 40(%r12),%r15
mov 48(%r12),%rdx /* saved rsp */
mov 56(%r12),%rcx /* saved rip */
mov 24(%r12),%r13
mov 16(%r12),%r12 /* last read of env */
mov %rdx,%rsp
jmp *%rcx
.size longjmp,.-longjmp
.size _longjmp,.-_longjmp
.section .note.GNU-stack,"",@progbits
+49
View File
@@ -0,0 +1,49 @@
/*
* vlibc — x86_64 setjmp (todo 4).
*
* setjmp(env) / _setjmp(env): save the calling environment into env and
* return 0. Both entry points share one body; the setjmp macro in
* include/setjmp.h expands to _setjmp, while the plain symbol is kept so
* address-takers and #undef-users still link.
*
* jmp_buf slot map (the authoritative layout; see include/setjmp.h):
*
* [0] rbx [1] rbp [2] r12 [3] r13 [4] r14
* [5] r15 [6] rsp [7] rip [8] mxcsr [9] x87 control word
*
* The saved rsp is the caller's stack pointer (rsp + 8, past the return
* address pushed by the call); the saved rip is that return address, so
* longjmp can resume exactly as if setjmp had returned. The FP control
* state (MXCSR and the x87 control word) is saved as well, so a longjmp out
* of code that changed rounding/trap state restores it — this must be
* assembly: C cannot access the register file.
*
* Only the caller's callee-saved registers are preserved by this function
* itself (rbx is saved into the buffer before any use); caller-saved
* registers are untouched, and the return value 0 is delivered in eax.
*/
.global setjmp
.global _setjmp
.type setjmp,@function
.type _setjmp,@function
setjmp:
_setjmp:
mov %rbx,0(%rdi)
mov %rbp,8(%rdi)
mov %r12,16(%rdi)
mov %r13,24(%rdi)
mov %r14,32(%rdi)
mov %r15,40(%rdi)
lea 8(%rsp),%rdx
mov %rdx,48(%rdi)
mov (%rsp),%rax
mov %rax,56(%rdi)
stmxcsr 64(%rdi)
fnstcw 72(%rdi)
xor %eax,%eax
ret
.size setjmp,.-setjmp
.size _setjmp,.-_setjmp
.section .note.GNU-stack,"",@progbits
+55
View File
@@ -0,0 +1,55 @@
/*
* vlibc — x86_64 siglongjmp (todo 4).
*
* siglongjmp(env, val): like longjmp, but when the buffer's saved-mask flag
* (word 10, see include/setjmp.h) is set, first restore the signal mask
* stored at word 11 with rt_sigprocmask(SIG_SETMASK). Never returns.
*
* The syscall is made inline here: siglongjmp is naked assembly that must
* not call into C while the register set is half-restored. The kernel ABI
* facts hardcoded below mirror src/internal/syscall.h:
*
* SYS_rt_sigprocmask = 14, SIG_SETMASK = 2, sigsetsize = 8
* (x86_64 Linux sigset_t is a single 64-bit word)
*
* Register discipline matches longjmp.s: env moves to r12 (callee-saved
* scratch, restored from the buffer last), val rides in rax — stashed in
* r13 across the syscall, which clobbers rax — and the saved rsp/rip travel
* through rdx/rcx until the final `mov %rdx,%rsp; jmp *%rcx`.
*/
.global siglongjmp
.type siglongjmp,@function
siglongjmp:
mov %rsi,%rax /* val; coerce 0 to 1 below */
test %rax,%rax
jnz 1f
inc %rax
1:
mov %rdi,%r12 /* env base (r12 is restored from the buffer later) */
cmpl $0,80(%r12) /* saved-mask flag */
jz 2f
mov %rax,%r13 /* val must survive the syscall */
mov $14,%eax /* SYS_rt_sigprocmask */
mov $2,%edi /* SIG_SETMASK */
lea 88(%r12),%rsi /* set = &env[11] */
xor %edx,%edx /* oldset = NULL */
mov $8,%r10d /* sigsetsize (x86_64 sigset_t = one word) */
syscall
mov %r13,%rax /* val back in rax */
2:
ldmxcsr 64(%r12)
fldcw 72(%r12)
mov 0(%r12),%rbx
mov 8(%r12),%rbp
mov 32(%r12),%r14
mov 40(%r12),%r15
mov 48(%r12),%rdx /* saved rsp */
mov 56(%r12),%rcx /* saved rip */
mov 24(%r12),%r13
mov 16(%r12),%r12 /* last read of env */
mov %rdx,%rsp
jmp *%rcx
.size siglongjmp,.-siglongjmp
.section .note.GNU-stack,"",@progbits
+68
View File
@@ -0,0 +1,68 @@
/*
* vlibc — x86_64 sigsetjmp (todo 4).
*
* __sigsetjmp(env, savemask) / sigsetjmp(env, savemask): like setjmp, plus
* the current signal mask is saved into env when savemask is nonzero. Both
* entry points share one body; the sigsetjmp macro in include/setjmp.h
* expands to __sigsetjmp, while the plain symbol is kept so address-takers
* and #undef-users still link.
*
* Slot map (authoritative; see include/setjmp.h): a sigjmp_buf holds the
* base jmp_buf layout at words 0..9 (same offsets as plain setjmp, so the
* shared longjmp restore logic works on either storage), plus:
*
* [10] saved-mask flag (1 when the signal mask was saved)
* [11] signal mask (the x86_64 Linux sigset_t: a single 64-bit word)
*
* The saved-mask flag is cleared before anything else, so a buffer saved
* with savemask == 0 — or jumped to by plain longjmp — never presents a
* stale flag to siglongjmp. When savemask is nonzero, the mask is queried
* with a raw rt_sigprocmask syscall made inline right here: this function
* must stay pure assembly, because the saved rip/rsp resume DIRECTLY in the
* caller (an intermediate C frame would have its return-address slot above
* the saved stack pointer, where intervening calls would clobber it before
* the longjmp). The kernel ABI facts below mirror src/internal/syscall.h:
*
* SYS_rt_sigprocmask = 14, SIG_BLOCK = 0 (NULL newset queries),
* sigsetsize = 8 (x86_64 sigset_t is one 64-bit word)
*
* The mask slot is written through &env[11]; on success the flag is set and
* 0 is returned in eax, exactly like plain setjmp.
*/
.global __sigsetjmp
.global sigsetjmp
.type __sigsetjmp,@function
.type sigsetjmp,@function
__sigsetjmp:
sigsetjmp:
movq $0,80(%rdi) /* clear the saved-mask flag */
mov %rbx,0(%rdi)
mov %rbp,8(%rdi)
mov %r12,16(%rdi)
mov %r13,24(%rdi)
mov %r14,32(%rdi)
mov %r15,40(%rdi)
lea 8(%rsp),%rdx
mov %rdx,48(%rdi)
mov (%rsp),%rax
mov %rax,56(%rdi)
stmxcsr 64(%rdi)
fnstcw 72(%rdi)
test %esi,%esi
jz 1f
mov %rdi,%rbx /* env base (rbx is restored from the buffer later) */
mov $14,%eax /* SYS_rt_sigprocmask */
xor %edi,%edi /* how = SIG_BLOCK(0) */
xor %esi,%esi /* set = NULL (query only) */
lea 88(%rbx),%rdx /* oldset = &env[11] */
mov $8,%r10d /* sigsetsize (x86_64 sigset_t = one word) */
syscall
movq $1,80(%rbx) /* mask saved: set the flag */
1:
xor %eax,%eax
ret
.size __sigsetjmp,.-__sigsetjmp
.size sigsetjmp,.-sigsetjmp
.section .note.GNU-stack,"",@progbits