Skip to content

Commit 1414990

Browse files
committed
signals: Implement fake_stack_pop for Linux on x86_64, i686, and aarch64
Add jl_fake_signal_return (was fake_stack_pop) implementation for Linux platforms to improve stack unwinding in debuggers when analyzing core dumps from signals like SIGQUIT. This provides proper DWARF Call Frame Information (CFI) directives that help unwinders locate saved register values on the manipulated stack. The implementation follows the same pattern as the existing macOS version, with fake_stack_pop now unified in signals-unix.c to support both platforms: - x86_64: Uses .cfi_def_cfa %rsp with offsets for %rip and %rsp - i686: Uses .cfi_def_cfa %esp with offsets for %eip and %esp - aarch64: Uses .cfi_def_cfa sp with offsets for lr and sp - uses file scope assembly, since aarch64 gcc has an open bug about implementing the naked attribute. The jl_call_in_ctx function on Linux now sets up the stack similarly to jl_call_in_state on macOS, pushing saved register state and a return address pointing to fake_stack_pop to enable proper unwinding. 🤖 Generated with Claude Code
1 parent 941e459 commit 1414990

File tree

2 files changed

+123
-44
lines changed

2 files changed

+123
-44
lines changed

src/signals-mach.c

Lines changed: 7 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -220,35 +220,6 @@ typedef arm_exception_state64_t host_exception_state_t;
220220
#define HOST_EXCEPTION_STATE_COUNT ARM_EXCEPTION_STATE64_COUNT
221221
#endif
222222

223-
// create a fake function that describes the variable manipulations in jl_call_in_state
224-
__attribute__((naked)) static void fake_stack_pop(void)
225-
{
226-
#ifdef _CPU_X86_64_
227-
__asm__ volatile (
228-
" .cfi_signal_frame\n"
229-
" .cfi_def_cfa %rsp, 0\n" // CFA here uses %rsp directly
230-
" .cfi_offset %rip, 0\n" // previous value of %rip at CFA
231-
" .cfi_offset %rsp, 8\n" // previous value of %rsp at CFA
232-
" nop\n"
233-
);
234-
#elif defined(_CPU_AARCH64_)
235-
__asm__ volatile (
236-
" .cfi_signal_frame\n"
237-
" .cfi_def_cfa sp, 0\n" // use sp as fp here
238-
" .cfi_offset lr, 0\n"
239-
" .cfi_offset sp, 8\n"
240-
// Anything else got smashed, since we didn't explicitly copy all of the
241-
// state object to the stack (to build a real sigreturn frame).
242-
// This is also not quite valid, since the AArch64 DWARF spec lacks the ability to define how to restore the LR register correctly,
243-
// so normally libunwind implementations on linux detect this function specially and hack around the invalid info:
244-
// https://github.com/llvm/llvm-project/commit/c82deed6764cbc63966374baf9721331901ca958
245-
" nop\n"
246-
);
247-
#else
248-
CFI_NORETURN
249-
#endif
250-
}
251-
252223
static void jl_call_in_state(host_thread_state_t *state, void (*fptr)(void))
253224
{
254225
#ifdef _CPU_X86_64_
@@ -258,19 +229,21 @@ static void jl_call_in_state(host_thread_state_t *state, void (*fptr)(void))
258229
#endif
259230
sp = (sp - 256) & ~(uintptr_t)15; // redzone and re-alignment
260231
assert(sp % 16 == 0);
261-
sp -= 16;
262232
#ifdef _CPU_X86_64_
263233
// set return address to NULL
234+
sp -= sizeof(void*);
235+
*(uintptr_t*)sp = 0;
236+
sp -= sizeof(void*);
264237
*(uintptr_t*)sp = 0;
265238
// pushq %sp
266239
sp -= sizeof(void*);
267240
*(uintptr_t*)sp = state->__rsp;
268241
// pushq %rip
269242
sp -= sizeof(void*);
270243
*(uintptr_t*)sp = state->__rip;
271-
// pushq .fake_stack_pop + 1; aka call from fake_stack_pop
244+
// pushq .jl_fake_signal_return + 1; aka call from jl_fake_signal_return
272245
sp -= sizeof(void*);
273-
*(uintptr_t*)sp = (uintptr_t)&fake_stack_pop + 1;
246+
*(uintptr_t*)sp = (uintptr_t)&jl_fake_signal_return + 1;
274247
state->__rsp = sp; // set stack pointer
275248
state->__rip = (uint64_t)fptr; // "call" the function
276249
#elif defined(_CPU_AARCH64_)
@@ -281,7 +254,7 @@ static void jl_call_in_state(host_thread_state_t *state, void (*fptr)(void))
281254
*(uintptr_t*)sp = (uintptr_t)state->__pc;
282255
state->__sp = sp; // x31
283256
state->__pc = (uint64_t)fptr; // pc
284-
state->__lr = (uintptr_t)&fake_stack_pop + 4; // x30
257+
state->__lr = (uintptr_t)&jl_fake_signal_return + 4; // x30
285258
#else
286259
#error "julia: throw-in-context not supported on this platform"
287260
#endif
@@ -577,7 +550,7 @@ static void jl_try_deliver_sigint(void)
577550
HANDLE_MACH_ERROR("thread_resume", ret);
578551
}
579552

