Unwinding through a signal handler
2022-4-10 15:0:0 Author: maskray.me(查看原文) 阅读量:29 收藏

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.


文章来源: https://maskray.me/blog/2022-04-10-unwinding-through-signal-handler
如有侵权请联系:admin#unsafe.sh