Skip to content

Commit

Permalink
aarch64: implement signal handler
Browse files Browse the repository at this point in the history
This patch implements the signal handling on aarch64. I will not
be repeating the details of what it changes and why as it is quite
well explained in the code changes.

But in essence, this patch updates the build_signal_frame() which
I believe was based on the x86_64 version of it with the changes
specific to aarch64. It also adds missing handling of the SA_ONSTACK
flag.

Secondly, this patch also enhances entry.S to implement the call_signal_handler_thunk
which is probably the most tricky part. The call_signal_handler_thunk is
called on exit from a page fault as an effect of build_signal_frame()
setting the field `elr` into the exception frame. Unlike on x86_64,
the stack pointer register (sp) is not changed automatically based on
the content of the frame on exit. To that end, the call_signal_handler_thunk
has to carefully switch to SP_EL0 (exception stack) to read the value of
the sp field from the exception frame in order to set SP_EL1 which is used
normally for non-expection-handling by kernel and apps. Eventually,
it calls the call_signal_handler() which is implemented logically in
similar fashion as on x86_64 except for different registers.

Finally, this patch also enables 3 unit tests to run on aarch64.

Fixes #1154

Fixes #1151
Fixes #1152
Fixes #1153

Signed-off-by: Waldemar Kozaczuk <[email protected]>
  • Loading branch information
