linux驱动调试方法
声明: 文章的内容来自于韦东山老师的第二期驱动视频中的驱动调试一章。
第一次写博客,写的目的是为了将自己的所学的东西记录下来,防止像以前一样学了就忘了。内容写的乱七八糟,主要是为了以后看的时候有个印象 ,内容可能会有很多错误,欢迎指正。下面就是具体内容:
驱动程序的调试的方法:
-
打印: printk,自制proc文件
1.1 打印
启动开发板,进入u-boot
print:打印信息,然后设置boot参数
set bootargs noinitrd root=/dev/nfs nfsroot=121.251.65.100:/work/nfs_root/first_fs
ip=121.251.65.103:121.251.65.100:121.251.65.1:255.255.255.0::eth0:off init=/linuxrc console=tty1
最后一个参数的意思是打印在哪儿,tty1表示的是LCD,而ttySAC0表示的是打印在第一串口
1.2 内核
内核中用printk打印,打印时候必然会发送到具体的硬件,所以会调用硬件处理函数,也就是命令行参数bootargs中的console参数
在内核中搜索"console = ", 然后可以找到f:\linux-2.6.22.6\kernel\Printk.c下面的__setup(“console=”, console_setup);
可以大胆的猜测一下,当内核处理参数时, 就会调用console_setup来进行处理。__setup这个函数的作用是一个结构体,这个结构体
里面有一个name等于什么,后面会有一个函数,把这样的结构体放在一起,以后发现了字符串,就会调用相应的函数来处理。
console_setup
add_preferred_console //添加我想用的名为"ttySAC0"控制台,先记录下来
memcpy(c->name, name, sizeof(c->name)); //名字放进字符串,代码很简单,自己可以看看
大胆猜测,硬件操作函数包括LCD打印,串口打印
这个数组是个静态变量,可以直接从这个函数中查看是谁调用了这个数组:
register_console //注册的时候会和console_setup中的name,如果一致的话以后就调用这个函数,当然了这个也是猜测,
接下来看谁调用了register_console,使用默认的规则,带*的表示调用者:
register_console
*s3c24xx_serial_initconsole //f:\linux-2.6.22.6\drivers\serial\S3c2410.c
register_console(&s3c24xx_serial_console);
console s3c24xx_serial_console
.name = S3C24XX_SERIAL_NAME,
.device = uart_console_device,
.flags = CON_PRINTBUFFER,
.index = -1,
.write = s3c24xx_serial_console_write,
.setup = s3c24xx_serial_console_setup1.3. printk
vprintk
/* Emit the output into the temporary buffer */
// 先把输出信息放入临时BUFFER
vscnprintf// Copy the output into log_buf. // 把临时BUFFER里的数据稍作处理,再写入log_buf // 比如printk("abc")会得到"<4>abc", 再写入log_buf // 可以用dmesg命令把log_buf里的数据打印出来重现内核的输出信息 // 调用硬件的write函数输出 release_console_sem(); call_console_drivers(_con_start, _log_end); // 从log_buf得到数据,算出打印级别 _call_console_drivers(start_print, cur_index, msg_level); // 如果可以级别够格打印 if ((msg_log_level < console_loglevel __call_console_drivers con->write(con, &LOG_BUF(start), end - start);
测试: 利用printk测试第一个程序first_drv,修改以下的代码:
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
修改为: gpfcon = (volatile unsigned long *)0x56000050;
然后分别编译驱动程序和测试程序, 测试程序的编译方法采用arm-linux-gcc -o firstdrvtest firstdrvtest.c
接下来就是开始进行真正的测试的过程了,首先是:算了直接贴代码,很简单的:
#define DBG_PRINTK printk //定义一个宏定义
//#define DBG_PRINTK(x…) //这个宏定义的目的是为了不需要看见这些乱七八糟的打印的信息可以取消,…是说参数不固定的意思
DBG_PRINTK("%s %s %d\n", FILE, FUNCTION, LINE); //这三个参数的意思是文件名、函数名、所在行
/work/nfs_root/first_fs/first_drv/first_drv.c first_drv_init 71 //这个是实际测试的过程中的打印信息
ps:打印是一个非常好用的工具,要是对内核比较了解的情况下,可以使用打印的功能,对于bug的调试是非常快的
-
自制工具
根据上节课的总结,printk函数会兵分两路,一个是放在内核的缓冲区中,还有一个是利用硬件打印出来。如果我们想查看哪些信息,可以使用
dmesg将那些信息打印出来
dmesg中的信息是存放在ls -l /proc/kmsg
proc是一个虚拟的文件系统
vi /etc/init.d/rcS
#mount -t proc none /proc
ifconfig eth0 192.168.1.103
mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
系统启动的时候会有一个mount -a,这个mount -a是什么意思呢,这个a的意思是all,意思就是把所有的文件系统都挂接上去
那么所有的文件系统是指哪些文件系统呢,是指这些文件:
cat /etc/fstab fstab的意思是file system tab的意思,就是文件系统列表的意思
#device mount-point type options dump fsck order
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
就是把上面定义的文件系统全部都挂接上去,其中proc是虚拟文件系统,挂载在proc目录下面,可以采用下面的语句进行查看:
cat /proc/mounts
rootfs / rootfs rw 0 0
/dev/root / nfs rw,vers=2,rsize=4096,wsize=4096,hard,nolock,proto=udp,timeo=11,retrans=2,sec=sys,addr=192.168.1.100 0 0
proc /proc proc rw 0 0
sysfs /sys sysfs rw 0 0
tmpfs /dev tmpfs rw 0 0
devpts /dev/pts devpts rw 0 0
这是一个虚拟的文件系统,里面的文件是内核帮助我们生成的。
可以将里面的内容读取出来看看, 不用dmesg命令:
cat /proc/kmsg
<3>s3c2440-sdi s3c2440-sdi: CMD[TIMEOUT] #7 op:ALL_SEND_OCR(1) arg:0x00000000 flags:0x0861 retries:0 Status:nothing to complete
<7>mmc0: req done (CMD1): 1/0/0: 00000000 00000000 00000000 00000000
<7>mmc0: clock 0Hz busmode 1 powermode 0 cs 0 Vdd 0 width 0 timing 0
从上面的语句可以看见,前面会带有<>,里面的数字是代表打印级别,打印级别可以修改,修改的方法参考上一课的内容。
然后使用ctrl + c 退出
注意一点,貌似内核只能cat一次,应该是因为指针指到了结尾了,这个是内核自身的原因,不需要深究。接下来就是最终要的配置自己的工具了,可以模仿kmsg,写出一个符合自己需要的my_kmsg, 这个用来打印一些自己需要的信息
可以想象: 要是内核打印的信息非常之多,那么那么多的打印信息很可能会干扰自己的查看,所以如果我们只想看看某个驱动程序的一些打印
内容,我们就可以自己制造一个属于自己的,属于这个驱动程序的my_kmsg来打印驱动程序的一些信息,把它存放一个区域中。方法: 构造proc里面的某个文件,专门用来存放这个驱动程序的打印信息
上面也已经说了,printk的信息一个是存放在log_buf中,另外一个是利用硬件打印出来
其中log_buf中的信息可以通过cat /proc/kmsg来读取出来,其实dmesg其实就是这个读取命令
所以可以这样仿照: 定义一个my_log_buf, 然后再建立/proc/mymsg, 在驱动程序里面写出my_printk, 将my_printk中的信息存放在
my_lon_buf中,然后使用cat /proc/mymsg将驱动的打印信息打印出来。分析kmsg:
e:\linux-2.6.22.6\fs\proc\Proc_misc.c
entry = create_proc_entry(“kmsg”, S_IRUSR, &proc_root); //三个参数, name: kmsg mode:S_IRUSR(只读的意思) parent:proc_root (这个意思是位于proc的根目录下面)
entry->proc_fops = &proc_kmsg_operations;
const struct file_operations proc_kmsg_operations = {
.read = kmsg_read,
.poll = kmsg_poll,
.open = kmsg_open,
.release = kmsg_release,
};
这个解释就是当应用程序打开 cat /proc/kmsg的时候,实际上就是打开了const struct file_operations proc_kmsg_operations这个文件仿照e:\linux-2.6.22.6\fs\proc\Proc_misc.c写一个程序,my_msg.c程序主要是用来打印驱动程序中的信息
App:cat /proc/my_msg 就会进入内核态,发现这是一个虚拟文件系统的文件,就会找到file_operation, 找到里面的read函数
而read函数是干什么呢,当然是copy_to_user,应该就是这么一回事。
static ssize_t kmsg_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
if ((file->f_flags & O_NONBLOCK) && !do_syslog(9, NULL, 0))
return -EAGAIN;
return do_syslog(2, buf, count);
}
这个是内核自带的读函数,判断要是非阻塞方式或者没有数据的情况下就会返回错误,要不然就会执行下面的程序:return do_syslog(2, buf, count);
do_syslog(2, buf, count)
if (!access_ok(VERIFY_WRITE, buf, len)) //看看能否访问
wait_event_interruptible(log_wait,(log_start - log_end)); //等待唤醒,环形缓冲区没有数据的时候就会保持睡眠
插讲一下环形缓冲区: 空: R==W
写:buf[w] = val
w = (w + 1) % 10
读:val = buf[R]
R = (R + 1) % 10
满:(W + 1) % 10 = R
所以当log_start - log_end == 0的时候说明没有数据,这个时候就是休眠的状态后台运行的方式: cat /proc/my_msg &
看看有哪些进程正在运行: ps
杀死进程的方式:在后台运行之后,kill -9 790 -
根据内核打印的错误信息进行分析
下面是一个很简单的例子,可以进行下面的分析:
insmod first_drv.ko
./firstdrvtest
Unable to handle kernel paging request at virtual address 56000050
内核使用56000050访问时发生了错误
pgd = c3ecc000
[56000050] *pgd=00000000
Internal error: Oops: 5 [#1]
Modules linked in: first_drv
CPU: 0 Not tainted (2.6.22.6 #1)
PC is at first_drv_open+0x18(该指令的偏移值)/0x3c(该函数的总大小) [first_drv]
PC就是发生错误的指令的地址
大多的情况下,PC只是给出一个地址值,不会指出是在哪个函数里面LR is at chrdev_open+0x14c/0x164
LR就是寄存器而已pc = 0xbf000018
pc : [] lr : [] psr: a0000013
sp : c3ecbe88 ip : c3ecbe98 fp : c3ecbe94
r10: 00000000 r9 : c3eca000 r8 : c04deaa0
r7 : 00000000 r6 : 00000000 r5 : c3e9a0c0 r4 : c06dc5a0
r3 : bf000000 r2 : 56000050 r1 : bf000964 r0 : 00000000
执行这条导致错误的指令时各个寄存器的值Flags: NzCv IRQs on FIQs on Mode SVC_32 Segment user
Control: c000717f Table: 33ecc000 DAC: 00000015
Process firstdrvtest (pid: 777, stack limit = 0xc3eca258)
发生错误时当前进程的名称是firstdrvtest //pid是进程id栈:这个是个非常有用的东西,以后会对这个栈进行分析,从这个栈可以倒推出函数的调用关系
Stack: (0xc3ecbe88 to 0xc3ecc000)
be80: c3ecbebc c3ecbe98 c008d888 bf000010 00000000 c04deaa0
bea0: c3e9a0c0 c008d73c c0474e20 c3ebac38 c3ecbee4 c3ecbec0 c0089e48 c008d74c
bec0: c04deaa0 c3ecbf04 00000003 ffffff9c c002c044 c3d0d000 c3ecbefc c3ecbee8
bee0: c0089f64 c0089d58 00000000 00000002 c3ecbf68 c3ecbf00 c0089fb8 c0089f40
bf00: c3ecbf04 c3ebac38 c0474e20 00000000 00000000 c3ecd000 00000101 00000001
bf20: 00000000 c3eca000 c046d148 c046d140 ffffffe8 c3d0d000 c3ecbf68 c3ecbf48
bf40: c008a16c c009fc70 00000003 00000000 c04deaa0 00000002 beca9edc c3ecbf94
bf60: c3ecbf6c c008a2f4 c0089f88 00008520 beca9ed4 0000860c 00008670 00000005
bf80: c002c044 4013365c c3ecbfa4 c3ecbf98 c008a3a8 c008a2b0 00000000 c3ecbfa8
bfa0: c002bea0 c008a394 beca9ed4 0000860c 00008720 00000002 beca9edc 00000001
bfc0: beca9ed4 0000860c 00008670 00000001 00008520 00000000 4013365c beca9ea8
bfe0: 00000000 beca9e84 0000266c 400c98e0 60000010 00008720 fffff3ff ffff57ff回溯
有点内核不具有回溯信息,因为有点内核经过的裁剪,要是想具备回溯信息,就需要在内核中进行一写配置,在内核中搜索
在.config中搜索FRAME_P,可以看见这样的一句话: CONFIG_FRAME_POINTER=y
然后在make menuconfig中搜索FRAME_POINTER,
│ Symbol: FRAME_POINTER [=y] │
│ Prompt: Compile the kernel with frame pointers │
│ Defined at lib/Kconfig.debug:357 │
│ Depends on: DEBUG_KERNEL && (X86 || CRIS || M68K || M68KNOMMU || FRV || UML || S390 || AVR32 || SUPERH || BFIN) │
│ Location: │
│ -> Kernel hacking │
│ -> Kernel debugging (DEBUG_KERNEL [=y]) │
│ Selected by: LOCKDEP && DEBUG_KERNEL && TRACE_IRQFLAGS_SUPPORT && STACKTRACE_SUPPORT && LOCKDEP_SUPPORT && !X86 && !MIPS || FAULT_INJECTION_STACKTRACE_FILTER && FAULT_INJECTION_DEBUG_FS && STACKTR │
│
这个就是FRAME_POINTER的位置Backtrace:(回溯)
[] (first_drv_open+0x0/0x3c [first_drv]) from [] (chrdev_open+0x14c/0x164)
[] (chrdev_open+0x0/0x164) from [] (__dentry_open+0x100/0x1e8)
r8:c3ebac38 r7:c0474e20 r6:c008d73c r5:c3e9a0c0 r4:c04deaa0
[] (__dentry_open+0x0/0x1e8) from [] (nameidata_to_filp+0x34/0x48)
[] (nameidata_to_filp+0x0/0x48) from [] (do_filp_open+0x40/0x48)
r4:00000002
[] (do_filp_open+0x0/0x48) from [] (do_sys_open+0x54/0xe4)
r5:beca9edc r4:00000002
[] (do_sys_open+0x0/0xe4) from [] (sys_open+0x24/0x28)
[] (sys_open+0x0/0x28) from [] (ret_fast_syscall+0x0/0x2c)
Code: e24cb004 e59f1024 e3a00000 e5912000 (e5923000)
Segmentation fault上面也已经解释了,有的时候使用的内核是别人编译的,可能并没有回溯信息,需要自行配置,另一方面可以从根本去分析:
根据栈信息,找出函数之间的调用关系,先恢复之前的,去掉内核中的first_drv.o,然后重新编译:
make uImage
cp arch/arm/boot/uImage /work/nfs_root/ //这样写的话就不用重新命名了,就是uImage根据PC值找出导致错误的是内核还是加载的驱动程序的模块,举一个很简单的例子:
pc = 0xbf000018 他是什么地址,是内核,还是通过insmod加载的驱动程序?
3.1. 先判断是否属于内核的地址,通过system.map来看看内核的函数的地址范围,如果不属于System.map里面的地址范围
那么他就是insmod加载的驱动程序
从system.map里面可以看到,内核的地址范围是: c0004000 ~ c03c63d4(G在vi编辑器中可以直接跳到结尾)
而0xbf000018不是在这个范围,说明他肯定是外面加载的驱动程序3.2. 假设他是加载驱动程序引入的错误,那么怎么确定是哪一个驱动程序?
先看看加载的驱动程序的函数的地址范围,找出差不多的地址,这个地址应该 >= pc
cat /proc/kallsyms > /kallsyms.txt
cp /work/nfs_root/first_fs/kallsyms.txt /work/nfs_root/first_fs/kernel/
然后找到一个相近的地址: Line 19861: bf000000 t first_drv_open [first_drv], 找到了first_drv.ko
这个地址和0xbf000018相比,相当于在bf000000的基础上偏移了18个字节,然后查看反汇编文件:
在PC上进行反汇编: arm-linux-objdump -D first_drv.ko > first_drv.dis
00000000 <first_drv_open>:
0: e1a0c00d mov ip, sp //ip = 栈
4: e92dd800 stmdb sp!, {fp, ip, lr, pc} //把这些寄存器的值存到栈中
8: e24cb004 sub fp, ip, #4 ; 0x4 // fp = ip(sp 也就是栈) + 4
稍微解释一下,fp 应该就是FRAME_POINTER(帧指针) ,回溯的时候就可以根据每一个fp寄存器找到栈,从栈里面找到lr寄存器
而lr寄存器中存放着返回地址,把返回值打印出来,就知道调用者是谁
c: e59f1024 ldr r1, [pc, #36] ; 38 <__mod_vermagic5>
10: e3a00000 mov r0, #0 ; 0x0
14: e5912000 ldr r2, [r1]很明显, 出错的地方就是下面这一句话,这句话的一是是从r2寄存器中取出一个值放到r3中,从打印的错误信息中可以看到 r2的值是: r2 : 56000050,就是这个指令错误,因为驱动程序需要的是一个映射地址,这个地址是非法的,不能够访问的,所以 我们去看看这个地址是在什么地方被初始化,然后就可以看到这个地址没有映射 18: e5923000 ldr r3, [r2] //从r2中读取一个值放到r3中去 1c: e3c33c3f bic r3, r3, #16128 ; 0x3f00 //清楚某些数值 20: e5823000 str r3, [r2] 24: e5912000 ldr r2, [r1] 28: e5923000 ldr r3, [r2] 2c: e3833c15 orr r3, r3, #5376 ; 0x1500 30: e5823000 str r3, [r2] 34: e89da800 ldmia sp, {fp, sp, pc} 38: 00000000 andeq r0, r0, r0 在实际的工作过程中可能使用的内核并不会提供下面的信息: CPU: 0 Not tainted (2.6.22.6 #1) PC is at first_drv_open+0x18(该指令的偏移值)/0x3c(该函数的总大小) [first_drv] 所以我们需要掌握最根本的方法,就是利用PC值找到是哪个驱动程序出了问题
3.3. 假设是内核模块出现的错误,可以这样解决:
这样,先将错误的驱动模块写进内核中去:
cp /work/nfs_root/first_fs/kernel/first_drv.c /work/system/linux-2.6.22.6/drivers/char/
vi /work/system/linux-2.6.22.6/drivers/char/Makefile
加上这么一句话,在11行空白处: obj-y += first_drv.o
重新对内核进行编译 make uImage
cp arch/arm/boot/uImage /work/nfs_root/uImage_bad
然后在串口那儿重启:reboot
nfs 30000000 192.168.1.100:/work/nfs_root/uImage_bad
bootm 30000000
./firstdrvtest查看输出信息: Unable to handle kernel paging request at virtual address 56000050 pgd = c3c78000 [56000050] *pgd=00000000 Internal error: Oops: 5 [#1] Modules linked in: //内核出错后就不知道是哪个模块了 CPU: 0 Not tainted (2.6.22.6 #12) PC is at first_drv_open+0x18/0x3c 这句话有的时候不存在,所以最根本的方法还是看下面的PC值 LR is at chrdev_open+0x14c/0x164 pc : [<c019cf5c>] lr : [<c008c888>] psr: a0000013 sp : c3c77e88 ip : c3c77e98 fp : c3c77e94 r10: 00000000 r9 : c3c76000 r8 : c3d3b8e0 r7 : 00000000 r6 : 00000000 r5 : c3d52b64 r4 : c04d7240 r3 : c019cf44 r2 : 56000050 r1 : c03bb5bc r0 : 00000000 Flags: NzCv IRQs on FIQs on Mode SVC_32 Segment user Control: c000717f Table: 33c78000 DAC: 00000015 Process firstdrvtest (pid: 749, stack limit = 0xc3c76258) Stack: (0xc3c77e88 to 0xc3c78000) 7e80: c3c77ebc c3c77e98 c008c888 c019cf54 00000000 c3d3b8e0 7ea0: c3d52b64 c008c73c c0467e20 c3d5160c c3c77ee4 c3c77ec0 c0088e48 c008c74c 7ec0: c3d3b8e0 c3c77f04 00000003 ffffff9c c002b044 c0024000 c3c77efc c3c77ee8 7ee0: c0088f64 c0088d58 00000000 00000002 c3c77f68 c3c77f00 c0088fb8 c0088f40 7f00: c3c77f04 c3d5160c c0467e20 00000000 00000000 c3c79000 00000101 00000001 7f20: 00000000 c3c76000 c0475748 c0475740 ffffffe8 c0024000 c3c77f68 c3c77f48 7f40: c008916c c009ec70 00000003 00000000 c3d3b8e0 00000002 bef68edc c3c77f94 7f60: c3c77f6c c00892f4 c0088f88 00008520 bef68ed4 0000860c 00008670 00000005 7f80: c002b044 4013365c c3c77fa4 c3c77f98 c00893a8 c00892b0 00000000 c3c77fa8 7fa0: c002aea0 c0089394 bef68ed4 0000860c 00008720 00000002 bef68edc 00000001 7fc0: bef68ed4 0000860c 00008670 00000001 00008520 00000000 4013365c bef68ea8 7fe0: 00000000 bef68e84 0000266c 400c98e0 60000010 00008720 776f677f fdbf0469 Backtrace: [<c019cf44>] (first_drv_open+0x0/0x3c) from [<c008c888>] (chrdev_open+0x14c/0x164) [<c008c73c>] (chrdev_open+0x0/0x164) from [<c0088e48>] (__dentry_open+0x100/0x1e8) r8:c3d5160c r7:c0467e20 r6:c008c73c r5:c3d52b64 r4:c3d3b8e0 [<c0088d48>] (__dentry_open+0x0/0x1e8) from [<c0088f64>] (nameidata_to_filp+0x34/0x48) [<c0088f30>] (nameidata_to_filp+0x0/0x48) from [<c0088fb8>] (do_filp_open+0x40/0x48) r4:00000002 [<c0088f78>] (do_filp_open+0x0/0x48) from [<c00892f4>] (do_sys_open+0x54/0xe4) r5:bef68edc r4:00000002 [<c00892a0>] (do_sys_open+0x0/0xe4) from [<c00893a8>] (sys_open+0x24/0x28) [<c0089384>] (sys_open+0x0/0x28) from [<c002aea0>] (ret_fast_syscall+0x0/0x2c) Code: e24cb004 e59f1024 e3a00000 e5912000 (e5923000) 然后开始分析上面的输出信息: 最根本的方式是从PC值开始分析,根据PC值确定是内核还是后来加载的驱动的模块 假设已经确定是内核的模块导致错误,确定方式如下: 3.3.1. pc : [<c019cf5c>] 从system.map中查看内核的地址范围: c0004000 ~ c03c6454, 很明显,PC值是在内核的范围内 3.3.2. 反汇编内核: arm-linux-objdump -D vmlinux > vmlinux.dis vi vmlinux.dis ./c019cf5c 然后就可以看到下面的信息: 425773 c019cf44 <first_drv_open>: 425774 c019cf44: e1a0c00d mov ip, sp 425775 c019cf48: e92dd800 stmdb sp!, {fp, ip, lr, pc} 425776 c019cf4c: e24cb004 sub fp, ip, #4 ; 0x4 425777 c019cf50: e59f1024 ldr r1, [pc, #36] ; c019cf7c <.text+0x172f7c> 425778 c019cf54: e3a00000 mov r0, #0 ; 0x0 425779 c019cf58: e5912000 ldr r2, [r1] 425780 c019cf5c: e5923000 ldr r3, [r2] //可以看见依然是这句话出现了问题,具体就不分析了,同上 425781 c019cf60: e3c33c3f bic r3, r3, #16128 ; 0x3f00 425782 c019cf64: e5823000 str r3, [r2] 425783 c019cf68: e5912000 ldr r2, [r1] 425784 c019cf6c: e5923000 ldr r3, [r2] 425785 c019cf70: e3833c15 orr r3, r3, #5376 ; 0x1500 425786 c019cf74: e5823000 str r3, [r2] 425787 c019cf78: e89da800 ldmia sp, {fp, sp, pc} 425788 c019cf7c: c03bb5bc ldrgth fp, [fp], -ip
3.4 根据栈信息分析函数的调用过程:
Unable to handle kernel paging request at virtual address 56000050
pgd = c3d34000
[56000050] *pgd=00000000
Internal error: Oops: 5 [#1]
Modules linked in: first_drv
CPU: 0 Not tainted (2.6.22.6 #13)
PC is at first_drv_open+0x18/0x3c [first_drv]
LR is at chrdev_open+0x14c/0x164
pc : [] lr : [] psr: a0000013
sp : c3e8be88 ip : c3e8be98 fp : c3e8be94
r10: 00000000 r9 : c3e8a000 r8 : c04ef820
r7 : 00000000 r6 : 00000000 r5 : c3e7d0a0 r4 : c07046c0
r3 : bf000000 r2 : 56000050 r1 : bf000964 r0 : 00000000
Flags: NzCv IRQs on FIQs on Mode SVC_32 Segment user
Control: c000717f Table: 33d34000 DAC: 00000015
Process firstdrvtest (pid: 752, stack limit = 0xc3e8a258)
Stack: (0xc3e8be88 to 0xc3e8c000)
be80: c3e8bebc c3e8be98 c008c888 bf000010 00000000 c04ef820
bea0: c3e7d0a0 c008c73c c0467e20 c3e7b4f4 c3e8bee4 c3e8bec0 c0088e48 c008c74c
bec0: c04ef820 c3e8bf04 00000003 ffffff9c c002b044 c3e7e000 c3e8befc c3e8bee8
bee0: c0088f64 c0088d58 00000000 00000002 c3e8bf68 c3e8bf00 c0088fb8 c0088f40
bf00: c3e8bf04 c3e7b4f4 c0467e20 00000000 00000000 c3d35000 00000101 00000001
bf20: 00000000 c3e8a000 c04752c8 c04752c0 ffffffe8 c3e7e000 c3e8bf68 c3e8bf48
bf40: c008916c c009ec70 00000003 00000000 c04ef820 00000002 be837edc c3e8bf94
bf60: c3e8bf6c c00892f4 c0088f88 00008520 be837ed4 0000860c 00008670 00000005
bf80: c002b044 4013365c c3e8bfa4 c3e8bf98 c00893a8 c00892b0 00000000 c3e8bfa8
bfa0: c002aea0 c0089394 be837ed4 0000860c 00008720 00000002 be837edc 00000001
bfc0: be837ed4 0000860c 00008670 00000001 00008520 00000000 4013365c be837ea8
bfe0: 00000000 be837e84 0000266c 400c98e0 60000010 00008720 00000000 00000000
Backtrace:
[] (first_drv_open+0x0/0x3c [first_drv]) from [] (chrdev_open+0x14c/0x164)
[] (chrdev_open+0x0/0x164) from [] (__dentry_open+0x100/0x1e8)
r8:c3e7b4f4 r7:c0467e20 r6:c008c73c r5:c3e7d0a0 r4:c04ef820
[] (__dentry_open+0x0/0x1e8) from [] (nameidata_to_filp+0x34/0x48)
[] (nameidata_to_filp+0x0/0x48) from [] (do_filp_open+0x40/0x48)
r4:00000002
[] (do_filp_open+0x0/0x48) from [] (do_sys_open+0x54/0xe4)
r5:be837edc r4:00000002
[] (do_sys_open+0x0/0xe4) from [] (sys_open+0x24/0x28)
[] (sys_open+0x0/0x28) from [] (ret_fast_syscall+0x0/0x2c)
Code: e24cb004 e59f1024 e3a00000 e5912000 (e5923000)
Segmentation fault
根据PC值: pc : []可以知道这个错误的地方是属于insmod的模块,至于内核模块的地址范围上面已经详细的解释了3.4.1 确定它属于哪个函数 cat /proc/kallsyms > kallsyms.txt cp kallsyms.txt /work/nfs_root/first_fs/ //这句话单纯的目的只是将这个文件放到一个合适的地方,没有什么意思 然后在pc(windows)下查看这个文件kallsyms.txt ,可以找到这样的一句话 Line 19766: bf000000 t first_drv_open [first_drv] 反汇编first_drv.ko : arm-linux-objdump -D first_drv.ko > first_drv.dis 6 00000000 <first_drv_open>: 7 0: e1a0c00d mov ip, sp 8 4: e92dd800 stmdb sp!, {fp, ip, lr, pc} 9 8: e24cb004 sub fp, ip, #4 ; 0x4 10 c: e59f1024 ldr r1, [pc, #36] ; 38 <__mod_vermagic5> 11 10: e3a00000 mov r0, #0 ; 0x0 12 14: e5912000 ldr r2, [r1] 13 18: e5923000 ldr r3, [r2] 这个是在这个出错的地方 14 1c: e3c33c3f bic r3, r3, #16128 ; 0x3f00 15 20: e5823000 str r3, [r2] 16 24: e5912000 ldr r2, [r1] 17 28: e5923000 ldr r3, [r2] 18 2c: e3833c15 orr r3, r3, #5376 ; 0x1500 19 30: e5823000 str r3, [r2] 20 34: e89da800 ldmia sp, {fp, sp, pc} 21 38: 00000000 andeq r0, r0, r0 3.4.2 分析回溯(使用打印的寄存器的值分析调用的关系) 在反汇编的文件中,高位寄存器(stmdb sp!, {fp, ip, lr, pc})也就是里面靠后的寄存器在分配的空间里占据高位, 也就是高地址的位置,在回溯打印信息中他较为靠后,下面是分析的过程:很简单,不行具体详述。 Stack: (0xc3e8be88 to 0xc3e8c000) be80: c3e8bebc c3e8be98 c008c888 bf000010 00000000 c04ef820 fp ip lr pc r4 first_drv_open chrdev_open bea0: c3e7d0a0 c008c73c c0467e20 c3e7b4f4 c3e8bee4 c3e8bec0 c0088e48 c008c74c r5 r6 r7 r8 fp ip lr pc bec0: c04ef820 c3e8bf04 00000003 ffffff9c c002b044 c3e7e000 c3e8befc c3e8bee8 r4 r5 r6 r7 r8 sl fp ip __dentry_open bee0: c0088f64 c0088d58 00000000 00000002 c3e8bf68 c3e8bf00 c0088fb8 c0088f40 lr pc r4 fp ip lr pc nameidata_to_filp bf00: c3e8bf04 c3e7b4f4 c0467e20 00000000 00000000 c3d35000 00000101 00000001 bf20: 00000000 c3e8a000 c04752c8 c04752c0 ffffffe8 c3e7e000 c3e8bf68 c3e8bf48 bf40: c008916c c009ec70 00000003 00000000 c04ef820 00000002 be837edc c3e8bf94 bf60: c3e8bf6c c00892f4 c0088f88 00008520 be837ed4 0000860c 00008670 00000005 bf80: c002b044 4013365c c3e8bfa4 c3e8bf98 c00893a8 c00892b0 00000000 c3e8bfa8 bfa0: c002aea0 c0089394 be837ed4 0000860c 00008720 00000002 be837edc 00000001 bfc0: be837ed4 0000860c 00008670 00000001 00008520 00000000 4013365c be837ea8 bfe0: 00000000 be837e84 0000266c 400c98e0 60000010 00008720 00000000 00000000 仅仅分析第一个调用关系,前面已经说了高位寄存器在前,所以错误指令的函数first_drv_open的四个寄存器 stmdb sp!, {fp, ip, lr, pc}占据的位置依次是c3e8bebc c3e8be98 c008c888 bf000010,lr = c008c888 这个是返回地址,然后反汇编内核,说错了,不是反汇编内核,这个已经确定了是后面的加载模块,好吧,还是反汇编内核 因为调用是调用内核中的一些函数,反汇编的方法: arm-linux-objdump -D vmlinux > vmlinux.dis 注意两点,中间的 > 千万不要忘了,还有就是只能在linux-2.6.22.6下面反汇编这个文件,因为其他的目录下并没有vmlinux lr是返回地址,也就是说谁调用了first_drv_open,执行完first_drv_open后就会一个返回地址,从这个返回地址可以看出是谁 调用了first_drv_open这份函数,在内核的反汇编文件中vmlinux.dis搜索c008c888, 可以看见这么一段话: 138118 c008c73c <chrdev_open>: 138119 c008c73c: e1a0c00d mov ip, sp 138120 c008c740: e92dd9f0 stmdb sp!, {r4, r5, r6, r7, r8, fp, ip, lr, pc} 138121 c008c744: e24cb004 sub fp, ip, #4 ; 0x4 138122 c008c748: e24dd004 sub sp, sp, #4 ; 0x4 注意这一句话: 138122 c008c748: e24dd004 sub sp, sp, #4 ; 0x4 这句话说明栈(sp)实际占据了十个位置,依次在上面对应的位置写出这九个寄存器,其中第一个位置为空。 按照这个思路接着搜索: lr = c0088e48, 看见了这么一段话: 134207 c0088d48 <__dentry_open>: 134208 c0088d48: e1a0c00d mov ip, sp 134209 c0088d4c: e92dddf0 stmdb sp!, {r4, r5, r6, r7, r8, sl, fp, ip, lr, pc} 因为lr = c0088f64,在vmlinux.dis中继续搜索c0088f64,得到下面的一段话: 134331 c0088f30 <nameidata_to_filp>: 134332 c0088f30: e1a0c00d mov ip, sp 134333 c0088f34: e92dd810 stmdb sp!, {r4, fp, ip, lr, pc} 134334 c0088f38: e24cb004 sub fp, ip, #4 ; 0x4 134335 c0088f3c: e24dd004 sub sp, sp, #4 ; 0x4 注意这句话134335 c0088f3c: e24dd004 sub sp, sp, #4 ; 0x4 后面的就不再详述的,方式都是一样,可以对照回溯信息,会发现按照寄存器分析的调用的关系和回溯信息中的调用关系 是完全一样的,但是并不是什么内核都有回溯信息,这个方法是最根本的方法。
-
修改内核
最后一小节还没有学习,暂时就到这儿了。以后我会总结每次的学习。内容可能会有很多错误,欢迎指正,如果对你有帮助的话,我也佷开心,共同进步