linux驅動調試
㈠ 如何調試linux的網路驅動
如何根據oops定位代碼行
我們借用linux設備驅動第二篇:構造和運行模塊裡面的hello world程序來演示出錯的情況,含有錯誤代碼的hello world如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <linux/init.h>
#include <linux/mole.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
char *p = NULL;
memcpy(p, "test", 4);
printk(KERN_ALERT "Hello, world\n");
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye, cruel world\n");
}
mole_init(hello_init);
mole_exit(hello_exit);
Makefile文件如下:
1
2
3
4
5
6
7
8
9
10
11
ifneq ($(KERNELRELEASE),)
obj-m := helloworld.o
else
KERNELDIR ?= /lib/moles/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) moles
endif
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions moles.order Mole.symvers
很明顯,以上代碼的第8行是一個空指針錯誤。insmod後會出現下面的oops信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[ 459.516441] BUG: unable to handle kernel NULL pointer dereference at (null)
[ 459.516445]
[ 459.516448] PGD 0
[ 459.516450] Oops: 0002 [#1] SMP
[ 459.516452] Moles linked in: helloworld(OE+) vmw_vsock_vmci_transport vsock coretemp crct10dif_pclmul crc32_pclmul ghash_clmulni_intel aesni_intel vmw_balloon snd_ens1371 aes_x86_64 lrw snd_ac97_codec gf128mul glue_helper ablk_helper cryptd ac97_bus gameport snd_pcm serio_raw snd_seq_midi snd_seq_midi_event snd_rawmidi snd_seq snd_seq_device snd_timer vmwgfx btusb ttm snd drm_kms_helper drm soundcore shpchp vmw_vmci i2c_piix4 rfcomm bnep bluetooth 6lowpan_iphc parport_pc ppdev mac_hid lp parport hid_generic usbhid hid psmouse ahci libahci floppy e1000 vmw_pvscsi vmxnet3 mptspi mptscsih mptbase scsi_transport_spi pata_acpi [last unloaded: helloworld]
[ 459.516476] CPU: 0 PID: 4531 Comm: insmod Tainted: G OE 3.16.0-33-generic #44~14.04.1-Ubuntu
[ 459.516478] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 05/20/2014
[ 459.516479] task: ffff88003821f010 ti: ffff880038fa0000 task.ti: ffff880038fa0000
[ 459.516480] RIP: 0010:[<ffffffffc061400d>] [<ffffffffc061400d>] hello_init+0xd/0x30 [helloworld]
[ 459.516483] RSP: 0018:ffff880038fa3d40 EFLAGS: 00010246
[ 459.516484] RAX: ffff88000c31d901 RBX: ffffffff81c1a020 RCX: 000000000004b29f
[ 459.516485] RDX: 000000000004b29e RSI: 0000000000000017 RDI: ffffffffc0615024
[ 459.516485] RBP: ffff880038fa3db8 R08: 0000000000015e80 R09: ffff88003d615e80
[ 459.516486] R10: ffffea000030c740 R11: ffffffff81002138 R12: ffff88000c31d0c0
[ 459.516487] R13: 0000000000000000 R14: ffffffffc0614000 R15: ffffffffc0616000
[ 459.516488] FS: 00007f8a6fa86740(0000) GS:ffff88003d600000(0000) knlGS:0000000000000000
[ 459.516489] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 459.516490] CR2: 0000000000000000 CR3: 0000000038760000 CR4: 00000000003407f0
[ 459.516522] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
[ 459.516524] DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
[ 459.516524] Stack:
[ 459.57] ffff880038fa3db8 ffffffff81002144 0000000000000001 0000000000000001
[ 459.516540] 0000000000000001 ffff880028ab5040 0000000000000001 ffff880038fa3da0
[ 459.516541] ffffffff8119d0b2 ffffffffc0616018 00000000bd1141ac ffffffffc0616018
[ 459.516543] Call Trace:
[ 459.516548] [<ffffffff81002144>] ? do_one_initcall+0xd4/0x210
[ 459.516550] [<ffffffff8119d0b2>] ? __vunmap+0xb2/0x100
[ 459.516554] [<ffffffff810ed9b1>] load_mole+0x13c1/0x1b80
[ 459.516557] [<ffffffff810e9560>] ? store_uevent+0x40/0x40
[ 459.516560] [<ffffffff810ee2e6>] SyS_finit_mole+0x86/0xb0
[ 459.516563] [<ffffffff8176be6d>] system_call_fastpath+0x1a/0x1f
[ 459.516564] Code: <c7> 04 25 00 00 00 00 74 65 73 74 31 c0 48 89 e5 e8 a2 86 14 c1 31
[ 459.516573] RIP [<ffffffffc061400d>] hello_init+0xd/0x30 [helloworld]
[ 459.516575] RSP <ffff880038fa3d40>
[ 459.516576] CR2: 0000000000000000
[ 459.516578] ---[ end trace 7c52cc8624b7ea60 ]---
下面簡單分析下oops信息的內容。
由BUG: unable to handle kernel NULL pointer dereference at (null)知道出錯的原因是使用了空指針。標紅的部分確定了具體出錯的函數。Moles linked in: helloworld表明了引起oops問題的具體模塊。call trace列出了函數的調用信息。這些信息中其中標紅的部分是最有用的,我們可以根據其信息找到具體出錯的代碼行。下面就來說下,如何定位到具體出錯的代碼行。
第一步我們需要使用objmp把編譯生成的bin文件反匯編,我們這里就是helloworld.o,如下命令把反匯編信息保存到err.txt文件中:
1
objmp helloworld.o -D > err.txt
err.txt內容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
helloworld.o: file format elf64-x86-64
Disassembly of section .text:
<span style="color:#ff0000;">0000000000000000 <init_mole>:</span>
0: e8 00 00 00 00 callq 5 <init_mole+0x5>
5: 55 push %rbp
6: 48 c7 c7 00 00 00 00 mov $0x0,%rdi
d: c7 04 25 00 00 00 00 movl $0x74736574,0x0
14: 74 65 73 74
18: 31 c0 xor %eax,%eax
1a: 48 89 e5 mov %rsp,%rbp
1d: e8 00 00 00 00 callq 22 <init_mole+0x22>
22: 31 c0 xor %eax,%eax
24: 5d pop %rbp
25: c3 retq
26: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
2d: 00 00 00
0000000000000030 <cleanup_mole>:
30: e8 00 00 00 00 callq 35 <cleanup_mole+0x5>
35: 55 push %rbp
36: 48 c7 c7 00 00 00 00 mov $0x0,%rdi
3d: 31 c0 xor %eax,%eax
3f: 48 89 e5 mov %rsp,%rbp
42: e8 00 00 00 00 callq 47 <cleanup_mole+0x17>
47: 5d pop %rbp
48: c3 retq
Disassembly of section .rodata.str1.1:
0000000000000000 <.rodata.str1.1>:
0: 01 31 add %esi,(%rcx)
2: 48 rex.W
3: 65 gs
4: 6c insb (%dx),%es:(%rdi)
5: 6c insb (%dx),%es:(%rdi)
6: 6f outsl %ds:(%rsi),(%dx)
7: 2c 20 sub $0x20,%al
9: 77 6f ja 7a <cleanup_mole+0x4a>
b: 72 6c jb 79 <cleanup_mole+0x49>
d: 64 0a 00 or %fs:(%rax),%al
10: 01 31 add %esi,(%rcx)
12: 47 6f rex.RXB outsl %ds:(%rsi),(%dx)
14: 6f outsl %ds:(%rsi),(%dx)
15: 64 fs
16: 62 (bad)
17: 79 65 jns 7e <cleanup_mole+0x4e>
19: 2c 20 sub $0x20,%al
1b: 63 72 75 movslq 0x75(%rdx),%esi
1e: 65 gs
1f: 6c insb (%dx),%es:(%rdi)
20: 20 77 6f and %dh,0x6f(%rdi)
23: 72 6c jb 91 <cleanup_mole+0x61>
25: 64 0a 00 or %fs:(%rax),%al
Disassembly of section .modinfo:
0000000000000000 <__UNIQUE_ID_license0>:
0: 6c insb (%dx),%es:(%rdi)
1: 69 63 65 6e 73 65 3d imul $0x3d65736e,0x65(%rbx),%esp
8: 44 75 61 rex.R jne 6c <cleanup_mole+0x3c>
b: 6c insb (%dx),%es:(%rdi)
c: 20 42 53 and %al,0x53(%rdx)
f: 44 2f rex.R (bad)
11: 47 50 rex.RXB push %r8
13: 4c rex.WR
...
Disassembly of section .comment:
0000000000000000 <.comment>:
0: 00 47 43 add %al,0x43(%rdi)
3: 43 3a 20 rex.XB cmp (%r8),%spl
6: 28 55 62 sub %dl,0x62(%rbp)
9: 75 6e jne 79 <cleanup_mole+0x49>
b: 74 75 je 82 <cleanup_mole+0x52>
d: 20 34 2e and %dh,(%rsi,%rbp,1)
10: 38 2e cmp %ch,(%rsi)
12: 32 2d 31 39 75 62 xor 0x62753931(%rip),%ch # 62753949 <cleanup_mole+0x62753919>
18: 75 6e jne 88 <cleanup_mole+0x58>
1a: 74 75 je 91 <cleanup_mole+0x61>
1c: 31 29 xor %ebp,(%rcx)
1e: 20 34 2e and %dh,(%rsi,%rbp,1)
21: 38 2e cmp %ch,(%rsi)
23: 32 00 xor (%rax),%al
Disassembly of section __mcount_loc:
0000000000000000 <__mcount_loc>:
由oops信息我們知道出錯的地方是hello_init的地址偏移0xd。而有mp信息知道,hello_init的地址即init_mole的地址,因為hello_init即本模塊的初始化入口,如果在其他函數中出錯,mp信息中就會有相應符號的地址。由此我們得到出錯的地址是0xd,下一步我們就可以使用addr2line來定位具體的代碼行:
addr2line -C -f -e helloworld.o d
此命令就可以得到行號了。以上就是通過oops信息來定位驅動崩潰的行號。
其他調試手段
以上就是通過oops信息來獲取具體的導致崩潰的代碼行,這種情況都是用在遇到比較嚴重的錯誤導致內核掛掉的情況下使用的,另外比較常用的調試手段就是使用printk來輸出列印信息。printk的使用方法類似printf,只是要注意一下列印級別,詳細介紹在linux設備驅動第二篇:構造和運行模塊中已有描述,另外需要注意的是大量使用printk會嚴重拖慢系統,所以使用過程中也要注意。
以上兩種調試手段是我工作中最常用的,還有一些其他的調試手段,例如使用/proc文件系統,使用trace等用戶空間程序,使用gdb,kgdb等,這些調試手段一般不太容易使用或者不太方便使用,所以這里就不在介紹了。
㈡ 如何系統的學習Linux驅動開發
在學習之前一直對驅動開發非常的陌生,感覺有點神秘。不知道驅動開發和普通的程序開發究竟有什麼不同;它的基本框架又是什麼樣的;他的開發環境有什麼特殊的地方;以及怎麼寫編寫一個簡單的字元設備驅動前編譯載入,下面我就對這些問題一個一個的介紹。
一、驅動的基本框架
1.那麼究竟什麼是驅動程序,它有什麼用呢:
l驅動是硬體設備與應用程序之間的一個中間軟體層
l它使得某個特定硬體能夠響應一個定義良好的內部編程介面,同時完全隱蔽了設備的工作細節
l用戶通過一組與具體設備無關的標准化的調用來完成相應的操作
l驅動程序的任務就是把這些標准化的系統調用映射到具體設備對於實際硬體的特定操作上
l驅動程序是內核的一部分,可以使用中斷、DMA等操作
l驅動程序在用戶態和內核態之間傳遞數據
2.Linux驅動的基本框架
3.Linux下設備驅動程序的一般可以分為以下三類
1)字元設備
a)所有能夠象位元組流一樣訪問的設備都通過字元設備來實現
b)它們被映射為文件系統中的節點,通常在/dev/目錄下面
c)一般要包含open read write close等系統調用的實現
2)塊設備
d)通常是指諸如磁碟、內存、Flash等可以容納文件系統的存儲設備。
e)塊設備也是通過文件系統來訪問,與字元設備的區別是:內核管理數據的方式不同
f)它允許象字元設備一樣以位元組流的方式來訪問,也可一次傳遞任意多的位元組。
3)網路介面設備
g)通常它指的是硬體設備,但有時也可能是一個軟體設備(如回環介面loopback),它們由內核中網路子系統驅動,負責發送和接收數據包。
h)它們的數據傳送往往不是面向流的,因此很難將它們映射到一個文件系統的節點上。
二、怎麼搭建一個驅動的開發環境
因為驅動是要編譯進內核,在啟動內核時就會驅動此硬體設備;或者編譯生成一個.o文件,當應用程序需要時再動態載入進內核空間運行。因此編譯任何一個驅動程序都要鏈接到內核的源碼樹。所以搭建環境的第一步當然是建內核源碼樹
1.怎麼建內核源碼樹
a)首先看你的系統有沒有源碼樹,在你的/lib/ moles目錄下會有內核信息,比如我當前的系統里有兩個版本:
#ls /lib/ moles
2.6.15-rc72.6.21-1.3194.fc7
查看其源碼位置:
## ll /lib/moles/2.6.15-rc7/build
lrwxrwxrwx 1 root root 27 2008-04-28 19:19 /lib/moles/2.6.15-rc7/build -> /root/xkli/linux-2.6.15-rc7
發現build是一個鏈接文件,其所對應的目錄就是源碼樹的目錄。但現在這里目標目錄已經是無效的了。所以得自己重新下載
b)下載並編譯源碼樹
有很多網站上可以下載,但官方網址是:
http://www.kernel.org/pub/linux/kernel/v2.6/
下載完後當然就是解壓編譯了
# tar –xzvf linux-2.6.16.54.tar.gz
#cd linux-2.6.16.54
## make menuconfig (配置內核各選項,如果沒有配置就無法下一步編譯,這里可以不要改任何東西)
#make
…
如果編譯沒有出錯。那麼恭喜你。你的開發環境已經搭建好了
三、了解驅動的基本知識
1.設備號
1)什麼是設備號呢?我們進系統根據現有的設備來講解就清楚了:
#ls -l /dev/
crwxrwxrwx 1 root root1,3 2009-05-11 16:36 null
crw------- 1 root root4,0 2009-05-11 16:35 systty
crw-rw-rw- 1 root tty5,0 2009-05-11 16:36 tty
crw-rw---- 1 root tty4,0 2009-05-11 16:35 tty0
在日期前面的兩個數(如第一列就是1,3)就是表示的設備號,第一個是主設備號,第二個是從設備號
2)設備號有什麼用呢?
l傳統上,主編號標識設備相連的驅動.例如, /dev/null和/dev/zero都由驅動1來管理,而虛擬控制台和串口終端都由驅動4管理
l次編號被內核用來決定引用哪個設備.依據你的驅動是如何編寫的自己區別
3)設備號結構類型以及申請方式
l在內核中, dev_t類型(在中定義)用來持有設備編號,對於2.6.0內核, dev_t是32位的量, 12位用作主編號, 20位用作次編號.
l能獲得一個dev_t的主或者次編號方式:
MAJOR(dev_t dev); //主要
MINOR(dev_t dev);//次要
l但是如果你有主次編號,需要將其轉換為一個dev_t,使用: MKDEV(int major, int minor);
4)怎麼在程序中分配和釋放設備號
在建立一個字元驅動時需要做的第一件事是獲取一個或多個設備編號來使用.可以達到此功能的函數有兩個:
l一個是你自己事先知道設備號的
register_chrdev_region,在中聲明:
int register_chrdev_region(dev_t first, unsigned int count, char *name);
first是你要分配的起始設備編號. first的次編號部分常常是0,count是你請求的連續設備編號的總數. name是應當連接到這個編號范圍的設備的名子;它會出現在/proc/devices和sysfs中.
l第二個是動態動態分配設備編號
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
使用這個函數, dev是一個只輸出的參數,它在函數成功完成時持有你的分配范圍的第一個數. fisetminor應當是請求的第一個要用的次編號;它常常是0. count和name參數如同給request_chrdev_region的一樣.
5)設備編號的釋放使用
不管你是採用哪些方式分配的設備號。使用之後肯定是要釋放的,其方式如下:
void unregister_chrdev_region(dev_t first, unsigned int count);
6)
2.驅動程序的二個最重要數據結構
1)file_operation
倒如字元設備scull的一般定義如下:
struct file_operations scull_fops = {
.owner = THIS_MODULE,
.llseek = scull_llseek,
.read = scull_read,
.write = scull_write,
.ioctl = scull_ioctl,
.open = scull_open,
.release = scull_release,
};
file_operation也稱為設備驅動程序介面
定義在,是一個函數指針的集合.每個打開文件(內部用一個file結構來代表)與它自身的函數集合相關連(通過包含一個稱為f_op的成員,它指向一個file_operations結構).這些操作大部分負責實現系統調用,因此,命名為open, read,等等
2)File
定義位於include/fs.h
struct file結構與驅動相關的成員
lmode_t f_mode標識文件的讀寫許可權
lloff_t f_pos當前讀寫位置
lunsigned int_f_flag文件標志,主要進行阻塞/非阻塞型操作時檢查
lstruct file_operation * f_op文件操作的結構指針
lvoid * private_data驅動程序一般將它指向已經分配的數據
lstruct dentry* f_dentry文件對應的目錄項結構
3.字元設備注冊
1)內核在內部使用類型struct cdev的結構來代表字元設備.在內核調用你的設備操作前,必須編寫分配並注冊一個或幾個這些結構.有2種方法來分配和初始化一個這些結構.
l如果你想在運行時獲得一個獨立的cdev結構,可以這樣使用:
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;
l如果想將cdev結構嵌入一個你自己的設備特定的結構;你應當初始化你已經分配的結構,使用:
void cdev_init(struct cdev *cdev, struct file_operations *fops);
2)一旦cdev結構建立,最後的步驟是把它告訴內核,調用:
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
說明:dev是cdev結構, num是這個設備響應的第一個設備號, count是應當關聯到設備的設備號的數目.常常count是1,但是有多個設備號對應於一個特定的設備的情形.
3)為從系統去除一個字元設備,調用:
void cdev_del(struct cdev *dev);
4.open和release
㈢ 如何編寫Linux 驅動程序
以裝載和卸載模塊為例:
1、首先輸入代碼
#include <linux/init.h>
#include <linux/mole.h>
㈣ linux 安裝了驅動後,怎麼運行驅動
linux的驅動一般有兩種格式,分別為:tar和rpm格式。
rpm安裝步驟:
1.將驅動程序文件bcm5700-.src.rpm復制到一個臨時目錄中,並在此目錄中運行以下命令:
rpm –ivh bcm5700-.src.rpm
2.運行以下命令切換到驅動目錄中:
cd /usr/src/redhat/SPECS/
3.此目錄中會生成一個名字為bcm5700.spec的文件,運行以下命令對驅動程序進行編譯:
rpmbuild –bb bcm5700.spec (對4.x.x版本的RPM適用)或 rpm -bb bcm5700.spec
4.運行以下命令切換到RPM目錄中:
cd /usr/src/redhat/RPMS/i386/
5.運行以下命令安裝驅動程序:
rpm –ivh bcm5700-.i386.rpm (對於Red Hat 7.2, 7.3, 2.1AS和其他包含老版本驅動的系統需要使用--force的參數,強制用新的驅動替換系統自帶的老版本驅動)
6.運行以下命令載入驅動模塊:
insmod bcm5700
7.運行kudzu命令,系統會自動搜索到硬體,進行配置即可。
或者重新啟動系統,啟動過程中系統會自動找到硬體,進行相應配置即可。
tar格式安裝步驟:
1. 將驅動程序壓縮文件bcm5700-.tar.gz復制到一個臨時目錄中,並使用以下命令解壓縮:
tar xvzf bcm5700-.tar.gz
2.構建驅動程序為運行內核可載入模塊
cd bcm5700-/src
make
3.載入測試
insmod bcm5700
4.載入驅動程序
make install
5.重新啟動系統,啟動過程中找到硬體,進行相應配置。
或者直接運行kudzu命令,系統會自動搜索到硬體,進行配置即可。
㈤ linux驅動有哪些
1、將驅動程序文件bcm5700src.rpm復制到一個臨時目錄中,並在此目錄中運行以下命令;
2、運行以下命令切換到驅動目錄中;
3、此目錄中會生成一個名字為bcm5700.spec的文件,運行以下命令對驅動程序進行編譯;
4、運行以下命令切換到RPM目錄中;
5、運行以下命令安裝驅動程序;
6、運行以下命令載入驅動模塊;
7、運行kudzu命令,系統會自動搜索到硬體,進行配置即可。
linux是文件型系統,在linux中,一切皆文件,所有硬體都會在對應的目錄(/dev)下面用相應的文件表示。 文件系統的linux下面,都有對於文件與這些設備關聯的,訪問這些文件就可以訪問實際硬體。 通過訪問文件去操作硬體設備,一切都會簡單很多,不需要再調用各種復雜的介面。 直接讀文件,寫文件就可以向設備發送、接收數據。 按照讀寫存儲數據方式,我們可以把設備分為以下幾種:字元設備(character device)、塊設備(Block device)和網路設備( network interface)。
字元設備(character device):指應用程序採用字元流方式訪問的設備。這些設備節點通常為傳真、虛擬終端和串口數據機、鍵盤之類設備提供流通信服務, 它通常只支持順序訪問。字元設備在實現時,大多不使用緩存器。系統直接從設備讀取/寫入每一個字元。
塊設備(Block device):通常支持隨機存取和定址,並使用緩存器,支持mount文件系統。典型的塊設備有硬碟、SD卡、快閃記憶體等,但此類設備一般不需要自己開發,linux對此提過了大部分的驅動。
網路設備(network interface):是一種特殊設備,它並不存在於/dev下面,主要用於網路數據的收發。網路驅動同塊驅動最大的不同在於網路驅動非同步接受外界數據,而塊驅動只對內核的請求作出響應。
上述設備中,字元設備驅動程序適合於大多數簡單的硬體設備,算是各類驅動程序中最簡單的一類,一般也是從這類驅動開始學習,然後再開始學習採用IIC、SPI等通訊介面的一些設備驅動。可以基於此類驅動調試LKT和LCS系列加密晶元。注意7位IIC地址是0x28。