In few cases, the backtrace shown by crash (in default mode), can be different than the backtrace shown by gdb (ie. in gdb mode), due to different implementations (on PPC64, crash uses backchains/framepointer, while gdb uses debuginfo, to unwind) Example, in "crash mode", 'bt' might print these frames: ------ A1 A2 C1 ------ While, in "gdb mode", 'bt' might print: ------ A1 B1 <--- extra inline frame A2 C1 ------ This can cause confusion, since 'bt' is implemented in crash, while 'frame'/'up'/'down' are gdb passthroughs. So 'frame' might show frame 'B1' at level #1, which isn't even there in the backtrace crash shows Implement 'frame', 'up', 'down' in crash ("default mode"), which any architecture can support by implementing both of these functions: 1. 'machdep->print_stack_frame': to print any arbitrary frame, identified by the frame level 2. 'machdep->is_frame_num_valid': to check if frame level is valid This patch implements these machdep functions for powerpc64 This has an implication on other architectures that in default mode, 'frame','up','down' might show "command not supported" Signed-off-by: Aditya Gupta <adityag@xxxxxxxxxxxxx> --- defs.h | 10 ++++ global_data.c | 3 + help.c | 34 ++++++++++++ kernel.c | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++ ppc64.c | 145 ++++++++++++++++++++++++++++++++++++++++++++++++ task.c | 1 + 6 files changed, 343 insertions(+) diff --git a/defs.h b/defs.h index 798a49e7ec41..198e805db119 100644 --- a/defs.h +++ b/defs.h @@ -828,6 +828,7 @@ struct kernel_table { /* kernel data */ struct task_context { /* context stored for each task */ ulong task; ulong thread_info; + int frame_num; ulong pid; char comm[TASK_COMM_LEN+1]; int processor; @@ -921,6 +922,7 @@ struct task_table { /* kernel/local task table data */ #define CURRENT_TASK() (tt->current->task) #define CURRENT_PID() (tt->current->pid) #define CURRENT_COMM() (tt->current->comm) +#define CURRENT_FRAME() (tt->current->frame_num) #define RUNNING_TASKS() (tt->running_tasks) #define FIRST_CONTEXT() (tt->context_array) @@ -1021,6 +1023,8 @@ struct machdep_table { uint64_t memsize; int (*eframe_search)(struct bt_info *); void (*back_trace)(struct bt_info *); + void (*print_stack_frame)(int frame_num, struct bt_info *bt); + int (*is_frame_num_valid)(int frame_num, struct bt_info *bt); ulong (*processor_speed)(void); int (*uvtop)(struct task_context *, ulong, physaddr_t *, int); int (*kvtop)(struct task_context *, ulong, physaddr_t *, int); @@ -5310,11 +5314,14 @@ void cmd_runq(void); /* task.c */ void cmd_sig(void); /* task.c */ void cmd_bt(void); /* kernel.c */ void cmd_dis(void); /* kernel.c */ +void cmd_down(void); /* kernel.c */ +void cmd_frame(void); /* kernel.c */ void cmd_mod(void); /* kernel.c */ void cmd_log(void); /* kernel.c */ void cmd_sys(void); /* kernel.c */ void cmd_irq(void); /* kernel.c */ void cmd_timer(void); /* kernel.c */ +void cmd_up(void); /* kernel.c */ void cmd_waitq(void); /* kernel.c */ void cmd_sym(void); /* symbols.c */ void cmd_struct(void); /* symbols.c */ @@ -5884,9 +5891,11 @@ extern char *help_alias[]; extern char *help_ascii[]; extern char *help_bpf[]; extern char *help_bt[]; +extern char *help_frame[]; extern char *help_btop[]; extern char *help_dev[]; extern char *help_dis[]; +extern char *help_down[]; extern char *help_eval[]; extern char *help_exit[]; extern char *help_extend[]; @@ -5925,6 +5934,7 @@ extern char *help_sys[]; extern char *help_task[]; extern char *help_timer[]; extern char *help_union[]; +extern char *help_up[]; extern char *help_vm[]; extern char *help_vtop[]; extern char *help_waitq[]; diff --git a/global_data.c b/global_data.c index f9bb7d079f51..bb8282e50c43 100644 --- a/global_data.c +++ b/global_data.c @@ -77,11 +77,13 @@ struct command_table_entry linux_command_table[] = { {"btop", cmd_btop, help_btop, 0}, {"dev", cmd_dev, help_dev, 0}, {"dis", cmd_dis, help_dis, MINIMAL}, + {"down", cmd_down, help_down, REFRESH_TASK_TABLE}, {"eval", cmd_eval, help_eval, MINIMAL}, {"exit", cmd_quit, help_exit, MINIMAL}, {"extend", cmd_extend, help_extend, MINIMAL}, {"files", cmd_files, help_files, REFRESH_TASK_TABLE}, {"foreach", cmd_foreach, help_foreach, REFRESH_TASK_TABLE}, + {"frame", cmd_frame, help_frame, REFRESH_TASK_TABLE}, {"fuser", cmd_fuser, help_fuser, REFRESH_TASK_TABLE}, {"gdb", cmd_gdb, help_gdb, REFRESH_TASK_TABLE}, {"help", cmd_help, help_help, MINIMAL}, @@ -116,6 +118,7 @@ struct command_table_entry linux_command_table[] = { {"task", cmd_task, help_task, REFRESH_TASK_TABLE}, {"test", cmd_test, NULL, HIDDEN_COMMAND}, {"timer", cmd_timer, help_timer, 0}, + {"up", cmd_up, help_up, REFRESH_TASK_TABLE}, {"union", cmd_union, help_union, 0}, {"vm", cmd_vm, help_vm, REFRESH_TASK_TABLE}, {"vtop", cmd_vtop, help_vtop, REFRESH_TASK_TABLE}, diff --git a/help.c b/help.c index cc7ab20e343e..a289ece93833 100644 --- a/help.c +++ b/help.c @@ -2476,6 +2476,40 @@ char *help_extend[] = { NULL }; +char *help_frame[] = { +"frame", +"Select and print a stack frame.", +"[-f|-F|-l] [frame_number]", +"If no argument[s] passed, print the current stack frame.", +"A single numerical argument specifies the frame number to select", +" -f display all stack data contained in a frame", +" -F[F] similar to -f, except that the stack data is displayed symbolically", +" when appropriate; if the stack data references a slab cache object,", +" the name of the slab cache will be displayed in brackets; on ia64,", +" the substitution is done to the argument register contents.", +" If -F is entered twice, and the stack data references a slab", +" object, both the address and the name of the slab cache will be", +" displayed in brackets.", +" -l show file and line number of stack trace text location.", +NULL +}; + +char *help_up[] = { +"up", +"Select and print stack frame that called this one.", +"[number_of_frames]", +"Can be used to move between frames. Optionally takes number of frames to go up.", +NULL +}; + +char *help_down[] = { +"down", +"Select and print stack frame called by this one.", +"[number_of_frames]", +"Can be used to move between frames. Optionally takes number of frames to go down.", +NULL +}; + char *help_mach[] = { "mach", "machine specific data", diff --git a/kernel.c b/kernel.c index e6920e241c4e..a7dd32dd21a1 100644 --- a/kernel.c +++ b/kernel.c @@ -22,6 +22,7 @@ #include <libgen.h> #include <ctype.h> #include <stdbool.h> +#include <stdlib.h> #include "xendump.h" #if defined(GDB_7_6) || defined(GDB_10_2) #define __CONFIG_H__ 1 @@ -3547,6 +3548,155 @@ get_dumpfile_regs(struct bt_info *bt, ulong *eip, ulong *esp) bt->stkptr = *esp; } +/* + * Get registers including EIP, ESP, and pass on to machine-specific + * is_frame_num_valid command + */ +static int +is_frame_num_valid(int frame_num) +{ + struct bt_info bt_info, bt_setup = { 0 }; + ulong ip, sp; + + clone_bt_info(&bt_setup, &bt_info, CURRENT_CONTEXT()); + + if (frame_num < 0) + return FALSE; + + // fill kernel stack into a buffer + fill_stackbuf(&bt_info); + + get_dumpfile_regs(&bt_info, &ip, &sp); + + return machdep->is_frame_num_valid(frame_num, &bt_info); +} + +/* + * Get registers including EIP, ESP, and pass on to machine-specific + * print_stack_frame + */ +void +print_current_frame(ulonglong bt_flags) +{ + struct bt_info bt_info, bt_setup = { 0 }; + struct task_context *tc; + ulong ip, sp; + + tc = CURRENT_CONTEXT(); + clone_bt_info(&bt_setup, &bt_info, tc); + + bt_info.flags |= bt_flags; + fill_stackbuf(&bt_info); + get_dumpfile_regs(&bt_info, &sp, &ip); + + machdep->print_stack_frame(CURRENT_FRAME(), &bt_info); +} + +void +cmd_frame(void) +{ + struct task_context *tc; + int frame_num; + int c; + ulonglong bt_flags = 0; + + // If these functions are not defined by arch-specific init code, return early + if (!machdep->print_stack_frame || !machdep->is_frame_num_valid) { + command_not_supported(); + return; + } + + while ((c = getopt(argcnt, args, "fFl")) != EOF) { + switch (c) { + case 'f': + bt_flags |= BT_FULL; + break; + case 'F': + if (bt_flags & BT_FULL_SYM_SLAB) // is 'F' repeated multiple times + bt_flags |= BT_FULL_SYM_SLAB2; + else + bt_flags |= (BT_FULL | BT_FULL_SYM_SLAB); + break; + case 'l': + if (NO_LINE_NUMBERS()) + error(INFO, "line numbers are not available\n"); + else + bt_flags |= BT_LINE_NUMBERS; + break; + default: + argerrs++; + break; + }; + + if (argerrs) + cmd_usage(pc->curcmd, SYNOPSIS); + } + + tc = CURRENT_CONTEXT(); + + /* purpose of using args[optind] is to allow passing frame number and + * options in any order + */ + if (args[optind]) { + frame_num = strtol(args[optind], NULL, 10); + if (!is_frame_num_valid(frame_num)) { + error(FATAL, "Passed frame number is invalid."); + return; + } + + tc->frame_num = frame_num; + } + + print_current_frame(bt_flags); +} + +void +cmd_up(void) +{ + struct task_context *tc; + int frame_num; + + if (!machdep->print_stack_frame || !machdep->is_frame_num_valid) { + command_not_supported(); + return; + } + tc = CURRENT_CONTEXT(); + if (args[optind + 1]) + frame_num = CURRENT_FRAME() + strtol(args[optind + 1], NULL, 10); + else + frame_num = CURRENT_FRAME() + 1; + if (!is_frame_num_valid(frame_num)) { + error(FATAL, "Initial frame selected; you cannot go up."); + return; + } + + tc->frame_num = frame_num; + print_current_frame(0); +} + +void +cmd_down(void) +{ + struct task_context *tc; + int frame_num; + + if (!machdep->print_stack_frame) { + command_not_supported(); + return; + } + tc = CURRENT_CONTEXT(); + if (args[optind + 1]) + frame_num = CURRENT_FRAME() - strtol(args[optind + 1], NULL, 10); + else + frame_num = CURRENT_FRAME() - 1; + if (CURRENT_FRAME() <= 0) { + error(INFO, "Bottom (innermost) frame selected; you cannot go down."); + return; + } + + tc->frame_num = frame_num; + print_current_frame(0); +} /* * Store the head of the kernel module list for future use. diff --git a/ppc64.c b/ppc64.c index 61ff47c2c8d9..dd5549ed9ecf 100644 --- a/ppc64.c +++ b/ppc64.c @@ -33,6 +33,8 @@ static int ppc64_eframe_search(struct bt_info *); static void ppc64_back_trace_cmd(struct bt_info *); static void ppc64_back_trace(struct gnu_request *, struct bt_info *); static void get_ppc64_frame(struct bt_info *, ulong *, ulong *); +static int ppc64_is_frame_num_valid(int, struct bt_info *); +static void ppc64_print_stack_frame(int, struct bt_info *); static void ppc64_print_stack_entry(int,struct gnu_request *, ulong, ulong, struct bt_info *); static void ppc64_dump_irq(int); @@ -395,6 +397,8 @@ ppc64_init(int when) machdep->is_uvaddr = generic_is_uvaddr; machdep->eframe_search = ppc64_eframe_search; machdep->back_trace = ppc64_back_trace_cmd; + machdep->print_stack_frame = ppc64_print_stack_frame; + machdep->is_frame_num_valid = ppc64_is_frame_num_valid; machdep->processor_speed = ppc64_processor_speed; machdep->uvtop = ppc64_uvtop; machdep->kvtop = ppc64_kvtop; @@ -2262,6 +2266,147 @@ ppc64_display_full_frame(struct bt_info *bt, ulong nextsp, FILE *ofp) fprintf(ofp, "\n"); } +/* Check whether a frame number is valid + */ +static int +ppc64_is_frame_num_valid(int frame_num, struct bt_info *bt) +{ + ulong sp = bt->stkptr; + + while (frame_num-- > 0) { + sp = *(ulong *)&bt->stackbuf[sp - bt->stackbase]; + + if (!INSTACK(sp, bt)) + return FALSE; + } + + return TRUE; +} + +/* Prints one frame, identified by the frame_num, where frame_num=0 is the + * innermost frame (first frame in backtrace) + * + * It gets the `pc` and `sp` and finally calls `ppc64_print_stack_entry` + */ +static void +ppc64_print_stack_frame(int frame_num, struct bt_info *bt) +{ + struct gnu_request req = { 0 }; + ulong newpc = 0, newsp; + ulong eframe_lr = 0; + int eframe_found, marker; + enum emergency_stack_type estype; + int c = bt->tc->processor; + ulong nmi_sp = 0; + + req.task = bt->task; + + req.pc = bt->instptr; + req.sp = bt->stkptr; + + int cnt = frame_num; + + newsp = req.sp; + newpc = req.pc; + + while (cnt-- >= 0) { + req.sp = newsp; + req.pc = newpc; + + newsp = *(ulong *)&bt->stackbuf[newsp - bt->stackbase]; + + if (!INSTACK(req.sp, bt)) { + error(FATAL, + "%s: current frame numberx (%d) is not valid (likely more than number of frames available)", + __func__, frame_num); + return; + } + + // rest of the loop is copied from ppc64_back_trace, focused on updating req.pc + if (IS_KVADDR(newsp)) { + /* + * In 2.4, HW interrupt stack will be used to save + * smp_call_functions symbols. i.e, when the dumping + * CPU is issued IPI call to freeze other CPUS, + */ + if (INSTACK(newsp, bt) && (newsp + 16 > bt->stacktop)) + newsp = + *(ulong *)&bt->stackbuf[newsp - bt->stackbase]; + if (!INSTACK(newsp, bt)) { + estype = ppc64_in_emergency_stack(c, newsp, true); + if (estype) { + if (!nmi_sp && estype == NMI_EMERGENCY_STACK) + nmi_sp = newsp; + ppc64_set_bt_emergency_stack(estype, bt); + } else { + /* + * Switch HW interrupt stack or emergency stack + * to process's stack. + */ + bt->stackbase = GET_STACKBASE(bt->task); + bt->stacktop = GET_STACKTOP(bt->task); + alter_stackbuf(bt); + } + } + + if (IS_KVADDR(newsp) && INSTACK(newsp, bt)) + newpc = *(ulong *)&bt->stackbuf[newsp + 16 - + bt->stackbase]; + } + + if (BT_REFERENCE_FOUND(bt)) + return; + + eframe_found = FALSE; + /* + * Is this frame an exception one? + * In 2.6, 0x7265677368657265 is saved and used + * to determine the exception frame. + */ + if (THIS_KERNEL_VERSION < LINUX(2, 6, 0)) { + if (frame_num && (newsp - req.sp - STACK_FRAME_OVERHEAD) >= + sizeof(struct ppc64_pt_regs)) + eframe_found = TRUE; + else if (STREQ(req.name, ".ret_from_except")) + eframe_found = TRUE; + } else if ((newsp - req.sp - STACK_FRAME_OVERHEAD) >= + sizeof(struct ppc64_pt_regs)) { + readmem(req.sp + 0x60, KVADDR, &marker, + sizeof(ulong), "stack frame", FAULT_ON_ERROR); + + if (marker == EXCP_FRAME_MARKER) + eframe_found = TRUE; + } + if (eframe_found) { + char *efrm_str = NULL; + struct ppc64_pt_regs regs; + + readmem(req.sp + STACK_FRAME_OVERHEAD, KVADDR, ®s, + sizeof(struct ppc64_pt_regs), + "exception frame", FAULT_ON_ERROR); + + efrm_str = ppc64_check_eframe(®s); + if (efrm_str) { + eframe_lr = regs.link; + newpc = regs.nip; + newsp = regs.gpr[1]; + } + } + } + + req.name = closest_symbol(req.pc); + if (!req.name) { + if (CRASHDEBUG(1)) + error(FATAL, "%s hit unknown symbol (%lx).\n", __func__, req.pc); + } + + bt->flags |= BT_SAVE_LASTSP; + + ppc64_print_stack_entry(frame_num, &req, newsp, eframe_lr, bt); + + bt->flags |= ~(ulonglong)BT_SAVE_LASTSP; +} + /* * print one entry of a stack trace */ diff --git a/task.c b/task.c index b9076da35565..5c43a1600114 100644 --- a/task.c +++ b/task.c @@ -2948,6 +2948,7 @@ add_context(ulong task, char *tp) tc->mm_struct = *mm_addr; tc->task = task; tc->tc_next = NULL; + tc->frame_num = 0; /* * Fill a tgid_context structure with the data from -- 2.41.0 -- Crash-utility mailing list Crash-utility@xxxxxxxxxx https://listman.redhat.com/mailman/listinfo/crash-utility Contribution Guidelines: https://github.com/crash-utility/crash/wiki