UNDER CONSTRUCTION
Some notes about unwinding through a signal frame.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <libunwind.h> #include <signal.h> #include <stdio.h> static void handler (int ) { unw_context_t context; unw_cursor_t cursor; unw_getcontext(&context); unw_init_local(&cursor, &context); unw_word_t pc, sp; do { unw_get_reg(&cursor, UNW_REG_IP, &pc); unw_get_reg(&cursor, UNW_REG_SP, &sp); printf ("pc=0x%016zx sp=0x%016zx\n" , (size_t )pc, (size_t )sp); } while (unw_step(&cursor) > 0 ); exit (0 ); } int main () { signal(SIGUSR1, handler); raise(SIGUSR1); return 1 ; }
Build the program with either llvm-project libunwind or nongnu libunwind:
1 2 3 4 5 clang++ -g -I llvm-project/libunwind/include -funwind-tables a.cc --unwindlib=libunwind --rtlib=compiler-rt -no-pie -Wl,-rpath,/tmp/Debug/lib/x86_64-unknown-linux-gnu -o llvm clang++ -g -funwind-tables a.cc /tmp/p/libunwind/out/debug/src/.libs/libunwind.a /tmp/p/libunwind/out/debug/src/.libs/libunwind-x86_64.a -llzma -no-pie -o nongnu
(Some targets default to -fno-asynchronous-unwind-tables
. In the absence of C++ exceptions, we need at least -funwind-tables
.)
With either implementation, the output looks like the following on Linux glibc x86-64. I annotated the lines with location information.
1 2 3 4 5 6 pc=0x0000000000201aaa sp=0x00007ffd751fa580 exe:handler, the instruction after call unw_getcontext pc=0x00007fde2ad7a920 sp=0x00007ffd751fb080 libc.so.6:__restore_rt pc=0x00007fde2ad7a8a1 sp=0x00007ffd751fbd70 libc.so.6:raise pc=0x0000000000201a7d sp=0x00007ffd751fbe90 exe:main pc=0x00007fde2ad657fd sp=0x00007ffd751fbeb0 libc.so.6:__libc_start_main pc=0x000000000020191a sp=0x00007ffd751fbf80 exe:_start (from crt1.o)
__restore_rt
is a signal trampoline defined in glibc sysdeps/unix/sysv/linux/x86_64/libc_sigaction.c
:
1 2 3 4 5 nop .align 16 __restore_rt: movq $15, %rax # __NR_rt_sigreturn syscall
glibc's sigaction
sets the sa_restorer
field of sigaction
to __restore_rt
, and sets the SA_RESTORER
. The kernel sets up the __restore_rt
frame with saved process context information (ucontext_t
structure) before jumping to the signal handler. See kernel arch/x86/kernel/signal.c:setup_rt_frame
. Upon returning from the signal handler, control passes to __restore_rt
. See man 2 sigreturn
.
__restore_rt
is implemented in assembly. It comes with DWARF call frame information in .eh_frame
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 % llvm-dwarfdump -eh-frame /lib/x86_64-linux-gnu/libc.so.6 ... 00002458 00000010 00000000 CIE Format: DWARF32 Version: 1 Augmentation: "zRS" Code alignment factor: 1 Data alignment factor: -8 Return address column: 16 Augmentation data: 1B DW_CFA_nop: DW_CFA_nop: 0000246c 00000078 00000018 FDE cie=00002458 pc=0003c91f...0003c929 Format: DWARF32 DW_CFA_def_cfa_expression: DW_OP_breg7 RSP+160, DW_OP_deref DW_CFA_expression: R8 DW_OP_breg7 RSP+40 DW_CFA_expression: R9 DW_OP_breg7 RSP+48 DW_CFA_expression: R10 DW_OP_breg7 RSP+56 DW_CFA_expression: R11 DW_OP_breg7 RSP+64 DW_CFA_expression: R12 DW_OP_breg7 RSP+72 DW_CFA_expression: R13 DW_OP_breg7 RSP+80 DW_CFA_expression: R14 DW_OP_breg7 RSP+88 DW_CFA_expression: R15 DW_OP_breg7 RSP+96 DW_CFA_expression: RDI DW_OP_breg7 RSP+104 DW_CFA_expression: RSI DW_OP_breg7 RSP+112 DW_CFA_expression: RBP DW_OP_breg7 RSP+120 DW_CFA_expression: RBX DW_OP_breg7 RSP+128 DW_CFA_expression: RDX DW_OP_breg7 RSP+136 DW_CFA_expression: RAX DW_OP_breg7 RSP+144 DW_CFA_expression: RCX DW_OP_breg7 RSP+152 DW_CFA_expression: RSP DW_OP_breg7 RSP+160 DW_CFA_expression: RIP DW_OP_breg7 RSP+168 DW_CFA_nop: DW_CFA_nop: 0x3c91f: CFA=DW_OP_breg7 RSP+160, DW_OP_deref: RAX=[DW_OP_breg7 RSP+144], RDX=[DW_OP_breg7 RSP+136], RCX=[DW_OP_breg7 RSP+152], RBX=[DW_OP_breg7 RSP+128], RSI=[DW_OP_breg7 RSP+112], RDI=[DW_OP_breg7 RSP+104], RBP=[DW_OP_breg7 RSP+120], RSP=[DW_OP_breg7 RSP+160], R8=[DW_OP_breg7 RSP+40], R9=[DW_OP_breg7 RSP+48], R10=[DW_OP_breg7 RSP+56], R11=[DW_OP_breg7 RSP+64], R12=[DW_OP_breg7 RSP+72], R13=[DW_OP_breg7 RSP+80], R14=[DW_OP_breg7 RSP+88], R15=[DW_OP_breg7 RSP+96], RIP=[DW_OP_breg7 RSP+168] ...
The DW_OP_breg7 RSP
offsets correspond to the ucontext_t
offsets of these registers.
% cat sysdeps/unix/sysv/linux/x86_64/libc_sigaction.c
...
do_cfa_expr \
do_expr (8 /* r8 */, oR8) \
do_expr (9 /* r9 */, oR9) \
do_expr (10 /* r10 */, oR10) \
% cat sysdeps/unix/sysv/linux/x86_64/ucontext_i.sym
...
#define ucontext(member) offsetof (ucontext_t, member)
#define mcontext(member) ucontext (uc_mcontext.member)
#define mreg(reg) mcontext (gregs[REG_##reg])
oRBP mreg (RBP)
oRSP mreg (RSP)
oRBX mreg (RBX)
With the information, libunwind can unwind through the trampoline without knowing the ucontext_t
structure. Note that all general purpose registers are encoded. libunwind/docs/unw_get_reg.man
says
However, for signal frames (see unw_is_signal_frame(3)), it is usually possible to access all registers.
Volatile registers are also saved in the saved process context information. This is different from other frames where volatile registers's information is typically lost.
Older
Analysis and introspection options in linkers
文章来源: https://maskray.me/blog/2022-04-10-unwinding-through-signal-handler 如有侵权请联系:admin#unsafe.sh