| // Copyright 2021 The Fuchsia Authors> | |
| // | |
| // Use of this source code is governed by a MIT-style | |
| // license that can be found in the LICENSE file or at | |
| // https://opensource.org/licenses/MIT | |
| #include <lib/arch/arm64/exception-asm.h> | |
| #include <lib/arch/arm64/system-asm.h> | |
| #include <lib/arch/asm.h> | |
| #include <phys/runtime/exception.h> | |
| #include <phys/runtime/stack.h> | |
| #include "regs.h" | |
| // The given register points to a zx_thread_state_general_regs_t at the CFA. | |
| // Store a pair of registers there and update the CFI. The first two arguments | |
| // are adjacent raw register numbers (e.g. 0, 1 for x0 and x1). | |
| .macro stp_cfa r0, r1, cfa=sp | |
| stp x\r0, x\r1, [\cfa, #REGS_X(\r0)] | |
| .cfi_offset x\r0, REGS_X(\r0) | |
| .cfi_offset x\r1, REGS_X(\r1) | |
| .endm | |
| // Reload a pair of registers saved by stp_cfa. | |
| .macro ldp_cfa r0, r1, cfa=sp | |
| ldp x\r0, x\r1, [\cfa, #REGS_X(\r0)] | |
| .cfi_same_value x\r0 | |
| .cfi_same_value x\r1 | |
| .endm | |
| // Invoke `\macro 2, 3`, ..., `\macro 28, 29`. | |
| .macro .for_each_pair macro:vararg | |
| \macro 2, 3 | |
| \macro 4, 5 | |
| \macro 6, 7 | |
| \macro 8, 9 | |
| \macro 10, 11 | |
| \macro 12, 13 | |
| \macro 14, 15 | |
| \macro 16, 17 | |
| \macro 18, 19 | |
| \macro 20, 21 | |
| \macro 22, 23 | |
| \macro 24, 25 | |
| \macro 26, 27 | |
| \macro 28, 29 | |
| .endm | |
| // This supplies the code (as if inside `.vbar_function`) that the phys | |
| // exception table will use by default. | |
| .macro phys_exception_entry name, offset | |
| // On entry the CFI state thinks the CFA is the SP. But the SP is the | |
| // interrupted SP, or SP_ELx if coming from lower EL. On the assumption | |
| // we're coming from the current EL so the interrupted SP is still in SP, | |
| // we don't want to use that SP. Instead we'll move to a dedicated stack | |
| // used only for exceptions. (There's just one, so nested any exceptions | |
| // will just start over reusing it from the top.) So the SP we're about | |
| // to switch to is what we'd really like to call the CFA. That value is | |
| // not in any register. | |
| #define EXC_ENTRY_CFA (phys_exception_stack + BOOT_STACK_SIZE - REGS_SIZE) | |
| #if BOOT_STACK_SIZE % BOOT_STACK_ALIGN != 0 | |
| #error "BOOT_STACK_SIZE not aligned" | |
| #endif | |
| #if REGS_SIZE % BOOT_STACK_ALIGN != 0 | |
| #error "REGS_SIZE not aligned" | |
| #endif | |
| // TODO(mcgrathr): DW_CFA_def_cfa_expression with DW_OP_addr could just point | |
| // to its fixed address. But the assembler doesn't know how to generate that | |
| // and .cfi_escape only takes byte values and can't generate a relocation for | |
| // the address. The GNU assembler has a .cfi_val_encoded_addr directive that | |
| // generates a DW_OP_addr expression just like that for you, but that is only | |
| // for DW_CFA_val_expression (a register rule, not the CFA) and LLVM doesn't | |
| // implement that anyway. So the CFA is inaccurate at the first instruction. | |
| //.cfi_def_cfa_encoded_addr EXC_ENTRY_CFA | |
| // Since the percpu_ptr register is reserved in all kernel code, and not | |
| // actually used at all by phys code, we can clobber it here to avoid | |
| // touching any other register whose value might be interesting to dump, | |
| // while also not relying on the incoming SP value. When the exception is | |
| // from the current EL, the incoming SP might be close to overflow or | |
| // entirely bogus but it's definitely interesting state to dump. | |
| adr_global percpu_ptr, EXC_ENTRY_CFA | |
| // The interrupted percpu_ptr has been clobbered and cannot be recovered. | |
| .cfi_undefined percpu_ptr | |
| // Temporarily use the percpu_ptr as the CFA. It will become the new SP. | |
| .cfi_def_cfa percpu_ptr, 0 | |
| // First save the incoming x30 and sp so we have two registers to work with. | |
| str x30, [percpu_ptr, #REGS_X(30)] | |
| mov x30, sp | |
| str x30, [percpu_ptr, #REGS_SP] | |
| // Now we can reset the SP to the reserved exception stack, just below where | |
| // the registers will be saved. This is the SP value that's the CFA. | |
| mov sp, percpu_ptr | |
| .cfi_def_cfa_register sp | |
| // Update CFI for the registers now visible relative to the CFA (the SP). | |
| .cfi_offset x30, REGS_X(30) | |
| .cfi_offset sp, REGS_SP | |
| // Spill the first pair of registers inline so we get some more to use here. | |
| stp_cfa 0, 1 | |
| // The first argument register gets the vector offset as a handy identifier. | |
| mov x0, #\offset | |
| // The second argument register gets the name of this entry point. | |
| adr_global x1, 1f | |
| .pushsection .rodata.str1.1, "aMS?", %progbits, 1 | |
| 1: .string "\name" | |
| .popsection | |
| // Now we can do the rest out of line. | |
| b phys_exception_trampoline | |
| .endm | |
| // Define the vector table for phys. There are no .vbar_function definitions | |
| // here so they all get the default defined above. | |
| .vbar_table phys_exception, global, phys_exception_entry | |
| .end_vbar_table | |
| // This is reached by each phys_exception_entry, with x0, x1, x30, sp already | |
| // saved in the zx_thread_state_general_regs_t that sp now points to. | |
| .function phys_exception_trampoline, cfi=custom | |
| // This is discontiguous code so it gets its own fresh CFI state (FDE). | |
| // Reset the CFI state to just as it was at the end of phys_exception_entry. | |
| .vbar_function.cfi | |
| // The SP that points to the saved registers is the CFA. This is still the | |
| // same "frame" as the phys_exception_entry code fragment, though it's at a | |
| // discontiguous PC location. | |
| .cfi_def_cfa sp, 0 | |
| // These registers were already saved in the phys_exception_entry code above. | |
| .cfi_offset x0, REGS_X(0) | |
| .cfi_offset x1, REGS_X(1) | |
| .cfi_offset x30, REGS_X(30) | |
| .cfi_offset sp, REGS_SP | |
| // Now save the remaining registers in the zx_thread_state_general_regs_t at | |
| // sp and update CFI to find them relative to the CFA. | |
| .for_each_pair stp_cfa | |
| // Normal registers are all free as scratch right now, except x0 and x1. | |
| // Use a few to collect the special registers we need to record. | |
| // Which ones they are depends on the current EL. | |
| mrs x2, CurrentEL | |
| // First get the SPSR_ELx for the current EL. | |
| cmp x2, #CURRENT_EL_EL_FIELD(1) | |
| beq 1f | |
| cmp x2, #CURRENT_EL_EL_FIELD(2) | |
| beq 2f | |
| 3:mrs x3, SPSR_EL3 | |
| b 0f | |
| 2:mrs x3, SPSR_EL2 | |
| b 0f | |
| 1:mrs x3, SPSR_EL1 | |
| 0: | |
| // These bits in SPSR_ELx indicate what EL the exception came from. | |
| // Now fetch the SPSR_ELx for that EL. | |
| and x4, x3, #CURRENT_EL_EL | |
| .macro exc_regs_for_el el | |
| mrs x5, SPSR_EL\el | |
| mrs x6, TPIDR_EL\el | |
| mrs x7, ELR_EL\el | |
| mrs x8, ESR_EL\el | |
| mrs x9, FAR_EL\el | |
| .endm | |
| cmp x4, #CURRENT_EL_EL_FIELD(1) | |
| beq 1f | |
| cmp x4, #CURRENT_EL_EL_FIELD(2) | |
| beq 2f | |
| 3:exc_regs_for_el 3 | |
| b 0f | |
| 2:exc_regs_for_el 2 | |
| b 0f | |
| 1:exc_regs_for_el 1 | |
| 0: | |
| #if REGS_CPSR + 8 != REGS_TPIDR | |
| #error zx_thread_state_general_regs_t expected cpsr before tpidr | |
| #endif | |
| stp x5, x6, [sp, REGS_CPSR] | |
| str x7, [sp, REGS_PC] | |
| #if REGS_ESR + 8 != REGS_FAR | |
| #error zx_exception_context_t expected arm.esr before arm.far | |
| #endif | |
| stp x8, x9, [sp, REGS_ESR] | |
| stp xzr, xzr, [sp, REGS_XZR] | |
| #if __has_feature(shadow_call_Stack) | |
| // Reset the shadow call stack to the one reserved for phys exceptions. | |
| adr_global shadow_call_sp, phys_exception_shadow_call_stack // Grows up. | |
| #endif | |
| // Reset the thread pointer, though nothing should have changed it. | |
| adr_global x8, boot_thread_pointer | |
| msr TPIDR_EL1, x8 | |
| // We now have complete CFI representing all the general registers so a | |
| // debugger can unwind through this frame and back to the interrupted code. | |
| // A normal call into C++ code will permit unwinding back into this frame. | |
| // All the ABI stacks have been set up safely so normal C++ code can run. | |
| // The first two argument registers were loaded in phys_exception_entry. | |
| // Load the third argument register with the zx_thread_state_general_regs_t*. | |
| mov x2, sp | |
| // Make a first frame pointer record on the exception stack that links to | |
| // the interrupted state's FP and PC. | |
| stp x29, x7, [sp, #-16]! | |
| .cfi_adjust_cfa_offset 16 | |
| mov x29, sp | |
| // Show the interrupted PC as the earliest caller on the shadow call stack. | |
| // A backtrace via the exception shadow call stack won't go past there like | |
| // the frame pointer might, but it will at least clearly bottom out at the | |
| // exception PC. | |
| #if __has_feature(shadow_call_stack) | |
| str x7, [shadow_call_sp], #8 | |
| #endif | |
| // uint64_t PhysException(uint64_t vector_offset, const char* vector_name, | |
| // zx_thread_state_general_regs_t* regs); | |
| // x0 = vector_offset | |
| // x1 = vector_name | |
| // x2 = regs | |
| bl PhysException | |
| // If it returns the magic number, we can resume. | |
| movlit x1, PHYS_EXCEPTION_RESUME | |
| cmp x0, x1 | |
| beq .Lresume_from_exception | |
| // Otherwise it really shouldn't have returned. Trigger an exception reentry. | |
| // What else? | |
| .label PhysExceptionHandler_returned_BUG, global | |
| brk #0 | |
| b PhysExceptionHandler_returned_BUG | |
| .Lresume_from_exception: | |
| // The handler code should have restored the SP it got on entry. | |
| cmp sp, x29 | |
| bne PhysExceptionHandler_returned_BUG | |
| // Pop the frame pointer link pair pushed above. | |
| add sp, sp, 16 | |
| .cfi_adjust_cfa_offset -16 | |
| // Reload the interrupted x0..x29 registers. | |
| ldp_cfa 0, 1 | |
| .for_each_pair ldp_cfa | |
| // Reload the interrupted x30 and sp registers. | |
| ldp x30, percpu_ptr, [sp, #REGS_X(30)] | |
| .cfi_same_value x30 | |
| // The SP isn't allowed in a load instruction, so stage it through the | |
| // reserved percpu_ptr. This one register will always be reset (to match | |
| // the interrupted SP). | |
| mov sp, percpu_ptr | |
| .cfi_same_value sp | |
| .cfi_def_cfa_register percpu_ptr | |
| // Return to the interrupted PC. The C++ code is responsible for updating | |
| // ELR_ELx. We don't reload it from [sp, #REGS_PC] here. Likewise if | |
| // returning to a lower EL, the C++ code must update SPSR_ELx first. | |
| eret | |
| .end_function // phys_exception_save |