嵌入式Linux应用调试【2】—— 打印用户态oops

修改内核,使得应用程序出错时,也能打印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中的地址信息,分析调用流程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值