41.Linux应用调试-修改内核来打印用户态的oops
1.在之前第36章里,我们学习了通过驱动的oops定位错误代码行
第36章的oops代码如下所示:
Unable to handle kernel paging request at virtual address
//无法处理内核页面请求的虚拟地址56000050
pgd = c3850000
[] *pgd=
Internal error: Oops: [#]
//内部错误oops
Modules linked in: 26th_segmentfault
//表示内部错误发生在26th_segmentfault.ko驱动模块里
CPU: Not tainted (2.6.22.6 #)
PC is at first_drv_open+0x78/0x12c [26th_segmentfault]
//PC值:程序运行成功的最后一次地址,位于first_drv_open()函数里,偏移值0x78,该函数总大小0x12c
LR is at 0xc0365ed8 //LR值 /*发生错误时的各个寄存器值*/
pc : [<bf000078>] lr : [<c0365ed8>] psr:
sp : c3fcbe80 ip : c0365ed8 fp : c3fcbe94
r10: r9 : c3fca000 r8 : c04df960
r7 : r6 : r5 : bf000de4 r4 :
r3 : r2 : r1 : r0 : Flags: Nzcv IRQs on FIQs on Mode SVC_32 Segment user
Control: c000717f Table: DAC:
Process 26th_segmentfau (pid: , stack limit = 0xc3fca258)
//发生错误时,进程名称为26th_segmentfault Stack: (0xc3fcbe80 to 0xc3fcc000) //栈信息,从栈底0xc3fcbe80到栈顶0xc3fcc000
be80: c06d7660 c3e880c0 c3fcbebc c3fcbe98 c008d888 bf000010 c04df960
bea0: c3e880c0 c008d73c c0474e20 c3fb9534 c3fcbee4 c3fcbec0 c0089e48 c008d74c
bec0: c04df960 c3fcbf04 ffffff9c c002c044 c380a000 c3fcbefc c3fcbee8
bee0: c0089f64 c0089d58 c3fcbf68 c3fcbf00 c0089fb8 c0089f40
bf00: c3fcbf04 c3fb9534 c0474e20 c3851000
bf20: c3fca000 c04c90a8 c04c90a0 ffffffe8 c380a000 c3fcbf68 c3fcbf48
bf40: c008a16c c009fc70 c04df960 be84ce38 c3fcbf94
bf60: c3fcbf6c c008a2f4 c0089f88 be84ce84 0000877c
bf80: c002c044 4013365c c3fcbfa4 c3fcbf98 c008a3a8 c008a2b0 c3fcbfa8
bfa0: c002bea0 c008a394 be84ce84 be84ce30 be84ce38 be84ce30
bfc0: be84ce84 0000877c 4013365c be84ce58
bfe0: be84ce28 0000266c 400c98e0 be84ce30 Backtrace: //回溯信息
[<bf000000>] (first_drv_open+0x0/0x12c [26th_segmentfault]) from [<c008d888>] (chrdev_open+0x14c/0x164)
r5:c3e880c0 r4:c06d7660
[<c008d73c>] (chrdev_open+0x0/0x164) from [<c0089e48>] (__dentry_open+0x100/0x1e8)
r8:c3fb9534 r7:c0474e20 r6:c008d73c r5:c3e880c0 r4:c04df960
[<c0089d48>] (__dentry_open+0x0/0x1e8) from [<c0089f64>] (nameidata_to_filp+0x34/0x48)
[<c0089f30>] (nameidata_to_filp+0x0/0x48) from [<c0089fb8>] (do_filp_open+0x40/0x48)
r4:
[<c0089f78>] (do_filp_open+0x0/0x48) from [<c008a2f4>] (do_sys_open+0x54/0xe4)
r5:be84ce38 r4:
[<c008a2a0>] (do_sys_open+0x0/0xe4) from [<c008a3a8>] (sys_open+0x24/0x28)
[<c008a384>] (sys_open+0x0/0x28) from [<c002bea0>] (ret_fast_syscall+0x0/0x2c)
Code: bf000094 bf0000b4 bf0000d4 e5952000 (e5923000) Segmentation fault
1.1那为什么在上一章,我们用错误的应用程序,却没有打印oops,如下图所示:
接下来,我们便来配置内核,从而打印应用程序的oops
2.首先来搜索oops里的:Unable to handle kernel打印语句,看在哪个函数打印的
如下图所示,找到位于__do_kernel_fault()函数中:
3.继续找,发现__do_kernel_fault()被do_bad_area()调用
do_bad_area()函数,从字面上分析,表示代码执行到错误段位置
其中user_mode(regs)函数,通过判断CPSR寄存器若是用户模式则返回0,否则返回正数.
所以我们上一章的错误的应用程序便会调用__do_user_fault()函数
4.__do_user_fault()函数如下所示:
从上图来看,要想打印应用程序的错误信息,还需要:
3.1配置内核,设置宏CONFIG_DEBUG_USER(只要宏是以"CONFIG_"开头,都是与配置相关)
1)在make menuconfig里搜索DEBUG_USER,如下图所示:
所以将Kernel hacking-> Verbose user fault messages 置为Y,并重新烧内核
3.2使if (user_debug & UDBG_SEGV)为真
1)其中user_debug定义如下所示:
显然当uboot传递进来的命令行字符里含有"user_debug="时,便会调用user_debug_setup()->get_option(),最终会将"user_debug="后面带的字符串提取给user_debug变量.
比如:当命令行字符里含有"user_debug=0xff"时,则user_debug变量等于0xff
2)其中UDBG_SEGV定义如下所示:
#define UDBG_UNDEFINED (1 << 0) //用户态的代码出现未定义指令(UNDEFINED) #define UDBG_SYSCALL (1 << 1) //用户态系统调用已过时(SYSCALL) #define UDBG_BADABORT (1 << 2) //用户态数据错误已中止(BADABORT) #define UDBG_SEGV (1 << 3) //用户态的代码出现段错误(SEGV) #define UDBG_BUS (1 << 4) //用户态访问忙(BUS)
从上面的定义分析得出,我们只需要将user_debug设为0xff,上面的所有条件就都成立.
比如:当用户态的代码出现未定义指令时,由于user_debug最低位=1,所以打印出oops.
所以,进入uboot,在uboot命令行里添加: "user_debug=0xff"
4. 启动内核,试验
如下图所示,执行错误的应用程序,只打印了各个寄存器值,以及函数调用关系,而没有栈信息:
5.接下来,继续修改内核,使应用程序的oops也打印栈信息出来
在驱动的oops里有"Stack: "这个字段,搜索"Stack: "看看,位于哪个函数
5.1如下图所示, 找到位于__die()函数中:
这个__die()会被die()调用,die()又会被__do_kernel_fault()调用,而我们应用程序调用的__do_user_fault()里没有die()函数,所以没有打印出Stack栈信息。
上图里dump_mem():
dump_mem("Stack: ", regs->ARM_sp,THREAD_SIZE + (unsigned long)task_stack_page(tsk)); //打印stack栈信息
主要是通过sp寄存器里存的栈地址,每打印一个栈地址里的32位数据, 栈地址便加4(一个地址存8位,所以加4)。
接下来我们便通过这个原理,来修改应用程序调用的__do_user_fault()
5.2 在__do_user_fault(),添加以下带红色的字:
static void __do_user_fault(struct task_struct *tsk, unsigned long addr,unsigned int fsr, unsignedint sig, int code,struct pt_regs *regs)
{
struct siginfo si;
unsigned long val ;
int i=0;
#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);
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");
}
#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);
}
6.重新烧写内核,试验
如下图所示:
接下来,便来分析PC值,Stack栈,到底如何调用的
7.首先来分析PC值,确定错误的代码
1)生成反汇编:
arm-linux-objdump -D test_debug > test_debug.dis
2)搜索PC值84ac,如下图所示:
从上面看出,主要是将0x12(r3)放入地址0x00(r2)中
而0x00是个非法地址,所以出错
8.分析Stack栈信息,确定函数调用过程
参考: 37.Linux驱动调试-根据oops的栈信息,确定函数调用过程
8.1分析过程中,遇到main()函数的返回地址为:LR=40034f14
内核的虚拟地址是c0004000~c03cebf4,而反汇编里也没有该地址,所以这是个动态库的地址.
需要用到静态链接方法,接下来重新编译,反汇编,运行:
#arm-linux-gcc -o -static test_debug test_debug.c
//-static 静态链接,生成的文件会非常大, 好处在于不需要动态链接库,也可以运行
#arm-linux-objdump -D test_debug > test_debug.dis
8.2最终, 找到main()函数的返回地址在__lobc_start_main()里
所以函数出错时的调用过程:
__lobc_start_main()->
main()->
A()->
B()->
C() //将0x12(r3)放入地址0x00(r2)中
41.Linux应用调试-修改内核来打印用户态的oops的更多相关文章
- linux驱动调试--修改系统时钟终端来定位僵死问题【转】
本文转载自:http://blog.chinaunix.net/uid-20671208-id-4940381.html 原文地址:linux驱动调试--修改系统时钟终端来定位僵死问题 作者:枫露清愁 ...
- linux中如何修改文件夹的用户权限 chown命令
linux中,可以使用chown命令来修改文件夹的用户权限. 1. 以普通用户 A 登录linux,利用su -切换到root用户 2. 在root用户下,可以看到文件夹内容 3. 但通过文件系统, ...
- 例说linux内核与应用数据通信(四):映射设备内核空间到用户态
[版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet.文章仅供学习交流,请勿用于商业用途] 一个进程的内存映象由以下几部分组成:代码段.数据段.BSS段和 ...
- linux mce的一些相关内容和用户态监控的设计方法
之所以想起写一点关于mce的东西,倒不是因为遇到mce的异常了,之前遇到过很多mce的异常,内存居多,但没有好好记录下来,写这个是因为参加2018 clk南京会议的一点想法. void __init ...
- Linux CentOS 7修改内核启动默认顺序
1.查看系统有几个内核 a.进入grub2.cfg文件中进行查看 b.通过grub界面查看 3.设置默认启动内核 grub2-set-default "内核版本" 配置默认内核4. ...
- Linux 内核态与用户态通信 netlink
参考资料: https://blog.csdn.net/zqixiao_09/article/details/77131283 https://www.cnblogs.com/lopnor/p/615 ...
- 在linux系统中实现各项监控的关键技术(2)--内核态与用户态进程之间的通信netlink
Netlink 是一种在内核与用户应用间进行双向数据传输的非常好的方式,用户态应用使用标准的 socket API 就可以使用 netlink 提供的强大功能,内核态需要使用专门的内核 API 来使用 ...
- v79.01 鸿蒙内核源码分析(用户态锁篇) | 如何使用快锁Futex(上) | 百篇博客分析OpenHarmony源码
百篇博客分析|本篇为:(用户态锁篇) | 如何使用快锁Futex(上) 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁) ...
- 42.Linux应用调试-初步制作系统调用(用户态->内核态)
1首先来讲讲应用程序如何实现系统调用(用户态->内核态)? 我们以应用程序的write()函数为例: 1)首先用户态的write()函数会进入glibc库,里面会将write()转换为swi(S ...
随机推荐
- xml读取一行数据
#include<map>#include<iostream>#include<fstream>#include<string>using namesp ...
- c语言的,三个工具可以使编译器生成性能更佳的代码。
内联函数声明inline 函数有时可以非常短.短函数的每次调用可以用实现该函数功能的内联代码替代,以提高执行性能.意味着不需要给函数传递或返回一个值,要让编译器采用这种技术,可以把短函数指定为inli ...
- 最长回文子串---Manacher算法
百度:Manacher算法 代码 #include <iostream> #include <string> #include <cstring> #include ...
- Best Coder #86 1001 Price List(大水题)
Price List Accepts: 880 Submissions: 2184 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 26214 ...
- 线程轮循打印ABC...
package com.java.concurrent; import java.util.concurrent.locks.Condition; import java.util.concurren ...
- 面向亿万级用户的QQ一般做什么?——兴趣部落的Web同构直出分享
作者:李强,腾讯web开发工程师 商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处. 原文链接:http://wetest.qq.com/lab/view/348.html 一.什么是同构 ...
- 基于python3.x,使用Tornado中的torndb模块操作数据库
目前Tornado中的torndb模块是不支持python3.x,所以需要修改部分torndb源码即可正常使用 1.开发环境介绍 操作系统:win8(64位),python版本:python3.6(3 ...
- [转]oracle系统表v$session、v$sql字段说明
在本视图中,每一个连接到数据库实例中的 session都拥有一条记录.包括用户 session及后台进程如 DBWR, LGWR, arcchiver等等. V$SESSION中的常用列 V$SESS ...
- javascript第十章--Ajax与Comet
① XMLHttpRequest对象 ② XMLHttpRequest2级 ③ 进度事件 ④ 跨域源资源共享 ⑤ 其他跨域技术
- 前端设计师如何提高UI界面中的阅读性
阅读体验是ui设计中必不可少的一项,良好的设计应该都是可读的设计,如果信息都无法正常而清晰的传达,那么设计就失去了意义.设计的可读性和排版设计息息相关,这也就跟设计师的设计功底息息相关.下面简单介绍文 ...