Some registers not available when backtracing from through a signal handler on AArch64 Linux
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
The following example generates a `SIGILL`:
```
#include
#include
#include
#include
static void sigill_handler(int sig)
{
fprintf(stderr, "Caught SIGILL (%d)\n", sig);
/* Usually terminate or recover via longjmp-style logic.
Returning from a SIGILL handler is generally unsafe. */
_Exit(1);
}
int main() {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = sigill_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
if (sigaction(SIGILL, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
asm volatile (
"mov x0, #0xcafe\n\t"
"udf #1\n\t"
);
return 0;
}
```
```
$ ./bin/lldb /tmp/test.o
(lldb) target create "/tmp/test.o"
Current executable set to '/tmp/test.o' (aarch64).
(lldb) b sigill_handler
Breakpoint 1: where = test.o`sigill_handler + 12 at test.c:8:5, address = 0x00000000000009e0
(lldb) c
error: Command requires a current process.
(lldb) run
Process 3284 launched: '/tmp/test.o' (aarch64)
Process 3284 stopped
* thread #1, name = 'test.o', stop reason = signal SIGILL: illegal opcode
frame #0: 0x0000aaaaaaaa0a8c test.o`main at test.c:28:3
25 return 1;
26 }
27
-> 28 asm volatile (
29 "mov x0, #0xcafe\n\t"
30 "udf #1\n\t"
31 );
(lldb) register read v0
v0 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
(lldb) register read z0
z0 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
```
This first stop is before the signal is delivered to the application. At this point, reading registers works as it normally does.
If we continue we enter the registered signal handler.
```
(lldb) c
Process 3284 resuming
Process 3284 stopped
* thread #1, name = 'test.o', stop reason = breakpoint 1.1
frame #0: 0x0000aaaaaaaa09e0 test.o`sigill_handler(sig=4) at test.c:8:5
5
6 static void sigill_handler(int sig)
7 {
-> 8 fprintf(stderr, "Caught SIGILL (%d)\n", sig);
9
10 /* Usually terminate or recover via longjmp-style logic.
11 Returning from a SIGILL handler is generally unsafe. */
(lldb) bt
* thread #1, name = 'test.o', stop reason = breakpoint 1.1
* frame #0: 0x0000aaaaaaaa09e0 test.o`sigill_handler(sig=4) at test.c:8:5
frame #1: 0x0000fffff7ffb8f8 [vdso]`__kernel_rt_sigreturn
frame #2: 0x0000aaaaaaaa0a8c test.o`main at test.c:28:3
frame #3: 0x0000fffff7e27400 libc.so.6`__libc_start_call_main(main=(test.o`main at test.c:15:12), argc=1, argv=0x0000ffffffffef08) at libc_start_call_main.h:58:16
frame #4: 0x0000fffff7e274d8 libc.so.6`__libc_start_main_impl(main=(test.o`main at test.c:15:12), argc=1, argv=0x0000ffffffffef08, init=, fini=, rtld_fini=, stack_end=) at libc-start.c:392:3
frame #5: 0x0000aaaaaaaa08f0 test.o`_start + 48
```
Now if I go up to the place that caused the signal:
```
(lldb) up
frame #1: 0x0000fffff7ffb8f8 [vdso]`__kernel_rt_sigreturn
[vdso]`__kernel_rt_sigreturn:
-> 0xfffff7ffb8f8 <+0>: mov x8, #0x8b ; =139
0xfffff7ffb8fc <+4>: svc #0
0xfffff7ffb900:
0xfffff7ffb904:
(lldb) up
frame #2: 0x0000aaaaaaaa0a8c test.o`main at test.c:28:3
25 return 1;
26 }
27
-> 28 asm volatile (
29 "mov x0, #0xcafe\n\t"
30 "udf #1\n\t"
31 );
(lldb) bt
* thread #1, name = 'test.o', stop reason = breakpoint 1.1
frame #0: 0x0000aaaaaaaa09e0 test.o`sigill_handler(sig=4) at test.c:8:5
frame #1: 0x0000fffff7ffb8f8 [vdso]`__kernel_rt_sigreturn
* frame #2: 0x0000aaaaaaaa0a8c test.o`main at test.c:28:3
frame #3: 0x0000fffff7e27400 libc.so.6`__libc_start_call_main(main=(test.o`main at test.c:15:12), argc=1, argv=0x0000ffffffffef08) at libc_start_call_main.h:58:16
frame #4: 0x0000fffff7e274d8 libc.so.6`__libc_start_main_impl(main=(test.o`main at test.c:15:12), argc=1, argv=0x0000ffffffffef08, init=, fini=, rtld_fini=, stack_end=) at libc-start.c:392:3
frame #5: 0x0000aaaaaaaa08f0 test.o`_start + 48
```
Many of the registers are unavailable:
```
(lldb) register read v0
v0 = error: unavailable
(lldb) register read z0
z0 = error: unavailable
```
This is down to my half finished job in `lldb/source/Plugins/Platform/Linux/PlatformLinux.cpp` `GetAArch64TrapHandlerUnwindPlan`. That unwind plan has GPRs but nothing else.
Everything else in the sigcontext is not at a fixed position (see https://github.com/torvalds/linux/blob/8fde5d1d47f69db6082dfa34500c27f8485389a5/arch/arm64/include/uapi/asm/sigcontext.h#L28). So to get FP/SVE/etc, we need to parse the extra data.
RISC-V also has a trap handler plan but it's all statically known offsets (and their FP registers are at a static location it seems).
I don't know if we have any dynamic unwind plans but that's what we would need to do. Parse the `__reserved` as it is at the time we are unwinding.
Contributor guide
Assessment
This issue has not been assessed yet.