修改内核,使得应用程序出错时,也能打印oops。
1 pc值
1.1 源码
打印的oops,有“Unable to handle kernel paging request”等描述,在内核代码中搜索,找到在arch/arm/mm/fault.c中,__do_kernel_fault()
static void __do_kernel_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr,
struct pt_regs *regs)
{
...
printk(KERN_ALERT
"Unable to handle kernel %s at virtual address %08lx\n",
(addr < PAGE_SIZE) ? "NULL pointer dereference" :
"paging request", addr);
...
}
查看被do_bad_area调用
void do_bad_area(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
{
struct task_struct *tsk = current;
struct mm_struct *mm = tsk->active_mm;
if (user_mode(regs))
__do_user_fault(tsk, addr, fsr, SIGSEGV, SEGV_MAPERR, regs);
else
__do_kernel_fault(mm, addr, fsr, regs);
}
若是用户模式,则调用__do_user_fault
static void
__do_user_fault(struct task_struct *tsk, unsigned long addr,
unsigned int fsr, unsigned int sig, int code,
struct pt_regs *regs)
{
struct siginfo si;
#ifdef CONFIG_DEBUG_USER
if (user_debug & UDBG_SEGV) {
printk(KERN_DEBUG "%s: unhandled page fault (%d) at 0x%08lx, code 0x%03x\n",
tsk->comm, sig, addr, fsr);
show_pte(tsk->mm, addr);
show_regs(regs);
}
#endif
...
}
要打印错误信息,需配置CONFIG_DEBUG_USER。
在内核中添加配置
Kernel hacking --->
[*]Verbose user fault messages
还需使if (user_debug & UDBG_SEGV)为真。
在arch/arm/kernel中
unsigned int user_debug;
static int __init user_debug_setup(char *str)
{
get_option(&str, &user_debug);
return 1;
}
__setup("user_debug=", user_debug_setup);
在uboot的启动命令中,通过设置user_debug=xxx,来设置变量。UDBG_SEGV定义
#define UDBG_SEGV (1 << 3) //用户态的代码出现段错误(SEGV)
进入uboot,添加“user_debug=0xff”
1.2 测试
启动新内核,测试
./test_debug
打印出oops。
2 stack信息
在驱动的oops里有"Stack: "这个字段,搜索"Stack: ",发现位于__die()中
static void __die(const char *str, int err, struct thread_info *thread, struct pt_regs *regs)
{
...
dump_mem(KERN_EMERG, "Stack: ", regs->ARM_sp,
THREAD_SIZE + (unsigned long)task_stack_page(tsk));
...
}
调用流程:__do_kernel_fault()–>die()–>__die(),在__do_user_fault()中没有die()。添加信息,使得能打印stack信息。
修改__do_user_fault()
static void
__do_user_fault(struct task_struct *tsk, unsigned long addr,
unsigned int fsr, unsigned int sig, int code,
struct pt_regs *regs)
{
struct siginfo si;
//start:添加
unsigned long val;
int i = 0;
//end
#ifdef CONFIG_DEBUG_USER
if (user_debug & UDBG_SEGV) {
printk(KERN_DEBUG "%s: unhandled page fault (%d) at 0x%08lx, code 0x%03x\n",
tsk->comm, sig, addr, fsr);
show_pte(tsk->mm, addr);
show_regs(regs);
//start:添加
printk("Stack: \n");
while(i<1024)
{
/* copy_from_user()只是用来检测该地址是否有效,如有效,便获取地址数据,否则break */
if(copy_from_user(&val, (const void __user *)(regs->ARM_sp+i*4), 4))
break;
printk("%08x ",val);//打印数据
i++;
if(i%8==0)
printk("\n");
}
printk("\n END of Stack\n");
//end
}
#endif
tsk->thread.address = addr;
tsk->thread.error_code = fsr;
tsk->thread.trap_no = 14;
si.si_signo = sig;
si.si_errno = 0;
si.si_code = code;
si.si_addr = (void __user *)addr;
force_sig_info(sig, &si, tsk);
}
3 问题分析
(1)PC值
反汇编
arm-linux-objdump -D test_debug > test_debug.dis
根据PC值,确定问题所在。
(2)stack调用流程
由于是动态加载,main的返回地址不在内核的地址空间中,需要使用静态链接方法
arm-linux-gcc -o -static test_debug test_debug.c
#-static 静态链接,生成的文件会非常大, 好处在于不需要动态链接库,也可以运行
arm-linux-objdump -D test_debug > test_debug.dis
根据反汇编和stack中的地址信息,分析调用流程。