日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

linux驱动程序设计21 Linux设备驱动的调试

發布時間:2023/12/8 linux 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux驱动程序设计21 Linux设备驱动的调试 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本章導讀
“工欲善其事,必先利其器”,為了方便進行Linux設備驅動的開發和調試,建立良好的開發環境很重
要,還要使用必要的工具軟件以及掌握常用的調試技巧等。
21.1節講解了Linux下調試器GDB的基本用法和技巧。
21.2節講解了Linux內核的調試方法。
21.3~21.10節對21.3節的概述展開了講解,內容有:Linux內核調試用的printk()、BUG_ON()、
WARN_ON()、/proc、Oops、strace、KGDB,以及使用仿真器進行調試的方法。
21.11節講解了Linux應用程序的調試方法,驅動工程師往往需要編寫用戶空間的應用程序以對自身編
寫的驅動進行驗證和測試,因此,掌握應用程序調試方法對驅動工程師而言也是必需的。
21.12節講解了Linux常用的一些穩定性、性能分析和調優工具。
21.1 GDB調試器的用法
21.1.1 GDB的基本用法
GDB是GNU開源組織發布的一個強大的UNIX下的程序調試工具,GDB主要可幫助工程師完成下面4
個方面的功能。
·啟動程序,可以按照工程師自定義的要求運行程序。
·讓被調試的程序在工程師指定的斷點處停住,斷點可以是條件表達式。
·當程序被停住時,可以檢查此時程序中所發生的事,并追蹤上文。
·動態地改變程序的執行環境。
不管是調試Linux內核空間的驅動還是調試用戶空間的應用程序,都必須掌握GDB的用法。而且,在
調試內核和調試應用程序時使用的GDB命令是完全相同的,下面以代碼清單21.1的應用程序為例演示
GDB調試器的用法。
代碼清單21.1 GDB調試器用法的演示程序
1int add(int a, int b)
2{
3 return a + b;
4}
5
6main()
7{
8 int sum[10] =
9 {
10 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
11 };
12 int i;
13
14 int array1[10] =
15 {
16 48, 56, 77, 33, 33, 11, 226, 544, 78, 90
17 };
18 int array2[10] =
19 {
20 85, 99, 66, 0x199, 393, 11, 1, 2, 3, 4
21 };
22
23 for (i = 0; i < 10; i++)
24 {
25 sum[i] = add(array1[i], array2[i]);
26 }
27}
使用命令gcc–g gdb_example.c–o gdb_example編譯上述程序,得到包含調試信息的二進制文件
example,執行gdb gdb_example命令進入調試狀態,如下所示:
$ gdb gdb_example
GNU gdb (Ubuntu 7.7-0ubuntu3.1) 7.7
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb)
1.list命令
在GDB中運行list命令(縮寫l)可以列出代碼,list的具體形式如下。
·list<linenum>,顯示程序第linenum行周圍的源程序,如下所示:
(gdb) list 15
10
11 int array1[10] =
12 {
13 48, 56, 77, 33, 33, 11, 226, 544, 78, 90
14 };
15 int array2[10] =
16 {
17 85, 99, 66, 0x199, 393, 11, 1, 2, 3, 4
18 };
19
·list<function>,顯示函數名為function的函數的源程序,如下所示:
(gdb) list main
2 {
3 return a + b;
4 }
56
main()
7 {
8 int sum[10];
9 int i;
10
11 int array1[10] =
·list,顯示當前行后面的源程序。
·list-,顯示當前行前面的源程序。
下面演示了使用GDB中的run(縮寫為r)、break(縮寫為b)、next(縮寫為n)命令控制程序的運
行,并使用print(縮寫為p)命令打印程序中的變量sum的過程:
(gdb) break add
Breakpoint 1 at 0x80482f7: file gdb_example.c, line 3.
(gdb) run
Starting program: /driver_study/gdb_example
Breakpoint 1, add (a=48, b=85) at gdb_example.c:3
warning: Source file is more recent than executable.
3 return a + b;
(gdb) next
4 }
(gdb) next
main () at gdb_example.c:23
23 for (i = 0; i < 10; i++)
(gdb) next
25 sum[i] = add(array1[i], array2[i]);
(gdb) print sum
$1 = {133, 0, 0, 0, 0, 0, 0, 0, 0, 0}
2.run命令
在GDB中,運行程序使用run命令。在程序運行前,我們可以設置如下4方面的工作環境。
(1)程序運行參數
用set args可指定運行時參數,如set args 10 20 30 40 50;用show args命令可以查看設置好的運行參
數。
(2)運行環境
用path<dir>可設定程序的運行路徑;用how paths可查看程序的運行路徑;用set environment
varname[=value]可設置環境變量,如set env USER=baohua;用show environment[varname]則可查看環境變
量。
(3)工作目錄
cd<dir>相當于shell的cd命令,pwd可顯示當前所在的目錄。
(4)程序的輸入輸出
info terminal用于顯示程序用到的終端的模式;在GDB中也可以使用重定向控制程序輸出,如
run>outfile;用tty命令可以指定輸入輸出的終端設備,如tty/dev/ttyS1。
3.break命令
在GDB中用break命令來設置斷點,設置斷點的方法如下。
(1)break<function>
在進入指定函數時停住,在C++中可以使用class::function或function(type,type)格式來指定函數
名。
(2)break<linenum>
在指定行號停住。
(3)break+offset/break-offset。
在當前行號的前面或后面的offset行停住,offiset為自然數。
(4)break filename:linenum
在源文件filename的linenum行處停住。
(5)break filename:function
在源文件filename的function函數的入口處停住。
(6)break*address
在程序運行的內存地址處停住。
(7)break
break命令沒有參數時,表示在下一條指令處停住。
(8)break…if<condition>
…可以是上述的break<linenum>、break+offset/break–offset中的參數,condition表示條件,在條件成立
時停住。比如在循環體中,可以設置break if i=100,表示當i為100時停住程序。
查看斷點時,可使用info命令,如info breakpoints[n]、info break[n](n表示斷點號)。
4.單步命令
在調試過程中,next命令用于單步執行,類似于VC++中的step over。next的單步不會進入函數的內
部,與next對應的step(縮寫為s)命令則在單步執行一個函數時,進入其內部,類似于VC++中的step
into。下面演示了step命令的執行情況,在第23行的add()函數調用處執行step會進入其內部的return
a+b;語句:
(gdb) break 25
Breakpoint 1 at 0x8048362: file gdb_example.c, line 25.
(gdb) run
Starting program: /driver_study/gdb_example
Breakpoint 1, main () at gdb_example.c:25
25 sum[i] = add(array1[i], array2[i]);
(gdb) step
add (a=48, b=85) at gdb_example.c:3
3 return a + b;
單步執行的更復雜用法如下。
(1)step<count>
單步跟蹤,如果有函數調用,則進入該函數(進入函數的前提是,此函數被編譯有debug信息)。step
后面不加count表示一條條地執行,加count表示執行后面的count條指令,然后再停住。
(2)next<count>
單步跟蹤,如果有函數調用,它不會進入該函數。同理,next后面不加count表示一條條地執行,加
count表示執行后面的count條指令,然后再停住。
(3)set step-mode
set step-mode on用于打開step-mode模式,這樣,在進行單步跟蹤(運行step指令)時,若跨越某沒有
調試信息的函數,程序的執行則會在該函數的第一條指令處停住,而不會跳過整個函數。這樣我們可以查
看該函數的機器指令。
(4)finish
運行程序,直到當前函數完成返回,并打印函數返回時的堆棧地址、返回值及參數值等信息。
(5)until(縮寫為u)
一直在循環體內執行單步而退不出來是一件令人煩惱的事情,用until命令可以運行程序直到退出循環
體。
(6)stepi(縮寫為si)和nexti(縮寫為ni)
stepi和nexti用于單步跟蹤一條機器指令。比如,一條C程序代碼有可能由數條機器指令完成,stepi和
nexti可以單步執行機器指令,相反,step和next是C語言級別的命令。
另外,運行display/i$pc命令后,單步跟蹤會在打出程序代碼的同時打出機器指令,即匯編代碼。
5.continue命令
當程序被停住后,可以使用continue命令(縮寫為c,fg命令同continue命令)恢復程序的運行直到程序
結束,或到達下一個斷點,命令格式為:
continue [ignore-count]
c [ignore-count]
fg [ignore-count]
ignore-count表示忽略其后多少次斷點。
假設我們設置了函數斷點add(),并觀察i,則在continue過程中,每次遇到add()函數或i發生變
化,程序就會停住,如下所示:
(gdb) continue
Continuing.
Hardware watchpoint 3: i
Old value = 2
New value = 3
0x0804838d in main () at gdb_example.c:23
23 for (i = 0; i < 10; i++)
(gdb) continue
Continuing.
Breakpoint 1, main () at gdb_example.c:25
25 sum[i] = add(array1[i], array2[i]);
(gdb) continue
Continuing.
Hardware watchpoint 3: i
Old value = 3
New value = 4
0x0804838d in main () at gdb_example.c:23
23 for (i = 0; i < 10; i++)
6.print命令
在調試程序時,當程序被停住時,可以使用print命令(縮寫為p),或是同義命令inspect來查看當前
程序的運行數據。print命令的格式如下:
print <expr>
print /<f> <expr>
<expr>是表達式,也是被調試的程序中的表達式,<f>是輸出的格式,比如,如果要把表達式按十六
進制的格式輸出,那么就是/x。在表達式中,有幾種GDB所支持的操作符,它們可以用在任何一種語言
中,@是一個和數組有關的操作符,::指定一個在文件或是函數中的變量,{<type>}<addr>表示一個指
向內存地址<addr>的類型為type的對象。
下面演示了查看sum[]數組的值的過程:
(gdb) print sum
$2 = {133, 155, 0, 0, 0, 0, 0, 0, 0, 0}
(gdb) next
Breakpoint 1, main () at gdb_example.c:25
25 sum[i] = add(array1[i], array2[i]);
(gdb) next
23 for (i = 0; i < 10; i++)
(gdb) print sum
$3 = {133, 155, 143, 0, 0, 0, 0, 0, 0, 0}
當需要查看一段連續內存空間的值時,可以使用GDB的@操作符,@的左邊是第一個內存地址,@的
右邊則是想查看內存的長度。例如如下動態申請的內存:
int *array = (int *) malloc (len * sizeof (int));
在GDB調試過程中這樣顯示這個動態數組的值:
p *array@len
print的輸出格式如下。
·x:按十六進制格式顯示變量。
·d:按十進制格式顯示變量。
·u:按十六進制格式顯示無符號整型。
·o:按八進制格式顯示變量。
·t:按二進制格式顯示變量。
·a:按十六進制格式顯示變量。
·c:按字符格式顯示變量。
·f:按浮點數格式顯示變量。
我們可用display命令設置一些自動顯示的變量,當程序停住時,或是單步跟蹤時,這些變量會自動顯
示。
如果要修改變量,如x的值,可使用如下命令:
print x=4
當用GDB的print查看程序運行時數據時,每一個print都會被GDB記錄下來。GDB會以$1,$2,$3…
這樣的方式為每一個print命令編號。我們可以使用這個編號訪問以前的表達式,如$1。
7.watch命令
watch一般用來觀察某個表達式(變量也是一種表達式)的值是否有了變化,如果有變化,馬上停止
程序運行。我們有如下幾種方法來設置觀察點。
watch<expr>:為表達式(變量)expr設置一個觀察點。一旦表達式值有變化時,馬上停止程序運行。
rwatch<expr>:當表達式(變量)expr被讀時,停止程序運行。
awatch<expr>:當表達式(變量)的值被讀或被寫時,停止程序運行。
info watchpoints:列出當前所設置的所有觀察點。
下面演示了觀察i并在連續運行next時一旦發現i變化,i值就會顯示出來的過程:
(gdb) watch i
Hardware watchpoint 3: i
(gdb) next
23 for (i = 0; i < 10; i++)
(gdb) next
Hardware watchpoint 3: i
Old value = 0
New value = 1
0x0804838d in main () at gdb_example.c:23
23 for (i = 0; i < 10; i++)
(gdb) next
Breakpoint 1, main () at gdb_example.c:25
25 sum[i] = add(array1[i], array2[i]);
(gdb) next
23 for (i = 0; i < 10; i++)
(gdb) next
Hardware watchpoint 3: i
Old value = 1
New value = 2
0x0804838d in main () at gdb_example.c:23
23 for (i = 0; i < 10; i++)
8.examine命令
我們可以使用examine命令(縮寫為x)來查看內存地址中的值。examine命令的語法如下所示:
x/<n/f/u> <addr>
<addr>表示一個內存地址。“x/”后的n、f、u都是可選的參數,n是一個正整數,表示顯示內存的長
度,也就是說從當前地址向后顯示幾個地址的內容;f表示顯示的格式,如果地址所指的是字符串,那么
格式可以是s,如果地址是指令地址,那么格式可以是i;u表示從當前地址往后請求的字節數,如果不指
定的話,GDB默認的是4字節。u參數可以被一些字符代替:b表示單字節,h表示雙字節,w表示四字節,
g表示八字節。當我們指定了字節長度后,GDB會從指定的內存地址開始,讀寫指定字節,并把其當作一
個值取出來。n、f、u這3個參數可以一起使用,例如命令x/3uh 0x54320表示從內存地址0x54320開始以雙
字節為1個單位(h)、16進制方式(u)顯示3個單位(3)的內存。
9.set命令
examine命令用于查看內存,而set命令用于修改內存。它的命令格式是“set*有類型的指針=value”。
比如,下列程序,在用gdb運行起來后,通過Ctrl+C停住。
main()
{
void *p = malloc(16);
while(1);
}
我們可以在運行中用如下命令來修改p指向的內存。
(gdb) set *(unsigned char *)p='h'
(gdb) set *(unsigned char *)(p+1)='e'
(gdb) set *(unsigned char *)(p+2)='l'
(gdb) set *(unsigned char *)(p+3)='l'
(gdb) set *(unsigned char *)(p+4)='o'
看看結果:
(gdb) x/s p
0x804b008: "hello"
也可以直接使用地址常數:
(gdb) p p
$2 = (void *) 0x804b008
(gdb) set *(unsigned char *)0x804b008='w'
(gdb) set *(unsigned char *)0x804b009='o'
(gdb) set *(unsigned char *)0x804b00a='r'
(gdb) set *(unsigned char *)0x804b00b='l'
(gdb) set *(unsigned char *)0x804b00c='d'
(gdb) x/s 0x804b008
0x804b008: "world"
10.jump命令
一般來說,被調試程序會按照程序代碼的運行順序依次執行,但是GDB也提供了亂序執行的功能,
也就是說,GDB可以修改程序的執行順序,從而讓程序隨意跳躍。這個功能可以由GDB的jump命令
jump<linespec>來指定下一條語句的運行點。<linespec>可以是文件的行號,可以是file:line格式,也可以
是+num這種偏移量格式,表示下一條運行語句從哪里開始。
jump <address>
這里的<address>是代碼行的內存地址。
注意:jump命令不會改變當前程序棧中的內容,如果使用jump從一個函數跳轉到另一個函數,當跳
轉到的函數運行完返回,進行出棧操作時必然會發生錯誤,這可能會導致意想不到的結果,因此最好只用
jump在同一個函數中進行跳轉。
11.signal命令
使用singal命令,可以產生一個信號量給被調試的程序,如中斷信號Ctrl+C。于是,可以在程序運行
的任意位置處設置斷點,并在該斷點處用GDB產生一個信號量,這種精確地在某處產生信號的方法非常
有利于程序的調試。
signal命令的語法是signal<signal>,UNIX的系統信號量通常為1~15,因此<signal>的取值也在這個范
圍內。
12.return命令
如果在函數中設置了調試斷點,在斷點后還有語句沒有執行完,這時候我們可以使用return命令強制
函數忽略還沒有執行的語句并返回。
return
return <expression>
上述return命令用于取消當前函數的執行,并立即返回,如果指定了<expression>,那么該表達式的值
會被作為函數的返回值。
13.call命令
call命令用于強制調用某函數:
call <expr>
表達式可以是函數,以此達到強制調用函數的目的,它會顯示函數的返回值(如果函數返回值不是
void)。比如在下列程序執行while(1)的時候:
main()
{
void *p = malloc(16);
while(1);
}
我們強制要求其執行strcpy()和printf():
(gdb) call strcpy(p, "hello world")
$3 = 134524936
(gdb) call printf("%s\n", p)
hello world
$4 = 12
14.info命令
info命令可以用來在調試時查看寄存器、斷點、觀察點和信號等信息。要查看寄存器的值,可以使用
如下命令:
info registers (查看除了浮點寄存器以外的寄存器)
info all-registers (查看所有寄存器,包括浮點寄存器)
info registers <regname ...> (查看所指定的寄存器)
要查看斷點信息,可以使用如下命令:
info break要列出當前所設置的所有觀察點,可使用如下命令:
info watchpoints
要查看有哪些信號正在被GDB檢測,可使用如下命令:
info signals
info handle
也可以使用info line命令來查看源代碼在內存中的地址。info line后面可以跟行號、函數名、文件名:行
號、文件名:函數名等多種形式,例如用下面的命令會打印出所指定的源碼在運行時的內存地址:
info line tst.c:func
15.disassemble
disassemble命令用于反匯編,可用它來查看當前執行時的源代碼的機器碼,實際上只是把目前內存中
的指令沖刷出來。下面的示例用于查看函數func的匯編代碼:
(gdb) disassemble func
Dump of assembler code for function func:
0x8048450 <func>: push %ebp
0x8048451 <func+1>: mov %esp,%ebp
0x8048453 <func+3>: sub $0x18,%esp
0x8048456 <func+6>: movl $0x0,0xfffffffc(%ebp)
...
End of assembler dump.
21.1.2 DDD圖形界面調試工具
GDB本身是一種命令行調試工具,但是通過DDD(Data Display Debugger,見
http://www.gnu.org/software/ddd/)可以被圖形界面化。DDD可以作為GDB、DBX、WDB、Ladebug、
JDB、XDB、Perl Debugger或Python Debugger的可視化圖形前端,其特有的圖形數據顯示功能(Graphical
Data Display)可以把數據結構按照圖形的方式顯示出來。
DDD最初源于1990年Andreas Zeller編寫的VSL結構化語言,后來經過一些程序員的努力,演化成今天
的模樣。DDD的功能非常強大,可以調試用C/C++、Ada、Fortran、Pascal、Modula-2和Modula-3編寫的程
序;能以超文本方式瀏覽源代碼;能夠進行斷點設置、回溯調試和歷史記錄;具有程序在終端運行的仿真
窗口,具備在遠程主機上進行調試的能力;能夠顯示各種數據結構之間的關系,并將數據結構以圖形形式
顯示;具有GDB/DBX/XDB的命令行界面,包括完整的文本編輯、歷史紀錄、搜尋引擎等。
DDD的主界面如圖21.1所示,它和Visual Studio等集成開發環境非常相近,而且DDD包含了Visual
Studio所不包含的部分功能。
圖21.1 DDD的主界面
在設計DDD的時候,設計人員決定把它與GDB之間的耦合度盡量降低。因為像GDB這樣的開源軟
件,更新的速度比商業軟件快,所以為了使GDB的變化不會影響到DDD,在DDD中,GDB是作為獨立的
進程運行的,通過命令行接口與DDD進行交互。
圖21.2顯示了用戶、DDD、GDB和被調試進程之間的關系,DDD和GDB之間的所有通信都是異步進
行的。在DDD中發出的GDB命令都會與一個回調函數相連,放入命令隊列中。這個回調函數在合適的時
間會處理GDB的輸出。例如,如果用戶手動輸入一條GDB的命令,DDD就會把這條命令與顯示GDB輸出
的一個回調函數連起來。一旦GDB命令完成,就會觸發回調函數,GDB的輸出就會顯示在DDD的命令窗
口中。
圖21.2 DDD運行機理
DDD在事件循環時等待用戶輸入和GDB輸出,同時等著GDB進入等待輸入狀態。當GDB可用時,下
一條命令就會從命令隊列中取出,送給GDB。GDB到達的輸出由上次命令的回調函數過程來處理。這種
異步機制避免了DDD在等待GDB輸出時發生阻塞現象,到達的事件可以在任何時間得到處理。
不可否認的是,DDD和GDB的分離使得DDD的運行速度相對來說比較慢,但是這種方法帶來了靈活
性和兼容性的好處。例如,用戶可以把GDB調試器換成其他調試器,如DBX等。另外,GDB和DDD的分
離使得用戶可以在不同的機器上分別運行GDB和DDD。
在DDD中,可以直接在底部的控制臺中輸入GDB命令,也可以通過菜單和鼠標以圖形方式觸發GDB
命令的運行,使用方法甚為簡單,因此這里不再贅述。
DDD不僅可用于調試PC上的應用程序,也可調試目標板子,方法是用如下命令啟動DDD(通過-
debugger選項指定一個針對ARM的GDB):
ddd --debugger arm-linux-gnueabihf-gdb <要調試的程序>
除了DDD以外,在Linux環境下,也可以使用廣受歡迎的Eclipse來編寫代碼并進行調試。安裝Eclipse
IDE for C/C++Developer后,在Eclipse中,可以設置Using GDB(DSF)Manual Remote Debugging Launcher
以及ARM的GDB等,如圖21.3所示。
圖21.3 在Eclipse中設置Remote調試模式和GDB
21.2 Linux內核調試
在嵌入式系統中,由于目標機資源有限,因此往往在主機上先編譯好程序,再在目標機上運行。用戶
所有的開發工作都在主機開發環境下完成,包括編碼、編譯、連接、下載和調試等。目標機和主機通過串
口、以太網、仿真器或其他通信手段通信,主機用這些接口控制目標機,調試目標機上的程序。
調試嵌入式Linux內核的方法如下。
1)目標機“插樁”,如打上KGDB補丁,這樣主機上的GDB可與目標機的KGDB通過串口或網口通
信。
2)使用仿真器,仿真器可直接連接目標機的JTAG/BDM,這樣主機的GDB就可以通過與仿真器的通
信來控制目標機。
3)在目標板上通過printk()、Oops、strace等軟件方法進行“觀察”調試,這些方法不具備查看和修
改數據結構、斷點、單步等功能。
21.4~21.7節將對這些調試方法進行一一講解。
不管是目標機“插樁”還是使用仿真器連接目標機JTAG/SWD/BDM,在主機上,調試工具一般都采用
GDB。
GDB可以直接把Linux內核當成一個整體來調試,這個過程實際上可以被QEMU模擬出來。進入本書
配套Ubuntu的/home/baohua/develop/linux/extra目錄下,修改run-nolcd.sh的腳本,將其從
qemu-system-arm -nographic -sd vexpress.img -M vexpress-a9 -m 512M -kernel
zImage -dtb vexpress-v2p-ca9.dtb -smp 4 -append "init=/linuxrc root=/dev/
mmcblk0p1 rw rootwait e arlyprintk console=ttyAMA0" 2>/dev/null
改為:
qemu-system-arm –s –S -nographic -sd vexpress.img -M vexpress-a9 -m 512M -kernel
zImage -dtb vexpress-v2p-ca9.dtb -smp 4 -append "init=/linuxrc root=/dev/
mmcblk0p1 rw rootwait e arlyprintk console=ttyAMA0" 2>/dev/null
即添加-s–S選項,則會使嵌入式ARM Linux系統等待GDB遠程連入。在終端1運行新的./run-nolcd.sh,
這樣嵌入式ARM Linux的模擬平臺在1234端口偵聽。開一個新的終端2,進入/home/baohua/develop/linux/,
執行如下代碼:
baohua@baohua-VirtualBox:~/develop/linux$ arm-linux-gnueabihf-gdb ./vmlinux
GNU gdb (crosstool-NG linaro-1.13.1-4.8-2013.05 - Linaro GCC 2013.05) 7.6-2013.05
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=i686-build_pc-linux-gnu --target=arm-linux-gnueabihf".
For bug reporting instructions, please see:
<https://bugs.launchpad.net/gcc-linaro>...
Reading symbols from /home/baohua/develop/linux/vmlinux...done.
(gdb)
接下來我們遠程連接127.0.0.1:1234
(gdb) target remote 127.0.0.1:1234
Remote debugging using 127.0.0.1:1234
0x60000000 in ?? ()
設置一個斷點到start_kernel()。
(gdb) b start_kernel
Breakpoint 1 at 0x805fd8ac: file init/main.c, line 490.
繼續運行:
(gdb) c
Continuing.
Breakpoint 1, start_kernel () at init/main.c:490
490 {
(gdb)
斷點停在了內核啟動過程中的start_kernel()函數,這個時候我們按下Ctrl+X,A鍵,可以看到代
碼,如圖21.4所示。
進一步,可以看看jiffies值之類的:
(gdb) p jiffies
$1 = 775612
(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
cpu_v7_do_idle () at arch/arm/mm/proc-v7.S:74
74 ret lr
(gdb) p jiffies
$2 = 775687
(gdb)
圖21.4 GDB調試內核
盡管采用“插樁”和仿真器結合GDB的方式可以查看和修改數據結構、斷點、單步等,而printk()這
種最原始的方法卻應用得更廣泛。
printk()這種方法很原始,但是一般可以解決工程中95%以上的問題。因此具體何時打印,以及打
印什么東西,需要工程師逐步建立敏銳的嗅覺。加深對內核的認知,深入理解自己正在調試的模塊,這才
是快速解決問題的“王道”。工具只是一個輔助手段,無法代替工程師的思維。
工程師不能抱著得過且過的心態,也不能總是一知半解地進行低水平的重復建設。求知欲望對工程師
技術水平的提升有著最關鍵的作用。
21.3 內核打印信息——printk()
在Linux中,內核打印語句printk()會將內核信息輸出到內核信息緩沖區中,內核緩沖區是在
kernel/printk.c中通過如下語句靜態定義的:
static char __log_buf[__LOG_BUF_LEN] __aligned(LOG_ALIGN);
內核信息緩沖區是一個環形緩沖區(Ring Buffer),因此,如果塞入的消息過多,則就會將之前的消
息沖刷掉。
printk()定義了8個消息級別,分為級別0~7,級別越低(數值越大),消息越不重要,第0級是緊急
事件級,第7級是調試級,代碼清單21.2所示為printk()的級別定義。
代碼清單21.2 printk()的級別定義
1 #define KERN_EMERG "<0>" /* 緊急事件,一般是系統崩潰之前提示的消息 */
2 #define KERN_ALERT "<1>" /* 必須立即采取行動 */
3 #define KERN_CRIT "<2>" /* 臨界狀態,通常涉及嚴重的硬件或軟件操作失敗 */
4 #define KERN_ERR "<3>" /* 用于報告錯誤狀態,設備驅動程序會
5 經常使用KERN_ERR來報告來自硬件的問題 */
6 #define KERN_WARNING "<4>" /* 對可能出現問題的情況進行警告,
7 這類情況通常不會對系統造成嚴重的問題 */
8 #define KERN_NOTICE "<5>" /* 有必要進行提示的正常情形,
9 許多與安全相關的狀況用這個級別進行匯報 */
10#define KERN_INFO "<6>" /* 內核提示性信息,很多驅動程序
11 在啟動的時候,用這個級別打印出它們找到的硬件信息 */
12#define KERN_DEBUG "<7>" /* 用于調試信息 */
通過/proc/sys/kernel/printk文件可以調節printk()的輸出等級,該文件有4個數字值,如下所示。
·控制臺(一般是串口)日志級別:當前的打印級別,優先級高于該值的消息將被打印至控制臺。
·默認的消息日志級別:將用該優先級來打印沒有優先級前綴的消息,也就是在直接寫printk(“xxx”)
而不帶打印級別的情況下,會使用該打印級別。
·最低的控制臺日志級別:控制臺日志級別可被設置的最小值(一般都是1)。
·默認的控制臺日志級別:控制臺日志級別的默認值。
如在Ubuntu PC上,/proc/sys/kernel/printk的值一般如下:
$ cat /proc/sys/kernel/printk
4 4 1 7
而我們通過如下命令可以使得Linux內核的任何printk()都從控制臺輸出:
# echo 8 > /proc/sys/kernel/printk
在默認情況下,DEBUG級別的消息不會從控制臺輸出,我們可以通過在bootargs中設置ignore_loglevel
來忽略打印級別,以保證所有消息都被打印到控制臺。在系統啟動后,用戶還可以通過
寫/sys/module/printk/parameters/ignore_loglevel文件動態來設置是否忽略打印級別。
要注意的是,/proc/sys/kernel/printk并不控制內核消息進入__log_buf的門檻,因此無論消息級別是多
少,都會進入__log_buf中,但是最終只有高于當前打印級別的內核消息才會從控制臺打印。
用戶可以通過dmesg命令查看內核打印緩沖區,而如果使用dmesg-c命令,則不僅會顯示__log_buf,還
會清除該緩沖區的內容。也可以使用cat/proc/kmsg命令來顯示內核信息。/proc/kmsg是一個“永無休止的文
件”,因此,cat/proc/kmsg的進程只能通過“Ctrl+C”或kill終止。
在設備驅動中,經常需要輸出調試或系統信息,盡管可以直接采用printk(“<7>debug info…\n”)方式
的printk()語句輸出,但是通常可以使用封裝了printk()的更高級的宏,如pr_debug()、
dev_debug()等。代碼清單21.3所示為pr_debug()和pr_info()的定義。
代碼清單21.3 可替代printk()的宏pr_debug()和pr_info()的定義
1#ifdef DEBUG
2#define pr_debug(fmt,arg...) \
3 printk(KERN_DEBUG fmt,##arg)
4#else
5static inline int _ _attribute_ _ ((format (printf, 1, 2))) pr_debug(const char * fmt, ...)
6{
7 return 0;
8}
9#endif
10
11#define pr_info(fmt,arg...) \
12 printk(KERN_INFO fmt,##arg)
使用pr_xxx()族API的好處是,可以在文件最開頭通過pr_fmt()定義一個打印格式,比如在
kernel/watchdog.c的最開頭通過如下定義可以保證之后watchdog.c調用的所有pr_xxx()打印的消息都自動
帶有“NMI watchdog:”的前綴。
#define pr_fmt(fmt) "NMI watchdog: " fmt
#include <linux/mm.h>
#include <linux/cpu.h>
#include <linux/nmi.h>…
代碼清單21.4所示為dev_dbg()、dev_err()、dev_info()等的定義,使用dev_xxx()族API打印
的時候,設備名稱會被自動加到打印消息的前頭。
代碼清單21.4 包含設備信息的可替代printk()的宏
1#define dev_printk(level, dev, format, arg...) \
2 printk(level "%s %s: " format , dev_driver_string(dev) , (dev)->bus_id , ## arg)
3
4#ifdef DEBUG
5#define dev_dbg(dev, format, arg...) \
6 dev_printk(KERN_DEBUG , dev , format , ## arg)
7#else
8#define dev_dbg(dev, format, arg...) do { (void)(dev); } while (0)
9#endif
10
11#define dev_err(dev, format, arg...) \
12 dev_printk(KERN_ERR , dev , format , ## arg)
13#define dev_info(dev, format, arg...) \
14 dev_printk(KERN_INFO , dev , format , ## arg)
15#define dev_warn(dev, format, arg...) \
16 dev_printk(KERN_WARNING , dev , format , ## arg)
17#define dev_notice(dev, format, arg...) \
18 dev_printk(KERN_NOTICE , dev , format , ## arg)
在打印信息時,如果想輸出printk()調用所在的函數名,可以使用__func__;如果想輸出其所在代
碼的行號,可以使用__LINE__;想輸出源代碼文件名,可以使用__FILE__。例如drivers/block/sx8.c中的:
#ifdef CARM_NDEBUG
#define assert(expr)
#else
#define assert(expr) \
if(unlikely(!(expr))) { \
printk(KERN_ERR "Assertion failed! %s,%s,%s,line=%d\n", \
#expr, __FILE__, __func__, __LINE__); \
}
#endif
21.4 DEBUG_LL和EARLY_PRINTK
DEBUG_LL對應內核的Kernel low-level debugging功能,EARLY_PRINTK則對應內核中一個早期的控
制臺。為了在內核的drivers/tty/serial下的控制臺驅動初始化之前支持打印,可以選擇DEBUG_LL和
EARLY_PRINTK這兩個配置選項。另外,也需要在bootargs中設置earlyprintk的選項。
對于LDDD3_vexpress而言,沒有DEBUG_LL和EARLY_PRINTK的時候,我們看到的內核最早的打印
是:
Booting Linux on physical CPU 0x0
Initializing cgroup subsys cpuset
Linux version …
如果我們使能DEBUG_LL和EARLY_PRINTK,選擇如圖21.5所示的“Use PL011UART0at
0x10009000(V2P-CA9core tile)”這個低級別調試口,并在bootargs中設置earlyprintk,則我們看到了更早
的打印信息:
Uncompressing Linux... done, booting the kernel.
圖21.5 選擇低級別調試UART
21.5 使用“/proc”
在Linux系統中,“/proc”文件系統十分有用,它被內核用于向用戶導出信息。“/proc”文件系統是一個
虛擬文件系統,通過它可以在Linux內核空間和用戶空間之間進行通信。在/proc文件系統中,我們可以將
對虛擬文件的讀寫作為與內核中實體進行通信的一種手段,與普通文件不同的是,這些虛擬文件的內容都
是動態創建的。
“/proc”下的絕大多數文件是只讀的,以顯示內核信息為主。但是“/proc”下的文件也并不是完全只讀
的,若節點可寫,還可用于一定的控制或配置目的,例如前面介紹的寫/proc/sys/kernel/printk可以改變
printk()的打印級別。
Linux系統的許多命令本身都是通過分析“/proc”下的文件來完成的,如ps、top、uptime和free等。例
如,free命令通過分析/proc/meminfo文件得到可用內存信息,下面顯示了對應的meminfo文件和free命令的
結果。
1.meminfo文件
[root@localhost proc]# cat meminfo
MemTotal: 29516 kB
MemFree: 1472 kB
Buffers: 4096 kB
Cached: 12648 kB
SwapCached: 0 kB
Active: 14208 kB
Inactive: 8844 kB
HighTotal: 0 kB
HighFree: 0 kB
LowTotal: 29516 kB
LowFree: 1472 kB
SwapTotal: 265064 kB
SwapFree: 265064 kB
Dirty: 20 kB
Writeback: 0 kB
Mapped: 10052 kB
Slab: 3864 kB
CommitLimit: 279820 kB
Committed_AS: 13760 kB
PageTables: 444 kB
VmallocTotal: 999416 kB
VmallocUsed: 560 kB
VmallocChunk: 998580 kB
2. free命令
[root@localhost proc]# free
total used free shared buffers cached
Mem: 29516 28104 1412 0 4100 12700
-/+ buffers/cache: 11304 18212
Swap: 265064 0 265064
在Linux 3.9以及之前的內核版本中,可用如下函數創建“/proc”節點:
struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode,
struct proc_dir_entry *parent);
struct proc_dir_entry *create_proc_read_entry(const char *name, mode_t mode,
struct proc_dir_entry *base, read_proc_t *read_proc, void * data);
create_proc_entry()函數用于創建“/proc”節點,而create_proc_read_entry()調用
create_proc_entry()創建只讀的“/proc”節點。參數name為“/proc”節點的名稱,parent/base為父目錄的節
點,如果為NULL,則指“/proc”目錄,read_proc是“/proc”節點的讀函數指針。當read()系統調用
在“/proc”文件系統中執行時,它映像到一個數據產生函數,而不是一個數據獲取函數。
下列函數用于創建“/proc”目錄:
struct proc_dir_entry *proc_mkdir(const char *name, struct proc_dir_entry *parent);
結合create_proc_entry()和proc_mkdir(),代碼清單21.5中的程序可用于先在/proc下創建一個目錄
procfs_example,而后在該目錄下創建一個文件example_file。
代碼清單21.5 proc_mkdir()和create_proc_entry()函數使用范例
1/* 創建/proc下的目錄 */
2example_dir = proc_mkdir("procfs_example", NULL);
3if (example_dir == NULL) {
4 rv = -ENOMEM;
5 goto out;
6}
7
8example_dir->owner = THIS_MODULE;
9
10/* 創建一個/proc文件 */
11example_file = create_proc_entry("example_file", 0666, example_dir);
12if (example_file == NULL) {
13 rv = -ENOMEM;
14 goto out;
15}
16
17example_file->owner = THIS_MODULE;
18example_file->read_proc = example_file_read;
19example_file->write_proc = example_file_write;
作為上述函數返回值的proc_dir_entry結構體包含了“/proc”節點的讀函數指針
(read_proc_t*read_proc)、寫函數指針(write_proc_t*write_proc)以及父節點、子節點信息等。
/proc節點的讀寫函數的類型分別為:
typedef int (read_proc_t)(char *page, char **start, off_t off,
int count, int *eof, void *data);
typedef int (write_proc_t)(struct file *file, const char __user *buffer,
unsigned long count, void *data);
讀函數中page指針指向用于寫入數據的緩沖區,start用于返回實際的數據并寫到內存頁的位置,eof是
用于返回讀結束標志,offset是讀的偏移,count是要讀的數據長度。start參數比較復雜,對于/proc只包含
簡單數據的情況,通常不需要在讀函數中設置*start,這意味著內核將認為數據保存在內存頁偏移0的地
方。
寫函數與file_operations中的write()成員函數類似,需要一次從用戶緩沖區到內存空間的復制過程。
在Linux系統中可用如下函數刪除/proc節點:
void remove_proc_entry(const char *name, struct proc_dir_entry *parent);
在Linux系統中已經定義好的可使用的/proc節點宏包括:proc_root_fs(/proc)、
proc_net(/proc/net)、proc_bus(/proc/bus)、proc_root_driver(/proc/driver)等,proc_root_fs實際上就是
NULL。
代碼清單21.6所示為一個簡單的“/proc”文件系統使用范例,這段代碼在模塊加載函數中創
建/proc/test_dir目錄,并在該目錄中創建/proc/test_dir/test_rw文件節點,在模塊卸載函數中撤銷“/proc”節
點,而/proc/test_dir/test_rw文件中只保存了一個32位的整數。
代碼清單21.6 /proc文件系統使用范例
1#include <linux/module.h>
2#include <linux/kernel.h>
3#include <linux/init.h>
4#include <linux/proc_fs.h>
5
6static unsigned int variable;
7static struct proc_dir_entry *test_dir, *test_entry;
8
9static int test_proc_read(char *buf, char **start, off_t off, int count,
10 int *eof, void *data)
11{
12 unsigned int *ptr_var = data;
13 return sprintf(buf, "%u\n", *ptr_var);
14}
15
16static int test_proc_write(struct file *file, const char *buffer,
17 unsigned long count, void *data)
18{
19 unsigned int *ptr_var = data;
20
21 *ptr_var = simple_strtoul(buffer, NULL, 10);
22
23 return count;
24}
25
26static __init int test_proc_init(void)
27{
28 test_dir = proc_mkdir("test_dir", NULL);
29 if (test_dir) {
30 test_entry = create_proc_entry("test_rw", 0666, test_dir);
31 if (test_entry) {
32 test_entry->nlink = 1;
33 test_entry->data = &variable;
34 test_entry->read_proc = test_proc_read;
35 test_entry->write_proc = test_proc_write;
36 return 0;
37 }
38 }
39
40 return -ENOMEM;
41}
42module_init(test_proc_init);
43
44static __exit void test_proc_cleanup(void)
45{
46 remove_proc_entry("test_rw", test_dir);
47 remove_proc_entry("test_dir", NULL);
48}
49module_exit(test_proc_cleanup);
50
51MODULE_AUTHOR("Barry Song <baohua@kernel.org>");
52MODULE_DESCRIPTION("proc exmaple");
53MODULE_LICENSE("GPL v2");
上述代碼第21行調用的simple_strtoul()用于將用戶輸入的字符串轉換為無符號長整數,第3個參數
10意味著轉化方式是十進制。
編譯上述簡單的proc.c為proc.ko,運行insmod proc.ko加載該模塊后,“/proc”目錄下將多出一個目錄
test_dir,該目錄下包含一個test_rw,ls–l的結果如下:
$ ls -l /proc/test_dir/test_rw
-rw-rw-rw- 1 root root 0 Aug 16 20:45 /proc/test_dir/test_rw
測試/proc/test_dir/test_rw的讀寫:
$ cat /proc/test_dir/test_rw
0$
echo 111 > /proc/test_dir/test_rw
$ cat /proc/test_dir/test_rw
說明我們上一步執行的寫操作是正確的。
在Linux 3.10及以后的版本中,“/proc”的內核API和實現架構變更較大,create_proc_entry()、
create_proc_read_entry()之類的API都被刪除了,取而代之的是直接使用proc_create()、
proc_create_data()API。同時,也不再存在read_proc()、write_proc()之類的針對proc_dir_entry的成
員函數了,而是直接把file_operations結構體的指針傳入proc_create()或者proc_create_data()函數中,
其原型為:
static inline struct proc_dir_entry *proc_create(
const char *name, umode_t mode, struct proc_dir_entry *parent,
const struct file_operations *proc_fops);
struct proc_dir_entry *proc_create_data(
const char *name, umode_t mode, struct proc_dir_entry *parent,
const struct file_operations *proc_fops, void *data);
我們把代碼清單21.6的范例改造為同時支持Linux 3.10以前的內核和Linux3.10以后的內核。改造結果
如代碼清單21.7所示。#if LINUX_VERSION_CODE<KERNEL_VERSION(3,10,0)中的部分是舊版本
的代碼,與21.6相同,所以省略了。
代碼清單21.7 支持Linux 3.10以后內核的/proc文件系統使用范例
1#include <linux/module.h>
2#include <linux/kernel.h>
3#include <linux/init.h>
4#include <linux/version.h>
5#include <linux/proc_fs.h>
6#include <linux/seq_file.h>
7
8static unsigned int variable;
9static struct proc_dir_entry *test_dir, *test_entry;
10
11#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)
12...
13#else
14static int test_proc_show(struct seq_file *seq, void *v)
15{
16 unsigned int *ptr_var = seq->private;
17 seq_printf(seq, "%u\n", *ptr_var);
18 return 0;
19}
20
21static ssize_t test_proc_write(struct file *file, const char __user *buffer,
22 size_t count, loff_t *ppos)
23{
24 struct seq_file *seq = file->private_data;
25 unsigned int *ptr_var = seq->private;
26
27 *ptr_var = simple_strtoul(buffer, NULL, 10);
28 return count;
29}
30
31static int test_proc_open(struct inode *inode, struct file *file)
32{
33 return single_open(file, test_proc_show, PDE_DATA(inode));
34}
35
36static const struct file_operations test_proc_fops =
37{
38 .owner = THIS_MODULE,
39 .open = test_proc_open,
40 .read = seq_read,
41 .write = test_proc_write,
42 .llseek = seq_lseek,
43 .release = single_release,
44};
45#endif
46
47static __init int test_proc_init(void)
48{
49 test_dir = proc_mkdir("test_dir", NULL);
50 if (test_dir) {
51#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)
52 ...
53#else
54 test_entry = proc_create_data("test_rw",0666, test_dir, &test_proc_fops, &variable);
55 if (test_entry)
56 return 0;
57#endif
58 }
59
60 return -ENOMEM;
61}
62module_init(test_proc_init);
63
64static __exit void test_proc_cleanup(void)
65{
66 remove_proc_entry("test_rw", test_dir);
67 remove_proc_entry("test_dir", NULL);
68}
69module_exit(test_proc_cleanup);
21.6 Oops
當內核出現類似用戶空間的Segmentation Fault時(例如內核訪問一個并不存在的虛擬地址),Oops會
被打印到控制臺和寫入內核log緩沖區。
我們在globalmem.c的globalmem_read()函數中加上下面一行代碼:
} else {
*ppos += count;
ret = count;
*(unsigned int *)0 = 1; /* a kernel panic */
printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p);
}
假設這個字符設備對應的設備節點是/dev/globalmem,通過cat/dev/globalmem命令讀設備文件,將得到
如下Oops信息:
# cat /dev/globalmem
Unable to handle kernel NULL pointer dereference at virtual address 00000000
pgd = 9ec08000
[00000000] *pgd=7f733831, *pte=00000000, *ppte=00000000
Internal error: Oops: 817 [#1] SMP ARM
Modules linked in: globalmem
CPU: 0 PID: 609 Comm: cat Not tainted 3.16.0+ #13
task: 9f7d8000 ti: 9f722000 task.ti: 9f722000
PC is at globalmem_read+0xbc/0xcc [globalmem]
LR is at 0x0
pc : [<7f000200>] lr : [<00000000>] psr: 00000013
sp : 9f723f30 ip : 00000000 fp : 00000000
r10: 9f414000 r9 : 00000000 r8 : 00001000
r7 : 00000000 r6 : 00001000 r5 : 00001000 r4 : 00000000
r3 : 00000001 r2 : 00000000 r1 : 00001000 r0 : 7f0003cc
Flags: nzcv IRQs on FIQs on Mode SVC_32 ISA ARM Segment user
Control: 10c53c7d Table: 7ec08059 DAC: 00000015
Process cat (pid: 609, stack limit = 0x9f722240)
Stack: (0x9f723f30 to 0x9f724000)
3f20: 7ed5ff91 9f723f80 00000000 9f79ab40
3f40: 00001000 7ed5eb18 9f723f80 00000000 00000000 800cb114 00000020 9f722000
3f60: 9f5e4628 9f79ab40 9f79ab40 00001000 7ed5eb18 00000000 00000000 800cb2ec
3f80: 00001000 00000000 9f7168c0 00001000 7ed5eb18 00000003 00000003 8000e4e4
3fa0: 9f722000 8000e360 00001000 7ed5eb18 00000003 7ed5eb18 00001000 0000002f
3fc0: 00001000 7ed5eb18 00000003 00000003 7ed5eb18 00000001 00000003 00000000
3fe0: 0015c23c 7ed5eb00 0000f718 00008d8c 60000010 00000003 00000000 00000000
[<7f000200>] (globalmem_read [globalmem]) from [<800cb114>] (vfs_read+0x98/0x13c)
[<800cb114>] (vfs_read) from [<800cb2ec>] (SyS_read+0x44/0x84)
[<800cb2ec>] (SyS_read) from [<8000e360>] (ret_fast_syscall+0x0/0x30)
Code: e1a05008 e2a77000 e1c360f0 e3a03001 (e58c3000)
---[ end trace 5a36d6470da50d02 ]---
Segmentation fault
上述Oops的第一行給出了“原因”,即訪問了NULL pointer。Oops中的PC is at
globalmem_read+0xbc/0xcc這一行代碼也比較關鍵,給出了“事發現場”,即globalmem_read()函數偏移
0xbc字節的指令處。
通過反匯編globalmem.o可以尋找到globalmem_read()函數開頭位置偏移0xbc的指令,反匯編方法如
下:
drivers/char/globalmem$ arm-linux-gnueabihf-objdump -d -S globalmem.o
對應的反匯編代碼如下,global_read()開始于0x144,偏移0xbc的位置為0x200:
static ssize_t globalmem_read(struct file *filp, char __user * buf, size_t size,
loff_t * ppos)
{
144: e92d45f0 push {r4, r5, r6, r7, r8, sl, lr}
148: e24dd00c sub sp, sp, #12
unsigned long p = *ppos;
14c: e5934000 ldr r4, [r3]

*ppos += count;
1f4: e2a77000 adc r7, r7, #0
1f8: e1c360f0 strd r6, [r3]
ret = count;
*(unsigned int *)0 = 1; /* a kernel panic */
1fc: e3a03001 mov r3, #1
200: e58c3000 str r3, [ip]
printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p);
204: …
return ret;
}
“str r3,[ip]”是引起Oops的指令。這里僅僅給出了一個例子,工程實踐中的“事發現場”并不全那么容
易找到,但方法都是類似的。
21.7 BUG_ON()和WARN_ON()
內核中有許多地方調用類似BUG()的語句,它非常像一個內核運行時的斷言,意味著本來不該執
行到BUG()這條語句,一旦執行即拋出Oops。BUG()的定義為:
#define BUG() do { \
printk("BUG: failure at %s:%d/%s()!\n", __FILE__, __LINE__, __func__); \
panic("BUG!"); \
} while (0)
其中的panic()定義在kernel/panic.c中,會導致內核崩潰,并打印Oops。比如arch/arm/kernel/dma.c中的
enable_dma()函數:
void enable_dma (unsigned int chan)
{
dma_t *dma = dma_channel(chan);
if (!dma->lock)
goto free_dma;
if (dma->active == 0) {
dma->active = 1;
dma->d_ops->enable(chan, dma);
}
return;
free_dma:
printk(KERN_ERR "dma%d: trying to enable free DMA\n", chan);
BUG();
}
上述代碼的含義是,如果在dma->lock不成立的情況下,驅動直接調用了enable_dma(),實際上意味
著內核的一個bug。
BUG()還有一個變體叫BUG_ON(),它的內部會引用BUG(),形式為:
#define BUG_ON(condition) do { if (unlikely(condition)) BUG(); } while (0)
對于BUG_ON()而言,只有當括號內的條件成立的時候,才拋出Oops。比如drivers/char/random.c中
的類似代碼:
static void push_to_pool(struct work_struct *work)
{
struct entropy_store *r = container_of(work, struct entropy_store,
push_work);
BUG_ON(!r);
_xfer_secondary_pool(r, random_read_wakeup_bits/8);
trace_push_to_pool(r->name, r->entropy_count >> ENTROPY_SHIFT,
r->pull->entropy_count >> ENTROPY_SHIFT);
}
除了BUG_ON()外,內核有個稍微弱一些WARN_ON(),在括號中的條件成立的時候,內核會拋
出棧回溯,但是不會panic(),這通常用于內核拋出一個警告,暗示某種不太合理的事情發生了。如在
kernel/locking/mutex-debug.c中的debug_mutex_unlock()函數發現mutex_unlock()的調用者和
mutex_lock()的調用者不是同一個線程的時候或者mutex的owner為空的時候,會拋出警告信息:
void debug_mutex_unlock(struct mutex *lock)
{
if (likely(debug_locks)) {
DEBUG_LOCKS_WARN_ON(lock->magic != lock);
if (!lock->owner)
DEBUG_LOCKS_WARN_ON(!lock->owner);
else
DEBUG_LOCKS_WARN_ON(lock->owner != current);
DEBUG_LOCKS_WARN_ON(!lock->wait_list.prev && !lock->wait_list.next);
mutex_clear_owner(lock);
}
}
有時候,WARN_ON()也可以作為一個調試技巧。比如,我們進到內核某個函數后,不知道這個函
數是怎么一級一級被調用進來的,那可以在該函數中加入一個WARN_ON(1)。
21.8 strace
在Linux系統中,strace是一種相當有效的跟蹤工具,它的主要特點是可以被用來監視系統調用。我們
不僅可以用strace調試一個新開始的程序,也可以調試一個已經在運行的程序(這意味著把strace綁定到一
個已有的PID上)。對于第6章的globalmem字符設備文件,以strace方式運行如代碼清單21.8所示的用戶空
間應用程序globalmem_test,運行的結果如下:
execve("./globalmem_test", ["./globalmem_test"], [/* 24 vars */]) = 0
...
open("/dev/globalmem", O_RDWR) = 3 /* 打開的/dev/globalmem的fd是3 */
ioctl(3, FIBMAP, 0) = 0
read(3, 0xbff17920, 200) = -1 ENXIO (No such device or address)/* 讀取失敗 */
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f04000
write(1, "-1 bytes read from globalmem\n", 29-1 bytes read from globalmem
) = 29 /* 向標準輸出設備(fd為1)寫入printf中的字符串 */
write(3, "This is a test of globalmem", 27) = 27
write(1, "27 bytes written into globalmem\n", 3227 bytes written into globalmem
) = 32
...
輸出的每一行對應一次Linux系統調用,其格式為“左邊=右邊”,等號左邊是系統調用的函數名及其參
數,右邊是該調用的返回值。
代碼清單21.8 用戶空間應用程序globalmem_test
1#include ...
2
3#define MEM_CLEAR 0x1
4main()
5{
6 int fd, num, pos;
7 char wr_ch[200] = "This is a test of globalmem";
8 char rd_ch[200];
9 /* 打開/dev/globalmem */
10 fd = open("/dev/globalmem", O_RDWR, S_IRUSR | S_IWUSR);
11 if (fd != -1 ) { /* 清除globalmem */
12 if(ioctl(fd, MEM_CLEAR, 0) < 0)
13 printf("ioctl command failed\n");
14 /* 讀globalmem */
15 num = read(fd, rd_ch, 200);
16 printf("%d bytes read from globalmem\n",num);
17
18 /* 寫globalmem */
19 num = write(fd, wr_ch, strlen(wr_ch));
20 printf("%d bytes written into globalmem\n",num);
21 ...
22 close(fd);
23 }
24}
使用strace雖然無法直接追蹤到設備驅動中的函數,但是足以幫助工程師進行推演,如從
open(“/dev/globalmem”,O_RDWR)=3的返回結果知道/dev/globalmem的fd為3,之后對fd為3的文件進行
read()、write()和ioctl()系統調用,最終會使globalmem里file_operations中的相應函數被調用,通過
系統調用的結果就可以知道驅動中globalmem_read()、globalmem_write()和globalmem_ioctl()的運
行結果。
21.9 KGDB
Linux直接提供了對KGDB的支持,KGDB采用了典型的嵌入式系統“插樁”技巧,一般依賴于串口與調
試主機通信。為了支持KGDB,串口驅動應該實現純粹的輪詢收發單一字符的成員函數,以供
drivers/tty/serial/kgdboc.c調用,譬如drivers/tty/serial/8250/8250_core.c中的:
static struct uart_ops serial8250_pops = {

#ifdef CONFIG_CONSOLE_POLL
.poll_get_char = serial8250_get_poll_char,
.poll_put_char = serial8250_put_poll_char,
#endif
};
在編譯內核時,運行make ARCH=arm menuconfig時需選擇關于KGDB的編譯項目,如圖21.6所示。
圖21.6 KGDB編譯選項配置
對于目標板而言,需要在bootargs中設置與KGDB對應的串口等信息,如kgdboc=ttyS0,
115200kgdbcon。
如果想一開機內核就直接進入等待GDB連接的調試狀態,可以在bootargs中設置kgdbwait,kgdbwait的
含義是啟動時就等待主機的GDB連接。而若想在內核啟動后進入GDB調試模式,可運行echo
g>/proc/sysrq_trigger命令給內核傳入一個鍵值是g的magic_sysrq。
在調試PC上,依次運行如下命令就可以啟動調試并連接至目標機(假設串口在PC上對應的設備節點
是/dev/ttyS0):
# arm-eabi-gdb ./vmlinux
(gdb) set remotebaud 115200
(gdb) target remote /dev/ttyS0 //連接目標機
(gdb)
之后,在主機上,我們可以使用GDB像調試應用程序一樣調試使能了KGDB的目標機上的內核。
21.10 使用仿真器調試內核
在ARM Linux領域,目前比較主流的是采用ARM DS-5Development Studio方案。ARM DS-5是一個針
對基于Linux的系統和裸機嵌入式系統的專業軟件開發解決方案,它涵蓋了開發的所有階段,從啟動代
碼、內核移植直到應用程序調試、分析。如圖21.7所示,它使用了DSTREAM高性能仿真器(ARM已經停
止更新RVI-RVT2仿真器),在Eclipse內包含了DS-5和DSTREAM的開發插件。
調試主機一般通過網線與DSTREAM仿真器連接,而仿真器則連接與電路板類似的JTAG接口,之后
用DS-5調試器進行調試。DS-5圖形化調試器提供了全面和直觀的調試圖,非常易于調試Linux和裸機程
序,易于查看代碼,進行棧回溯,查看內存、寄存器、表達式、變量,分析內核線程,設置斷點。
圖21.7 DSTREAM仿真器和DS-5開發環境
值得一提的是,DS-5也提供了Streamline Performance Analyzer。ARM Streamline性能分析器(見圖
21.8)為軟件開發人員提供了一種用來分析和優化在ARM926、ARM11和Cortex-A系列平臺上運行的Linux
和Android系統的直觀方法。使用Streamline,Linux內核中需包含一個gator模塊,用戶空間則需要使能
gatord后臺服務器程序。關于Streamline具體的操作方法可以查看《ARM? DS-5Using ARM Streamline》。
圖21.8 ARMStreamline性能分析器
21.11 應用程序調試
在嵌入式系統中,為調試Linux應用程序,可在目標板上先運行GDBServer,再讓主機上的GDB與目
標板上的GDBServer通過網口或串口通信。
1.目標板
需要運行如下命令啟動GDBServer:
gdbserver <host_ip>:<port> <app>
<host_ip>:<port>為主機的IP地址和端口,app是可執行的應用程序名。
當然,也可以用系統中空閑的串口作為GDB調試器和GDBServer的底層通信手段,如:
gdbserver/dev/ttyS0./tdemo
2.主機
需要先運行如下命令啟動GDB:
arm-eabi-gdb <app>
app與GDBServer的app參數對應。
之后,運行如下命令就可以連接目標板:
target remote <target_ip>:<port>
<target_ip>:<port>為目標機的IP地址和端口。
如果目標板上的GDBServer使用串口,則在宿主機上GDB也應該使用串口,如:
(gdb)target remote/dev/ttyS1
之后,便可以使用GDB像調試本機上的程序一樣調試目標機上的程序。
3.通過GDB server和ARM GDB調試應用程序
在ARM開發板上放置GDB server,便可以通過目標板與調試PC之間的以太網等調試。要調試的應用
程序的源代碼如下:
/*
* gdb_example.c: program to show how to use arm-linux-gdb
*/
void increase_one(int *data)
{ *data = *data + 1;
}i
nt main(int argc, char *argv[])
{ int dat = 0;
int *p = 0;
increase_one(&dat);
/* program will crash here */
increase_one(p);
return 0;
}
通過debug方式編譯它:
arm-linux-gnueabi-gcc -g -o gdb_example gdb_example.c
將程序下載到目標板后,在目標板上運行:
# gdbserver 192.168.1.20:1234 gdb_example
Process gdb_example created; pid = 1096
Listening on port 1234
其中192.168.1.20為目標板的3IP,1234為GDBserver的偵聽端口。
如果目標機是Android系統,且沒有以太網,可以嘗試使用adb forward功能,比如adb forward tcp:
1234tcp:1234是把目標機1234端口與主機1234端口進行轉發。
在主機上運行:
$ arm-eabi-gdb gdb_example…
主機的GDB中運行如下命令以連接目標板:
(gdb) target remote 192.168.1.20:1234
Remote debugging using 192.168.1.20:1234
...
0x400007b0 in ?? ()
如果是Android的adb forward,則上述target remote 192.168.1.20:1234中的IP地址可以去掉,因為它變
成直接連接本機了,可直接寫成target remote:1234。
運行如下命令將斷點設置在increase_one(&dat);這一行:
(gdb) b gdb_example.c:16
Breakpoint 1 at 0x8390: file gdb_example.c, line 16.
通過c命令繼續運行目標板上的程序,發生斷點:
(gdb) c
Continuing.
...
Breakpoint 1, main (argc=1, argv=0xbead4eb4) at gdb_example.c:16
16increase_one(&dat);
運行n命令執行完increase_one(&dat);再查看dat的值:
(gdb) n
19increase_one(p); (gdb) p dat
$1 = 1
發現dat變成1。繼續運行c命令,由于即將訪問空指針,gdb_example將崩潰:
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x0000834c in increase_one (data=0x0) at gdb_example.c:8
8*data = *data + 1;
我們通過bt命令可以拿到backtrace:
(gdb) bt
#0 0x0000834c in increase_one (data=0x0) at gdb_example.c:8
#1 0x000083a4 in main (argc=1, argv=0xbead4eb4) at gdb_example.c:19
通過info reg命令可以查看當時的寄存器值:
(gdb) info reg
r00x0 0
r10xbead4eb43199028916
r20x1 1
r30x0 0
r40x4001e5e01073866208
r50x0 0
r60x826c33388
r70x0 0
r80x0 0
r90x0 0
r10 0x400250001073893376
r11 0xbead4d443199028548
r12 0xbead4d483199028552
sp 0xbead4d300xbead4d30
lr 0x83a433700
pc 0x834c0x834c <increase_one+24>
fps 0x0 0
cpsr 0x600000101610612752
21.12 Linux性能監控與調優工具
除了保證程序的正確性以外,在項目開發中往往還關心性能和穩定性。這時候,我們往往要對內核、
應用程序或整個系統進行性能優化。在性能優化中常用的手段如下。
1.使用top、vmstat、iostat、sysctl等常用工具
top命令用于顯示處理器的活動狀況。在缺省情況下,顯示占用CPU最多的任務,并且每隔5s做一次
刷新;vmstat命令用于報告關于內核線程、虛擬內存、磁盤、陷阱和CPU活動的統計信息;iostat命令用于
分析各個磁盤的傳輸閑忙狀況;netstat是用來檢測網絡信息的工具;sar用于收集、報告或者保存系統活動
信息,其中,sar用于顯示數據,sar1和sar2用于收集和保存數據。
sysctl是一個可用于改變正在運行中的Linux系統的接口。用sysctl可以讀取幾百個以上的系統變量,例
如用sysctl–a可讀取所有變量。
sysctl的實現原理是:所有的內核參數在/proc/sys中形成一個樹狀結構,sysctl系統調用的內核函數是
sys_sysctl,匹配項目后,最后的讀寫在do_sysctl_strategy中完成,如
echo "1" > /proc/sys/net/ipv4/ip_forward
就等價于:
sysctl –w net.ipv4.ip_forward ="1"
2.使用高級分析手段,如OProfile、gprof
OProfile可以幫助用戶識別諸如模塊的占用時間、循環的展開、高速緩存的使用率低、低效的類型轉
換和冗余操作、錯誤預測轉移等問題。它收集有關處理器事件的信息,其中包括TLB的故障、停機、存儲
器訪問以及緩存命中和未命中的指令的攫取數量。
OProfile支持兩種采樣方式:基于事件的采樣(Event Based)和基于時間的采樣(Time Based)。基于
事件的采樣是OProfile只記錄特定事件(比如L2緩存未命中)的發生次數,當達到用戶設定的定值時
Oprofile就記錄一下(采一個樣)。這種方式需要CPU內部有性能計數器(Performace Counter)。基于時
間的采樣是OProfile借助OS時鐘中斷的機制,在每個時鐘中斷,OProfile都會記錄一次(采一次樣)。引
入它的目的在于,提供對沒有性能計數器的CPU的支持,其精度相對于基于事件的采樣要低,因為要借助
OS時鐘中斷的支持,對于禁用中斷的代碼,OProfile不能對其進行分析。
OProfile在Linux上分兩部分,一個是內核模塊(oprofile.ko),另一個是用戶空間的守護進程
(oprofiled)。前者負責訪問性能計數器或者注冊基于時間采樣的函數,并將采樣值置于內核的緩沖區
內。后者在后臺運行,負責從內核空間收集數據,寫入文件。其運行步驟如下。
1)初始化opcontrol--init
2)配置opcontrol--setup--event=...
3)啟動opcontrol--start
4)運行待分析的程序xxx
5)取出數據
opcontrol--dump
opcontrol--stop
6)分析結果opreport-l./xxx
用GNU gprof可以打印出程序運行中各個函數消耗的時間,以幫助程序員找出眾多函數中耗時最多的
函數;還可產生程序運行時的函數調用關系,包括調用次數,以幫助程序員分析程序的運行流程。
GNU gprof的實現原理:在編譯和鏈接程序的時候(使用-pg編譯和鏈接選項),gcc在應用程序的每
個函數中都加入名為mcount(_mcount或__mcount,依賴于編譯器或操作系統)的函數,也就是說應用程
序里的每一個函數都會調用mcount,而mcount會在內存中保存一張函數調用圖,并通過函數調用堆棧的形
式查找子函數和父函數的地址。這張調用圖也保存了所有與函數相關的調用時間、調用次數等的所有信
息。
GNU gprof的基本用法如下。
1)使用-pg編譯和鏈接應用程序。
2)執行應用程序并使它生成供gprof分析的數據。
3)使用gprof程序分析應用程序生成的數據。
3.進行內核跟蹤,如LTTng
LTTng(Linux Trace Toolkit-next generation,官方網站為http://lttng.org/)是一個用于跟蹤系統詳細運行
狀態和流程的工具,它可以跟蹤記錄系統中的特定事件。這些事件包括:系統調用的進入和退出;陷阱/
中斷(Trap/Irq)的進入和退出;進程調度事件;內核定時器;進程管理相關事件——創建、喚醒、信號
處理等;文件系統相關事件——open/read/write/seek/ioctl等;內存管理相關事件——內存分配/釋放等;其
他IPC/套接字/網絡等事件。而對于這些記錄,我們可以通過圖形的方式經由lttv-gui查看,如圖21.9所示。
4.使用LTP進行壓力測試
LTP(Linux Test Project,官方網站為http://ltp.sourceforge.net/)是一個由SGI發起并由IBM負責維護的
合作計劃。它的目的是為開源社區提供測試套件來驗證Linux的可靠性、健壯性和穩定性。它通過壓力測
試來判斷系統的穩定性和可靠性,在工程中我們可使用LTP測試套件對Linux操作系統進行超長時間的測
試,它可進行文件系統壓力測試、硬盤I/O測試、內存管理壓力測試、IPC壓力測試、SCHED測試、命令
功能的驗證測試、系統調用功能的驗證測試等。
圖21.9 LTTng形成的時序圖
5.使用Benchmark評估系統
可用于Linux的Benchmark的包括lmbench、UnixBench、AIM9、Netperf、SSLperf、dbench、Bonnie、
Bonnie++、Iozone、BYTEmark等,它們可用于評估操作系統、網絡、I/O子系統、CPU等的性能,參考網
址http://lbs.sourceforge.net/列出了許多Benchmark工具。
21.13 總結
Linux程序的調試,尤其是內核的調試看起來比較復雜,沒有類似于VC++、Tornado的IDE開發環境,
最常用的調試手段依然是文本方式的GDB。文本方式的GDB調試器功能異常強大,當我們使用習慣后,
就會用得非常自然。
Linux內核驅動的調試方法包括“插樁”、使用仿真器和借助printk()、Oops、strace等,在大多數情況
下,原始的printk()仍然是最有效的手段。
除了本章介紹的方法外,在驅動的調試中很可能還會借助其他的硬件或軟件調試工具,如調試USB驅
動最好借助USB分析儀,用USB分析儀將可捕獲USB通信中的數據包,如同網絡中的Sniffer軟件一樣。

總結

以上是生活随笔為你收集整理的linux驱动程序设计21 Linux设备驱动的调试的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

中文字幕精品在线 | 国产最新91 | 久久伊人操 | 91久久久久久久一区二区 | 天天操天天曰 | 91视频免费看网站 | 国产精品免费观看国产网曝瓜 | 精品在线看 | 亚洲欧美国产视频 | 欧美午夜精品久久久久久孕妇 | 久久精品国产一区二区三 | 中文字幕日韩一区二区三区不卡 | 亚洲精品在线一区二区三区 | 韩国一区二区av | 久久精品99国产国产 | 国产亚洲精品久 | 美女网站黄免费 | 国产中文| 久久精品91久久久久久再现 | 九九免费观看视频 | 99热这里只有精品国产首页 | 欧美亚洲xxx| 国产在线精品一区二区三区 | 国产一级视频在线 | 午夜电影一区 | 安徽妇搡bbbb搡bbbb | 精品久久久久久久久久久久久久久久久久 | 992tv在线观看网站 | 亚洲视频axxx| 色综合天天视频在线观看 | 99热99热| 在线看片一区 | 久久精彩免费视频 | 超碰在线免费97 | 免费瑟瑟网站 | 波多野结衣最新 | 黄色三级免费看 | 中文字幕av最新 | 美女视频黄免费的久久 | 激情小说网站亚洲综合网 | 婷婷国产一区二区三区 | 一级片免费在线 | 久久久久草 | 五月激情丁香图片 | 人人干在线观看 | 亚洲美女免费精品视频在线观看 | 韩日精品在线 | 久久黄色免费视频 | 亚洲va韩国va欧美va精四季 | 久久久高清一区二区三区 | 亚洲影视九九影院在线观看 | 国产精品一区二区你懂的 | 亚洲精品午夜一区人人爽 | 麻豆视频免费在线 | 黄色大全免费观看 | 91精品国自产在线观看 | 久久久久这里只有精品 | 成人免费大片黄在线播放 | 亚洲国产精品第一区二区 | 天天射天天 | 日韩精品专区 | 草久视频在线观看 | 久久美女高清视频 | 久久综合桃花 | av大全在线观看 | 国产亚洲人成网站在线观看 | 国产精品成人一区 | 婷婷在线播放 | 国产中文字幕国产 | 欧洲一区二区在线观看 | 亚洲一区二区精品 | 欧美日一级片 | 丁香五月缴情综合网 | 在线看日韩av| 久久精品91久久久久久再现 | 国产在线观看你懂得 | 蜜臀aⅴ精品一区二区三区 久久视屏网 | 国产电影一区二区三区四区 | 五月天堂网 | 欧美另类v | 99久久99久久精品 | 中文字幕第一页在线 | 精品一区二区在线观看 | 九九视频在线观看视频6 | 日韩午夜电影 | 亚洲综合丁香 | 亚洲欧美日韩国产精品一区午夜 | 国产亚洲资源 | 欧美精品你懂的 | 激情婷婷六月 | 国产一区在线免费 | 天堂麻豆| 欧美日韩一区二区在线观看 | 日韩在线播放av | 十八岁以下禁止观看的1000个网站 | 国产伦精品一区二区三区… | 亚洲伊人网在线观看 | 天堂在线一区 | 欧美日韩中文在线观看 | 亚洲综合激情五月 | 欧美久久久久久久久久久久久 | 久久再线视频 | 亚洲手机天堂 | 欧美激情综合五月色丁香 | 精产嫩模国品一二三区 | www久| 在线免费观看黄色大片 | 狠狠狠狠狠狠天天爱 | 九九热在线视频免费观看 | 人人藻人人澡人人爽 | 国产成人精品女人久久久 | 九九视频一区 | 波多野结衣视频网址 | 中文字幕在线播放av | 中文字幕一区2区3区 | 国产中文在线观看 | 亚洲欧美日韩精品久久奇米一区 | 久久婷婷精品视频 | 国产伦精品一区二区三区照片91 | 亚洲免费观看视频 | 超碰国产在线 | 国产精品理论片在线播放 | 精品一区免费 | 国产精品原创 | 日韩在线视频免费播放 | 亚洲精品在线一区二区 | 亚洲精品综合在线 | 免费在线观看一区二区三区 | 国产在线毛片 | 欧美日本一二三 | 九月婷婷人人澡人人添人人爽 | 成人91在线| 在线色视频小说 | 中文字幕精品一区二区精品 | 日韩国产欧美在线视频 | 在线视频欧美日韩 | 人人躁| 91视频免费视频 | 欧美精品在线观看免费 | a视频在线看| 在线视频 你懂得 | 毛片一区二区 | 亚洲欧洲中文日韩久久av乱码 | 免费高清av在线看 | 一级a毛片高清视频 | 免费黄色a网站 | 国产中文字幕三区 | 狠狠操狠狠| 激情av网址 | 97在线精品视频 | 在线观看免费中文字幕 | 国产99久久九九精品 | 在线韩国电影免费观影完整版 | 日本精品久久久久中文字幕 | 中文字幕在线视频免费播放 | 激情xxxx| 亚洲精品在线观看的 | 在线中文字幕播放 | www.夜夜爽 | 91香蕉视频在线 | 国产精品成人一区二区 | 国产一区二区久久久 | 亚洲精品视频免费在线 | 免费观看的黄色片 | 夜色资源站wwwcom | 国产无区一区二区三麻豆 | 色丁香婷婷| 中文不卡视频 | 不卡视频一区二区三区 | 美女视频永久黄网站免费观看国产 | 色婷婷综合成人av | 久久大片网站 | 日韩av伦理片 | 成人免费影院 | 欧美夫妻性生活电影 | 亚洲精品中文字幕视频 | 欧美一级免费片 | 黄色三级免费看 | 色婷婷激情电影 | 天天干天天操 | 国产资源| 日韩一区正在播放 | 久久综合色影院 | av不卡免费在线观看 | 国产精品久久一卡二卡 | 天天操天天弄 | 国产视频精品久久 | 在线播放精品一区二区三区 | 国产成人av电影 | 久久综合狠狠综合久久狠狠色综合 | 九月婷婷色 | 久久精品波多野结衣 | 在线观看 国产 | 婷婷色网| 成人综合免费 | 成人91在线观看 | 国产精品系列在线播放 | 国产又粗又猛又黄又爽视频 | 国产精品区一区 | 国产一区二区三精品久久久无广告 | 美女视频网 | 在线看国产日韩 | 国产精品第三页 | 国产精品久久久久久久免费大片 | 在线观看日韩精品 | 四虎国产视频 | 久久99亚洲网美利坚合众国 | 色偷偷97 | 婷婷丁香九月 | 成人久久国产 | 视频二区在线视频 | 免费在线国产 | 中文字幕在线观看一区二区 | 国产中文字幕av | 激情五月婷婷激情 | 国产盗摄精品一区二区 | 日韩精品久久久久久久电影99爱 | 国产日韩中文字幕 | 在线视频1卡二卡三卡 | 国产精品v欧美精品 | 免费电影一区二区三区 | 91丨九色丨蝌蚪丰满 | 97色婷婷成人综合在线观看 | 日韩av偷拍 | 亚洲第一区在线观看 | 国产免费高清视频 | 日韩欧美一区二区三区在线观看 | 亚洲专区欧美 | 五月天综合在线 | 国产亚洲成av人片在线观看桃 | 九九久久国产精品 | 成人在线播放网站 | 久久精品亚洲一区二区三区观看模式 | 不卡视频在线 | 91在线日韩| 有码中文字幕 | 五月婷婷中文字幕 | 天天综合日 | 国产精品久久久久毛片大屁完整版 | 国产精品igao视频网网址 | 国产精品久久久久久久久免费 | 久久高清国产视频 | 西西444www大胆无视频 | 国产精品福利在线观看 | 国产精品不卡在线播放 | 国产免费久久久久 | 久久国产免费视频 | 免费观看成人av | 久久久久久久久久久福利 | 99精品一区二区 | 在线观看黄色 | 亚洲免费视频在线观看 | 午夜国产一区 | 欧美精品一区二区免费 | 在线观看黄色大片 | 狠狠黄| 毛片的网址 | 精品国产伦一区二区三区观看方式 | 天堂va在线高清一区 | 久久男人视频 | 黄色网址中文字幕 | 2024国产精品视频 | 伊人亚洲综合网 | 亚州性色 | 午夜在线免费观看 | 狠狠久久伊人 | 天天操天天操天天爽 | 精品国产欧美 | av九九| 超碰在线97免费 | 国产亚洲精品成人av久久ww | 中文字幕在线观看av | 在线中文字幕av观看 | 成片视频免费观看 | 午夜123| 国产亚洲人成网站在线观看 | av成人免费网站 | 国模精品一区二区三区 | 涩涩色亚洲一区 | 国产自制av | 午夜视频免费 | 天堂麻豆| 在线观看国产中文字幕 | 99久热在线精品视频观看 | 国产视频亚洲 | 天天操天天干天天操天天干 | 婷婷丁香花 | 最新久久久 | 国产精品嫩草影视久久久 | 午夜电影av| 国产欧美在线一区 | 久久国产高清视频 | 精品久久网 | 日韩精品中文字幕一区二区 | 国产色婷婷 | 182午夜在线观看 | 国产一区二区在线播放视频 | 日韩 在线 | 日韩三级视频在线看 | 992tv又爽又黄的免费视频 | 久久调教视频 | 亚洲精品色视频 | 激情网站 | 99久久婷婷国产综合精品 | av在线免费在线 | 日韩资源在线观看 | 免费在线观看黄色网 | 久久久久久久久久久久av | 欧美 另类 交 | 亚州视频在线 | 人人超碰免费 | 美女视频黄是免费的 | 在线观看成人 | 91精品色 | 四虎永久免费 | 黄网站色欧美视频 | 瑞典xxxx性hd极品 | 四虎在线免费观看视频 | 在线观看日本韩国电影 | 激情综合五月网 | 成人免费大片黄在线播放 | 国产精品久久久久久久久久久久午夜片 | 天天操夜夜想 | 成人毛片久久 | a天堂中文在线 | 国产精品乱码高清在线看 | 欧美在线观看小视频 | 成人欧美一区二区三区黑人麻豆 | 婷婷丁香色 | 中文字幕免费高清在线 | 色五月成人 | a视频免费在线观看 | 波多野结衣电影一区二区 | 亚洲免费观看视频 | 最新一区二区三区 | 99婷婷 | 99爱这里只有精品 | 国产精品久久久久久久妇 | 日韩免费播放 | 成人va在线观看 | 国产精品久久久久久久电影 | 色吊丝在线永久观看最新版本 | 国产成人精品一区一区一区 | 美女福利视频 | 激情五月综合网 | 天天干夜夜夜 | 在线观看你懂的网址 | 91视频成人免费 | 成年人在线视频观看 | 日韩精选在线观看 | 国产亚洲综合性久久久影院 | 亚洲视频久久久 | 五月花丁香婷婷 | 久久夜夜夜 | 国产视频亚洲视频 | 97超碰在线免费 | 亚洲视频免费在线看 | 欧美精品在线观看一区 | 成人av片免费观看app下载 | 九九九视频精品 | 色香天天| 成人av电影免费观看 | 免费在线观看av网站 | 亚洲经典视频在线观看 | 999久久久久久久久6666 | 五月天综合婷婷 | 国产日韩欧美精品在线观看 | 亚洲国产精品视频 | 久草影视在线 | 久久久三级视频 | 天天操天天操天天操 | 永久黄网站色视频免费观看w | 国产精品区二区三区日本 | 亚洲视频 一区 | 91免费试看 | 国产一二三区在线观看 | 国产精品热 | www.天天成人国产电影 | 日本不卡一区二区 | 综合网久久 | 国内精品视频一区二区三区八戒 | 久久伦理网 | 在线观看亚洲成人 | 精品国产一区二区三区男人吃奶 | 日本少妇高清做爰视频 | 国产香蕉av | 日韩成人精品一区二区 | 青青河边草免费观看 | 日本一区二区三区免费观看 | 欧洲高潮三级做爰 | 丁香六月婷婷激情 | 96久久久| 五月天丁香视频 | 亚洲一区久久 | 色综合婷婷 | 狠狠操综合 | 精品国产一区二区三区四区在线观看 | 免费网站观看www在线观看 | 992tv在线观看网站 | 在线视频 一区二区 | 成年人在线观看免费视频 | www.97色.com| 日韩三级免费 | 国产黄色精品在线 | 天天射网 | 91传媒激情理伦片 | 国产第一二区 | 国产成人精品久久二区二区 | 久久久免费观看视频 | 日韩久久影院 | 亚洲精品综合在线 | 日韩国产欧美视频 | 亚洲国产大片 | 色国产视频 | 97热久久免费频精品99 | 亚洲国产精品va在线 | 顶级欧美色妇4khd | 女人魂免费观看 | 日日夜夜天天操 | 日韩在线三级 | 五月天久久精品 | 久久久久久久18 | 欧女人精69xxxxxx | 国产精品青草综合久久久久99 | 最近的中文字幕大全免费版 | 美女搞黄国产视频网站 | 日韩mv欧美mv国产精品 | 亚洲视频在线观看 | 日日日天天天 | 黄色福利网站 | 丁香婷婷深情五月亚洲 | 91精品国产自产在线观看永久 | 亚洲欧美日韩不卡 | 久久婷婷一区二区三区 | 欧美专区日韩专区 | 成人免费看黄 | 亚洲一区二区三区毛片 | 西西大胆免费视频 | 亚洲精品一区二区久 | 精品一区91 | 免费久久视频 | 婷婷丁香社区 | 91丨九色丨蝌蚪丨对白 | 开心激情五月婷婷 | 免费高清在线视频一区· | 免费在线观看av网站 | 欧美日韩在线视频一区二区 | 国产精品videoxxxx | 91插插插网站 | 成人羞羞免费 | 91在线视频免费播放 | 国产v在线播放 | 午夜久久久精品 | 中文字幕一区二区三区乱码在线 | 免费人成网| 黄p网站在线观看 | av一区二区三区在线播放 | 精品日本视频 | 欧美日韩免费在线观看视频 | av大全在线免费观看 | 国产福利精品在线观看 | 久久av免费电影 | 狠狠88综合久久久久综合网 | 欧美电影在线观看 | 欧美日韩在线网站 | 国产精品免费视频观看 | av青草| 国产精品原创 | 日韩久久精品一区二区三区下载 | 日韩精品免费在线观看视频 | 最近中文字幕在线 | 久久久三级视频 | 国产91av视频在线观看 | 色婷婷综合久久久久中文字幕1 | 国产精品久久久久免费 | 国产精品视频最多的网站 | 97成人超碰 | 麻豆首页| 欧美亚洲xxx | 天天操操操操操 | 一级c片 | av色图天堂网 | 日韩免费三区 | 国产资源网 | 人人超碰97 | 成人在线一区二区三区 | 成人黄色国产 | 久久久久久久久久免费 | 国产99免费视频 | 亚洲综合少妇 | 中文字幕观看视频 | 日韩欧美极品 | 国产精品视频全国免费观看 | 丁香六月婷婷开心婷婷网 | 国产精品久久久久影院日本 | 精品国产美女 | 欧美精品久久 | 国产在线播放观看 | 精品国产一区二区三区四 | 国产精品免费久久久久影院仙踪林 | 精品五月天 | 最新久久久 | 久久久wwww| 国产三级在线播放 | 99国产一区二区三精品乱码 | 国产一线二线三线在线观看 | 一区二区不卡视频在线观看 | 久久久精品国产一区二区电影四季 | 97超碰精品 | 欧美va天堂va视频va在线 | 丝袜足交在线 | 免费在线激情电影 | 极品久久久久久久 | 丁香激情五月 | 在线免费观看羞羞视频 | 日韩三级精品 | 婷婷在线视频观看 | 精品999久久久 | 亚洲精品在线观看不卡 | 日韩在线播放欧美字幕 | 色婷婷免费 | 二区中文字幕 | 人人澡澡人人 | a√天堂中文在线 | 麻豆91精品 | 久久久这里有精品 | 久久激情视频网 | 久久草网 | 日韩av片无码一区二区不卡电影 | 久久久久| 免费观看性生活大片 | 国产亚洲久一区二区 | 超碰97免费在线 | 精品国产人成亚洲区 | 玖玖在线视频观看 | 欧美日韩视频一区二区三区 | 欧美日韩精品在线观看 | 操操日日 | 亚洲欧洲av | 亚洲欧洲一级 | 尤物九九久久国产精品的分类 | 色婷婷欧美 | 久久99精品国产91久久来源 | 国产一线二线三线性视频 | 日韩高清 一区 | 91黄色在线看 | 国产很黄很色的视频 | 国产精品爽爽爽 | 黄色av免费电影 | 国产福利一区二区三区在线观看 | 婷婷色网址 | 国产美女精品在线 | 亚洲视频播放 | 欧美韩国日本在线观看 | 综合在线色 | 中文字幕在线观看一区二区 | 日日夜夜精品视频 | 91视频免费视频 | 久久婷婷亚洲 | 欧美另类激情 | 操操操日日日干干干 | 色婷婷狠狠干 | 国产免费视频在线 | 久久精品99国产 | 久99久精品视频免费观看 | 色之综合网 | 欧美性生活大片 | 亚洲精品黄网站 | www.久久视频| 亚洲国产精品电影 | av无限看| 99久高清在线观看视频99精品热在线观看视频 | av电影在线免费 | 国产精品成人自拍 | 色九九视频 | a黄色影院 | 黄污污网站 | 正在播放久久 | 国产精品自产拍在线观看 | 美女网站在线看 | 国产xvideos免费视频播放 | 国产欧美精品一区aⅴ影院 99视频国产精品免费观看 | 综合久久2023 | 国产精品久久久精品 | 国产一级一片免费播放放 | 亚洲91在线| 国产999精品 | 中文字幕在线观看完整 | 日韩精品一区二区在线观看 | 99热在线国产精品 | 日本久久久久久久久久久 | 超碰在线亚洲 | 日韩理论在线播放 | 免费成人av在线看 | 亚洲精品午夜一区人人爽 | 波多野结衣电影一区二区 | 大胆欧美gogo免费视频一二区 | 免费观看国产视频 | av大片网址 | 国产精品扒开做爽爽的视频 | 91av在线免费视频 | 精品久久久久久国产91 | 五月婷婷六月丁香 | 日韩特黄一级欧美毛片特黄 | 午夜精品一区二区三区免费 | 成人av中文字幕在线观看 | 日韩在线观看第一页 | 国产精品美女视频网站 | 婷婷在线免费视频 | 极品久久久久久久 | 国产精品成人免费精品自在线观看 | www激情网 | 欧美日韩性生活 | 色婷婷久久久综合中文字幕 | 五月综合婷 | 国产一区私人高清影院 | 在线观看av的网站 | 激情视频二区 | 婷婷久久网站 | www.在线看片.com | 91av在线免费观看 | 涩涩资源网 | 国产一卡在线 | www.夜夜爽| 日b视频国产 | 成人a免费看 | 久久香蕉电影 | 天堂在线v| 成人在线观看网址 | 久久在线影院 | www.久久色.com | 欧美爽爽爽| 成人a在线观看高清电影 | 日夜夜精品视频 | 久久精品一级片 | 国产在线观看高清视频 | 日本黄色大片免费看 | 99久免费精品视频在线观看 | a精品视频 | 97成人啪啪网| 国产精彩在线视频 | 色婷婷视频在线 | 亚洲综合激情五月 | 日本精品在线 | 久久免费视频观看 | 色婷婷福利 | 西西大胆啪啪 | 中文字幕在线久一本久 | 久久久久国产精品午夜一区 | 日本公妇在线观看高清 | 91色国产在线 | 99久久久久久久 | 天天摸日日摸人人看 | 欧美日韩国产在线精品 | 日本xxxx裸体xxxx17 | 欧美va日韩va| 精品在线观看一区二区三区 | 丰满少妇对白在线偷拍 | 免费黄色av电影 | 婷婷综合伊人 | 9色在线视频 | 婷婷色 亚洲 | 日本久久中文 | av+在线播放在线播放 | 在线一级片 | 国产黄色免费 | 日韩精品一区二区三区视频播放 | 国产精品网址在线观看 | 激情中文字幕 | 日韩精品视频一二三 | 日韩精品一区二区在线观看 | 在线成人小视频 | 国内视频1区 | 91香蕉亚洲精品 | 在线看欧美 | 四虎国产精品免费 | 国产视频精选 | 日韩精品在线视频免费观看 | 欧美日韩高清一区二区 | 久久免费成人精品视频 | 成年人在线免费看视频 | 国产精品一区二区62 | 天天干天天操天天干 | 97视频在线免费观看 | 色婷婷视频在线观看 | 丁香六月婷婷开心 | 国产免费嫩草影院 | 国产不卡视频在线播放 | 999久久久久久久久6666 | 欧美一二三区在线观看 | 成人一区二区在线观看 | 天天天干夜夜夜操 | 在线免费中文字幕 | 国产剧情av在线播放 | 在线性视频日韩欧美 | 综合久久网 | 国产手机视频在线观看 | 少妇自拍av| 69国产精品视频免费观看 | 在线观看av网 | 黄色一级免费网站 | aaa免费毛片| 依人成人综合网 | 一级片黄色片网站 | 337p日本大胆噜噜噜噜 | 在线免费观看欧美日韩 | 蜜臀久久99精品久久久久久网站 | 97视频网站 | 在线观看中文字幕一区二区 | 国产超碰在线 | 免费精品久久久 | 奇米影视8888 | 国产一区精品在线 | 亚洲综合导航 | 精品人人人 | 激情视频综合网 | 婷婷精品在线视频 | 国产精品久久久久永久免费观看 | 亚洲精品乱码久久久久久久久久 | 国产999精品久久久久久绿帽 | 伊人色综合久久天天 | 久久夜夜夜 | 免费在线黄色av | 亚洲成av人片在线观看www | 粉嫩一区二区三区粉嫩91 | 久久久久区 | 免费欧美精品 | 操操操人人人 | 亚洲精品h | 日韩中文字幕第一页 | a久久久久久 | 欧美黑吊大战白妞欧美 | 国产精品久久久久久婷婷天堂 | 久久av影院 | 国产一区二区日本 | 日韩,精品电影 | 在线观看黄色大片 | 欧美特一级片 | 免费av网址在线观看 | 亚洲一级电影视频 | 黄色片网站av | 午夜在线免费观看视频 | 蜜臀av性久久久久蜜臀av | 久久a免费视频 | 色婷婷五| 日日噜噜噜噜夜夜爽亚洲精品 | 一级免费看 | 亚洲影视九九影院在线观看 | 久久婷亚洲五月一区天天躁 | 亚洲一级片在线看 | 久久视| 探花系列在线 | 黄色免费网站下载 | 五月婷婷中文 | a在线观看免费视频 | 久久久久久久久网站 | 国产精品毛片一区二区 | 日韩在线观看一区二区三区 | 日韩精品不卡在线 | 特黄色大片 | 在线视频日韩欧美 | 久草在线免费新视频 | 婷婷在线免费视频 | www.久久婷婷| 久久久久久久久电影 | 爱色婷婷| 中文字幕在线观看第二页 | 日韩av一卡二卡三卡 | 国产精品 视频 | 四虎永久网站 | 日韩精品在线视频免费观看 | 欧美影片 | 91视频免费网站 | 日本特黄特色aaa大片免费 | av网站免费线看精品 | 丝袜av网站| 在线91视频 | 91精品在线播放 | 日韩黄色免费在线观看 | 色永久免费视频 | 亚洲区另类春色综合小说校园片 | 亚洲高清激情 | 久久国产手机看片 | 免费看污黄网站 | 欧美成人黄色 | 99久久久国产精品免费99 | av免费看在线 | 国产精品久久久久影视 | 91视频在线看 | 国产精品每日更新 | 午夜在线免费观看 | 国产精品福利在线播放 | 在线观看一区 | 欧美另类交在线观看 | 综合国产视频 | 视频在线播放国产 | 中国一级特黄毛片大片久久 | 色综合天天色 | 五月天天av | 超薄丝袜一二三区 | 日韩久久精品一区二区三区下载 | 四虎永久网站 | 91高清视频 | 亚洲精品视频中文字幕 | 欧美动漫一区二区三区 | 久久精品国产一区二区 | 国产黄色成人av | 91av资源在线| 日韩av一区二区三区 | 国产成a人亚洲精v品在线观看 | 在线看黄网站 | 国内精品久久久久久久影视简单 | 久久精品www人人爽人人 | 四虎www | 4p变态网欧美系列 | 欧美欧美| 亚洲精品视频在线播放 | 一级一片免费看 | 成人黄色电影在线观看 | 99精品系列 | 欧美一区二区免费在线观看 | 欧美日韩18 | 欧美日韩一级在线 | 天天玩天天干天天操 | 狠狠精品 | 国产成年免费视频 | 99久久久国产精品美女 | 日本婷婷色 | 菠萝菠萝在线精品视频 | 伊人超碰在线 | 国产成人精品一区二区三区在线观看 | 91亚洲精品久久久中文字幕 | www.国产视频 | 狠狠插天天干 | 国内精品久久久久久久久久久 | 欧美-第1页-屁屁影院 | 一区二区视频在线免费观看 | 日韩v欧美v日本v亚洲v国产v | 久久艹在线| 国产一二三区在线观看 | 久久av电影 | 99视频精品视频高清免费 | 亚洲乱码国产乱码精品天美传媒 | 久草视频在 | 久久手机免费观看 | 在线中文字幕观看 | 欧美日韩精品综合 | 亚洲婷婷免费 | 欧美一区二区在线免费观看 | 久久伦理视频 | 久久国产精品免费观看 | 欧美日韩精品在线播放 | 亚洲精品视频大全 | 黄色av电影网 | 久久撸在线视频 | 91色视频| 亚洲精品视频在线观看免费视频 | 久久精品小视频 | 九九热有精品 | 激情久久小说 | 在线影视 一区 二区 三区 | 免费观看一级 | 亚洲精品白浆高清久久久久久 | 亚洲精品tv久久久久久久久久 | 国产一卡久久电影永久 | 欧美日韩久久不卡 | 一区二区在线影院 | 色偷偷网站视频 | 三级免费黄色 | 国产一区二区三区免费观看视频 | 久久福利电影 | 国产精品免费不 | bbw av | 精品国产伦一区二区三区观看方式 | 欧美午夜a | 黄色片免费电影 | 久久久久麻豆 | 久久久久久久久电影 | 国产一区免费视频 | 香蕉视频国产在线观看 | 久热爱 | 美女视频黄色免费 | 一级黄色免费 | 日韩丝袜 | 久久精美视频 | 亚洲综合在 | 天天激情天天干 | 欧美日韩大片在线观看 | 免费看wwwwwwwwwww的视频 久久久久久99精品 91中文字幕视频 | 婷婷网站天天婷婷网站 | 96精品高清视频在线观看软件特色 | 亚洲做受高潮欧美裸体 | 国内精品久久久久久久久久清纯 | 国产成人综 | 2019久久精品| 日日摸日日爽 | 亚洲午夜久久久影院 | 深爱激情五月综合 | 香蕉网址 | 婷婷激情五月综合 | www久久久久 | 干干干操操操 | 欧美色图视频一区 | 欧美日韩高清不卡 | 国产久视频| 亚洲欧美日韩精品久久奇米一区 | 精品国产乱码久久久久久三级人 | 一级特黄aaa大片在线观看 | 色干干 | 亚洲免费专区 | 天天射天天添 | 国产只有精品 | 黄www在线观看 | 日韩欧美国产激情在线播放 | 91精品老司机久久一区啪 | 精品国产aⅴ一区二区三区 在线直播av | 在线国产精品视频 | 五月天亚洲婷婷 | 久久国产综合视频 | 成人av影视| 国产录像在线观看 | 国产精品一区二区三区在线免费观看 | 久草免费手机视频 | 久久久精品午夜 | 日韩免费一区二区 | 人人玩人人添人人 | 免费久久久久久久 | 日韩在线观看视频中文字幕 | 欧美一级小视频 | 在线观看中文字幕第一页 | 久久国产欧美日韩 | 天天干天天摸天天操 | 日韩黄色av网站 | 91视频黄色 | 在线观看av中文字幕 | 最新中文字幕视频 | 美女搞黄国产视频网站 | 久久精品国产精品亚洲 | 91九色蝌蚪视频 | 最新av网站在线观看 | 欧美91在线| 免费观看www视频 | 国产精品自产拍在线观看桃花 | 久久国色夜色精品国产 | 99国产精品久久久久久久久久 | 男女拍拍免费视频 | 国产精品美女www爽爽爽视频 | 久久99国产精品二区护士 | 欧美综合在线观看 | 国产五月色婷婷六月丁香视频 | 色国产精品 | 日韩乱理| 亚洲成人资源在线观看 | 亚洲区二区| 日韩欧美在线国产 | 久草精品在线播放 | 黄色在线免费观看网站 | 国产中年夫妇高潮精品视频 | 美女一级毛片视频 | 日韩av一区二区三区在线观看 | 久久久久久久久久网站 | 日日爽天天爽 | adn—256中文在线观看 | 中文字幕在线观看视频网站 | 五月婷婷久久综合 | 久久精品99国产精品日本 | 视频一区亚洲 | 中文字幕在线中文 | 欧美综合色在线图区 | 在线播放日韩 | 91福利社在线观看 | 五月婷婷另类国产 | 美女免费视频一区 | 亚洲 精品在线视频 | 国产不卡在线观看 | 色婷婷综合久久久久中文字幕1 | 综合网天天 | 亚洲黄色成人网 | 国产无套一区二区三区久久 | 国产精品视频不卡 | 中文国产成人精品久久一 | 国产精品福利一区 | 天堂视频中文在线 | 亚洲一级国产 | 久久黄视频 | 亚洲性少妇性猛交wwww乱大交 | 国产精品高潮呻吟久久av无 | 欧美激情第十页 | 五月婷婷,六月丁香 | 五月开心婷婷 | 国产日产欧美在线观看 | 亚洲情感电影大片 | 91在线播放视频 | 五月天婷婷狠狠 | 超碰久热| 激情视频在线观看网址 | 国产传媒中文字幕 | www.色com|