diff --git a/nx/include/switch/arm/thread_context.h b/nx/include/switch/arm/thread_context.h index 1a596d1c..b97710e5 100644 --- a/nx/include/switch/arm/thread_context.h +++ b/nx/include/switch/arm/thread_context.h @@ -34,6 +34,17 @@ typedef enum { RegisterGroup_All = RegisterGroup_CpuAll | RegisterGroup_FpuAll, ///< All registers. } RegisterGroup; +/// This is for \ref ThreadExceptionDump error_desc. +typedef enum { + ThreadExceptionDesc_InstructionAbort = 0x100, ///< Instruction abort + ThreadExceptionDesc_MisalignedPC = 0x102, ///< Misaligned PC + ThreadExceptionDesc_MisalignedSP = 0x103, ///< Misaligned SP + ThreadExceptionDesc_SError = 0x106, ///< SError [not in 1.0.0?] + ThreadExceptionDesc_BadSVC = 0x301, ///< Bad SVC + ThreadExceptionDesc_Trap = 0x104, ///< Uncategorized, CP15RTTrap, CP15RRTTrap, CP14RTTrap, CP14RRTTrap, IllegalState, SystemRegisterTrap + ThreadExceptionDesc_Other = 0x101, ///< None of the above, EC <= 0x34 and not a breakpoint +} ThreadExceptionDesc; + /// Thread context structure (register dump) typedef struct { CpuRegister cpu_gprs[29]; ///< GPRs 0..28. Note: also contains AArch32 SPRs. @@ -50,6 +61,54 @@ typedef struct { u64 tpidr; ///< EL0 Read/Write Software Thread ID Register. } ThreadContext; +/// Thread exception dump structure. +typedef struct { + u32 error_desc; ///< See \ref ThreadExceptionDesc. + u32 pad[3]; + + CpuRegister cpu_gprs[29]; ///< GPRs 0..28. Note: also contains AArch32 registers. + CpuRegister fp; ///< Frame pointer. + CpuRegister lr; ///< Link register. + CpuRegister sp; ///< Stack pointer. + CpuRegister pc; ///< Program counter (elr_el1). + + u64 padding; + + FpuRegister fpu_gprs[32]; ///< 32 general-purpose NEON registers. + + u32 pstate; ///< pstate & 0xFF0FFE20 + u32 afsr0; + u32 afsr1; + u32 esr; + + CpuRegister far; ///< Fault Address Register. +} ThreadExceptionDump; + +typedef struct { + u64 cpu_gprs[9]; ///< GPRs 0..8. + u64 lr; + u64 sp; + u64 elr_el1; + u32 pstate; ///< pstate & 0xFF0FFE20 + u32 afsr0; + u32 afsr1; + u32 esr; + u64 far; +} ThreadExceptionFrameA64; + +typedef struct { + u32 cpu_gprs[8]; ///< GPRs 0..7. + u32 sp; + u32 lr; + u32 elr_el1; + u32 tpidr_el0; ///< tpidr_el0 = 1 + u32 cpsr; ///< cpsr & 0xFF0FFE20 + u32 afsr0; + u32 afsr1; + u32 esr; + u32 far; +} ThreadExceptionFrameA32; + /** * @brief Determines whether a thread context belong to an AArch64 process based on the PSR. * @param[in] ctx Thread context to which PSTATE/cspr has been dumped to. @@ -59,3 +118,13 @@ static inline bool threadContextIsAArch64(const ThreadContext *ctx) { return (ctx->psr & 0x10) == 0; } + +/** + * @brief Determines whether a ThreadExceptionDump belongs to an AArch64 process based on the PSTATE. + * @param[in] ctx ThreadExceptionDump. + * @return true if and only if the ThreadExceptionDump belongs to an AArch64 process. + */ +static inline bool threadExceptionIsAArch64(const ThreadExceptionDump *ctx) +{ + return (ctx->pstate & 0x10) == 0; +} diff --git a/nx/include/switch/kernel/svc.h b/nx/include/switch/kernel/svc.h index c4f9ad72..c43ed999 100644 --- a/nx/include/switch/kernel/svc.h +++ b/nx/include/switch/kernel/svc.h @@ -510,6 +510,13 @@ Result svcOutputDebugString(const char *str, u64 size); ///@name Miscellaneous ///@{ +/** + * @brief Returns from an exception. + * @param[in] res Result code. + * @note Syscall number 0x28. + */ +void NORETURN svcReturnFromException(Result res); + /** * @brief Retrieves information about the system, or a certain kernel object. * @param[out] out Variable to which store the information. diff --git a/nx/source/kernel/svc.s b/nx/source/kernel/svc.s index bad320b6..e11fe61e 100644 --- a/nx/source/kernel/svc.s +++ b/nx/source/kernel/svc.s @@ -235,6 +235,11 @@ SVC_BEGIN svcOutputDebugString ret SVC_END +SVC_BEGIN svcReturnFromException + svc 0x28 + ret +SVC_END + SVC_BEGIN svcGetInfo str x0, [sp, #-16]! svc 0x29 diff --git a/nx/source/runtime/exception.s b/nx/source/runtime/exception.s new file mode 100644 index 00000000..e7f6a522 --- /dev/null +++ b/nx/source/runtime/exception.s @@ -0,0 +1,182 @@ +.macro CODE_BEGIN name + .section .text.\name, "ax", %progbits + .global \name + .type \name, %function + .align 2 + .cfi_startproc +\name: +.endm + +.macro CODE_END + .cfi_endproc +.endm + +// Called by crt0 when the args at the time of entry indicate an exception occured. + +.weak __libnx_exception_entry +CODE_BEGIN __libnx_exception_entry + cmp x1, #0 + beq __libnx_exception_entry_abort + + // Load IsCurrentProcessBeingDebugged. + + stp x9, x10, [sp, #-16]! + stp x11, x12, [sp, #-16]! + stp x13, x14, [sp, #-16]! + stp x15, x16, [sp, #-16]! + stp x17, x18, [sp, #-16]! + stp x19, x20, [sp, #-16]! + str x21, [sp, #-16]! + + stp x0, x1, [sp, #-16]! + sub sp, sp, #16 + + mov x0, sp + mov x1, #8 + mov w2, wzr + mov x3, #0 + bl svcGetInfo + mov w6, w0 + ldr x7, [sp], #16 + + ldp x0, x1, [sp], #16 + + ldr x21, [sp], #16 + ldp x19, x20, [sp], #16 + ldp x17, x18, [sp], #16 + ldp x15, x16, [sp], #16 + ldp x13, x14, [sp], #16 + ldp x11, x12, [sp], #16 + ldp x9, x10, [sp], #16 + + // Abort when svcGetInfo failed. + cbnz w6, __libnx_exception_entry_abort + + // Abort when IsCurrentProcessBeingDebugged is set where __nx_exception_ignoredebug==0. + adrp x6, __nx_exception_ignoredebug + ldr w5, [x6, #:lo12:__nx_exception_ignoredebug] + + cbnz w5, __libnx_exception_entry_start + cbnz x7, __libnx_exception_entry_abort + +__libnx_exception_entry_start: + adrp x2, __nx_exceptiondump + add x2, x2, #:lo12:__nx_exceptiondump + mov x5, x2 + + // error_desc + str w0, [x2], #4 + // pad + str wzr, [x2], #4 + str wzr, [x2], #4 + str wzr, [x2], #4 + + // GPRs 0..8 + ldp x3, x4, [x1] + str x5, [x1], #16 // x0 = __nx_exceptiondump + stp x3, x4, [x2], #16 + ldp x3, x4, [x1], #16 + stp x3, x4, [x2], #16 + ldp x3, x4, [x1], #16 + stp x3, x4, [x2], #16 + ldp x3, x4, [x1], #16 + stp x3, x4, [x2], #16 + ldr x3, [x1], #8 + str x3, [x2], #8 + + // GPRs 9..28 + str x9, [x2], #8 + stp x10, x11, [x2], #16 + stp x12, x13, [x2], #16 + stp x14, x15, [x2], #16 + stp x16, x17, [x2], #16 + stp x18, x19, [x2], #16 + stp x20, x21, [x2], #16 + stp x22, x23, [x2], #16 + stp x24, x25, [x2], #16 + stp x26, x27, [x2], #16 + str x28, [x2], #8 + + // fp + str x29, [x2], #8 + + // lr + ldr x3, [x1], #8 + str x3, [x2], #8 + + // sp + adrp x4, __nx_exception_stack + add x4, x4, #:lo12:__nx_exception_stack + + adrp x5, __nx_exception_stack_size + ldr x5, [x5, #:lo12:__nx_exception_stack_size] + add x4, x4, x5 + + ldr x3, [x1] + str x4, [x1], #8 // sp = __nx_exception_stack + __nx_exception_stack_size + str x3, [x2], #8 + + // elr_el1 (pc) + adrp x4, __libnx_exception_returnentry + add x4, x4, #:lo12:__libnx_exception_returnentry + + ldr x3, [x1] + str x4, [x1], #8 // elr_el1 = __libnx_exception_returnentry + str x3, [x2], #8 + + // padding + str xzr, [x2], #8 + + // fpu_gprs + stp q0, q1, [x2], #32 + stp q2, q3, [x2], #32 + stp q4, q5, [x2], #32 + stp q6, q7, [x2], #32 + stp q8, q9, [x2], #32 + stp q10, q11, [x2], #32 + stp q12, q13, [x2], #32 + stp q14, q15, [x2], #32 + stp q16, q17, [x2], #32 + stp q18, q19, [x2], #32 + stp q20, q21, [x2], #32 + stp q22, q23, [x2], #32 + stp q24, q25, [x2], #32 + stp q26, q27, [x2], #32 + stp q28, q29, [x2], #32 + stp q30, q31, [x2], #32 + + // 4 u32s: pstate, afsr0, afsr1, and esr. + ldr w3, [x1], #4 + str w3, [x2], #4 + ldr w3, [x1], #4 + str w3, [x2], #4 + ldr w3, [x1], #4 + str w3, [x2], #4 + ldr w3, [x1], #4 + str w3, [x2], #4 + + //far + ldr x3, [x1], #8 + str x3, [x2], #8 + + mov w0, wzr + b __libnx_exception_entry_end + +__libnx_exception_entry_abort: + mov w0, #0xf801 +__libnx_exception_entry_end: + bl svcReturnFromException + b . +CODE_END + +// Jumped to by kernel in svcReturnFromException via the overridden elr_el1, with x0 set to __nx_exceptiondump. +CODE_BEGIN __libnx_exception_returnentry + bl __libnx_exception_handler + + mov w0, wzr + mov x1, #0 + mov x2, #0 + bl svcBreak + b . +CODE_END + diff --git a/nx/source/runtime/init.c b/nx/source/runtime/init.c index e729c5ce..0a4ea64f 100644 --- a/nx/source/runtime/init.c +++ b/nx/source/runtime/init.c @@ -21,6 +21,14 @@ extern u32 __nx_applet_type; // Must be a multiple of 0x200000. __attribute__((weak)) size_t __nx_heap_size = 0; +/// Override these with your own if you're using __libnx_exception_handler. __nx_exception_stack is the stack-bottom. +__attribute__((weak)) alignas(16) u8 __nx_exception_stack[0x400]; +__attribute__((weak)) u64 __nx_exception_stack_size = sizeof(__nx_exception_stack); +/// By default exception handling will be aborted when the current process is being debugged. Set this to non-zero to disable that. +__attribute__((weak)) u32 __nx_exception_ignoredebug = 0; + +ThreadExceptionDump __nx_exceptiondump; + /* There are three ways of allocating heap: @@ -166,3 +174,8 @@ void __attribute__((weak)) NORETURN __libnx_exit(int rc) __nx_exit(0, envGetExitFuncPtr()); } + +/// You can override this with your own func to handle exceptions. See here: https://switchbrew.org/wiki/SVC#Exception_handling +void __attribute__((weak)) __libnx_exception_handler(ThreadExceptionDump *ctx) +{ +} diff --git a/nx/source/runtime/switch_crt0.s b/nx/source/runtime/switch_crt0.s index 226280d3..c06df8fb 100644 --- a/nx/source/runtime/switch_crt0.s +++ b/nx/source/runtime/switch_crt0.s @@ -9,16 +9,24 @@ _start: .org _start+0x80 startup: // save lr - mov x27, x30 + mov x7, x30 // get aslr base bl +4 - sub x28, x30, #0x88 + sub x6, x30, #0x88 // context ptr and main thread handle - mov x25, x0 - mov x26, x1 + mov x5, x0 + mov x4, x1 + // Handle the exception if needed. + // if (ctx != NULL && main_thread != -1)__libnx_exception_entry(); + cmp x5, #0 + ccmn x4, #1, #4, ne // 4 = Z + beq bssclr_start + b __libnx_exception_entry + +bssclr_start: // clear .bss adrp x0, __bss_start__ adrp x1, __bss_end__ @@ -39,15 +47,15 @@ bss_loop: str x1, [x0, #:lo12:__stack_top] // process .dynamic section - mov x0, x28 + mov x0, x6 adrp x1, _DYNAMIC add x1, x1, #:lo12:_DYNAMIC bl __nx_dynamic // initialize system - mov x0, x25 - mov x1, x26 - mov x2, x27 + mov x0, x5 + mov x1, x4 + mov x2, x7 bl __libnx_init // call entrypoint