Skip to content

Commit a2aac97

Browse files
committed
signals: Implement fake_stack_pop for Linux on x86_64, i686, and aarch64
Add 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 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 ad6e4bb commit a2aac97

File tree

2 files changed

+79
-38
lines changed

2 files changed

+79
-38
lines changed

src/signals-mach.c

Lines changed: 3 additions & 30 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,9 +229,11 @@ 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*);

src/signals-unix.c

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,43 @@ static void jl_exit_thread0(int signo, jl_bt_element_t *bt_data, size_t bt_size)
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);
7171

72+
// create a fake function that describes the variable manipulations in jl_call_in_ctx/jl_call_in_state
73+
__attribute__((naked)) static void fake_stack_pop(void)
74+
{
75+
#if (defined(_OS_LINUX_) || defined(_OS_DARWIN_)) && defined(_CPU_X86_64_)
76+
__asm__ volatile (
77+
" .cfi_signal_frame\n"
78+
" .cfi_def_cfa %rsp, 0\n" // CFA here uses %rsp directly
79+
" .cfi_offset %rip, 0\n" // previous value of %rip at CFA
80+
" .cfi_offset %rsp, 8\n" // previous value of %rsp at CFA
81+
" nop\n"
82+
);
83+
#elif defined(_OS_LINUX_) && defined(_CPU_X86_)
84+
__asm__ volatile (
85+
" .cfi_signal_frame\n"
86+
" .cfi_def_cfa %esp, 0\n" // CFA here uses %esp directly
87+
" .cfi_offset %eip, 0\n" // previous value of %eip at CFA
88+
" .cfi_offset %esp, 4\n" // previous value of %esp at CFA
89+
" nop\n"
90+
);
91+
#elif (defined(_OS_LINUX_) || defined(_OS_DARWIN_)) && defined(_CPU_AARCH64_)
92+
__asm__ volatile (
93+
" .cfi_signal_frame\n"
94+
" .cfi_def_cfa sp, 0\n" // use sp as fp here
95+
" .cfi_offset lr, 0\n"
96+
" .cfi_offset sp, 8\n"
97+
// Anything else got smashed, since we didn't explicitly copy all of the
98+
// state object to the stack (to build a real sigreturn frame).
99+
// This is also not quite valid, since the AArch64 DWARF spec lacks the ability to define how to restore the LR register correctly,
100+
// so normally libunwind implementations on linux detect this function specially and hack around the invalid info:
101+
// https://github.com/llvm/llvm-project/commit/c82deed6764cbc63966374baf9721331901ca958
102+
" nop\n"
103+
);
104+
#else
105+
CFI_NORETURN
106+
#endif
107+
}
108+
72109
#if !defined(_OS_DARWIN_)
73110
static inline uintptr_t jl_get_rsp_from_ctx(const void *_ctx)
74111
{
@@ -123,12 +160,25 @@ JL_NO_ASAN static void jl_call_in_ctx(jl_ptls_t ptls, void (*fptr)(void), int si
123160
// will not be part of the validation...
124161
uintptr_t rsp = jl_get_rsp_from_ctx(_ctx);
125162
rsp = (rsp - 256) & ~(uintptr_t)15; // redzone and re-alignment
163+
assert(rsp % 16 == 0);
126164
#if defined(_OS_LINUX_) && defined(_CPU_X86_64_)
127165
ucontext_t *ctx = (ucontext_t*)_ctx;
166+
// set return address to NULL
128167
rsp -= sizeof(void*);
129168
*(uintptr_t*)rsp = 0;
130-
ctx->uc_mcontext.gregs[REG_RSP] = rsp;
131-
ctx->uc_mcontext.gregs[REG_RIP] = (uintptr_t)fptr;
169+
rsp -= sizeof(void*);
170+
*(uintptr_t*)rsp = 0;
171+
// pushq %rsp
172+
rsp -= sizeof(void*);
173+
*(uintptr_t*)rsp = ctx->uc_mcontext.gregs[REG_RSP];
174+
// pushq %rip
175+
rsp -= sizeof(void*);
176+
*(uintptr_t*)rsp = ctx->uc_mcontext.gregs[REG_RIP];
177+
// pushq .fake_stack_pop + 1; aka call from fake_stack_pop
178+
rsp -= sizeof(void*);
179+
*(uintptr_t*)rsp = (uintptr_t)&fake_stack_pop + 1;
180+
ctx->uc_mcontext.gregs[REG_RSP] = rsp; // set stack pointer
181+
ctx->uc_mcontext.gregs[REG_RIP] = (uintptr_t)fptr; // "call" the function
132182
#elif defined(_OS_FREEBSD_) && defined(_CPU_X86_64_)
133183
ucontext_t *ctx = (ucontext_t*)_ctx;
134184
rsp -= sizeof(void*);
@@ -137,10 +187,22 @@ JL_NO_ASAN static void jl_call_in_ctx(jl_ptls_t ptls, void (*fptr)(void), int si
137187
ctx->uc_mcontext.mc_rip = (uintptr_t)fptr;
138188
#elif defined(_OS_LINUX_) && defined(_CPU_X86_)
139189
ucontext_t *ctx = (ucontext_t*)_ctx;
190+
// set return address to NULL
140191
rsp -= sizeof(void*);
141192
*(uintptr_t*)rsp = 0;
142-
ctx->uc_mcontext.gregs[REG_ESP] = rsp;
143-
ctx->uc_mcontext.gregs[REG_EIP] = (uintptr_t)fptr;
193+
rsp -= sizeof(void*);
194+
*(uintptr_t*)rsp = 0;
195+
// pushl %esp
196+
rsp -= sizeof(void*);
197+
*(uintptr_t*)rsp = ctx->uc_mcontext.gregs[REG_ESP];
198+
// pushl %eip
199+
rsp -= sizeof(void*);
200+
*(uintptr_t*)rsp = ctx->uc_mcontext.gregs[REG_EIP];
201+
// pushl .fake_stack_pop + 1; aka call from fake_stack_pop
202+
rsp -= sizeof(void*);
203+
*(uintptr_t*)rsp = (uintptr_t)&fake_stack_pop + 1;
204+
ctx->uc_mcontext.gregs[REG_ESP] = rsp; // set stack pointer
205+
ctx->uc_mcontext.gregs[REG_EIP] = (uintptr_t)fptr; // "call" the function
144206
#elif defined(_OS_FREEBSD_) && defined(_CPU_X86_)
145207
ucontext_t *ctx = (ucontext_t*)_ctx;
146208
rsp -= sizeof(void*);
@@ -155,13 +217,19 @@ JL_NO_ASAN static void jl_call_in_ctx(jl_ptls_t ptls, void (*fptr)(void), int si
155217
ctx->sc_rip = fptr;
156218
#elif defined(_OS_LINUX_) && defined(_CPU_AARCH64_)
157219
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;
220+
// push {%sp, %pc}
221+
rsp -= sizeof(void*);
222+
*(uintptr_t*)rsp = ctx->uc_mcontext.sp;
223+
rsp -= sizeof(void*);
224+
*(uintptr_t*)rsp = (uintptr_t)ctx->uc_mcontext.pc;
225+
ctx->uc_mcontext.sp = rsp; // sp
226+
ctx->uc_mcontext.pc = (uint64_t)fptr; // pc
227+
ctx->uc_mcontext.regs[30] = (uintptr_t)&fake_stack_pop + 4; // lr (x30)
161228
#elif defined(_OS_FREEBSD_) && defined(_CPU_AARCH64_)
162229
ucontext_t *ctx = (ucontext_t*)_ctx;
163230
ctx->uc_mcontext.mc_gpregs.gp_sp = rsp;
164-
ctx->uc_mcontext.mc_gpregs.gp_x[29] = 0; // Clear link register (x29)
231+
ctx->uc_mcontext.mc_gpregs.gp_x[29] = 0; // Clear frame pointer (x29)
232+
ctx->uc_mcontext.mc_gpregs.gp_lr = 0; // Clear link register (x30)
165233
ctx->uc_mcontext.mc_gpregs.gp_elr = (uintptr_t)fptr;
166234
#elif defined(_OS_LINUX_) && defined(_CPU_ARM_)
167235
ucontext_t *ctx = (ucontext_t*)_ctx;

0 commit comments

Comments
 (0)