linux驱动调试方法

linux驱动调试方法

声明: 文章的内容来自于韦东山老师的第二期驱动视频中的驱动调试一章。
第一次写博客,写的目的是为了将自己的所学的东西记录下来,防止像以前一样学了就忘了。内容写的乱七八糟,主要是为了以后看的时候有个印象 ,内容可能会有很多错误,欢迎指正。下面就是具体内容:
驱动程序的调试的方法:

  1. 打印: 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_setup

    1.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的调试是非常快的
  1. 自制工具
    根据上节课的总结,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

  2. 根据内核打印的错误信息进行分析

    下面是一个很简单的例子,可以进行下面的分析:

    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 :
    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 : []    lr : []    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:
     [] (first_drv_open+0x0/0x3c) from [] (chrdev_open+0x14c/0x164)
     [] (chrdev_open+0x0/0x164) from [] (__dentry_open+0x100/0x1e8)
      r8:c3d5160c r7:c0467e20 r6:c008c73c r5:c3d52b64 r4:c3d3b8e0
     [] (__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:bef68edc 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)
     
     
     然后开始分析上面的输出信息: 最根本的方式是从PC值开始分析,根据PC值确定是内核还是后来加载的驱动的模块
     假设已经确定是内核的模块导致错误,确定方式如下:
     3.3.1.  pc : []
     	从system.map中查看内核的地址范围: c0004000 ~  c03c6454, 很明显,PC值是在内核的范围内
     3.3.2.  反汇编内核: 
     	arm-linux-objdump -D vmlinux > vmlinux.dis
     	vi vmlinux.dis
     	./c019cf5c
     	然后就可以看到下面的信息:
     	 425773 c019cf44 :
     	 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 :
     	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 :
     		 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 :
     		 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
     		 
     		 后面的就不再详述的,方式都是一样,可以对照回溯信息,会发现按照寄存器分析的调用的关系和回溯信息中的调用关系
     		 是完全一样的,但是并不是什么内核都有回溯信息,这个方法是最根本的方法。
    
  3. 修改内核
    最后一小节还没有学习,暂时就到这儿了。以后我会总结每次的学习。内容可能会有很多错误,欢迎指正,如果对你有帮助的话,我也佷开心,共同进步

你可能感兴趣的:(linux驱动调试方法)