驱动程序的调试
一. 打印: printk, 自制proc文件
UBOOT传入console=ttySAC0(串口) console=tty1(LCD)
1. 内核处理UBOOT传入的参数
console_setup
add_preferred_console // 我想用名为"ttySAC0"的控制台,先记录下来
2. 硬件驱动的入口函数里:
drivers/serial/s3c2410.c
register_console(&s3c24xx_serial_console);
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);
4.语句
printk(KERN_DEBUG"%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
//将打印出 绝对路径,函数,行数
cat /proc/sys/kernel/printk //查看默认的打印级别console_loglevel
echo “8 4 17”>/proc/sys/kernel/printk //修改默认的打印级别console_loglevel
set bootargs loglevel=0 //不打印任何东西
set bootargs loglevel=10 //打印所有东西
执行dmsg本质是读取/proc/kmsg文件
5.打印到proc虚拟文件
要打印的信息会存放在log_buf中,通过文件/proc/kmsg可以来访问这个buf,然后将信息打印出来。由此我们就想了,我们是否可以构造这样一个mylog_buf,里面存放我们所需要的打印信息,通过一个/proc/kmsg文件可以访问该buf,然后打印出来?答案是肯定的!
5.1 proc机制分析
1 #ifdef CONFIG_PRINTK 2 { 3 struct proc_dir_entry *entry; 4 entry = create_proc_entry("kmsg", S_IRUSR, &proc_root); 5 if (entry) 6 entry->proc_fops = &proc_kmsg_operations; 7 } 8 #endif
创建一个proc入口,并设置操作函数
其操作函数如下:
1 const struct file_operations proc_kmsg_operations = { 2 .read = kmsg_read, 3 .poll = kmsg_poll, 4 .open = kmsg_open, 5 .release = kmsg_release, 6 };
所以我们可以仿照该文件写出我们自己的mymsg
1 #include <linux/module.h> 2 #include <linux/kernel.h> 3 #include <linux/fs.h> 4 #include <linux/init.h> 5 #include <linux/delay.h> 6 #include <asm/uaccess.h> 7 #include <asm/irq.h> 8 #include <asm/io.h> 9 #include <asm/arch/regs-gpio.h> 10 #include <asm/hardware.h> 11 #include <linux/proc_fs.h> 12 13 #define MYLOG_BUF_LEN 1024 14 15 struct proc_dir_entry *myentry; 16 17 static char mylog_buf[MYLOG_BUF_LEN]; 18 static char tmp_buf[MYLOG_BUF_LEN]; 19 static int mylog_r = 0; 20 static int mylog_r_for_read = 0; 21 static int mylog_w = 0; 22 23 static DECLARE_WAIT_QUEUE_HEAD(mymsg_waitq); 24 25 static int is_mylog_empty(void) 26 { 27 return (mylog_r == mylog_w); 28 } 29 30 static int is_mylog_empty_for_read(void) 31 { 32 return (mylog_r_for_read == mylog_w); 33 } 34 35 static int is_mylog_full(void) 36 { 37 return ((mylog_w + 1)% MYLOG_BUF_LEN == mylog_r); 38 } 39 40 static void mylog_putc(char c) 41 { 42 if (is_mylog_full()) 43 { 44 /* 丢弃一个数据 */ 45 mylog_r = (mylog_r + 1) % MYLOG_BUF_LEN; 46 47 if ((mylog_r_for_read + 1) % MYLOG_BUF_LEN == mylog_r) 48 { 49 mylog_r_for_read = mylog_r; 50 } 51 } 52 53 mylog_buf[mylog_w] = c; 54 mylog_w = (mylog_w + 1) % MYLOG_BUF_LEN; 55 56 /* 唤醒等待数据的进程 */ 57 wake_up_interruptible(&mymsg_waitq); /* 唤醒休眠的进程 */ 58 } 59 60 static int mylog_getc(char *p) 61 { 62 if (is_mylog_empty()) 63 { 64 return 0; 65 } 66 *p = mylog_buf[mylog_r]; 67 mylog_r = (mylog_r + 1) % MYLOG_BUF_LEN; 68 return 1; 69 } 70 71 static int mylog_getc_for_read(char *p) 72 { 73 if (is_mylog_empty_for_read()) 74 { 75 return 0; 76 } 77 *p = mylog_buf[mylog_r_for_read]; 78 mylog_r_for_read = (mylog_r_for_read + 1) % MYLOG_BUF_LEN; 79 return 1; 80 } 81 82 83 int myprintk(const char *fmt, ...) 84 { 85 va_list args; 86 int i; 87 int j; 88 89 va_start(args, fmt); 90 i = vsnprintf(tmp_buf, INT_MAX, fmt, args); 91 va_end(args); 92 93 for (j = 0; j < i; j++) 94 mylog_putc(tmp_buf[j]); 95 96 return i; 97 } 98 99 static ssize_t mymsg_read(struct file *file, char __user *buf, 100 size_t count, loff_t *ppos) 101 { 102 int error = 0; 103 int i = 0; 104 char c; 105 106 /* 把mylog_buf的数据copy_to_user, return */ 107 if ((file->f_flags & O_NONBLOCK) && is_mylog_empty_for_read()) 108 return -EAGAIN; 109 110 //printk("%s %d\n", __FUNCTION__, __LINE__); 111 //printk("count = %d\n", count); 112 //printk("mylog_r = %d\n", mylog_r); 113 //printk("mylog_w = %d\n", mylog_w); 114 115 error = wait_event_interruptible(mymsg_waitq, !is_mylog_empty_for_read()); 116 117 //printk("%s %d\n", __FUNCTION__, __LINE__); 118 //printk("count = %d\n", count); 119 //printk("mylog_r = %d\n", mylog_r); 120 //printk("mylog_w = %d\n", mylog_w); 121 122 /* copy_to_user */ 123 while (!error && (mylog_getc_for_read(&c)) && i < count) { 124 error = __put_user(c, buf); 125 buf++; 126 i++; 127 } 128 129 if (!error) 130 error = i; 131 132 return error; 133 } 134 135 static int mymsg_open(struct inode *inode, struct file *file) 136 { 137 mylog_r_for_read = mylog_r; 138 return 0; 139 } 140 141 const struct file_operations proc_mymsg_operations = { 142 .open = mymsg_open, 143 .read = mymsg_read, 144 }; 145 146 static int mymsg_init(void) 147 { 148 myentry = create_proc_entry("mymsg", S_IRUSR, &proc_root); 149 if (myentry) 150 myentry->proc_fops = &proc_mymsg_operations; 151 return 0; 152 } 153 154 static void mymsg_exit(void) 155 { 156 remove_proc_entry("mymsg", &proc_root); 157 } 158 159 module_init(mymsg_init); 160 module_exit(mymsg_exit); 161 162 EXPORT_SYMBOL(myprintk); 163 164 MODULE_LICENSE("GPL");
打印到proc虚拟文件
可以直接使用myprintk,然后用cat /proc/mymsg指令查到我们的打印信息。
小结:在本文件里面我们做了两件事情,一件事情是定义了一个写函数,当我们在用户空间使用命令:cat /proc/mymsg的时候,就会调用到这个读函数,这个读函数会将mylog_buf中的数据拷贝到用户空间,那么mylog_buf里面的数据哪里来的呢?这就是我们做的另外一件事情,我们定义了一个打印函数,这个打印函数会将要打印的数据写入一个临时缓冲区,然后又从临时缓冲区里面取出数据放入mylog_buf中。cat /proc/mymsg的候就会将mylog_buf中的数据拷贝到用户空间,就可以显示出来了!
二. 根据内核打印的段错误信息分析
a. 作为模块:
1. 根据pc值确定该指令属于内核还是外加的模块
pc=0xbf000018 它属于什么的地址?是内核还是通过insmod加载的驱动程序?
先判断是否属于内核的地址: 看System.map确定内核的函数的地址范围:c0004000~c03265a4
如果不属于System.map里的范围,则它属于insmod加载的驱动程序
2. 假设它是加载的驱动程序引入的错误,怎么确定是哪一个驱动程序?
先看看加载的驱动程序的函数的地址范围
cat /proc/kallsyms (内核函数、加载的函数的地址)
从这些信息里找到一个相近的地址, 这个地址<=0xbf000018
比如找到了:
bf000000 t first_drv_open [first_drv]
3. 找到了first_drv.ko
在PC上反汇编它: arm-linux-objdump -D first_drv.ko > frist_drv.dis
在dis文件里找到first_drv_open
first_drv.dis文件里 insmod后
00000000 <first_drv_open>: bf000000 t first_drv_open [first_drv]
00000018 pc = bf000018
./firstdrvtest on
Unable to handle kernel paging request at virtual address 56000050
内核使用56000050来访问时发生了错误
pgd = c3eb0000
[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 : [<bf000018>] lr : [<c008d888>] psr: a0000013
sp : c3c7be88 ip : c3c7be98 fp : c3c7be94
r10: 00000000 r9 : c3c7a000 r8 : c049abc0
r7 : 00000000 r6 : 00000000 r5 : c3e740c0 r4 : c06d41e0
r3 : bf000000 r2 : 56000050 r1 : bf000964 r0 : 00000000
执行这条导致错误的指令时各个寄存器的值
Flags: NzCv IRQs on FIQs on Mode SVC_32 Segment user
Control: c000717f Table: 33eb0000 DAC: 00000015
Process firstdrvtest (pid: 777, stack limit = 0xc3c7a258)
发生错误时当前进程的名称是firstdrvtest
栈
Stack: (0xc3c7be88 to 0xc3c7c000)
be80: c3c7bebc c3c7be98 c008d888 bf000010 00000000 c049abc0
bea0: c3e740c0 c008d73c c0474e20 c3e766a8 c3c7bee4 c3c7bec0 c0089e48 c008d74c
bec0: c049abc0 c3c7bf04 00000003 ffffff9c c002c044 c3d10000 c3c7befc c3c7bee8
bee0: c0089f64 c0089d58 00000000 00000002 c3c7bf68 c3c7bf00 c0089fb8 c0089f40
bf00: c3c7bf04 c3e766a8 c0474e20 00000000 00000000 c3eb1000 00000101 00000001
bf20: 00000000 c3c7a000 c04a7468 c04a7460 ffffffe8 c3d10000 c3c7bf68 c3c7bf48
bf40: c008a16c c009fc70 00000003 00000000 c049abc0 00000002 bec1fee0 c3c7bf94
bf60: c3c7bf6c c008a2f4 c0089f88 00008520 bec1fed4 0000860c 00008670 00000005
bf80: c002c044 4013365c c3c7bfa4 c3c7bf98 c008a3a8 c008a2b0 00000000 c3c7bfa8
bfa0: c002bea0 c008a394 bec1fed4 0000860c 00008720 00000002 bec1fee0 00000001
bfc0: bec1fed4 0000860c 00008670 00000002 00008520 00000000 4013365c bec1fea8
bfe0: 00000000 bec1fe84 0000266c 400c98e0 60000010 00008720 00000000 00000000
Backtrace: (回溯)
[<bf000000>] (first_drv_open+0x0/0x3c [first_drv]) from [<c008d888>] (chrdev_open+0x14c/0x164)
[<c008d73c>] (chrdev_open+0x0/0x164) from [<c0089e48>] (__dentry_open+0x100/0x1e8)
r8:c3e766a8 r7:c0474e20 r6:c008d73c r5:c3e740c0 r4:c049abc0
[<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:00000002
[<c0089f78>] (do_filp_open+0x0/0x48) from [<c008a2f4>] (do_sys_open+0x54/0xe4)
r5:bec1fee0 r4:00000002
[<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: e24cb004 e59f1024 e3a00000 e5912000 (e5923000)
Segmentation fault
#
b. 编入内核
Modules linked in:
CPU: 0 Not tainted (2.6.22.6 #2)
PC is at first_drv_open+0x18/0x3c
LR is at chrdev_open+0x14c/0x164
pc : [<c014e6c0>] lr : [<c008638c>] psr: a0000013
sp : c3a03e88 ip : c3a03e98 fp : c3a03e94
r10: 00000000 r9 : c3a02000 r8 : c03f3c60
r7 : 00000000 r6 : 00000000 r5 : c38a0c50 r4 : c3c1e780
r3 : c014e6a8 r2 : 56000050 r1 : c031a47c r0 : 00000000
Flags: NzCv IRQs on FIQs on Mode SVC_32 Segment user
Control: c000717f Table: 339f0000 DAC: 00000015
Process firstdrvtest (pid: 750, stack limit = 0xc3a02258)
1. 根据pc值确定该指令属于内核还是外加的模块
pc=c014e6c0 属于内核(看System.map)
2. 反汇编内核: arm-linux-objdump -D vmlinux > vmlinux.dis
在dis文件里搜c014e6c0
c014e6a8 <first_drv_open>:
c014e6a8: e1a0c00d mov ip, sp
c014e6ac: e92dd800 stmdb sp!, {fp, ip, lr, pc}
c014e6b0: e24cb004 sub fp, ip, #4 ; 0x4
c014e6b4: e59f1024 ldr r1, [pc, #36] ; c014e6e0 <.text+0x1276e0>
c014e6b8: e3a00000 mov r0, #0 ; 0x0
c014e6bc: e5912000 ldr r2, [r1]
c014e6c0: e5923000 ldr r3, [r2] // 在此出错 r2=56000050
3. 根据栈信息分析函数调用过程
# ./firstdrvtest on
Unable to handle kernel paging request at virtual address 56000050
pgd = c3e78000
[56000050] *pgd=00000000
Internal error: Oops: 5 [#1]
Modules linked in: first_drv
CPU: 0 Not tainted (2.6.22.6 #48)
PC is at first_drv_open+0x18/0x3c [first_drv]
LR is at chrdev_open+0x14c/0x164
pc : [<bf000018>] lr : [<c008c888>] psr: a0000013
3.1 根据PC确定出错位置
bf000018 属于 insmod的模块
bf000000 t first_drv_open [first_drv]
3.2 确定它属于哪个函数
反汇编first_drv.ko
sp : c3e69e88 ip : c3e69e98 fp : c3e69e94
r10: 00000000 r9 : c3e68000 r8 : c0490620
r7 : 00000000 r6 : 00000000 r5 : c3e320a0 r4 : c06a8300
r3 : bf000000 r2 : 56000050 r1 : bf000964 r0 : 00000000
Flags: NzCv IRQs on FIQs on Mode SVC_32 Segment user
Control: c000717f Table: 33e78000 DAC: 00000015
Process firstdrvtest (pid: 752, stack limit = 0xc3e68258)
Stack: (0xc3e69e88 to 0xc3e6a000)
9e80: c3e69ebc c3e69e98 c008c888 bf000010 00000000 c0490620
first_drv_open‘sp lr chrdev_open‘sp
9ea0: c3e320a0 c008c73c c0465e20 c3e36cb4 c3e69ee4 c3e69ec0 c0088e48 c008c74c
lr
9ec0: c0490620 c3e69f04 00000003 ffffff9c c002b044 c06e0000 c3e69efc c3e69ee8
__dentry_open‘sp
9ee0: c0088f64 c0088d58 00000000 00000002 c3e69f68 c3e69f00 c0088fb8 c0088f40
lr nameidata_to_filp‘sp lr
9f00: c3e69f04 c3e36cb4 c0465e20 00000000 00000000 c3e79000 00000101 00000001
do_filp_open‘sp
9f20: 00000000 c3e68000 c04c1468 c04c1460 ffffffe8 c06e0000 c3e69f68 c3e69f48
9f40: c008916c c009ec70 00000003 00000000 c0490620 00000002 be94eee0 c3e69f94
9f60: c3e69f6c c00892f4 c0088f88 00008520 be94eed4 0000860c 00008670 00000005
lr do_sys_open‘sp
9f80: c002b044 4013365c c3e69fa4 c3e69f98 c00893a8 c00892b0 00000000 c3e69fa8
lr sys_open‘sp
9fa0: c002aea0 c0089394 be94eed4 0000860c 00008720 00000002 be94eee0 00000001
lr ret_fast_syscall‘sp
9fc0: be94eed4 0000860c 00008670 00000002 00008520 00000000 4013365c be94eea8
9fe0: 00000000 be94ee84 0000266c 400c98e0 60000010 00008720 00000000 00000000
三. 自制工具—寄存器编辑器
当我们调试驱动程序的时候,可能要调整寄存器的设置。按照我们之前的作法就是直接在程序里面修改,然后重新编译程序。但是这种方法比较麻烦,我们可以编写一个工具,可以直接对寄存器进行修改,这就是我们说的寄存器编辑器。
1 #include <linux/module.h> 2 #include <linux/kernel.h> 3 #include <linux/fs.h> 4 #include <linux/init.h> 5 #include <linux/delay.h> 6 #include <linux/irq.h> 7 #include <asm/uaccess.h> 8 #include <asm/irq.h> 9 #include <asm/io.h> 10 #include <asm/arch/regs-gpio.h> 11 #include <asm/hardware.h> 12 #include <linux/poll.h> 13 #include <linux/device.h> 14 15 #define KER_RW_R8 0 16 #define KER_RW_R16 1 17 #define KER_RW_R32 2 18 19 #define KER_RW_W8 3 20 #define KER_RW_W16 4 21 #define KER_RW_W32 5 22 23 24 static int major; 25 static struct class *class; 26 static struct class_device *ker_dev; 27 28 29 static int ker_rw_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) 30 { 31 volatile unsigned char *p8; 32 volatile unsigned short *p16; 33 volatile unsigned int *p32; 34 unsigned int val; 35 unsigned int addr; 36 37 unsigned int buf[2]; 38 39 copy_from_user(buf, (const void __user *)arg, 8); 40 addr = buf[0]; 41 val = buf[1]; 42 43 p8 = (volatile unsigned char *)ioremap(addr, 4); 44 p16 = p8; 45 p32 = p8; 46 47 switch (cmd) 48 { 49 case KER_RW_R8: 50 { 51 val = *p8; 52 copy_to_user((void __user *)(arg+4), &val, 4); 53 break; 54 } 55 56 case KER_RW_R16: 57 { 58 val = *p16; 59 copy_to_user((void __user *)(arg+4), &val, 4); 60 break; 61 } 62 63 case KER_RW_R32: 64 { 65 val = *p32; 66 copy_to_user((void __user *)(arg+4), &val, 4); 67 break; 68 } 69 70 case KER_RW_W8: 71 { 72 *p8 = val; 73 break; 74 } 75 76 case KER_RW_W16: 77 { 78 *p16 = val; 79 break; 80 } 81 82 case KER_RW_W32: 83 { 84 *p32 = val; 85 break; 86 } 87 } 88 89 iounmap(p8); 90 return 0; 91 } 92 93 static struct file_operations ker_rw_ops = { 94 .owner = THIS_MODULE, 95 .ioctl = ker_rw_ioctl, 96 }; 97 98 static int ker_rw_init(void) 99 { 100 major = register_chrdev(0, "ker_rw", &ker_rw_ops); 101 102 class = class_create(THIS_MODULE, "ker_rw"); 103 104 /* 为了让mdev根据这些信息来创建设备节点 */ 105 ker_dev = class_device_create(class, NULL, MKDEV(major, 0), NULL, "ker_rw"); /* /dev/ker_rw */ 106 107 return 0; 108 } 109 110 static void ker_rw_exit(void) 111 { 112 class_device_unregister(ker_dev); 113 class_destroy(class); 114 unregister_chrdev(major, "ker_rw"); 115 } 116 117 module_init(ker_rw_init); 118 module_exit(ker_rw_exit); 119 120 121 MODULE_LICENSE("GPL");
自制寄存器修改器
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <fcntl.h> 4 #include <stdio.h> 5 #include <poll.h> 6 #include <signal.h> 7 #include <sys/types.h> 8 #include <unistd.h> 9 #include <fcntl.h> 10 #include <stdlib.h> 11 #include <string.h> 12 13 #define KER_RW_R8 0 14 #define KER_RW_R16 1 15 #define KER_RW_R32 2 16 17 #define KER_RW_W8 3 18 #define KER_RW_W16 4 19 #define KER_RW_W32 5 20 21 22 /* Usage: 23 * ./regeditor r8 addr [num] 24 * ./regeditor r16 addr [num] 25 * ./regeditor r32 addr [num] 26 * 27 * ./regeditor w8 addr val 28 * ./regeditor w16 addr val 29 * ./regeditor w32 addr val 30 */ 31 32 void print_usage(char *file) 33 { 34 printf("Usage:\n"); 35 printf("%s <r8 | r16 | r32> <phy addr> [num]\n", file); 36 printf("%s <w8 | w16 | w32> <phy addr> <val>\n", file); 37 } 38 39 int main(int argc, char **argv) 40 { 41 int fd; 42 unsigned int buf[2]; 43 unsigned int i; 44 unsigned int num; 45 46 if ((argc != 3) && (argc != 4)) 47 { 48 print_usage(argv[0]); 49 return -1; 50 } 51 52 fd = open("/dev/ker_rw", O_RDWR); 53 if (fd < 0) 54 { 55 printf("can‘t open /dev/ker_rw\n"); 56 return -2; 57 } 58 59 /* addr */ 60 buf[0] = strtoul(argv[2], NULL, 0); 61 62 if (argc == 4) 63 { 64 buf[1] = strtoul(argv[3], NULL, 0); 65 num = buf[1]; 66 } 67 else 68 { 69 num = 1; 70 } 71 72 if (strcmp(argv[1], "r8") == 0) 73 { 74 for ( i = 0; i < num; i++) 75 { 76 ioctl(fd, KER_RW_R8, buf); /* val = buf[1] */ 77 printf("%02d. [%08x] = %02x\n", i, buf[0], (unsigned char)buf[1]); 78 buf[0] += 1; 79 } 80 } 81 else if (strcmp(argv[1], "r16") == 0) 82 { 83 for ( i = 0; i < num; i++) 84 { 85 ioctl(fd, KER_RW_R16, buf); /* val = buf[1] */ 86 printf("%02d. [%08x] = %02x\n", i, buf[0], (unsigned short)buf[1]); 87 buf[0] += 2; 88 } 89 } 90 else if (strcmp(argv[1], "r32") == 0) 91 { 92 for ( i = 0; i < num; i++) 93 { 94 ioctl(fd, KER_RW_R32, buf); /* val = buf[1] */ 95 printf("%02d. [%08x] = %02x\n", i, buf[0], (unsigned int)buf[1]); 96 buf[0] += 4; 97 } 98 } 99 else if (strcmp(argv[1], "w8") == 0) 100 { 101 ioctl(fd, KER_RW_W8, buf); /* val = buf[1] */ 102 } 103 else if (strcmp(argv[1], "w16") == 0) 104 { 105 ioctl(fd, KER_RW_W16, buf); /* val = buf[1] */ 106 } 107 else if (strcmp(argv[1], "w32") == 0) 108 { 109 ioctl(fd, KER_RW_W32, buf); /* val = buf[1] */ 110 } 111 else 112 { 113 printf(argv[0]); 114 return -1; 115 } 116 117 return 0; 118 119 }
测试程序
四. 修改内核来定位系统僵死问题
在系统运行的过程中可能出现一种状况:系统僵死。
系统处于僵死状态时,程序将不再运行。但是即便系统僵死,系统时钟中断还是以固定的频率发生我们可以进入时钟中断处理函数把当前僵死的进程
信息打印出来
我们在命令行输入:cat /proc/interrupts
1 打印出如下信息: 2 CPU0 3 30: 85713 s3c S3C2410 Timer Tick //这个就是系统时钟中断 4 33: 0 s3c s3c-mci 5 34: 0 s3c I2SSDI 6 35: 0 s3c I2SSDO 7 37: 12 s3c s3c-mci 8 42: 0 s3c ohci_hcd:usb1 9 43: 0 s3c s3c2440-i2c 10 51: 3509 s3c-ext eth0 11 60: 0 s3c-ext s3c-mci 12 70: 96 s3c-uart0 s3c2440-uart 13 71: 92 s3c-uart0 s3c2440-uart 14 83: 0 - s3c2410-wdt 15 Err: 0 16 17 30: 85713 s3c S3C2410 Timer Tick :这个就是系统时钟中断,我们可以再内核中查找:S3C2410 Timer Tick,会找到这样一个结构体: 18 static struct irqaction s3c2410_timer_irq = { 19 .name= "S3C2410 Timer Tick", 20 .flags= IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL, 21 .handler= s3c2410_timer_interrupt, 22 }; 23 24 其中:s3c2410_timer_interrupt就是中断处理函数!我们在其中加入一些信息: 25 26 s3c2410_timer_interrupt(int irq, void *dev_id) 27 { 28 static pid_t pre_pid; 29 static int cnt=0; 30 31 if(pre_pid==current->pid) 32 { 33 cnt++; 34 } 35 else 36 { 37 cnt=0; 38 pre_pid=current->pid; 39 } 40 //如果本进程十秒钟还没有离开的话,就会打印下面的语句 41 if(cnt==10*HZ) 42 { 43 cnt=0; 44 printk("s3c2410_timer_interrupt : pid = %d, task_name = %s\n",current->pid,current->comm); 45 } 46 47 write_seqlock(&xtime_lock); 48 timer_tick(); 49 write_sequnlock(&xtime_lock); 50 return IRQ_HANDLED; 51 }
输出信息
1 关于我们加入的代码有两点需要说一下: 2 第一:每一个进程都需要用一个结构体来表示:task_struct,这里面保存着与进程的一些状态信息,而current是一个宏,它代表当前的进程,也是一个task_struct结构体。所以current->pid就代表本进程的id,而current->comm就代表本进程的名字! 3 第二:HZ是一个宏定义,它表示1秒钟发生多少次中断,10*HZ就代表10秒钟发生多少次中断! 4 5 下面我们测试一下: 6 我们可以某个驱动程序里面放入语句:while(1);这样的话,当程序执行到这里的时候就会僵死掉,在之前没有加入上述信息之前,没有任何打印信息,我们根本不知道是哪一个进程发生了僵死,现在的话,没过10秒就会打印相关信息,告诉我们现在是什么进程正在发生僵死!我的测试打印信息如下: 7 s3c2410_timer_interrupt : pid = 752, task_name = firstdrvtest 8 s3c2410_timer_interrupt : pid = 752, task_name = firstdrvtest 9 s3c2410_timer_interrupt : pid = 752, task_name = firstdrvtest 10 11 不过这样还不够详细,有没有办法知道是在哪里发生了僵死呢?这也是有办法的! 12 先来说一下原理:在应用程序的执行的时候,会一直以固定的频率发生时钟中断,那么发生中断的时候肯定会保存现场吧,那么这个保存现场的时候就要保存发生中断处的PC值,这样才能返回,那么如果把这个PC值打印出来不就知道在哪里发生中断了吗! 13 14 我们之前分析过,发生中断的时候经过一些前期处理之后会调用:asm_do_IRQ这个函数 15 在这个函数里面我们发现了一个结构体:struct pt_regs,这个结构体就用来保存发生中断时的现场,其中PC值就是:ARM_pc 16 我们将上面在:s3c2410_timer_interrupt里面加入的信息都删除,并在:asm_do_IRQ函数里面加入如下信息:
进一步优化
1 static pid_t pre_pid; 2 static int cnt=0; 3 //时钟中断的中断号是30 4 if(irq==30) 5 { 6 if(pre_pid==current->pid) 7 { 8 cnt++; 9 } 10 else 11 { 12 cnt=0; 13 pre_pid=current->pid; 14 } 15 16 if(cnt==10*HZ) 17 { 18 cnt=0; 19 printk("s3c2410_timer_interrupt : pid = %d, task_name = %s\n",current->pid,current->comm); 20 printk("pc = %08x\n",regs->ARM_pc);//打印pc值 21 } 22 } 23 24 我们在次测试的话,会打印出如下信息: 25 s3c2410_timer_interrupt : pid = 752, task_name = firstdrvtest 26 pc = bf000084 27 s3c2410_timer_interrupt : pid = 752, task_name = firstdrvtest 28 pc = bf000084 29 s3c2410_timer_interrupt : pid = 752, task_name = firstdrvtest 30 pc = bf000084 31 s3c2410_timer_interrupt : pid = 752, task_name = firstdrvtest 32 pc = bf000084
输出信息和测试
./firstdrvtest on
asm_do_IRQ => s3c2410_timer_interrupt : pid = 752, task name = firstdrvtest
pc = bf000084
asm_do_IRQ => s3c2410_timer_interrupt : pid = 752, task name = firstdrvtest
pc = bf000084 // 对于中断, pc-4才是发生中断瞬间的地址
看/proc/kallsyms
first_drv.dis
00000000 <first_drv_open>: bf000000 t first_drv_open [first_drv]
0000003c <first_drv_write>:
3c: e1a0c00d mov ip, sp
40: e92dd800 stmdb sp!, {fp, ip, lr, pc}
44: e24cb004 sub fp, ip, #4 ; 0x4
48: e24dd004 sub sp, sp, #4 ; 0x4
4c: e3cd3d7f bic r3, sp, #8128 ; 0x1fc0
50: e3c3303f bic r3, r3, #63 ; 0x3f
54: e5933008 ldr r3, [r3, #8]
58: e0910002 adds r0, r1, r2
5c: 30d00003 sbcccs r0, r0, r3
60: 33a03000 movcc r3, #0 ; 0x0
64: e3530000 cmp r3, #0 ; 0x0
68: e24b0010 sub r0, fp, #16 ; 0x10
6c: 1a00001c bne e4 <init_module+0x5c>
70: ebfffffe bl 70 <first_drv_write+0x34>
74: ea00001f b f8 <init_module+0x70>
78: e3520000 cmp r2, #0 ; 0x0
7c: 11a01002 movne r1, r2
80: 1bfffffe blne 80 <first_drv_write+0x44> // 卡死的地方
84: ea00001f b 108 <init_module+0x80>