Module Name: src Committed By: ryo Date: Tue Jun 7 08:08:31 UTC 2022
Modified Files: src/sys/arch/aarch64/aarch64: cpuswitch.S db_trace.c src/usr.sbin/crash/arch: aarch64.c Log Message: On aarch64, ddb backtrace can be performed without framepointer by specifying the /s modifier to the ddb trace command (trace/s, bt/s). The default is trace with framepointer (same as before). This allows backtracing even on kernels compiled with -fomit-frame-pointer. To generate a diff of this commit: cvs rdiff -u -r1.37 -r1.38 src/sys/arch/aarch64/aarch64/cpuswitch.S cvs rdiff -u -r1.17 -r1.18 src/sys/arch/aarch64/aarch64/db_trace.c cvs rdiff -u -r1.1 -r1.2 src/usr.sbin/crash/arch/aarch64.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/arch/aarch64/aarch64/cpuswitch.S diff -u src/sys/arch/aarch64/aarch64/cpuswitch.S:1.37 src/sys/arch/aarch64/aarch64/cpuswitch.S:1.38 --- src/sys/arch/aarch64/aarch64/cpuswitch.S:1.37 Tue Jun 7 04:12:10 2022 +++ src/sys/arch/aarch64/aarch64/cpuswitch.S Tue Jun 7 08:08:31 2022 @@ -1,4 +1,4 @@ -/* $NetBSD: cpuswitch.S,v 1.37 2022/06/07 04:12:10 ryo Exp $ */ +/* $NetBSD: cpuswitch.S,v 1.38 2022/06/07 08:08:31 ryo Exp $ */ /*- * Copyright (c) 2014, 2020 The NetBSD Foundation, Inc. @@ -38,7 +38,7 @@ #include "opt_ddb.h" #include "opt_kasan.h" -RCSID("$NetBSD: cpuswitch.S,v 1.37 2022/06/07 04:12:10 ryo Exp $") +RCSID("$NetBSD: cpuswitch.S,v 1.38 2022/06/07 08:08:31 ryo Exp $") ARMV8_DEFINE_OPTIONS @@ -158,6 +158,9 @@ END(cpu_switchto) * } */ ENTRY_NP(cpu_switchto_softint) +#ifdef DDB + mov x7, sp /* keep original sp for backtrace */ +#endif stp x19, x20, [sp, #-16]! /* save */ sub sp, sp, #TF_SIZE /* make switchframe */ adr x2, softint_cleanup /* return address for cpu_switchto() */ @@ -180,7 +183,13 @@ ENTRY_NP(cpu_switchto_softint) #ifdef KASAN /* clear the new stack */ stp x0, x1, [sp, #-16]! +#ifdef DDB + stp x7, lr, [sp, #-16]! /* original sp and lr */ +#endif bl _C_LABEL(kasan_softint) +#ifdef DDB + ldp x7, lr, [sp], #16 +#endif ldp x0, x1, [sp], #16 #endif @@ -212,10 +221,19 @@ ENTRY_NP(cpu_switchto_softint) #endif ENABLE_INTERRUPT +#ifdef DDB + /* for db_trace.c:db_sp_trace() */ + stp x7, lr, [sp, #-16]! /* push original sp,lr for backtrace info */ +#endif + /* softint_dispatch(pinned_lwp, ipl) */ mov x0, x19 /* x0 := pinned_lwp */ bl _C_LABEL(softint_dispatch) +#ifdef DDB + add sp, sp, #16 /* pop backtrace info */ +#endif + ldr x6, [x19, #L_PCB] /* x6 = lwp_getpcb(curlwp) */ ldr x4, [x6, #PCB_TF] /* x4 := pinned_lwp->l_addr->pcb_tf */ #ifdef DDB Index: src/sys/arch/aarch64/aarch64/db_trace.c diff -u src/sys/arch/aarch64/aarch64/db_trace.c:1.17 src/sys/arch/aarch64/aarch64/db_trace.c:1.18 --- src/sys/arch/aarch64/aarch64/db_trace.c:1.17 Thu Jun 2 05:09:01 2022 +++ src/sys/arch/aarch64/aarch64/db_trace.c Tue Jun 7 08:08:31 2022 @@ -1,4 +1,4 @@ -/* $NetBSD: db_trace.c,v 1.17 2022/06/02 05:09:01 ryo Exp $ */ +/* $NetBSD: db_trace.c,v 1.18 2022/06/07 08:08:31 ryo Exp $ */ /* * Copyright (c) 2017 Ryo Shimizu <r...@nerv.org> @@ -28,9 +28,10 @@ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: db_trace.c,v 1.17 2022/06/02 05:09:01 ryo Exp $"); +__KERNEL_RCSID(0, "$NetBSD: db_trace.c,v 1.18 2022/06/07 08:08:31 ryo Exp $"); #include <sys/param.h> +#include <sys/bitops.h> #include <sys/proc.h> #include <aarch64/db_machdep.h> @@ -150,6 +151,7 @@ getlwpnamebysp(uint64_t sp) } #define TRACEFLAG_LOOKUPLWP 0x00000001 +#define TRACEFLAG_USERSPACE 0x00000002 static void pr_traceaddr(const char *prefix, uint64_t frame, uint64_t pc, int flags, @@ -182,6 +184,420 @@ pr_traceaddr(const char *prefix, uint64_ } } +static __inline uint64_t +SignExtend(int bitwidth, uint64_t imm, unsigned int multiply) +{ + const uint64_t signbit = ((uint64_t)1 << (bitwidth - 1)); + const uint64_t immmax = signbit << 1; + + if (imm & signbit) + imm -= immmax; + return imm * multiply; +} + +static __inline uint64_t +ZeroExtend(int bitwidth, uint64_t imm, unsigned int multiply) +{ + return imm * multiply; +} + +/* rotate right. if n < 0, rotate left. */ +static __inline uint64_t +rotate(int bitwidth, uint64_t v, int n) +{ + uint64_t result; + + n &= (bitwidth - 1); + result = (((v << (bitwidth - n)) | (v >> n))); + if (bitwidth < 64) + result &= ((1ULL << bitwidth) - 1); + return result; +} + +static __inline uint64_t +DecodeBitMasks(uint64_t sf, uint64_t n, uint64_t imms, uint64_t immr) +{ + const int bitwidth = (sf == 0) ? 32 : 64; + uint64_t result; + int esize, len; + + len = fls64((n << 6) + (~imms & 0x3f)) - 1; + esize = (1 << len); + imms &= (esize - 1); + immr &= (esize - 1); + result = rotate(esize, (1ULL << (imms + 1)) - 1, immr); + while (esize < bitwidth) { + result |= (result << esize); + esize <<= 1; + } + if (sf == 0) + result &= ((1ULL << bitwidth) - 1); + return result; +} + +static int +analyze_func(db_addr_t func_entry, db_addr_t pc, db_addr_t sp, + db_addr_t *lrp, vsize_t *stacksizep, + void (*pr)(const char *, ...) __printflike(1, 2)) +{ + vsize_t ssize = 0, lr_off = 0; + db_addr_t lr = 0; + uint64_t alloc_by_Xn_kvalue = 0; + uint64_t alloc_by_Xn_kmask = 0; + int alloc_by_Xn_reg = -1; + bool found_lr_off = false; + bool func_entry_autodetect = false; + +#define MAX_BACKTRACK_ANALYZE_INSN (1024 * 4) + if (func_entry == 0) { + if (pc > MAX_BACKTRACK_ANALYZE_INSN) + func_entry = pc - MAX_BACKTRACK_ANALYZE_INSN; + else + func_entry = 4; + func_entry_autodetect = true; + }; + + + /* + * Locate the following instructions that allocates a stackframe. + * Only the following patterns are supported: + * + * sub sp, sp, #ALLOCSIZE -> ssize += ALLOCSIZE + * sub sp, sp, #ALLOCSIZE, lsl #12 -> ssize += (ALLOCSIZE << 12) + * + * mov xN, #ALLOCSIZE1 + * (movk xN, #ALLOCSIZE2, lsl #xx) + * sub sp, sp, xN -> ssize += ALLOCSIZE + * + * stp x30, x??, [sp, #-ALLOCSIZE]! -> ssize =+ ALLOCSIZE, lr_off=0 + * stp x??, x30, [sp, #-ALLOCSIZE]! -> ssize =+ ALLOCSIZE, lr_off=8 + * stp x??, x??, [sp, #-ALLOCSIZE]! -> ssize =+ ALLOCSIZE + * + * str x30, [sp, #-ALLOCSIZE]! -> ssize =+ ALLOCSIZE, lr_off=0 + * + * stp x30, x??, [sp, #LR_OFF] -> lr_off = LR_OFF + * stp x??, x30, [sp, #LR_OFF] -> lr_off = LR_OFF+8 + * str x30, [sp, #LR_OFF] -> lr_off = LR_OFF + */ + +/* #define BACKTRACE_ANALYZE_DEBUG */ +#ifdef BACKTRACE_ANALYZE_DEBUG +#define TRACE_DEBUG(fmt, args...) pr("BACKTRACE: " fmt, ## args) +#else +#define TRACE_DEBUG(args...) __nothing +#endif + + TRACE_DEBUG("func_entry=%016lx\n", func_entry); + TRACE_DEBUG(" pc=%016lx (+%#lx)\n", pc, pc - func_entry); + TRACE_DEBUG(" sp=%016lx\n", sp); + + for (pc -= 4; pc >= func_entry; pc -= 4) { + uint32_t insn; + + db_read_bytes(pc, sizeof(insn), (char *)&insn); + if (insn == 0) + break; + LE32TOH(insn); + + TRACE_DEBUG("INSN: %016lx: %04x\n", pc, insn); + + /* "ret", "eret", or "paciasp" to detect function entry */ + if (func_entry_autodetect && ( + insn == 0xd65f03e0 || /* "ret" */ + insn == 0xd69f03e0 || /* "eret" */ + insn == 0xd503233f)) /* "paciasp" */ + break; + + /* "sub sp,sp,#imm" or "sub sp,sp,#imm,lsl #12" */ + if ((insn & 0xff8003ff) == 0xd10003ff) { + unsigned int sh = (insn >> 22) & 1; + uint64_t imm12 = + ZeroExtend(12, (insn >> 10) & 0xfff, 1); + if (sh) + imm12 <<= 12; + ssize += imm12; + TRACE_DEBUG("sub sp,sp,#%lu\n", imm12); + continue; + } + + /* sub sp,sp,Xn */ + if ((insn & 0xffe0ffff) == 0xcb2063ff) { + alloc_by_Xn_reg = (insn >> 16) & 0x1f; + alloc_by_Xn_kvalue = 0; + alloc_by_Xn_kmask = 0; + TRACE_DEBUG("sub sp,sp,x%d\n", alloc_by_Xn_reg); + continue; + } + if (alloc_by_Xn_reg >= 0) { + /* movk xN,#ALLOCSIZE2,lsl #xx */ + if ((insn & 0xff80001f) == + (0xf2800000 | alloc_by_Xn_reg)) { + int hw = (insn >> 21) & 3; + alloc_by_Xn_kvalue = ZeroExtend(16, + (insn >> 5) & 0xffff, 1) << (hw * 16); + alloc_by_Xn_kmask = (0xffffULL << (hw * 16)); + TRACE_DEBUG("movk x%d,#%#lx,lsl #%d\n", + alloc_by_Xn_reg, alloc_by_Xn_kvalue, + hw * 16); + continue; + } + + /* (orr) mov xN,#ALLOCSIZE1 */ + if ((insn & 0xff8003ff) == + (0xb20003e0 | alloc_by_Xn_reg)) { + uint64_t n = (insn >> 22) & 1; + uint64_t immr = (insn >> 16) & 0x3f; + uint64_t imms = (insn >> 10) & 0x3f; + uint64_t v = DecodeBitMasks(1, n, imms, immr); + TRACE_DEBUG("(orr) mov x%d,#%#lx\n", + alloc_by_Xn_reg, v); + ssize += v; + alloc_by_Xn_reg = -1; + continue; + } + + /* (movz) mov xN,#ALLOCSIZE1 */ + if ((insn & 0xffe0001f) == + (0xd2800000 | alloc_by_Xn_reg)) { + uint64_t v = + ZeroExtend(16, (insn >> 5) & 0xffff, 1); + TRACE_DEBUG("(movz) mov x%d,#%#lx\n", + alloc_by_Xn_reg, v); + v &= ~alloc_by_Xn_kmask; + v |= alloc_by_Xn_kvalue; + ssize += v; + alloc_by_Xn_reg = -1; + continue; + } + /* (movn) mov xN,#ALLOCSIZE1 */ + if ((insn & 0xffe0001f) == + (0x92800000 | alloc_by_Xn_reg)) { + uint64_t v = + ~ZeroExtend(16, (insn >> 5) & 0xffff, 1); + TRACE_DEBUG("(movn) mov x%d,#%#lx\n", + alloc_by_Xn_reg, v); + v &= ~alloc_by_Xn_kmask; + v |= alloc_by_Xn_kvalue; + ssize += v; + alloc_by_Xn_reg = -1; + continue; + } + } + + /* stp x??,x??,[sp,#-imm7]! */ + if ((insn & 0xffe003e0) == 0xa9a003e0) { + int64_t imm7 = SignExtend(7, (insn >> 15) & 0x7f, 8); + uint64_t Rt2 = (insn >> 10) & 0x1f; + uint64_t Rt1 = insn & 0x1f; + if (Rt1 == 30) { + TRACE_DEBUG("stp x30,Xn[sp,#%ld]!\n", imm7); + lr_off = ssize; + ssize += -imm7; + found_lr_off = true; + } else if (Rt2 == 30) { + TRACE_DEBUG("stp Xn,x30,[sp,#%ld]!\n", imm7); + lr_off = ssize + 8; + ssize += -imm7; + found_lr_off = true; + } else { + ssize += -imm7; + TRACE_DEBUG("stp Xn,Xn,[sp,#%ld]!\n", imm7); + } + + /* + * "stp x29,x30,[sp,#-n]!" is the code to create + * a frame pointer at the beginning of the function. + */ + if (func_entry_autodetect && Rt1 == 29 && Rt2 == 30) + break; + + continue; + } + + /* stp x??,x??,[sp,#imm7] */ + if ((insn & 0xffc003e0) == 0xa90003e0) { + int64_t imm7 = SignExtend(7, (insn >> 15) & 0x7f, 8); + uint64_t Rt2 = (insn >> 10) & 0x1f; + uint64_t Rt1 = insn & 0x1f; + if (Rt1 == 30) { + lr_off = ssize + imm7; + found_lr_off = true; + TRACE_DEBUG("stp x30,X%lu[sp,#%ld]\n", + Rt2, imm7); + TRACE_DEBUG("lr off = %lu = %#lx\n", + lr_off, lr_off); + } else if (Rt2 == 30) { + lr_off = ssize + imm7 + 8; + found_lr_off = true; + TRACE_DEBUG("stp X%lu,x30,[sp,#%ld]\n", + Rt1, imm7); + TRACE_DEBUG("lr off = %lu = %#lx\n", + lr_off, lr_off); + } + continue; + } + + /* str x30,[sp,#imm12] */ + if ((insn & 0xffc003ff) == 0xf90003fe) { + uint64_t imm12 = + ZeroExtend(12, (insn >> 10) & 0xfff, 8); + lr_off = ssize + imm12; + found_lr_off = true; + TRACE_DEBUG("str x30,[sp,#%lu]\n", imm12); + TRACE_DEBUG("lr off = %lu = %#lx\n", lr_off, lr_off); + continue; + } + + /* str x30,[sp,#-imm9]! */ + if ((insn & 0xfff00fff) == 0xf8100ffe) { + int64_t imm9 = SignExtend(9, (insn >> 12) & 0x1ff, 1); + lr_off = ssize; + ssize += -imm9; + found_lr_off = true; + TRACE_DEBUG("str x30,[sp,#%ld]!\n", imm9); + TRACE_DEBUG("lr off = %lu = %#lx\n", lr_off, lr_off); + continue; + } + } + + if (found_lr_off) { + if (lr_off >= ssize) { + pr("cannot locate return address\n"); + return -1; + } + db_read_bytes((db_addr_t)sp + lr_off, sizeof(lr), (char *)&lr); + lr = aarch64_strip_pac(lr); + } + *stacksizep = ssize; + *lrp = lr; + + TRACE_DEBUG("-----------\n"); + TRACE_DEBUG(" sp: %#lx\n", sp); + TRACE_DEBUG("stacksize: %#06lx = %lu\n", ssize, ssize); + TRACE_DEBUG("lr offset: %#06lx = %lu\n", lr_off, lr_off); + TRACE_DEBUG(" new lr: %#lx\n", lr); + TRACE_DEBUG("===========\n\n"); + + return 0; +} + +/* + * Backtrace without framepointer ($fp). + * + * Examines the contents of a function and returns the stack size allocated + * by the function and the stored $lr. + * + * This works well for code compiled with -fomit-frame-pointer. + */ +static void +db_sp_trace(struct trapframe *tf, db_addr_t fp, db_expr_t count, int flags, + void (*pr)(const char *, ...) __printflike(1, 2)) +{ + struct trapframe tf_buf; + db_addr_t pc, sp, lr0; + bool allow_leaf_function = false; + + if (tf == NULL) { + /* + * In the case of "trace/s <frame-address>", + * the specified frame pointer address is considered + * a trapframe (or a switchframe) address. + */ + tf = (struct trapframe *)fp; + } + + pr_frame(tf, pr); + + db_read_bytes((db_addr_t)tf, sizeof(tf_buf), (char *)&tf_buf); + if (tf_buf.tf_sp == 0) { + /* switchframe */ + lr0 = 0; + pc = aarch64_strip_pac(tf_buf.tf_lr); + sp = (uint64_t)(tf + 1); + } else { + /* trapframe */ + lr0 = aarch64_strip_pac(tf_buf.tf_lr); + pc = tf_buf.tf_pc; + sp = tf_buf.tf_sp; + allow_leaf_function = true; + } + + TRACE_DEBUG("pc =%016lx\n", pc); + TRACE_DEBUG("sp =%016lx\n", sp); + TRACE_DEBUG("lr0=%016lx\n", lr0); + + for (; (count > 0) && (sp != 0); count--) { + if (((pc - 4) == (db_addr_t)el0_trap) || + ((pc - 4) == (db_addr_t)el1_trap)) { + + pr_traceaddr("tf", sp, pc - 4, flags, pr); + + db_read_bytes((db_addr_t)sp, sizeof(tf_buf), + (char *)&tf_buf); + if (tf_buf.tf_lr == 0) + break; + pr_frame((struct trapframe *)sp, pr); + + sp = tf_buf.tf_sp; + pc = tf_buf.tf_pc; + if (pc == 0) + pc = aarch64_strip_pac(tf_buf.tf_lr); + if (pc == 0) + break; + + pr_traceaddr("sp", sp, pc, flags, pr); + + } else { + db_sym_t sym; + db_addr_t func_entry, lr; + db_expr_t func_offset; + vsize_t stacksize; + + pr_traceaddr("sp", sp, pc, flags, pr); + + if ((flags & TRACEFLAG_USERSPACE) == 0 && + !IN_KERNEL_VM_ADDRESS(pc)) + break; + + sym = db_search_symbol(pc, DB_STGY_ANY, &func_offset); + if (sym != 0) { + func_entry = pc - func_offset; + if (func_entry == + (db_addr_t)cpu_switchto_softint) { + /* + * In cpu_switchto_softint(), backtrace + * information for DDB is pushed onto + * the stack. + */ + db_read_bytes((db_addr_t)sp + 8, + sizeof(pc), (char *)&pc); + db_read_bytes((db_addr_t)sp, + sizeof(sp), (char *)&sp); + continue; + } + } else { + func_entry = 0; /* autodetect mode */ + } + + if (analyze_func(func_entry, pc, sp, &lr, &stacksize, + pr) != 0) + break; + + if (allow_leaf_function) { + if (lr == 0) + lr = lr0; + allow_leaf_function = false; + } else { + if (lr == 0) + break; + } + + sp += stacksize; + pc = lr; + } + } +} + void db_stack_trace_print(db_expr_t addr, bool have_addr, db_expr_t count, const char *modif, void (*pr)(const char *, ...) __printflike(1, 2)) @@ -189,9 +605,9 @@ db_stack_trace_print(db_expr_t addr, boo uint64_t lr, fp, lastlr, lastfp; struct trapframe *tf = NULL; int flags = 0; - bool trace_user = false; bool trace_thread = false; bool trace_lwp = false; + bool trace_sp = false; for (; *modif != '\0'; modif++) { switch (*modif) { @@ -205,17 +621,21 @@ db_stack_trace_print(db_expr_t addr, boo trace_thread = true; trace_lwp = false; break; + case 's': + trace_sp = true; + break; case 'u': - trace_user = true; + flags |= TRACEFLAG_USERSPACE; break; case 'x': flags |= TRACEFLAG_LOOKUPLWP; break; default: - pr("usage: bt[/ulx] [frame-address][,count]\n"); - pr(" bt/t[ulx] [pid][,count]\n"); - pr(" bt/a[ulx] [lwpaddr][,count]\n"); + pr("usage: bt[/ulsx] [frame-address][,count]\n"); + pr(" bt/t[ulsx] [pid][,count]\n"); + pr(" bt/a[ulsx] [lwpaddr][,count]\n"); pr("\n"); + pr(" /s trace without framepointer\n"); pr(" /x reverse lookup lwp name from sp\n"); return; } @@ -311,6 +731,13 @@ db_stack_trace_print(db_expr_t addr, boo if (count > MAXBACKTRACE) count = MAXBACKTRACE; + if (trace_sp) { + /* trace $lr pushed to sp */ + db_sp_trace(tf, fp, count, flags, pr); + return; + } + + /* trace $fp linked list */ if (tf != NULL) { pr_frame(tf, pr); lastfp = lastlr = lr = fp = 0; @@ -335,7 +762,8 @@ db_stack_trace_print(db_expr_t addr, boo db_read_bytes(lastfp + 8, sizeof(lr), (char *)&lr); lr = aarch64_strip_pac(lr); - if (lr == 0 || (!trace_user && IN_USER_VM_ADDRESS(lr))) + if (lr == 0 || ((flags & TRACEFLAG_USERSPACE) == 0 && + IN_USER_VM_ADDRESS(lr))) break; if (((char *)(lr - 4) == (char *)el0_trap) || @@ -371,11 +799,11 @@ db_stack_trace_print(db_expr_t addr, boo pr_frame(tf, pr); tf = NULL; - if (!trace_user && IN_USER_VM_ADDRESS(lr)) + if ((flags & TRACEFLAG_USERSPACE) == 0 && + IN_USER_VM_ADDRESS(lr)) break; pr_traceaddr("fp", fp, lr, flags, pr); - } else { pr_traceaddr("fp", fp, lr - 4, flags, pr); } Index: src/usr.sbin/crash/arch/aarch64.c diff -u src/usr.sbin/crash/arch/aarch64.c:1.1 src/usr.sbin/crash/arch/aarch64.c:1.2 --- src/usr.sbin/crash/arch/aarch64.c:1.1 Thu Jun 2 05:09:01 2022 +++ src/usr.sbin/crash/arch/aarch64.c Tue Jun 7 08:08:31 2022 @@ -1,4 +1,4 @@ -/* $NetBSD: aarch64.c,v 1.1 2022/06/02 05:09:01 ryo Exp $ */ +/* $NetBSD: aarch64.c,v 1.2 2022/06/07 08:08:31 ryo Exp $ */ /*- * Copyright (c) 2022 The NetBSD Foundation, Inc. @@ -31,7 +31,7 @@ #include <sys/cdefs.h> #ifndef lint -__RCSID("$NetBSD: aarch64.c,v 1.1 2022/06/02 05:09:01 ryo Exp $"); +__RCSID("$NetBSD: aarch64.c,v 1.2 2022/06/07 08:08:31 ryo Exp $"); #endif /* not lint */ #include <kvm.h> @@ -43,10 +43,12 @@ __RCSID("$NetBSD: aarch64.c,v 1.1 2022/0 vaddr_t el0_trap; vaddr_t el1_trap; +vaddr_t cpu_switchto_softint; static struct nlist nl[] = { - { .n_name = "el0_trap" }, /* 0 */ - { .n_name = "el1_trap" }, /* 1 */ + { .n_name = "el0_trap" }, /* 0 */ + { .n_name = "el1_trap" }, /* 1 */ + { .n_name = "cpu_switchto_softint" }, /* 2 */ { .n_name = NULL }, }; @@ -58,4 +60,5 @@ db_mach_init(kvm_t *kd) el0_trap = nl[0].n_value; el1_trap = nl[1].n_value; + cpu_switchto_softint = nl[2].n_value; }