580-
static void JL_NORETURN jl_exit_thread0_cb(int signo)
553+
static void jl_exit_thread0_cb(int signo)
581554
{
582555
jl_fprint_critical_error(ios_safe_stderr, signo, 0, NULL, jl_current_task);
583556
jl_atexit_hook(128);

src/signals-unix.c

Lines changed: 116 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,82 @@ static void jl_exit_thread0(int signo, jl_bt_element_t *bt_data, size_t bt_size)
6868

6969
int jl_simulate_longjmp(jl_jmp_buf mctx, bt_context_t *c) JL_NOTSAFEPOINT;
7070
static void jl_longjmp_in_ctx(int sig, void *_ctx, jl_jmp_buf jmpbuf);
71+
extern void jl_fake_signal_return(void);
72+
73+
// create a fake function that describes the variable manipulations in jl_call_in_ctx/jl_call_in_state
74+
#if defined(_OS_LINUX_) && defined(_CPU_X86_64_)
75+
__asm__(
76+
" .type jl_fake_signal_return, @function\n"
77+
"jl_fake_signal_return:\n"
78+
" .cfi_startproc\n"
79+
" .cfi_signal_frame\n"
80+
" .cfi_def_cfa %rsp, 0\n" // CFA here uses %rsp directly
81+
" .cfi_offset %rip, 0\n" // previous value of %rip at CFA
82+
" .cfi_offset %rsp, 8\n" // previous value of %rsp at CFA
83+
" nop\n"
84+
" .cfi_endproc\n"
85+
" .size jl_fake_signal_return, .-jl_fake_signal_return\n"
86+
);
87+
#elif defined(_OS_DARWIN_) && defined(_CPU_X86_64_)
88+
__asm__(
89+
"_jl_fake_signal_return:\n"
90+
" .cfi_startproc\n"
91+
" .cfi_signal_frame\n"
92+
" .cfi_def_cfa %rsp, 0\n" // CFA here uses %rsp directly
93+
" .cfi_offset %rip, 0\n" // previous value of %rip at CFA
94+
" .cfi_offset %rsp, 8\n" // previous value of %rsp at CFA
95+
" nop\n"
96+
" .cfi_endproc\n"
97+
);
98+
#elif defined(_OS_LINUX_) && defined(_CPU_X86_)
99+
__asm__(
100+
" .type jl_fake_signal_return, @function\n"
101+
"jl_fake_signal_return:\n"
102+
" .cfi_startproc\n"
103+
" .cfi_signal_frame\n"
104+
" .cfi_def_cfa %esp, 0\n" // CFA here uses %esp directly
105+
" .cfi_offset %eip, 0\n" // previous value of %eip at CFA
106+
" .cfi_offset %esp, 4\n" // previous value of %esp at CFA
107+
" nop\n"
108+
" .cfi_endproc\n"
109+
" .size jl_fake_signal_return, .-jl_fake_signal_return\n"
110+
);
111+
#elif defined(_OS_LINUX_) && defined(_CPU_AARCH64_)
112+
__asm__(
113+
" .type jl_fake_signal_return, @function\n"
114+
"jl_fake_signal_return:\n"
115+
" .cfi_startproc\n"
116+
" .cfi_signal_frame\n"
117+
" .cfi_def_cfa sp, 0\n" // use sp as fp here
118+
" .cfi_offset lr, 0\n"
119+
" .cfi_offset sp, 8\n"
120+
// Anything else got smashed, since we didn't explicitly copy all of the
121+
// state object to the stack (to build a real sigreturn frame).
122+
// This is also not quite valid, since the AArch64 DWARF spec lacks the ability to define how to restore the LR register correctly,
123+
// so normally libunwind implementations on linux detect this function specially and hack around the invalid info:
124+
// https://github.com/llvm/llvm-project/commit/c82deed6764cbc63966374baf9721331901ca958
125+
" nop\n"
126+
" .cfi_endproc\n"
127+
" .size jl_fake_signal_return, .-jl_fake_signal_return\n"
128+
);
129+
#elif defined(_OS_DARWIN_) && defined(_CPU_AARCH64_)
130+
__asm__(
131+
"_jl_fake_signal_return:\n"
132+
" .cfi_startproc\n"
133+
" .cfi_signal_frame\n"
134+
" .cfi_def_cfa sp, 0\n" // use sp as fp here
135+
" .cfi_offset lr, 0\n"
136+
" .cfi_offset sp, 8\n"
137+
" nop\n"
138+
" .cfi_endproc\n"
139+
);
140+
#else
141+
extern void JL_NORETURN jl_fake_signal_return(void)
142+
{
143+
CFI_NORETURN
144+
abort();
145+
}
146+
#endif
71147

72148
#if !defined(_OS_DARWIN_)
73149
static inline uintptr_t jl_get_rsp_from_ctx(const void *_ctx)
@@ -123,12 +199,25 @@ JL_NO_ASAN static void jl_call_in_ctx(jl_ptls_t ptls, void (*fptr)(void), int si
123199
// will not be part of the validation...
124200
uintptr_t rsp = jl_get_rsp_from_ctx(_ctx);
125201
rsp = (rsp - 256) & ~(uintptr_t)15; // redzone and re-alignment
202+
assert(rsp % 16 == 0);
126203
#if defined(_OS_LINUX_) && defined(_CPU_X86_64_)
127204
ucontext_t *ctx = (ucontext_t*)_ctx;
205+
// set return address to NULL
206+
rsp -= sizeof(void*);
207+
*(uintptr_t*)rsp = 0;
128208
rsp -= sizeof(void*);
129209
*(uintptr_t*)rsp = 0;
130-
ctx->uc_mcontext.gregs[REG_RSP] = rsp;
131-
ctx->uc_mcontext.gregs[REG_RIP] = (uintptr_t)fptr;
210+
// pushq %rsp
211+
rsp -= sizeof(void*);
212+
*(uintptr_t*)rsp = ctx->uc_mcontext.gregs[REG_RSP];
213+
// pushq %rip
214+
rsp -= sizeof(void*);
215+
*(uintptr_t*)rsp = ctx->uc_mcontext.gregs[REG_RIP];
216+
// pushq .jl_fake_signal_return + 1; aka call from jl_fake_signal_return
217+
rsp -= sizeof(void*);
218+
*(uintptr_t*)rsp = (uintptr_t)&jl_fake_signal_return + 1;
219+
ctx->uc_mcontext.gregs[REG_RSP] = rsp; // set stack pointer
220+
ctx->uc_mcontext.gregs[REG_RIP] = (uintptr_t)fptr; // "call" the function
132221
#elif defined(_OS_FREEBSD_) && defined(_CPU_X86_64_)
133222
ucontext_t *ctx = (ucontext_t*)_ctx;
134223
rsp -= sizeof(void*);
@@ -137,10 +226,22 @@ JL_NO_ASAN static void jl_call_in_ctx(jl_ptls_t ptls, void (*fptr)(void), int si
137226
ctx->uc_mcontext.mc_rip = (uintptr_t)fptr;
138227
#elif defined(_OS_LINUX_) && defined(_CPU_X86_)
139228
ucontext_t *ctx = (ucontext_t*)_ctx;
229+
// set return address to NULL
230+
rsp -= sizeof(void*);
231+
*(uintptr_t*)rsp = 0;
140232
rsp -= sizeof(void*);
141233
*(uintptr_t*)rsp = 0;
142-
ctx->uc_mcontext.gregs[REG_ESP] = rsp;
143-
ctx->uc_mcontext.gregs[REG_EIP] = (uintptr_t)fptr;
234+
// pushl %esp
235+
rsp -= sizeof(void*);
236+
*(uintptr_t*)rsp = ctx->uc_mcontext.gregs[REG_ESP];
237+
// pushl %eip
238+
rsp -= sizeof(void*);
239+
*(uintptr_t*)rsp = ctx->uc_mcontext.gregs[REG_EIP];
240+
// pushl .jl_fake_signal_return + 1; aka call from jl_fake_signal_return
241+
rsp -= sizeof(void*);
242+
*(uintptr_t*)rsp = (uintptr_t)&jl_fake_signal_return + 1;
243+
ctx->uc_mcontext.gregs[REG_ESP] = rsp; // set stack pointer
244+
ctx->uc_mcontext.gregs[REG_EIP] = (uintptr_t)fptr; // "call" the function
144245
#elif defined(_OS_FREEBSD_) && defined(_CPU_X86_)
145246
ucontext_t *ctx = (ucontext_t*)_ctx;
146247
rsp -= sizeof(void*);
@@ -155,13 +256,19 @@ JL_NO_ASAN static void jl_call_in_ctx(jl_ptls_t ptls, void (*fptr)(void), int si
155256
ctx->sc_rip = fptr;
156257
#elif defined(_OS_LINUX_) && defined(_CPU_AARCH64_)
157258
ucontext_t *ctx = (ucontext_t*)_ctx;
158-
ctx->uc_mcontext.sp = rsp;
159-
ctx->uc_mcontext.regs[29] = 0; // Clear link register (x29)
160-
ctx->uc_mcontext.pc = (uintptr_t)fptr;
259+
// push {%sp, %pc}
260+
rsp -= sizeof(void*);
261+
*(uintptr_t*)rsp = ctx->uc_mcontext.sp;
262+
rsp -= sizeof(void*);
263+
*(uintptr_t*)rsp = (uintptr_t)ctx->uc_mcontext.pc;
264+
ctx->uc_mcontext.sp = rsp; // sp
265+
ctx->uc_mcontext.pc = (uint64_t)fptr; // pc
266+
ctx->uc_mcontext.regs[30] = (uintptr_t)&jl_fake_signal_return + 4; // lr (x30)
161267
#elif defined(_OS_FREEBSD_) && defined(_CPU_AARCH64_)
162268
ucontext_t *ctx = (ucontext_t*)_ctx;
163269
ctx->uc_mcontext.mc_gpregs.gp_sp = rsp;
164-
ctx->uc_mcontext.mc_gpregs.gp_x[29] = 0; // Clear link register (x29)
270+
ctx->uc_mcontext.mc_gpregs.gp_x[29] = 0; // Clear frame pointer (x29)
271+
ctx->uc_mcontext.mc_gpregs.gp_lr = 0; // Clear link register (x30)
165272
ctx->uc_mcontext.mc_gpregs.gp_elr = (uintptr_t)fptr;
166273
#elif defined(_OS_LINUX_) && defined(_CPU_ARM_)
167274
ucontext_t *ctx = (ucontext_t*)_ctx;
@@ -549,9 +656,8 @@ static void jl_try_deliver_sigint(void)
549656
// Write only by signal handling thread, read only by main thread
550657
// no sync necessary.
551658
static int thread0_exit_signo = 0;
552-
static void JL_NORETURN jl_exit_thread0_cb(void)
659+
static void jl_exit_thread0_cb(void)
553660
{
554-
CFI_NORETURN
555661
jl_atomic_fetch_add(&jl_gc_disable_counter, -1);
556662
jl_fprint_critical_error(ios_safe_stderr, thread0_exit_signo, 0, NULL, jl_current_task);
557663
jl_atexit_hook(128);

0 commit comments

Comments
 (0)