From 83f733ce595eaeda14e6510ddc153e75fe6f6905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C3=85berg?= Date: Tue, 16 Feb 2021 15:38:58 +0100 Subject: [PATCH] SPARC: improve fatal log MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fatal log now contains - Trap type in human readable representation - Integer registers visible to the program when trap was taken - Special register values such as PC and PSR - Backtrace with PC and SP If CONFIG_EXTRA_EXCEPTION_INFO is enabled, then all the above is logged. If not, only the special registers are logged. The format is inspired by the GRMON debug monitor and TSIM simulator. A quick guide on how to use the values is in fatal.c. It now looks like this: E: tt = 0x02, illegal_instruction E: E: INS LOCALS OUTS GLOBALS E: 0: 00000000 f3900fc0 40007c50 00000000 E: 1: 00000000 40004bf0 40008d30 40008c00 E: 2: 00000000 40004bf4 40008000 00000003 E: 3: 40009158 00000000 40009000 00000002 E: 4: 40008fa8 40003c00 40008fa8 00000008 E: 5: 40009000 f3400fc0 00000000 00000080 E: 6: 4000a1f8 40000050 4000a190 00000000 E: 7: 40002308 00000000 40001fb8 000000c1 E: E: psr: f30000c7 wim: 00000008 tbr: 40000020 y: 00000000 E: pc: 4000a1f4 npc: 4000a1f8 E: E: pc sp E: #0 4000a1f4 4000a190 E: #1 40002308 4000a1f8 E: #2 40003b24 4000a258 Signed-off-by: Martin Ã…berg --- arch/Kconfig | 1 + arch/sparc/core/fatal.c | 207 +++++++++++++++++- arch/sparc/core/fault_trap.S | 40 +++- arch/sparc/core/offsets/offsets.c | 4 +- .../generic_leon3/generic_leon3_defconfig | 1 + boards/sparc/qemu_leon3/qemu_leon3_defconfig | 1 + include/arch/sparc/arch.h | 6 +- 7 files changed, 243 insertions(+), 17 deletions(-) diff --git a/arch/Kconfig b/arch/Kconfig index be5beb5108fb..bf10957ede5e 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -46,6 +46,7 @@ config SPARC select ATOMIC_OPERATIONS_BUILTIN if SPARC_CASA select ATOMIC_OPERATIONS_C if !SPARC_CASA select ARCH_HAS_THREAD_LOCAL_STORAGE + select ARCH_HAS_EXTRA_EXCEPTION_INFO help SPARC architecture diff --git a/arch/sparc/core/fatal.c b/arch/sparc/core/fatal.c index f50b53c2021e..e693dd124095 100644 --- a/arch/sparc/core/fatal.c +++ b/arch/sparc/core/fatal.c @@ -8,18 +8,211 @@ #include LOG_MODULE_DECLARE(os, CONFIG_KERNEL_LOG_LEVEL); +/* + * EXAMPLE OUTPUT + * + * --------------------------------------------------------------------- + * + * tt = 0x02, illegal_instruction + * + * INS LOCALS OUTS GLOBALS + * 0: 00000000 f3900fc0 40007c50 00000000 + * 1: 00000000 40004bf0 40008d30 40008c00 + * 2: 00000000 40004bf4 40008000 00000003 + * 3: 40009158 00000000 40009000 00000002 + * 4: 40008fa8 40003c00 40008fa8 00000008 + * 5: 40009000 f3400fc0 00000000 00000080 + * 6: 4000a1f8 40000050 4000a190 00000000 + * 7: 40002308 00000000 40001fb8 000000c1 + * + * psr: f30000c7 wim: 00000008 tbr: 40000020 y: 00000000 + * pc: 4000a1f4 npc: 4000a1f8 + * + * pc sp + * #0 4000a1f4 4000a190 + * #1 40002308 4000a1f8 + * #2 40003b24 4000a258 + * + * --------------------------------------------------------------------- + * + * + * INTERPRETATION + * + * INS, LOCALS, OUTS and GLOBALS represent the %i, %l, %o and %g + * registers before the trap was taken. + * + * wim, y, pc and npc are the values before the trap was taken. + * tbr has the tbr.tt field (bits 11..4) filled in by hardware + * representing the current trap type. psr is read immediately + * after the trap was taken so it will have the new CWP and ET=0. + * + * The "#i pc sp" rows is the stack backtrace. All register + * windows are flushed to the stack prior to printing. First row + * is the trapping pc and sp (o6). + * + * + * HOW TO USE + * + * When invesetigating a crashed program, the first things to look + * at is typically the tt, pc and sp (o6). You can lookup the pc + * in the assembly list file or use addr2line. In the listing, the + * register values in the table above can be used. The linker map + * file will give a hint on which stack is active and if it has + * overflowed. + * + * psr bits 11..8 is the processor interrupt (priority) level. 0 + * is lowest priority level (all can be taken), and 0xf is the + * highest level where only non-maskable interrupts are taken. + * + * g0 is always zero. g5, g6 are never accessed by the compiler. + * g7 is the TLS pointer if enabled. A SAVE instruction decreases + * the current window pointer (psr bits 4..0) which results in %o + * registers becoming %i registers and a new set of %l registers + * appear. RESTORE does the oppposite. + */ + + +/* + * The SPARC V8 ABI guarantees that the stack pointer register + * (o6) points to an area organized as "struct savearea" below at + * all times when traps are enabled. This is the register save + * area where register window registers can be flushed to the + * stack. + * + * We flushed registers to this space in the fault trap entry + * handler. Note that the space is allocated by the ABI (compiler) + * for each stack frame. + * + * When printing the registers, we get the "local" and "in" + * registers from the ABI stack save area, while the "out" and + * "global" registares are taken from the exception stack frame + * generated in the fault trap entry. + */ +struct savearea { + uint32_t local[8]; + uint32_t in[8]; +}; + + +/* + * Exception trap type (tt) values according to The SPARC V8 + * manual, Table 7-1. + */ +static const struct { + int tt; + const char *desc; +} TTDESC[] = { + { .tt = 0x02, .desc = "illegal_instruction", }, + { .tt = 0x07, .desc = "mem_address_not_aligned", }, + { .tt = 0x2B, .desc = "data_store_error", }, + { .tt = 0x29, .desc = "data_access_error", }, + { .tt = 0x09, .desc = "data_access_exception", }, + { .tt = 0x21, .desc = "instruction_access_error", }, + { .tt = 0x01, .desc = "instruction_access_exception", }, + { .tt = 0x04, .desc = "fp_disabled", }, + { .tt = 0x08, .desc = "fp_exception", }, + { .tt = 0x2A, .desc = "division_by_zero", }, + { .tt = 0x03, .desc = "privileged_instruction", }, + { .tt = 0x20, .desc = "r_register_access_error", }, + { .tt = 0x0B, .desc = "watchpoint_detected", }, + { .tt = 0x2C, .desc = "data_access_MMU_miss", }, + { .tt = 0x3C, .desc = "instruction_access_MMU_miss", }, + { .tt = 0x05, .desc = "window_overflow", }, + { .tt = 0x06, .desc = "window_underflow", }, + { .tt = 0x0A, .desc = "tag_overflow", }, +}; + +static void print_trap_type(const z_arch_esf_t *esf) +{ + const int tt = (esf->tbr & TBR_TT) >> TBR_TT_BIT; + const char *desc = "unknown"; + + if (tt & 0x80) { + desc = "trap_instruction"; + } else if (tt >= 0x11 && tt <= 0x1F) { + desc = "interrupt"; + } else { + for (int i = 0; i < ARRAY_SIZE(TTDESC); i++) { + if (TTDESC[i].tt == tt) { + desc = TTDESC[i].desc; + break; + } + } + } + LOG_ERR("tt = 0x%02X, %s", tt, desc); +} + +static void print_integer_registers(const z_arch_esf_t *esf) +{ + const struct savearea *flushed = (struct savearea *) esf->out[6]; + + LOG_ERR(" INS LOCALS OUTS GLOBALS"); + for (int i = 0; i < 8; i++) { + LOG_ERR( + " %d: %08x %08x %08x %08x", + i, + flushed ? flushed->in[i] : 0, + flushed ? flushed->local[i] : 0, + esf->out[i], + esf->global[i] + ); + } +} + +static void print_special_registers(const z_arch_esf_t *esf) +{ + LOG_ERR( + "psr: %08x wim: %08x tbr: %08x y: %08x", + esf->psr, esf->wim, esf->tbr, esf->y + ); + LOG_ERR(" pc: %08x npc: %08x", esf->pc, esf->npc); +} + +static void print_backtrace(const z_arch_esf_t *esf) +{ + const int MAX_LOGLINES = 40; + const struct savearea *s = (struct savearea *) esf->out[6]; + + LOG_ERR(" pc sp"); + LOG_ERR(" #0 %08x %08x", esf->pc, (unsigned int) s); + for (int i = 1; s && i < MAX_LOGLINES; i++) { + const uint32_t pc = s->in[7]; + const uint32_t sp = s->in[6]; + + if (sp == 0 && pc == 0) { + break; + } + LOG_ERR(" #%-2d %08x %08x", i, pc, sp); + if (sp == 0 || sp & 7) { + break; + } + s = (const struct savearea *) sp; + } +} + +static void print_all(const z_arch_esf_t *esf) +{ + LOG_ERR(""); + print_trap_type(esf); + LOG_ERR(""); + print_integer_registers(esf); + LOG_ERR(""); + print_special_registers(esf); + LOG_ERR(""); + print_backtrace(esf); + LOG_ERR(""); +} + FUNC_NORETURN void z_sparc_fatal_error(unsigned int reason, const z_arch_esf_t *esf) { if (esf != NULL) { - LOG_ERR(" pc: %08x", esf->pc); - LOG_ERR("npc: %08x", esf->npc); - LOG_ERR("psr: %08x", esf->psr); - LOG_ERR("tbr: %08x", esf->tbr); - LOG_ERR(" sp: %08x", esf->sp); - LOG_ERR(" y: %08x", esf->y); + if (IS_ENABLED(CONFIG_EXTRA_EXCEPTION_INFO)) { + print_all(esf); + } else { + print_special_registers(esf); + } } - z_fatal_error(reason, esf); CODE_UNREACHABLE; } diff --git a/arch/sparc/core/fault_trap.S b/arch/sparc/core/fault_trap.S index c5db3526f92f..70427edf6509 100644 --- a/arch/sparc/core/fault_trap.S +++ b/arch/sparc/core/fault_trap.S @@ -31,8 +31,13 @@ GTEXT(__sparc_trap_fault) SECTION_FUNC(TEXT, __sparc_trap_except_reason) mov %g1, %l7 .Ldoit: + /* %g2, %g3 are used at manual window overflow so save temporarily */ + mov %g2, %l4 + mov %g3, %l5 + /* We may have trapped into the invalid window. If so, make it valid. */ rd %wim, %g2 + mov %g2, %l3 srl %g2, %l0, %g3 cmp %g3, 1 bne .Lwodone @@ -63,6 +68,9 @@ SECTION_FUNC(TEXT, __sparc_trap_except_reason) restore .Lwodone: + mov %l4, %g2 + mov %l5, %g3 + /* Allocate an ABI stack frame and exception stack frame */ sub %fp, 96 + __z_arch_esf_t_SIZEOF, %sp /* @@ -72,13 +80,20 @@ SECTION_FUNC(TEXT, __sparc_trap_except_reason) mov %l7, %o0 /* Fill in the content of the exception stack frame */ - st %l1, [%sp + 96 + __z_arch_esf_t_pc_OFFSET] - st %l2, [%sp + 96 + __z_arch_esf_t_npc_OFFSET] - st %l0, [%sp + 96 + __z_arch_esf_t_psr_OFFSET] - st %l6, [%sp + 96 + __z_arch_esf_t_tbr_OFFSET] - st %fp, [%sp + 96 + __z_arch_esf_t_sp_OFFSET] - rd %y, %g1 - st %g1, [%sp + 96 + __z_arch_esf_t_y_OFFSET] +#if defined(CONFIG_EXTRA_EXCEPTION_INFO) + std %i0, [%sp + 96 + __z_arch_esf_t_out_OFFSET + 0x00] + std %i2, [%sp + 96 + __z_arch_esf_t_out_OFFSET + 0x08] + std %i4, [%sp + 96 + __z_arch_esf_t_out_OFFSET + 0x10] + std %i6, [%sp + 96 + __z_arch_esf_t_out_OFFSET + 0x18] + std %g0, [%sp + 96 + __z_arch_esf_t_global_OFFSET + 0x00] + std %g2, [%sp + 96 + __z_arch_esf_t_global_OFFSET + 0x08] + std %g4, [%sp + 96 + __z_arch_esf_t_global_OFFSET + 0x10] + std %g6, [%sp + 96 + __z_arch_esf_t_global_OFFSET + 0x18] +#endif + std %l0, [%sp + 96 + __z_arch_esf_t_psr_OFFSET] /* psr pc */ + std %l2, [%sp + 96 + __z_arch_esf_t_npc_OFFSET] /* npc wim */ + rd %y, %l7 + std %l6, [%sp + 96 + __z_arch_esf_t_tbr_OFFSET] /* tbr y */ /* Enable traps, raise PIL to mask all maskable interrupts. */ or %l0, PSR_PIL, %o2 @@ -86,6 +101,17 @@ SECTION_FUNC(TEXT, __sparc_trap_except_reason) nop nop nop + +#if defined(CONFIG_EXTRA_EXCEPTION_INFO) + /* Flush all register windows to the stack. */ + .rept CONFIG_SPARC_NWIN-1 + save %sp, -64, %sp + .endr + .rept CONFIG_SPARC_NWIN-1 + restore + .endr +#endif + /* * reason is the first argument. * Exception stack frame prepared earlier is the second argument. diff --git a/arch/sparc/core/offsets/offsets.c b/arch/sparc/core/offsets/offsets.c index cb4e2a8a4859..328ae1f30afb 100644 --- a/arch/sparc/core/offsets/offsets.c +++ b/arch/sparc/core/offsets/offsets.c @@ -39,11 +39,13 @@ GEN_OFFSET_SYM(_callee_saved_t, o6); GEN_OFFSET_SYM(_callee_saved_t, o7); /* esf member offsets */ +GEN_OFFSET_SYM(z_arch_esf_t, out); +GEN_OFFSET_SYM(z_arch_esf_t, global); GEN_OFFSET_SYM(z_arch_esf_t, pc); GEN_OFFSET_SYM(z_arch_esf_t, npc); GEN_OFFSET_SYM(z_arch_esf_t, psr); +GEN_OFFSET_SYM(z_arch_esf_t, wim); GEN_OFFSET_SYM(z_arch_esf_t, tbr); -GEN_OFFSET_SYM(z_arch_esf_t, sp); GEN_OFFSET_SYM(z_arch_esf_t, y); GEN_ABSOLUTE_SYM(__z_arch_esf_t_SIZEOF, STACK_ROUND_UP(sizeof(z_arch_esf_t))); diff --git a/boards/sparc/generic_leon3/generic_leon3_defconfig b/boards/sparc/generic_leon3/generic_leon3_defconfig index 446ae9e9b309..861413149db5 100644 --- a/boards/sparc/generic_leon3/generic_leon3_defconfig +++ b/boards/sparc/generic_leon3/generic_leon3_defconfig @@ -8,3 +8,4 @@ CONFIG_SERIAL=y CONFIG_UART_APBUART=y CONFIG_UART_CONSOLE=y CONFIG_LEON_GPTIMER=y +CONFIG_EXTRA_EXCEPTION_INFO=y diff --git a/boards/sparc/qemu_leon3/qemu_leon3_defconfig b/boards/sparc/qemu_leon3/qemu_leon3_defconfig index 8c232b7de88c..905900f3f01d 100644 --- a/boards/sparc/qemu_leon3/qemu_leon3_defconfig +++ b/boards/sparc/qemu_leon3/qemu_leon3_defconfig @@ -8,4 +8,5 @@ CONFIG_SERIAL=y CONFIG_UART_APBUART=y CONFIG_UART_CONSOLE=y CONFIG_LEON_GPTIMER=y +CONFIG_EXTRA_EXCEPTION_INFO=y CONFIG_QEMU_ICOUNT_SHIFT=6 diff --git a/include/arch/sparc/arch.h b/include/arch/sparc/arch.h index 888306475e88..bac00d86e9ec 100644 --- a/include/arch/sparc/arch.h +++ b/include/arch/sparc/arch.h @@ -102,11 +102,13 @@ static inline uint32_t arch_k_cycle_get_32(void) struct __esf { + uint32_t out[8]; + uint32_t global[8]; + uint32_t psr; uint32_t pc; uint32_t npc; - uint32_t psr; + uint32_t wim; uint32_t tbr; - uint32_t sp; uint32_t y; };