wkozaczuk committed May 4, 2022
1 parent fd24359 commit d8d2719
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 26 deletions.
52 changes: 40 additions & 12 deletions arch/aarch64/entry.S
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,10 @@ exception_vectors:
.endif
mrs x2, elr_el1
mrs x3, spsr_el1
mrs x4, far_el1
stp x30, x1, [sp, #240] // store lr, old SP
stp x2, x3, [sp, #256] // store elr_el1, spsr_el1
str x4, [sp, #280] // store far_el1
.endm /* push_state_to_exception_frame */

.macro pop_state_from_exception_frame
Expand Down Expand Up @@ -294,18 +296,44 @@ entry_curr_el_irq x, 1 // the asynchronous exception handler used when the SP_EL
call_signal_handler_thunk:
.type call_signal_handler_thunk, @function
.cfi_startproc simple
# stack contains a signal_frame
/*
.cfi_offset reg, offset
...
mov x0, sp
call call_signal_handler
# FIXME: fpu
pop_pair...
add sp, sp, 16 # error_code
*/
ret
.cfi_signal_frame
.cfi_def_cfa %sp, 0
.cfi_offset x30, -32 // Point to the elr register located at the -32 offset
// of the exception frame to help gdb link to the
// address when interrupt was raised

# The call_signal_handler_thunk gets called on exit from the synchronous exception
# (most likely page fault handler) as a result of build_signal_frame placing the address
# of call_signal_handler_thunk into elr field of the exception frame.

# On exit from the exception, the stack selector is reset to point to SP_EL1 which
# is where we are now. However the build_signal_frame() placed the address of the stack
# we are supposed to use in the field 'sp' of the original exception frame still present
# on the exception stack (please note the exception have been disabled). So in order
# to read the value of the 'sp' field we need to switch back briefly to the exception
# stack.
mrs x1, SPsel
msr SPsel, #0 // switch back to SP_EL0 so we can see original exception frame
ldr x0, [sp, #-40] // read 'sp' field placed by build_signal_frame() in the original exception frame
msr SPsel, x1 // switch stack selector to the original value
mov sp, x0 // set sp to the stack setup by build_signal_frame()
// sp points to the signal frame and original exception frame at the same time
//TODO: Fix cfa to help debugger
msr daifclr, #2 // enable interrupts which were disabled by build_signal_frame()
isb

bl call_signal_handler //x0 (1st argument) points to the signal frame

pop_state_from_exception_frame
# Adjust stack pointer by the remaining part of the signal frame to get back
# to the position in the stack we should be according to the logic in build_signal_frame().
add sp, sp, #288
# Please note we may not be on the original stack when exception was triggered.
# We would be IF the signal handler was executed on the same stack. However if user set
# up his own stack and passed using sigalstack() with SA_ONSTACK to make it handle
# out of stack page fault, we would instead stay on that user stack rather than restore
# to the one that was exhausted.
eret
.cfi_endproc

// Keep fpu_state_save/load in sync with struct fpu_state in arch/aarch64/processor.hh
Expand Down
2 changes: 1 addition & 1 deletion arch/aarch64/exceptions.hh
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ struct exception_frame {
u64 spsr;
u32 esr;
u32 align1;
u64 align2; /* align to 16 */
u64 far;

void *get_pc(void) { return (void *)elr; }
unsigned int get_error(void) { return esr; }
Expand Down
115 changes: 111 additions & 4 deletions arch/aarch64/signal.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
#include <arch-cpu.hh>
#include <osv/debug.hh>

namespace osv {
extern struct sigaction signal_actions[];
};

namespace arch {

struct signal_frame {
Expand All @@ -35,20 +39,123 @@ void build_signal_frame(exception_frame* ef,
const siginfo_t& si,
const struct sigaction& sa)
{
void* sp = reinterpret_cast<void*>(ef->sp);
sp -= sizeof(signal_frame);
// If an alternative signal stack was defined for this thread with
// sigaltstack() and the SA_ONSTACK flag was specified, we should run
// the signal handler on that stack. Otherwise, we need to run further
// down the same stack the thread was using when it received the signal:
void *sp = nullptr;
if (sa.sa_flags & SA_ONSTACK) {
stack_t sigstack;
sigaltstack(nullptr, &sigstack);
if (!(sigstack.ss_flags & SS_DISABLE)) {
// ss_sp points to the beginning of the stack region, but aarch64
// stacks grow downward, from the end of the region
sp = sigstack.ss_sp + sigstack.ss_size;
}
}
if (!sp) {
sp = reinterpret_cast<void*>(ef->sp);
}
sp -= sizeof(signal_frame); // Make space for signal frame on stack
sp = align_down(sp, 16);
signal_frame* frame = static_cast<signal_frame*>(sp);
frame->state = *ef;
frame->state = *ef; // Save original exception frame and si and sa on stack
frame->si = si;
frame->sa = sa;
// Exit from this exception will make it call call_signal_handler_thunk
ef->elr = reinterpret_cast<ulong>(call_signal_handler_thunk);
// On x86_64 exiting from exception sets stack pointer to the value we
// would adjust in the exception frame. On aarch64 the stack pointer is not reset
// automatically to the value of the field sp and we have to rely on
// call_signal_handler_thunk to manually set it.
ef->sp = reinterpret_cast<ulong>(sp);
// To make sure it happens correctly we need to disable exceptions
// so that call_signal_handler_thunk has a chance to execute this logic.
ef->spsr |= processor::daif_i;
}

}

// This is called on exit from a synchronous exception after build_signal_frame()
void call_signal_handler(arch::signal_frame* frame)
{
processor::halt_no_interrupts();
sched::fpu_lock fpu;
SCOPE_LOCK(fpu);
if (frame->sa.sa_flags & SA_SIGINFO) {
ucontext_t uc = {};
auto& mcontext = uc.uc_mcontext;
auto& f = frame->state;
mcontext.regs[0] = f.regs[0];
mcontext.regs[1] = f.regs[1];
mcontext.regs[2] = f.regs[2];
mcontext.regs[3] = f.regs[3];
mcontext.regs[4] = f.regs[4];
mcontext.regs[5] = f.regs[5];
mcontext.regs[6] = f.regs[6];
mcontext.regs[7] = f.regs[7];
mcontext.regs[8] = f.regs[8];
mcontext.regs[9] = f.regs[9];
mcontext.regs[10] = f.regs[10];
mcontext.regs[11] = f.regs[11];
mcontext.regs[12] = f.regs[12];
mcontext.regs[13] = f.regs[13];
mcontext.regs[14] = f.regs[14];
mcontext.regs[15] = f.regs[15];
mcontext.regs[16] = f.regs[16];
mcontext.regs[17] = f.regs[17];
mcontext.regs[18] = f.regs[18];
mcontext.regs[19] = f.regs[19];
mcontext.regs[20] = f.regs[20];
mcontext.regs[21] = f.regs[21];
mcontext.regs[22] = f.regs[22];
mcontext.regs[23] = f.regs[23];
mcontext.regs[24] = f.regs[24];
mcontext.regs[25] = f.regs[25];
mcontext.regs[26] = f.regs[26];
mcontext.regs[27] = f.regs[27];
mcontext.regs[28] = f.regs[28];
mcontext.regs[29] = f.regs[29];
mcontext.regs[30] = f.regs[30];
mcontext.sp = f.sp;
mcontext.pc = f.elr;
mcontext.pstate = f.spsr;
mcontext.fault_address = f.far;
frame->sa.sa_sigaction(frame->si.si_signo, &frame->si, &uc);
f.regs[0] = mcontext.regs[0];
f.regs[1] = mcontext.regs[1];
f.regs[2] = mcontext.regs[2];
f.regs[3] = mcontext.regs[3];
f.regs[4] = mcontext.regs[4];
f.regs[5] = mcontext.regs[5];
f.regs[6] = mcontext.regs[6];
f.regs[7] = mcontext.regs[7];
f.regs[8] = mcontext.regs[8];
f.regs[9] = mcontext.regs[9];
f.regs[10] = mcontext.regs[10];
f.regs[11] = mcontext.regs[11];
f.regs[12] = mcontext.regs[12];
f.regs[13] = mcontext.regs[13];
f.regs[14] = mcontext.regs[14];
f.regs[15] = mcontext.regs[15];
f.regs[16] = mcontext.regs[16];
f.regs[17] = mcontext.regs[17];
f.regs[18] = mcontext.regs[18];
f.regs[19] = mcontext.regs[19];
f.regs[20] = mcontext.regs[20];
f.regs[21] = mcontext.regs[21];
f.regs[22] = mcontext.regs[22];
f.regs[23] = mcontext.regs[23];
f.regs[24] = mcontext.regs[24];
f.regs[25] = mcontext.regs[25];
f.regs[26] = mcontext.regs[26];
f.regs[27] = mcontext.regs[27];
f.regs[28] = mcontext.regs[28];
f.regs[29] = mcontext.regs[29];
f.regs[30] = mcontext.regs[30];
f.sp = mcontext.sp;
f.elr = mcontext.pc;
f.spsr = mcontext.pstate;
} else {
frame->sa.sa_handler(frame->si.si_signo);
}
}
9 changes: 0 additions & 9 deletions scripts/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,6 @@
"tcp_close_without_reading_on_qemu"
]

#At this point there are 128 out of 134 unit tests that pass on aarch64.
#The remaining ones are disabled below until we fix the issues that prevent them from passing.
aarch64_disabled_list= [
"tst-sigaltstack.so", #Most likely fails due to the lack of signals support on aarch64 - see #1151
"tst-elf-permissions.so", #Hangs - most likely fails due to the lack of signals support on aarch64 - see #1152
"tst-mmap.so", #Hangs - most likely fails due to the lack of signals support on aarch64 - see #1153
]

class TestRunnerTest(SingleCommandTest):
def __init__(self, name):
super(TestRunnerTest, self).__init__(name, '/tests/%s' % name)
Expand Down Expand Up @@ -185,7 +177,6 @@ def main():
disabled_list.extend(qemu_disabled_list)

if cmdargs.arch == 'aarch64':
disabled_list.extend(aarch64_disabled_list)
if host_arch != cmdargs.arch:
#Until the issue #1143 is resolved, we need to force running with 2 CPUs in TCG mode
run_py_args = run_py_args + ['-c', '2']
Expand Down

0 comments on commit d8d2719

Please sign in to comment.