feat(setjmp): x86_64 setjmp/longjmp/sigsetjmp
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user