From f66686f70a2a61e53ee8c2284f75ca342e4c0dc8 Mon Sep 17 00:00:00 2001 From: Atsushi Nemoto Date: Sat, 29 Jul 2006 23:27:20 +0900 Subject: [MIPS] dump_stack() based on prologue code analysis Instead of dump all possible address in the stack, unwind the stack frame based on prologue code analysis, as like as get_wchan() does. While the code analysis might fail for some reason, there is a new kernel option "raw_show_trace" to disable this feature. Signed-off-by: Atsushi Nemoto Signed-off-by: Ralf Baechle --- arch/mips/kernel/process.c | 66 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 11 deletions(-) (limited to 'arch/mips/kernel/process.c') diff --git a/arch/mips/kernel/process.c b/arch/mips/kernel/process.c index 7ab67f786bfe..8709a46a45c1 100644 --- a/arch/mips/kernel/process.c +++ b/arch/mips/kernel/process.c @@ -281,7 +281,7 @@ static struct mips_frame_info { } *schedule_frame, mfinfo[64]; static int mfinfo_num; -static int __init get_frame_info(struct mips_frame_info *info) +static int get_frame_info(struct mips_frame_info *info) { int i; void *func = info->func; @@ -329,14 +329,12 @@ static int __init get_frame_info(struct mips_frame_info *info) ip->i_format.simmediate / sizeof(long); } } - if (info->pc_offset == -1 || info->frame_size == 0) { - if (func == schedule) - printk("Can't analyze prologue code at %p\n", func); - info->pc_offset = -1; - info->frame_size = 0; - } - - return 0; + if (info->frame_size && info->pc_offset >= 0) /* nested */ + return 0; + if (info->pc_offset < 0) /* leaf */ + return 1; + /* prologue seems boggus... */ + return -1; } static int __init frame_info_init(void) @@ -367,8 +365,15 @@ static int __init frame_info_init(void) mfinfo[0].func = schedule; schedule_frame = &mfinfo[0]; #endif - for (i = 0; i < ARRAY_SIZE(mfinfo) && mfinfo[i].func; i++) - get_frame_info(&mfinfo[i]); + for (i = 0; i < ARRAY_SIZE(mfinfo) && mfinfo[i].func; i++) { + struct mips_frame_info *info = &mfinfo[i]; + if (get_frame_info(info)) { + /* leaf or unknown */ + if (info->func == schedule) + printk("Can't analyze prologue code at %p\n", + info->func); + } + } mfinfo_num = i; return 0; @@ -427,6 +432,8 @@ unsigned long get_wchan(struct task_struct *p) if (i < 0) break; + if (mfinfo[i].pc_offset < 0) + break; pc = ((unsigned long *)frame)[mfinfo[i].pc_offset]; if (!mfinfo[i].frame_size) break; @@ -437,3 +444,40 @@ unsigned long get_wchan(struct task_struct *p) return pc; } +#ifdef CONFIG_KALLSYMS +/* used by show_frametrace() */ +unsigned long unwind_stack(struct task_struct *task, + unsigned long **sp, unsigned long pc) +{ + unsigned long stack_page; + struct mips_frame_info info; + char *modname; + char namebuf[KSYM_NAME_LEN + 1]; + unsigned long size, ofs; + + stack_page = (unsigned long)task_stack_page(task); + if (!stack_page) + return 0; + + if (!kallsyms_lookup(pc, &size, &ofs, &modname, namebuf)) + return 0; + if (ofs == 0) + return 0; + + info.func = (void *)(pc - ofs); + info.func_size = ofs; /* analyze from start to ofs */ + if (get_frame_info(&info)) { + /* leaf or unknown */ + *sp += info.frame_size / sizeof(long); + return 0; + } + if ((unsigned long)*sp < stack_page || + (unsigned long)*sp + info.frame_size / sizeof(long) > + stack_page + THREAD_SIZE - 32) + return 0; + + pc = (*sp)[info.pc_offset]; + *sp += info.frame_size / sizeof(long); + return pc; +} +#endif -- cgit From c0efbb6dc2726785482d85a284c883d541a6e0be Mon Sep 17 00:00:00 2001 From: Franck Bui-Huu Date: Thu, 3 Aug 2006 09:29:15 +0200 Subject: [MIPS] Make get_frame_info() more readable. Signed-off-by: Atsushi Nemoto Signed-off-by: Franck Bui-Huu Signed-off-by: Ralf Baechle --- arch/mips/kernel/process.c | 67 +++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 31 deletions(-) (limited to 'arch/mips/kernel/process.c') diff --git a/arch/mips/kernel/process.c b/arch/mips/kernel/process.c index 8709a46a45c1..93d5432759db 100644 --- a/arch/mips/kernel/process.c +++ b/arch/mips/kernel/process.c @@ -281,48 +281,53 @@ static struct mips_frame_info { } *schedule_frame, mfinfo[64]; static int mfinfo_num; +static inline int is_ra_save_ins(union mips_instruction *ip) +{ + /* sw / sd $ra, offset($sp) */ + return (ip->i_format.opcode == sw_op || ip->i_format.opcode == sd_op) && + ip->i_format.rs == 29 && + ip->i_format.rt == 31; +} + +static inline int is_jal_jalr_jr_ins(union mips_instruction *ip) +{ + if (ip->j_format.opcode == jal_op) + return 1; + if (ip->r_format.opcode != spec_op) + return 0; + return ip->r_format.func == jalr_op || ip->r_format.func == jr_op; +} + +static inline int is_sp_move_ins(union mips_instruction *ip) +{ + /* addiu/daddiu sp,sp,-imm */ + if (ip->i_format.rs != 29 || ip->i_format.rt != 29) + return 0; + if (ip->i_format.opcode == addiu_op || ip->i_format.opcode == daddiu_op) + return 1; + return 0; +} + static int get_frame_info(struct mips_frame_info *info) { - int i; - void *func = info->func; - union mips_instruction *ip = (union mips_instruction *)func; + union mips_instruction *ip = info->func; + int i, max_insns = + min(128UL, info->func_size / sizeof(union mips_instruction)); + info->pc_offset = -1; info->frame_size = 0; - for (i = 0; i < 128; i++, ip++) { - /* if jal, jalr, jr, stop. */ - if (ip->j_format.opcode == jal_op || - (ip->r_format.opcode == spec_op && - (ip->r_format.func == jalr_op || - ip->r_format.func == jr_op))) - break; - if (info->func_size && i >= info->func_size / 4) + for (i = 0; i < max_insns; i++, ip++) { + + if (is_jal_jalr_jr_ins(ip)) break; - if ( -#ifdef CONFIG_32BIT - ip->i_format.opcode == addiu_op && -#endif -#ifdef CONFIG_64BIT - ip->i_format.opcode == daddiu_op && -#endif - ip->i_format.rs == 29 && - ip->i_format.rt == 29) { - /* addiu/daddiu sp,sp,-imm */ + if (is_sp_move_ins(ip)) { if (info->frame_size) continue; info->frame_size = - ip->i_format.simmediate; } - if ( -#ifdef CONFIG_32BIT - ip->i_format.opcode == sw_op && -#endif -#ifdef CONFIG_64BIT - ip->i_format.opcode == sd_op && -#endif - ip->i_format.rs == 29 && - ip->i_format.rt == 31) { - /* sw / sd $ra, offset($sp) */ + if (is_ra_save_ins(ip)) { if (info->pc_offset != -1) continue; info->pc_offset = -- cgit From 6057a7987608941a203f40f8b53513af433d8d2f Mon Sep 17 00:00:00 2001 From: Franck Bui-Huu Date: Thu, 3 Aug 2006 09:29:18 +0200 Subject: [MIPS] Make frame_info_init() more readable. Signed-off-by: Franck Bui-Huu Signed-off-by: Ralf Baechle --- arch/mips/kernel/process.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'arch/mips/kernel/process.c') diff --git a/arch/mips/kernel/process.c b/arch/mips/kernel/process.c index 93d5432759db..da332d707ce5 100644 --- a/arch/mips/kernel/process.c +++ b/arch/mips/kernel/process.c @@ -370,15 +370,15 @@ static int __init frame_info_init(void) mfinfo[0].func = schedule; schedule_frame = &mfinfo[0]; #endif - for (i = 0; i < ARRAY_SIZE(mfinfo) && mfinfo[i].func; i++) { - struct mips_frame_info *info = &mfinfo[i]; - if (get_frame_info(info)) { - /* leaf or unknown */ - if (info->func == schedule) - printk("Can't analyze prologue code at %p\n", - info->func); - } - } + for (i = 0; i < ARRAY_SIZE(mfinfo) && mfinfo[i].func; i++) + get_frame_info(mfinfo + i); + + /* + * Without schedule() frame info, result given by + * thread_saved_pc() and get_wchan() are not reliable. + */ + if (schedule_frame->pc_offset < 0) + printk("Can't analyze schedule() prologue at %p\n", schedule); mfinfo_num = i; return 0; -- cgit From 0cceb4aa9acf6192a5f02134e764b1feeea8b9de Mon Sep 17 00:00:00 2001 From: Franck Bui-Huu Date: Thu, 3 Aug 2006 09:29:20 +0200 Subject: [MIPS] Make get_frame_info() more robust Now get_frame_info() wants to detect move sp instruction first. It assumes that the save ra in the stack instruction can't happen before allocating frame size space into the stack. Signed-off-by: Franck Bui-Huu Signed-off-by: Ralf Baechle --- arch/mips/kernel/process.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'arch/mips/kernel/process.c') diff --git a/arch/mips/kernel/process.c b/arch/mips/kernel/process.c index da332d707ce5..309bfa4a1520 100644 --- a/arch/mips/kernel/process.c +++ b/arch/mips/kernel/process.c @@ -321,17 +321,15 @@ static int get_frame_info(struct mips_frame_info *info) if (is_jal_jalr_jr_ins(ip)) break; - if (is_sp_move_ins(ip)) { - if (info->frame_size) - continue; - info->frame_size = - ip->i_format.simmediate; + if (!info->frame_size) { + if (is_sp_move_ins(ip)) + info->frame_size = - ip->i_format.simmediate; + continue; } - - if (is_ra_save_ins(ip)) { - if (info->pc_offset != -1) - continue; + if (info->pc_offset == -1 && is_ra_save_ins(ip)) { info->pc_offset = ip->i_format.simmediate / sizeof(long); + break; } } if (info->frame_size && info->pc_offset >= 0) /* nested */ -- cgit From 4d157d5eac29d7d5559fdcabf20f3961bc5cb3e7 Mon Sep 17 00:00:00 2001 From: Franck Bui-Huu Date: Thu, 3 Aug 2006 09:29:21 +0200 Subject: [MIPS] Improve unwind_stack() This patch allows unwind_stack() to return ra for leaf function. But it tries to detects cases where get_frame_info() wrongly consider nested function as a leaf one. It also pass 'unsinged long *sp' instead of 'unsigned long **sp' as second parameter. The code looks cleaner. Signed-off-by: Franck Bui-Huu Signed-off-by: Ralf Baechle --- arch/mips/kernel/process.c | 35 ++++++++++++++++++++++------------- arch/mips/kernel/traps.c | 24 ++++++++++++------------ 2 files changed, 34 insertions(+), 25 deletions(-) (limited to 'arch/mips/kernel/process.c') diff --git a/arch/mips/kernel/process.c b/arch/mips/kernel/process.c index 309bfa4a1520..951bf9ca3ce9 100644 --- a/arch/mips/kernel/process.c +++ b/arch/mips/kernel/process.c @@ -448,15 +448,16 @@ unsigned long get_wchan(struct task_struct *p) } #ifdef CONFIG_KALLSYMS -/* used by show_frametrace() */ -unsigned long unwind_stack(struct task_struct *task, - unsigned long **sp, unsigned long pc) +/* used by show_backtrace() */ +unsigned long unwind_stack(struct task_struct *task, unsigned long *sp, + unsigned long pc, unsigned long ra) { unsigned long stack_page; struct mips_frame_info info; char *modname; char namebuf[KSYM_NAME_LEN + 1]; unsigned long size, ofs; + int leaf; stack_page = (unsigned long)task_stack_page(task); if (!stack_page) @@ -469,18 +470,26 @@ unsigned long unwind_stack(struct task_struct *task, info.func = (void *)(pc - ofs); info.func_size = ofs; /* analyze from start to ofs */ - if (get_frame_info(&info)) { - /* leaf or unknown */ - *sp += info.frame_size / sizeof(long); + leaf = get_frame_info(&info); + if (leaf < 0) return 0; - } - if ((unsigned long)*sp < stack_page || - (unsigned long)*sp + info.frame_size / sizeof(long) > - stack_page + THREAD_SIZE - 32) + + if (*sp < stack_page || + *sp + info.frame_size > stack_page + THREAD_SIZE - 32) return 0; - pc = (*sp)[info.pc_offset]; - *sp += info.frame_size / sizeof(long); - return pc; + if (leaf) + /* + * For some extreme cases, get_frame_info() can + * consider wrongly a nested function as a leaf + * one. In that cases avoid to return always the + * same value. + */ + pc = pc != ra ? ra : 0; + else + pc = ((unsigned long *)(*sp))[info.pc_offset]; + + *sp += info.frame_size; + return __kernel_text_address(pc) ? pc : 0; } #endif diff --git a/arch/mips/kernel/traps.c b/arch/mips/kernel/traps.c index 303f00843021..ab77034921c4 100644 --- a/arch/mips/kernel/traps.c +++ b/arch/mips/kernel/traps.c @@ -74,8 +74,9 @@ void (*board_ejtag_handler_setup)(void); void (*board_bind_eic_interrupt)(int irq, int regset); -static void show_raw_backtrace(unsigned long *sp) +static void show_raw_backtrace(unsigned long reg29) { + unsigned long *sp = (unsigned long *)reg29; unsigned long addr; printk("Call Trace:"); @@ -99,30 +100,29 @@ static int __init set_raw_show_trace(char *str) } __setup("raw_show_trace", set_raw_show_trace); -extern unsigned long unwind_stack(struct task_struct *task, - unsigned long **sp, unsigned long pc); +extern unsigned long unwind_stack(struct task_struct *task, unsigned long *sp, + unsigned long pc, unsigned long ra); + static void show_backtrace(struct task_struct *task, struct pt_regs *regs) { - unsigned long *sp = (long *)regs->regs[29]; + unsigned long sp = regs->regs[29]; + unsigned long ra = regs->regs[31]; unsigned long pc = regs->cp0_epc; - int top = 1; if (raw_show_trace || !__kernel_text_address(pc)) { show_raw_backtrace(sp); return; } printk("Call Trace:\n"); - while (__kernel_text_address(pc)) { + do { print_ip_sym(pc); - pc = unwind_stack(task, &sp, pc); - if (top && pc == 0) - pc = regs->regs[31]; /* leaf? */ - top = 0; - } + pc = unwind_stack(task, &sp, pc, ra); + ra = 0; + } while (pc); printk("\n"); } #else -#define show_backtrace(task, r) show_raw_backtrace((long *)(r)->regs[29]); +#define show_backtrace(task, r) show_raw_backtrace((r)->regs[29]); #endif /* -- cgit From 1fd6909802b837ed5510603846c0ce5938d296a1 Mon Sep 17 00:00:00 2001 From: Franck Bui-Huu Date: Fri, 18 Aug 2006 16:18:07 +0200 Subject: [MIPS] unwind_stack(): return ra if an exception occured at the first instruction Signed-off-by: Franck Bui-Huu Signed-off-by: Ralf Baechle --- arch/mips/kernel/process.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'arch/mips/kernel/process.c') diff --git a/arch/mips/kernel/process.c b/arch/mips/kernel/process.c index 951bf9ca3ce9..e7b0b385fb2b 100644 --- a/arch/mips/kernel/process.c +++ b/arch/mips/kernel/process.c @@ -465,8 +465,11 @@ unsigned long unwind_stack(struct task_struct *task, unsigned long *sp, if (!kallsyms_lookup(pc, &size, &ofs, &modname, namebuf)) return 0; - if (ofs == 0) - return 0; + /* + * Return ra if an exception occured at the first instruction + */ + if (unlikely(ofs == 0)) + return ra; info.func = (void *)(pc - ofs); info.func_size = ofs; /* analyze from start to ofs */ -- cgit From 29b376ff10aaea69ee4d93b70d0fbb2ebfd80f4e Mon Sep 17 00:00:00 2001 From: Franck Bui-Huu Date: Fri, 18 Aug 2006 16:18:08 +0200 Subject: [MIPS] get_frame_info(): null function size means size is unknown This patch adds 2 sanity checks. The first one test that the start address of the function to analyze has been set by the caller. If not return an error since nothing usefull can be done without. The second one checks that the function's size has been set. A null size can happen if CONFIG_KALLSYMS is not set and it means that we don't know the size of the function to analyze. In this case, we make it equal to 128 instructions by default. Signed-off-by: Franck Bui-Huu Signed-off-by: Ralf Baechle --- arch/mips/kernel/process.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'arch/mips/kernel/process.c') diff --git a/arch/mips/kernel/process.c b/arch/mips/kernel/process.c index e7b0b385fb2b..b160ea30de0f 100644 --- a/arch/mips/kernel/process.c +++ b/arch/mips/kernel/process.c @@ -311,12 +311,19 @@ static inline int is_sp_move_ins(union mips_instruction *ip) static int get_frame_info(struct mips_frame_info *info) { union mips_instruction *ip = info->func; - int i, max_insns = - min(128UL, info->func_size / sizeof(union mips_instruction)); + unsigned max_insns = info->func_size / sizeof(union mips_instruction); + unsigned i; info->pc_offset = -1; info->frame_size = 0; + if (!ip) + goto err; + + if (max_insns == 0) + max_insns = 128U; /* unknown function size */ + max_insns = min(128U, max_insns); + for (i = 0; i < max_insns; i++, ip++) { if (is_jal_jalr_jr_ins(ip)) @@ -337,6 +344,7 @@ static int get_frame_info(struct mips_frame_info *info) if (info->pc_offset < 0) /* leaf */ return 1; /* prologue seems boggus... */ +err: return -1; } -- cgit From b5943182592ba256639a569c7d5305cf60360733 Mon Sep 17 00:00:00 2001 From: Franck Bui-Huu Date: Fri, 18 Aug 2006 16:18:09 +0200 Subject: [MIPS] get_wchan(): remove uses of mfinfo[64] This array was used to 'cache' some frame info about scheduler functions to speed up get_wchan(). This array was 1Ko size and was only used when CONFIG_KALLSYMS was set but declared for all configs. Rather than make the array statement conditional, this patches removes this array and its uses. Indeed the common case doesn't seem to use this array and get_wchan() is not a critical path anyways. It results in a smaller bss and a smaller/cleaner code: text data bss dec hex filename 2543808 254148 139296 2937252 2cd1a4 vmlinux-new-get-wchan 2544080 254148 143392 2941620 2ce2b4 vmlinux~old Signed-off-by: Franck Bui-Huu Signed-off-by: Ralf Baechle --- arch/mips/kernel/process.c | 132 +++++++++++++++++---------------------------- 1 file changed, 50 insertions(+), 82 deletions(-) (limited to 'arch/mips/kernel/process.c') diff --git a/arch/mips/kernel/process.c b/arch/mips/kernel/process.c index b160ea30de0f..2613a0dd4b82 100644 --- a/arch/mips/kernel/process.c +++ b/arch/mips/kernel/process.c @@ -273,13 +273,15 @@ long kernel_thread(int (*fn)(void *), void *arg, unsigned long flags) return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, ®s, 0, NULL, NULL); } -static struct mips_frame_info { - void *func; - unsigned long func_size; - int frame_size; - int pc_offset; -} *schedule_frame, mfinfo[64]; -static int mfinfo_num; +/* + * + */ +struct mips_frame_info { + void *func; + unsigned long func_size; + int frame_size; + int pc_offset; +}; static inline int is_ra_save_ins(union mips_instruction *ip) { @@ -348,45 +350,30 @@ err: return -1; } +static struct mips_frame_info schedule_mfi __read_mostly; + static int __init frame_info_init(void) { - int i; + unsigned long size = 0; #ifdef CONFIG_KALLSYMS + unsigned long ofs; char *modname; char namebuf[KSYM_NAME_LEN + 1]; - unsigned long start, size, ofs; - extern char __sched_text_start[], __sched_text_end[]; - extern char __lock_text_start[], __lock_text_end[]; - - start = (unsigned long)__sched_text_start; - for (i = 0; i < ARRAY_SIZE(mfinfo); i++) { - if (start == (unsigned long)schedule) - schedule_frame = &mfinfo[i]; - if (!kallsyms_lookup(start, &size, &ofs, &modname, namebuf)) - break; - mfinfo[i].func = (void *)(start + ofs); - mfinfo[i].func_size = size; - start += size - ofs; - if (start >= (unsigned long)__lock_text_end) - break; - if (start == (unsigned long)__sched_text_end) - start = (unsigned long)__lock_text_start; - } -#else - mfinfo[0].func = schedule; - schedule_frame = &mfinfo[0]; + + kallsyms_lookup((unsigned long)schedule, &size, &ofs, &modname, namebuf); #endif - for (i = 0; i < ARRAY_SIZE(mfinfo) && mfinfo[i].func; i++) - get_frame_info(mfinfo + i); + schedule_mfi.func = schedule; + schedule_mfi.func_size = size; + + get_frame_info(&schedule_mfi); /* * Without schedule() frame info, result given by * thread_saved_pc() and get_wchan() are not reliable. */ - if (schedule_frame->pc_offset < 0) + if (schedule_mfi.pc_offset < 0) printk("Can't analyze schedule() prologue at %p\n", schedule); - mfinfo_num = i; return 0; } @@ -402,58 +389,11 @@ unsigned long thread_saved_pc(struct task_struct *tsk) /* New born processes are a special case */ if (t->reg31 == (unsigned long) ret_from_fork) return t->reg31; - - if (!schedule_frame || schedule_frame->pc_offset < 0) + if (schedule_mfi.pc_offset < 0) return 0; - return ((unsigned long *)t->reg29)[schedule_frame->pc_offset]; + return ((unsigned long *)t->reg29)[schedule_mfi.pc_offset]; } -/* get_wchan - a maintenance nightmare^W^Wpain in the ass ... */ -unsigned long get_wchan(struct task_struct *p) -{ - unsigned long stack_page; - unsigned long pc; -#ifdef CONFIG_KALLSYMS - unsigned long frame; -#endif - - if (!p || p == current || p->state == TASK_RUNNING) - return 0; - - stack_page = (unsigned long)task_stack_page(p); - if (!stack_page || !mfinfo_num) - return 0; - - pc = thread_saved_pc(p); -#ifdef CONFIG_KALLSYMS - if (!in_sched_functions(pc)) - return pc; - - frame = p->thread.reg29 + schedule_frame->frame_size; - do { - int i; - - if (frame < stack_page || frame > stack_page + THREAD_SIZE - 32) - return 0; - - for (i = mfinfo_num - 1; i >= 0; i--) { - if (pc >= (unsigned long) mfinfo[i].func) - break; - } - if (i < 0) - break; - - if (mfinfo[i].pc_offset < 0) - break; - pc = ((unsigned long *)frame)[mfinfo[i].pc_offset]; - if (!mfinfo[i].frame_size) - break; - frame += mfinfo[i].frame_size; - } while (in_sched_functions(pc)); -#endif - - return pc; -} #ifdef CONFIG_KALLSYMS /* used by show_backtrace() */ @@ -504,3 +444,31 @@ unsigned long unwind_stack(struct task_struct *task, unsigned long *sp, return __kernel_text_address(pc) ? pc : 0; } #endif + +/* + * get_wchan - a maintenance nightmare^W^Wpain in the ass ... + */ +unsigned long get_wchan(struct task_struct *task) +{ + unsigned long pc = 0; +#ifdef CONFIG_KALLSYMS + unsigned long sp; +#endif + + if (!task || task == current || task->state == TASK_RUNNING) + goto out; + if (!task_stack_page(task)) + goto out; + + pc = thread_saved_pc(task); + +#ifdef CONFIG_KALLSYMS + sp = task->thread.reg29 + schedule_mfi.frame_size; + + while (in_sched_functions(pc)) + pc = unwind_stack(task, &sp, pc, 0); +#endif + +out: + return pc; +} -- cgit