驅動程式的調試一. 列印: printk, 自製proc文件UBOOT傳入console=ttySAC0(串口) console=tty1(LCD)1. 內核處理UBOOT傳入的參數console_setup add_preferred_console // 我想用名為"ttySAC0"的控制台,先 ...
驅動程式的調試
一. 列印: 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<