轉自:http://www.cnblogs.com/shineshqw/articles/2359114.htmlphp
kdb:只能在彙編代碼級進行調試;html
優勢是不須要兩臺機器進行調試。前端
gdb:在調試模塊時缺乏一些相當重要的功能,它可用來查看內核的運行狀況,包括反彙編內核函數。node
kgdb:能很方便的在源碼級對內核進行調試,缺點是kgdb只能進行遠程調試,它須要一根串口線及兩臺機器來調試內核(也能夠是在同一臺主機上用vmware軟件運行兩個操做系統來調試)linux
printk() 是調試內核代碼時最經常使用的一種技術。在內核代碼中的特定位置加入printk() 調試調用,能夠直接把所關心的信息打打印到屏幕上,從而能夠觀察程序的執行路徑和所關心的變量、指針等信息。 Linux 內核調試器(Linux kernel debugger,kdb)是 Linux 內核的補丁,它提供了一種在系統能運行時對內核內存和數據結構進行檢查的辦法。Oops、KDB在文章掌握 Linux 調試技術有詳細介紹,你們能夠參考。 Kprobes 提供了一個強行進入任何內核例程,並從中斷處理器無干擾地收集信息的接口。使用 Kprobes 能夠輕鬆地收集處理器寄存器和全局數據結構等調試信息,而無需對Linux內核頻繁編譯和啓動,具體使用方法,請參考使用 Kprobes 調試內核。web
/proc文件系統shell
在 /proc 文件系統中,對虛擬文件的讀寫操做是一種與內核通訊的手段,要查看內核迴環緩衝區中的消息,可使用 dmesg 工具(或者經過 /proc 自己使用 cat /proc/kmsg 命令)。清單 6 給出了 dmesg 顯示的最後幾條消息。express
清單 6. 查看來自 LKM 的內核輸出編程
[root@plato]# dmesg | tail -5 cs: IO port probe 0xa00-0xaff: clean. eth0: Link is down eth0: Link is up, running at 100Mbit half-duplex my_module_init called. Module is now loaded. my_module_cleanup called. Module is now unloaded.
能夠在內核輸出中看到這個模塊的消息。如今讓咱們暫時離開這個簡單的例子,來看幾個能夠用來開發有用 LKM 的內核 API。vim
調試工具
使用調試器來一步步地跟蹤代碼,查看變量和計算機寄存器的值。在內核中使用交互式調試器是一個很複雜的問題。內核在它本身的地址空間中運行。許多用戶空間下的調試器所提供的經常使用功能很難用於內核之中,好比斷點和單步調試等。
目錄 |
(1)oops消息產生機制
oops(也稱 panic),稱程序運行崩潰,程序崩潰後會產生oops消息。應用程序或內核線程的崩潰都會產生oops消息,一般發生oops時,系統不會發生死機,而在終端或日誌中打印oops信息。
當使用NULL指針或不正確的指針值時,一般會引起一個 oops 消息,這是由於當引用一個非法指針時,頁面映射機制沒法將虛擬地址映像到物理地址,處理器就會向操做系統發出一個"頁面失效"的信號。內核沒法"換頁"到並不存在的地址上,系統就會產生一個"oops"。
oops 顯示發生錯誤時處理器的狀態,包括 CPU 寄存器的內容、頁描述符表的位置,以及其一些難理解的信息。這些消息由失效處理函數(arch/*/kernel/traps.c)中的printk 語句產生。較爲重要的信息就是指令指針(EIP),即出錯指令的地址。
因爲很難從十六進制數值中看出含義,可以使用符號解析工具klogd。klogd 守護進程能在 oops 消息到達記錄文件以前對它們解碼。klogd在缺省狀況下運行並進行符號解碼。
一般Oops文本由klogd從內核緩衝區裏讀取並傳給syslogd,由syslogd寫到syslog文件中,該文件典型爲/var/log/messages(依賴於/etc/syslog.conf)。若是klogd崩潰了,用戶可"dmesg > file"從內核緩衝區中讀取數據並保存下來。還可用"cat /proc/kmsg > file"讀取數據,此時,須要用戶停止傳輸,由於kmsg是一個"永不結束的文件"。
當保護錯誤發生時,klogd守護進程自動把內核日誌信息中的重要地址翻譯成它們相應的符號。klogd執行靜態地址翻譯和動態地址翻譯。靜態地址翻譯使用System.map文件將符號地址翻譯爲符號。klogd守護進程在初始化時必須能找到system.map文件。
動態地址翻譯一般對內核模塊中的符號進行翻譯。內核模塊的內存從內核動態內存池裏分配,內核模塊中符號的位置在內核裝載後才最終肯定。
Linux內核提供了調用,容許程序決定裝載哪些模塊和它們在內存中位置。經過這些系統調用,klogd守護進程生成一張符號表用於調試發生在可裝載模塊中的保護錯誤。內核模塊的裝載或者卸載都會自動向klogd發送信號,klogd可將內核模塊符號的地址動態翻譯爲符號字符串。
(2)產生oops的樣例代碼
使用空指針和緩衝區溢出是產生oops的兩個最多見緣由。下面兩個函數faulty_write和faulty_read是一個內核模塊中的寫和讀函數,分別演示了這兩種狀況。當內核調用這兩個函數時,會產生oops消息。
函數faulty_write刪除一個NULL指針的引用,因爲0不是一個有效的指針值,內核將打印oops信息,並接着,殺死調用些函數的進程。ssize_t faulty_write (struct file *filp, const char _ _user *buf, size_t count, loff_t *pos) { /* make a simple fault by dereferencing a NULL pointer */ *(int *)0 = 0; return 0; }
Unable to handle kernel NULL pointer dereference at virtual address \
00000000
printing eip: c48370c3 *pde = 00000000 Oops: 0002 CPU: 0 EIP: 0010:[faulty:faulty_write+3/576] EFLAGS: 00010286 eax: ffffffea ebx: c2c55ae0 ecx: c48370c0 edx: c2c55b00 esi: 0804d038 edi: 0804d038 ebp: c2337f8c esp: c2337f8c ds: 0018 es: 0018 ss: 0018 Process cat (pid: 23413, stackpage=c2337000) Stack: 00000001 c01356e6 c2c55ae0 0804d038 00000001 c2c55b00 c2336000 \
00000001 0804d038 bffffbd4 00000000 00000000 bffffbd4 c010b860 00000001 \ 0804d038 00000001 00000001 0804d038 bffffbd4 00000004 0000002b 0000002b \ 00000004
Call Trace: [sys_write+214/256] [system_call+52/56]
Code: c7 05 00 00 00 00 00 00 00 00 31 c0 89 ec 5d c3 8d b6 00 00
ssize_t faulty_read(struct file *filp, char _ _user *buf, size_t count, loff_t *pos) { int ret; char stack_buf[4]; /* Let's try a buffer overflow */ memset(stack_buf, 0xff, 20); if (count > 4) count = 4; /* copy 4 bytes to the user */ ret = copy_to_user(buf, stack_buf, count); if (!ret) return count; return ret; }
EIP: 0010:[<00000000>]
Unable to handle kernel paging request at virtual address ffffffff printing eip: ffffffff Oops: 0000 [#5] SMP CPU: 0 EIP: 0060:[] Not tainted EFLAGS: 00010296 (2.6.6) EIP is at 0xffffffff eax: 0000000c ebx: ffffffff ecx: 00000000 edx: bfffda7c esi: cf434f00 edi: ffffffff ebp: 00002000 esp: c27fff78 ds: 007b es: 007b ss: 0068 Process head (pid: 2331, threadinfo=c27fe000 task=c3226150) Stack: ffffffff bfffda70 00002000 cf434f20 00000001 00000286 cf434f00 fffffff7 bfffda70 c27fe000 c0150612 cf434f00 bfffda70 00002000 cf434f20 00000000 00000003 00002000 c0103f8f 00000003 bfffda70 00002000 00002000 bfffda70 Call Trace: [] sys_read+0x42/0x70 [] syscall_call+0x7/0xb
Code: Bad EIP value.
(3)oops信息分析
面對產生的oops信息,首先應查找源程序發生oops的位置,經過查看指令指令寄存器EIP的值,能夠找到位置,如:EIP: 0010:[faulty:faulty_write+3/576]。
再查找函數調用棧(call stack)能夠獲得更多的信息。從函數調用棧可辨別出局部變量、全局變量和函數參數。例如:在函數faulty_read的oops信息的函數調用棧中,棧頂爲ffffffff,棧頂值應爲一個小於ffffffff的值,爲此值,說明再找不回調用函數地址,說明有可能因緩衝區溢出等緣由形成指針錯誤。
在x86構架上,用戶空間的棧從0xc0000000如下開始,遞歸值bfffda70多是用戶空間的棧地址。實際上它就是傳遞給read系統調用的緩衝區地址,系統調用read進入內核時,將用戶空間緩衝區的數據拷貝到內核空間緩衝區。
若是oops信息顯示觸發oops的地址爲0xa5a5a5a5,則說明極可能是由於沒有初始化動態內存引發的。
另外,若是想看到函數調用棧的符號,編譯內核時,請打開CONFIG_KALLSYMS選項。
klogd 提供了許多信息來幫助分析。爲了使 klogd 正確地工做,必須在 /boot 中提供符號表文件 System.map。若是符號表與當前內核不匹配,klogd 就會拒絕解析符號。
有時內核錯誤會將系統徹底掛起。例如代碼進入一個死循環,系統不會再響應任何動做。這時可經過在一些關鍵點上插入 schedule 調用能夠防止死循環。
因爲內核運行錯誤,在某些極端狀況下,內核會運行崩潰,內核崩潰時會致使死機。爲了解決此問題,內核引入了快速裝載和重啓動新內核機制。內核經過kdump在崩潰時觸發啓動新內核,存儲舊內存映像以便於調試,讓系統在新內核上運行 ,從而避免了死機,加強了系統的穩定性。
kexec是一套系統調用,容許用戶從當前正執行的內核裝載另外一個內核。用戶可用shell命令"yum install kexec-tools"安裝kexec工具包,安裝後,就可使用kexec命令。
工具kexec直接啓動進入一個新內核,它經過系統調用使用戶可以從當前內核裝載並啓動進入另外一個內核。在當前內核中,kexec執行BootLoader的功能。在標準系統啓動和kexec啓動之間的主要區別是:在kexec啓動期間,依賴於硬件構架的固件或BIOS不會被執行來進行硬件初始化。這將大大下降重啓動的時間。
爲了讓內核的kexec功能起做用,內核編譯配置是應確認先擇了"CONFIG_KEXEC=y",在配置後生成的.config文件中應可看到此條目。
工具kexec的使用分爲兩步,首先,用kexec將調試的內核裝載進內存,接着,用kexec啓動裝載的內核。
裝載內核的語法列出以下:
kexec -l kernel-image --append=command-line-options --initrd=initrd-image
上述命令中,參數kernel-image爲裝載內核的映射文件,該命令不支持壓縮的內核映像文件bzImage,應使用非壓縮的內核映射文件vmlinux;參數initrd-image爲啓動時使用initrd映射文件;參數command-line-options爲命令行選項,應來自當前內核的命令行選項,可從文件"/proc/cmdline"中提取,該文件的內容列出以下:
^-^$ cat /proc/cmdline
ro root=/dev/VolGroup00/LogVol00 rhgb quiet
例如:用戶想啓動的內核映射爲/boot/vmlinux,initrd爲/boot/initrd,則kexec加載命令列出以下:
Kexec –l /boot/vmlinux –append=/dev/VolGroup00/LogVol00 initrd=/boot/initrd
還能夠加上選項-p或--load-panic,表示裝載新內核在系統內核崩潰使用。
在內核裝載後,用下述命令啓動裝載的內核,並進行新的內核中運行:
kexec -e
當kexec將當前內核遷移到新內核上運行時,kexec拷貝新內核到預保留內存塊,該保留位置如圖1所示, 原系統內核給kexec裝載內核預保留一塊內存(在圖中的陰影部分),用於裝載新內核,其餘內存區域在未裝載新內核時,由原系統內核使用。
在x86構架的機器上,系統啓動時須要使用第一個640KB物理內存,用於內核裝載,kexec在重啓動進入轉儲捕捉的內核以前備份此區域。類似地,PPC64構架的機器在啓動裏須要使用第一個32KB物理內核,並須要支持64K頁,kexec備份第一個64KB內存。
kdump是基於kexec的崩潰轉儲機制(kexec-based Crash Dumping),不管內核內核須要轉儲時,如:系統崩潰時,kdump使用kexec快速啓動進入轉儲捕捉的內核。在這裏,原運行的內核稱爲系統內核或原內核,新裝載運行的內核稱爲轉儲捕捉的內核或裝載內核或新內核。
在重啓動過程當中,原內核的內存映像被保存下來,而且轉儲捕捉的內核(新裝載的內核)能夠訪問轉儲的映像。用戶可使用命令cp和scp將內存映射拷貝到一個本地硬盤上的轉儲文件或經過網絡拷貝到遠程計算機上。
當前僅x86, x86_64, ppc64和ia64構架支持kdump和kexec。
當系統內核啓動時,它保留小部份內存給轉儲(dump)捕捉的內核,確保了來自系統內核正進行的直接內存訪問(Direct Memory Access:DMA)不會破壞轉儲捕捉的內核。命令kexec –p裝載新內核到這個保留的內存。
在崩潰前,全部系統內核的核心映像編碼爲ELF格式,並存儲在內核的保留區域。ELF頭的開始物理地址經過參數elfcorehdr=boot傳遞到轉儲捕捉的內核。
經過使用轉儲捕捉的內核,用戶能夠下面兩種方式訪問內存映像或舊內存:
(1)經過/dev/oldmem設備接口,捕捉工具程序能讀取設備文件並以原始流的格式寫出內存,它是一個內存原始流的轉儲。分析和捕捉工具必須足夠智能以判斷查找正確信息的位置。
(2)經過/proc/vmcore,能以ELF格式文件輸出轉儲信息,用戶能夠用GDB(GNU Debugger)和崩潰調試工具等分析工具調試轉儲文件。
(3)創建快速重啓動機制和安裝工具
1)安裝工具kexec-tools
能夠下載源代碼編譯安裝工具kexec-tools。因爲工具kexec-tools還依賴於一些其餘的庫,所以,最好的方法是使用命令"yum install kexec-tools"從網上下載安裝並自動解決依賴關係。
2)編譯系統和轉儲捕捉的內核
可編譯獨立的轉儲捕捉內核用於捕捉內核的轉儲,還可使用原系統內核做爲轉儲捕捉內核,在這種狀況下,不須要再編譯獨立的轉儲捕捉內核,但僅支持重定位內核的構架才能夠用做轉儲捕捉的內核,如:構架i386和ia64支持重定位內核。
對於系統和轉儲捕捉內核來講,爲了打開kdump支持,內核須要設置一些特殊的配置選項,下面分別對系統內核和轉儲捕捉內核的配置選項進行說明:
系統內核的配置選項說明以下:
轉儲捕捉內核配置選項(不依賴於處理器構架)說明以下:
轉儲捕捉內核配置選項(依賴於處理器構架i386和x86_64)說明以下:
若是設置了值"CONFIG_PHYSICAL_START=0x100000",則表示使用可重定位內核。它將編譯內核在物理地址1MB處,內核是可重定位的,所以,內核可從任何物理地址運行。Kexec BootLoader將裝載內核到用於轉儲捕捉內核的內核保留區域。
不然,將使用啓動參數"crashkernel=Y@X"指定第二個內核保留內核區域的開始地址,其中,Y表示內存區域的大小,X表示保留給轉儲捕捉內核的內存區域的開始地址,經過X爲16MB (0x1000000),所以用戶可設置"CONFIG_PHYSICAL_START=0x1000000"。
在配置完內核後,編譯和安裝內核及內核模塊。
3)擴展的crashkernel語法
在系統內核的啓動命令行選項中,一般語法"crashkernel=size[@offset]"對於大多數據配置已夠用了,但有時候保留的內存依賴於系統RAM。此時可經過擴展的crashkernel命令行對內存進行 限制避免從機器上移去一部份內核後形成系統不可啓動。擴展的crashkernel語法列出以下:
crashkernel=<range1>:<size1>[,<range2>:<size2>,...][@offset]
其中,range=start-[end]。
例如:crashkernel=512M-2G:64M,2G-:128M,含義爲:若是內存小於512M,不設置保留內存,若是內存爲512M到2G之間,設置保留內存區域爲64M,若是內存大於128M,設置保留內存區域爲128M。
4)啓動進入系統內核
必要時更新BootLoader。而後用參數"crashkernel=Y@X"啓動系統內核,如:crashkernel=64M@16M,表示告訴系統內核保留從物理地址0x01000000 (16MB)開始的64MB大小給轉儲捕捉內核使用。一般x86和x86_64平臺設置"crashkernel=64M@16M",ppc64平臺設置"crashkernel=128M@32M"。
5)裝載轉儲捕捉內核
在啓動進入系統內核後,須要裝載轉儲捕捉內核。根據處理器構架和映射文件的類型(能否重定位),能夠選擇裝載不壓縮的vmlinux或壓縮的bzImage/vmlinuz內核映像。選擇方法說明以下:
對於i386和x86_64平臺:
對於ppc64平臺:
對於ia64平臺:
kexec -p <dump-capture-kernel-vmlinux-image> \ --initrd=<initrd-for-dump-capture-kernel> --args-linux \ --append="root=<root-dev> <arch-specific-options>"
kexec -p <dump-capture-kernel-bzImage>\
--initrd=<initrd-for-dump-capture-kernel> \
--append="root=<root-dev> <arch-specific-options>"
下面是在裝載轉儲捕捉內核時使用的構架特定命令行選項:
在裝載轉儲捕捉內核時須要注意的事項說明以下:
6)內核崩潰時觸發內核啓動
在裝載轉儲捕捉內核後,若是系統發生崩潰(Kernel Panic),系統將重啓動進入轉儲捕捉內核。重啓動的觸發點在函數die(), die_nmi()和sysrq處理例程(按ALT-SysRq-c組合鍵)。
下面條件將執行一個崩潰觸發點:
7)寫出轉儲文件
在轉儲捕捉內核啓動後,可用下面的命令寫出轉儲文件:
cp /proc/vmcore <dump-file>
用戶還能夠將轉儲內存做爲設備/dev/oldmem以線性原始流視圖進行訪問,使用下面的命令建立該設備:
mknod /dev/oldmem c 1 12
使用命令dd拷貝轉儲內存的特定部分,拷貝整個內存的命令列出以下:
dd if=/dev/oldmem of=oldmem.001
8)轉儲文件分析
在分析轉儲映像以前,用戶應重啓動進入一個穩定的內核。用戶能夠用GDB對拷貝出的轉儲進行有限分析。編譯vmlinux時應加上-g選項,才能生成調試用的符號,而後,用下面的命令調試vmlinux:
gdb vmlinux <dump-file>
SysRq"魔術組合鍵"是一組按鍵,由鍵盤上的"Alt+SysRq+[CommandKey]"三個鍵組成,其中CommandKey爲可選的按鍵。SysRq魔術組合鍵根據組合鍵的不一樣,可提供控制內核或打印內核信息的功能。SysRq魔術組合鍵的功能說明如表1所示。
鍵名 | 功能說明 |
b | 在沒有同步或卸載硬盤的狀況下當即啓動。 |
c | 爲了獲取崩潰轉儲執行kexe重啓動。 |
d | 顯示被持的全部鎖。 |
e | 發送信號SIGTERM給全部進程,除了init外。 |
f | 將調用oom_kill殺死內存熱進程。 |
g | 在平臺ppc和sh上被kgdb使用。 |
h | 顯示幫助信息。 |
i | 發送信號SIGKILL給全部的進程,除了init外。 |
k | 安全訪問密鑰(Secure Access Key,SAK)殺死在當前虛擬終端上的全部程序。 |
m | 轉儲當前的內存信息到控制檯。 |
n | 用於設置實時任務爲可調整nice的。 |
o | 將關閉系統(若是配置爲支持)。 |
p | 打印當前寄存器和標識到控制檯。 |
q | 將轉儲全部正運行定時器的列表。 |
r | 關閉鍵盤Raw模式並設置爲XLATE模式。 |
s | 嘗試同步全部掛接的文件系統。 |
t | 將轉儲當前的任務列表和它們的信息到控制檯。 |
u | 嘗試以僅讀的方式重掛接全部已掛接的文件系統。 |
v | 轉儲Voyager SMP處理器信息到控制檯。 |
w | 轉儲的全部非可中斷(已阻塞)狀態的任務。 |
x | 在平臺ppc/powerpc上被xmon(X監視器)接口使用。 |
0~9 | 設備控制檯日誌級別,控制將打印到控制檯的內核信息。例如:0僅打印緊急信息,如:PANIC和OOPS信息。 |
默認SysRq組合鍵是關閉的。可用下面的命令打開此功能:
# echo 1 > /proc/sys/kernel/sysrq
關閉此功能的命令列出以下:
# echo 0 > /proc/sys/kernel/sysrq
若是想讓此功能老是起做用,可在/etc/sysctl.conf文件中設置kernel.sysrq值爲1。 系統從新啓動之後,此功能將會自動打開。
打開SysRq組合鍵功能後,有終端訪問權限的用戶就能夠自用它打印內核信息了。
注意:SysRq組合鍵在X windows上是沒法使用的。必須先要切換到文本虛擬終端下。若是在圖形界面,能夠按Ctrl+Alt+F1切換到虛擬終端。在串口終端上,須要先在終端上發送Break信號,而後在5秒內輸入sysrq組合鍵。若是用戶有root權限,可把commandkey字符寫入到/proc/sysrq-trigger文件,觸發一個內核信息打印,打印的信息存放在/var/log/messages中。下面是一個命令樣例:^-^$ echo 't' > sysrq-trigger ^-^vim /var/log/messages Oct 29 17:51:43 njllinux kernel: SysRq : Show State Oct 29 17:51:43 njllinux kernel: task PC stack pid father Oct 29 17:51:43 njllinux kernel: init S ffffffff812b76a0 0 1 0 Oct 29 17:51:43 njllinux kernel: ffff81013fa97998 0000000000000082 0000000000000000 ffff81013fa9795c Oct 29 17:51:43 njllinux kernel: 000000003fa97978 ffffffff81583700 ffffffff81583700 ffff81013fa98000 Oct 29 17:51:43 njllinux kernel: ffffffff813cc5b0 ffff81013fa98350 000000003c352a50 ffff81013fa98350 Oct 29 17:51:43 njllinux kernel: Call Trace: Oct 29 17:51:43 njllinux kernel: 000300000004 ffff8101333cb090 Oct 29 17:51:43 njllinux kernel: Call Trace: Oct 29 17:51:43 njllinux kernel: [<ffffffff81040c2e>] sys_pause+0x19/0x22 Oct 29 17:51:43 njllinux kernel: [<ffffffff8100c291>] tracesys+0xd0/0xd5 Oct 29 17:51:43 njllinux kernel: Oct 29 17:51:43 njllinux kernel: lighttpd S ffffffff812b76a0 0 3365 1 Oct 29 17:51:43 njllinux kernel: ffff810132d49b18 0000000000000082 0000000000000000 ffff810132d49adc Oct 29 17:51:43 njllinux kernel: ffff81013fb2d148 ffffffff81583700 ffffffff81583700 ffff8101354896a0 Oct 29 17:51:43 njllinux kernel: ffffffff813cc5b0 ffff8101354899f0 0000000032d49ac8 ffff8101354899f0 Oct 29 17:51:43 njllinux kernel: Call Trace: Oct 29 17:51:43 njllinux kernel: [<ffffffff81040722>] ? __mod_timer+0xbb/0xcd Oct 29 17:51:43 njllinux kernel: [<ffffffff8129b2ee>] schedule_timeout+0x8d/0xb4 Oct 29 17:51:43 njllinux kernel: [<ffffffff81040100>] ? process_timeout+0x0/0xb Oct 29 17:51:43 njllinux kernel: [<ffffffff8129b2e9>] ? schedule_timeout+0x88/0xb4 Oct 29 17:51:43 njllinux kernel: [<ffffffff810b9498>] do_sys_poll+0x2a8/0x370 ……
命令strace 顯示程序調用的全部系統調用。使用 strace 工具,用戶能夠清楚地看到這些調用過程及其使用的參數,瞭解它們與操做系統之間的底層交互。當系統調用失敗時,錯誤的符號值(如 ENOMEM)和對應的字符串(如Out of memory)都能被顯示出來。
starce 的另外一個用處是解決和動態庫相關的問題。當對一個可執行文件運行ldd時,它會告訴你程序使用的動態庫和找到動態庫的位置
strace命令行選項說明如表1。經常使用的選項爲-t, -T, -e, -o等。
選項 | 說明 |
-c | 統計每一個系統調用執行的時間、次數和出錯的次數等。 |
-d | 輸出一些strace自身的調試信息到標準輸出。 |
-f | 跟蹤當前進程由系統調用fork產生的子進程。 |
-ff | 若是使用選項-o filename,則將跟蹤結果輸出到相應的filename.pid中,pid是各進程的進程號。 |
-F | 嘗試跟蹤vfork調用.在-f時,vfork不被跟蹤。 |
-h | 輸出簡要的幫助信息。 |
-i | 在系統調用的時候打印指令指針。 |
-q | 禁止輸出關於粘附和脫離的信息,發生在輸出重定向到文件且直接而不是粘附運行命令時。 |
-r | 依賴於每一個系統調用的入口打印相對時間戳。 |
-t | 在輸出中的每一行前加上時間信息。 |
-tt | 在輸出中的每一行前加上時間信息,包括毫秒。 |
-ttt | 毫秒級輸出,以秒錶示時間。 |
-T | 顯示系統調用所花費的時間。 |
-v | 輸出全部的系統調用的信息。一些關於環境變量,狀態,輸入輸出等調用因爲使用頻繁,默認不輸出。 |
-V | 輸出strace的版本信息。 |
-x | 以十六進制形式輸出非ASCII標準字符串。 |
-xx | 全部字符串以十六進制形式輸出。 |
-a column | 以特定的列數對齊返回值,缺省值爲40。 |
-e expr | 指定一個表達式,用來控制如何跟蹤.格式以下: [qualifier=][!]value1[,value2]... qualifier只能是 trace,abbrev,verbose,raw,signal,read,write其中之一。value是用來限定的符號或數字。默認的qualifier是 trace。感嘆號是否認符號。 |
-eopen | 等價於 -e trace=open,表示只跟蹤open調用。而-etrace!=open表示跟蹤除了open之外的其餘調用。 |
-e trace=set | 只跟蹤指定的系統調用。例如:-e trace=open,close,rean,write表示只跟蹤這四個系統調用。默認的爲set=all。 |
-e trace=file | 只跟蹤文件名做爲參數的系統調用,通常爲文件操做。 |
-e trace=process | 只跟蹤有關進程控制的系統調用。 |
-e trace=network | 只跟蹤與網絡有關的全部系統調用。 |
-e strace=signal | 跟蹤全部與系統信號有關的系統調用。 |
-e trace=ipc | 跟蹤全部與進程間通訊有關的系統調用。 |
-o filename | 將strace的輸出寫入文件filename。 |
-p pid | 跟蹤指定的進程pid。 |
-s strsize | 指定最大字符串打印長度,默認值爲32。 |
-u username | 以username的UID和GID執行命令。 |
execve("/bin/pwd", ["pwd"], [/* 39 vars */]) = 0 uname({sys="Linux", node="sammy", ...}) = 0 brk(0) = 0x804c000 old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4001... fstat64(3, {st_mode=S_IFREG|0644, st_size=115031, ...}) = 0 old_mmap(NULL, 115031, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40017000 close(3) = 0 open("/lib/tls/libc.so.6", O_RDONLY) = 3 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\360U\1"..., 1024) = 1024 fstat64(3, {st_mode=S_IFREG|0755, st_size=1547996, ...}) = 0
Linux內核用函數printk打印調試信息,該函數的用法與C庫打印函數printf格式相似,但在內核使用。用戶可在內核代碼中的某位置加入函數printk,直接把所關心的信息打打印到屏幕上或日誌文件中。
函數printk根據日誌級別(loglevel)對調試信息進行分類。日誌級別用宏定義,展開爲一個字符串,在編譯時由預處理器將它和消息文本拼接成一個字符串,所以函數printk中的日誌級別和格式字符串間不能有逗號。
下面兩個 printk 的例子,一個是調試信息,一個是臨界信息:printk(KERN_DEBUG "Here I am: %s:%i\n", _ _FILE_ _, _ _LINE_ _); printk(KERN_CRIT "I'm trashed; giving up on %p\n", ptr);
/*debug_on_off.h*/
#undef PDEBUG /* undef it, just in case */ #ifdef SCULL_DEBUG #ifdef _ _KERNEL_ _ /* This one if debugging is on, and kernel space */ #define PDEBUG(fmt,args...) printk(KERN_DEBUG "scull: " fmt, ## args) #else /* This one for user space */ #define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args) #endif #else #define PDEBUG(fmt, args...) /* not debugging: nothing */ #endif
# Comment/uncomment the following line to disable/enable debugging
DEBUG = y # Add your debugging flag (or not) to CFLAGS ifeq ($(DEBUG),y) DEBFLAGS = -O -g -DSCULL_DEBUG # "-O" else DEBFLAGS = -O2 endif CFLAGS += $(DEBFLAGS)
更改makefile中的DEBUG值,須要調試信息時,DEBUG = y,不須要時,DEBUG賦其它值。再用make編譯便可。
kprobe(內核探測,kernel probe)是一個動態地收集調試和性能信息的工具,如:收集寄存器和全局數據結構等調試信息,無需對Linux內核頻繁編譯和啓動。用戶能夠在任何內核代碼地址進行陷阱,指定調試斷點觸發時的處理例程。工做機制是:用戶指定一個探測點,並把用戶定義的處理函數關聯到該探測點,當內核執行到該探測點時,相應的關聯函數被執行,而後繼續執行正常的代碼路徑。
kprobe容許用戶編寫內核模塊添加調試信息到內核。當在遠程機器上調試有bug的程序而日誌/var/log/messages不能看出錯誤時,kprobe顯得很是有用。用戶能夠編譯一個內核模塊,並將內核模塊插入到調試的內核中,就能夠輸出所須要的調試信息了。
內核探測分爲kprobe, jprobe和kretprobe(也稱return probe,返回探測)三種。kprobe可插入內核中任何指令處;jprobe插入內核函數入口,方便於訪問函數的參數;return probe用於探測指定函數的返回值。
內核模塊的初始化函數init安裝(或註冊)了多個探測函數,內核模塊的退出函數exit將註銷它們。註冊函數(如:register_kprobe())指定了探測器插入的地方、探測點觸發的處理例程。
(1)配置支持kprobe的內核
配置內核時確信在.config文件中設置了CONFIG_KPROBES、CONFIG_MODULES、CONFIG_MODULE_UNLOAD、CONFIG_KALLSYMS_ALL和CONFIG_DEBUG_INFO。
配置了CONFIG_KALLSYMS_ALL,kprobe可用函數kallsyms_lookup_name從地址解析代碼。配置了CONFIG_DEBUG_INFO後,能夠用命令"objdump -d -l vmlinux"查看源到對象的代碼映射。
調試文件系統debugfs含有kprobe的調試接口,能夠查看註冊的kprobe列表,還能夠關閉/打開kprobe。
查看系統註冊probe的方法列出以下:
#cat /debug/kprobes/list
c015d71a k vfs_read+0x0
c011a316 j do_fork+0x0
c03dedc5 r tcp_v4_rcv+0x0
第一列表示探測點插入的內核地址,第二列表示內核探測的類型,k表示kprobe,r表示kretprobe,j表示jprobe,第三列指定探測點的"符號+偏移"。若是被探測的函數屬於一個模塊,模塊名也被指定。
打開和關閉kprobe的方法列出以下:
#echo ‘1’ /debug/kprobes/enabled
#echo ‘0’ /debug/kprobes/enabled
(2)kprobe樣例
Linux內核源代碼在目錄samples/kpobges下提供了各類kprobe類型的探測處理例程編寫樣例,分別對應文件kprobe_example.c、jprobe_example.c和kretprobe_example.c,用戶稍加修改就能夠變成本身的內核探測模塊。下面僅說明kprobe類型的探測例程。
樣例kprobe_example是kprobe類型的探測例程內核模塊,顯示了在函數do_fork被調用時如何使用kprobe轉儲棧和選擇的寄存器。當內核函數do_fork被調用建立一個新進程時,在控制檯和/var/log/messages中將顯示函數printk打印的跟蹤數據。樣例kprobe_example列出以下(在samples/kprobe_example.c中):
#include <linux/kernel.h>
#include <linux/module.h> #include <linux/kprobes.h> /* 對於每一個探測,用戶須要分配一個kprobe對象*/ static struct kprobe kp = { .symbol_name = "do_fork", }; /* 在被探測指令執行前,將調用預處理例程 pre_handler,用戶須要定義該例程的操做*/ static int handler_pre(struct kprobe *p, struct pt_regs *regs) { #ifdef CONFIG_X86 printk(KERN_INFO "pre_handler: p->addr = 0x%p, ip = %lx," " flags = 0x%lx\n", p->addr, regs->ip, regs->flags); /*打印地址、指令和標識*/ #endif #ifdef CONFIG_PPC printk(KERN_INFO "pre_handler: p->addr = 0x%p, nip = 0x%lx," " msr = 0x%lx\n", p->addr, regs->nip, regs->msr); #endif /* 在這裏能夠調用內核接口函數dump_stack打印出棧的內容*/ return 0; } /* 在被探測指令執行後,kprobe調用後處理例程post_handler */ static void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags) { #ifdef CONFIG_X86 printk(KERN_INFO "post_handler: p->addr = 0x%p, flags = 0x%lx\n", p->addr, regs->flags); #endif #ifdef CONFIG_PPC printk(KERN_INFO "post_handler: p->addr = 0x%p, msr = 0x%lx\n", p->addr, regs->msr); #endif } /*在pre-handler或post-handler中的任何指令或者kprobe單步執行的被探測指令產生了例外時,會調用fault_handler*/ static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr) { printk(KERN_INFO "fault_handler: p->addr = 0x%p, trap #%dn", p->addr, trapnr); /* 不處理錯誤時應該返回*/ return 0; } /*初始化內核模塊*/ static int __init kprobe_init(void) { int ret; kp.pre_handler = handler_pre; kp.post_handler = handler_post; kp.fault_handler = handler_fault; ret = register_kprobe(&kp); /*註冊kprobe*/ if (ret < 0) { printk(KERN_INFO "register_kprobe failed, returned %d\n", ret); return ret; } printk(KERN_INFO "Planted kprobe at %p\n", kp.addr); return 0; } static void __exit kprobe_exit(void) { unregister_kprobe(&kp); printk(KERN_INFO "kprobe at %p unregistered\n", kp.addr); } module_init(kprobe_init) module_exit(kprobe_exit) MODULE_LICENSE("GPL");
Systemtap是一個基於kprobe調試內核的開源軟件。調試者只須要寫一些腳本,經過Systemtap提供的命令行接口對正在運行的內核進行診斷調試,不須要修改或插入調試代碼、從新編譯內核、安裝內核和重啓動等工做,使內核調試變得簡單容易。Systemtap調試過程與在gdb調試器中用斷點命令行調試相似。
Systemtap用相似於awk語言的腳本語言編寫調試腳本,該腳本命名事件並給這些事件指定處理例程。只要指定的事件發生,Linux內核將運行對應的處理例程。
有幾種類型的事件,如:進入或退出一個函數,一個定時器超時或整個systemtap會話開始或中止。處理例程是一系列腳本語言語句指定事件發生時所作的工做,包括從事件上下文提取數據,存儲它們進入內部變量或打印結果。
Systemtap的運行過程如圖2所示,用戶調試時用Systemtap編寫調試腳本,Systemtap的翻譯模塊(translator)將腳本經語法分析(parse)、功能處理(elaborate)和翻譯後生成C語言調試程序,而後,運行C編譯器編譯(build)建立調試內核模塊。再接着將該內核模塊裝載入內核,經過kprobe機制,內核的hook激活全部的探測事件。當任何處理器上有這些事件發生時,對應的處理例程被觸發工做,kprobe機制在內核獲取的調試數據經過文件系統relayfs傳回Systemtap,輸出調試數據probe.out。在調試結束時,會話中止,內核斷開hook鏈接,並卸載內核模塊。整個操做過程由單個命令行程序strap驅動控制。
stap程序是Systemtap工具的前端,它接受用systemtap腳本語言編寫的探測指令,翻譯這些指令到C語言代碼,編譯C代碼產生並裝載內核模塊到正運行的Linux內核,執行請求的跟蹤或探測函數。用戶可在一個命名文件中提供腳本或從命令行中提供調試語句。
命令stap的用法列出以下:
stap [ OPTIONS ] FILENAME [ ARGUMENTS ]
stap [ OPTIONS ] - [ ARGUMENTS ]
stap [ OPTIONS ] -e SCRIPT [ ARGUMENTS ]
stap [ OPTIONS ] -l PROBE [ ARGUMENTS ]
選項[ OPTIONS ]說明以下:
-h 顯示幫助信息。
-V 顯示版本信息。
-k 在全部操做完成後,保留臨時目錄。對於檢查產生的C代碼或重使用編譯的內核對象來講,這是有用的。
-u 非優化編譯模式。.
-w 關閉警告信息。
-b 讓內核到用戶數據傳輸使用bulk模式。
-t 收集時間信息:探測執行的次數、每一個探測花費的平均時間量。
-sNUM 內核到用戶數據傳輸使用NUM MB 的緩衝區。當多個處理器工做在bulk模式時,這是單個處理器的緩衝區大小。
-p NUM Systemtap在經過NUM個步驟後中止。步驟數爲1-5: parse, elaborate, translate, compile, run。
-I DIR 添加tapset庫(用於翻譯C代碼的函數集)搜索目錄。
-D NAME=VALUE 添加C語言宏定義給內核模塊Makefile,用於重寫有限的參數。
-R DIR 在給定的目錄查找Systemtap運行源代碼。
-r RELEASE 爲給定的內核發佈版本RELEASE而不是當前運行內核編譯內核模塊。
-m MODULE 給編譯產生的內核模塊命名MODULE,替代缺省下的隨機命名。產生的內核模塊被拷貝到當前目錄。
-o FILE 發送標準輸出到命名文件FILE。在bulk模式,每一個CPU的文件名將用"FILE_CPU序號"表示。
-c CMD 開始探測,運行CMD,當CMD完成時退出。
-x PID 設置target()𤣵到PID。這容許腳本做爲指定進程的過濾器。
-l PROBE 代替運行一個探測腳本,它僅列出全部匹配給定模式PROBE可用的探測點,模式PROBE可用通配符。
--kmap[=FILE] 指定符號文件,缺省文件爲/boot/System.map-VER-SION。探測的函數的地址和名字須要經過內核或內核模塊的符號表解析。若是內核編譯時沒有調試信息或者探測是在沒有調試信息的彙編語言言論的,這將是有用的。
--ignore-vmlinux 忽略vmlinux文件。
Systemtap腳本語法相似於C語言,它使用了三種數據類型:整數(integers)、字符串(strings)和關聯數組(associative Arrays)。它有與C語言同樣的控制結構。Systemtap腳本語法詳細內容請參考《Systemtap tutorial》。
Systemtap腳本由探測點(probe)和探測輸出函數組成。每一個Systemtap腳本至少定義一個探測點。函數是探測點的處理例程。
#!/usr/bin/env stap #Systemtap腳本的標誌
# # 顯示在最後5秒內調用最後10個系統調用 display the top 10 syscalls called in last 5 seconds # global syscalls #定義全局變量 function print_top () { #定義函數 cnt=0 #局部變量 log ("SYSCALL\t\t\t\tCOUNT") #打印表頭標題「SYSCALL COUNT」 foreach ([name] in syscalls-) { #查詢每一個系統調用的計數值 printf("%-20s\t\t%5d\n",name, syscalls[name]) #按格式打印 if (cnt++ == 10) break } printf("--------------------------------------\n") delete syscalls #刪除全局變量 } probe syscall.* { #在系統調用探測點 syscalls[probefunc()]++ #系統調用計數 } probe timer.ms(5000) { print_top () #調用函數 }
Kdb(Kernel Debug)是SGI公司開發的遵循GPL的內建Linux內核調試工具。標準的Linux內核不包括kdb,須要從ftp://oss.sgi.com/www/projects/kdb/download/ix86 下載對應標準版本內核的kdb補丁,對標準內核打補丁,而後,編譯打過補丁的內核代碼。目前kdb支持包括x86(IA32)、IA64和MIPS在內的體系結構。
Kdb調試器是Linux內核的一部分,提供了檢查內存和數據結構的方法。經過附加命令,它能夠格式化顯示給定地址或ID的基本系統數據結構。kdb當前的命令集能夠徹底控制內核的操做,包括單步運行一個處理器、在指定的指令執行處理暫停、在訪問或修改指定虛擬內存的位置暫停、在輸入-輸出地址空間對一個寄存器訪問處暫停、經過進程ID跟蹤任務、指令反彙編等。
標準內核不包含kdb,所以,用戶須要先下載kdb補丁,如:kdb-v4.4-2.6.24-x86-2.bz2,接着,應打補丁、配置、編譯和安裝內核。
(1)打補丁
下載和解壓縮補丁,將補丁打進標準內核中。方法以下:
$ upzip kdb-v4.4-2.6.24-x86-2.bz2
$cp kdb-v4.4-2.6.24-x86-2 linux-2.6.24/ $ cd linux-2.6.24 $ patch -p1 < kdb-v4.4-2.6.24-x86-2.bz $ make xconfig
(2)配置新內核
運行make xconfig,在配置界面上選擇CONFIG_KDB選項,爲了更好地調試,建議用戶從配置界面上選擇CONFIG_FRAME_POINTER選項,儘管該選項使用了格外的寄存器併產生稍慢一些的內核。
(3)編譯與安裝新內核
按下面步驟從新編譯和安裝新內核:
#make
#make install #make modules_install
運行支持kdb的內核後,在控制檯上按下 Pause(或 Break)鍵將啓動調試。當內核發生 oop或到達某個斷點時,也會啓動 kdb。kdb提示符以下所示:
Entering kdb (current=0xc03b0000,pid 0)on processor 0 due to Keyboard Entry
[0]kdb>
在kdb提示符下,用戶能夠輸入kdb命令,詳細的kdb命令使用說明請參考man kdb文檔,一些常見的命令說明如表1.
表1 常見kdb命令說明
命令 | 命令說明 |
' | 命令能夠用於顯示全部kdb命令。 |
bp | 設置或顯示一個斷點。 |
bph | 設置一個硬件斷點。 |
bc | 清除一個斷點。 |
bl | 列出全部當前斷點。 |
bt | 顯示當前進程的堆棧跟蹤狀況。 |
go | 退出調試器並重啓內核運行。 |
Id | 反彙編指令。 |
md | 顯示指定地址內容。 |
mds | 以符號形式顯示內存。 |
mm | 修改內存。 |
reboot | 當即重啓機器。 |
rd | 顯示寄存器內容。 |
ss | 單步執行(一次一條指令)。 |
ssb | 單步執行CPU直到到達一分支。 |
下面以調試scull驅動程序爲例簡單說明kdb的使用方法:
假定scull驅動程序內核模塊已裝載入內核,先在驅動程序的函數scull_read 中設置一個斷點,方法以下:
[1]kdb> bp scull_read
Instruction(i) BP #0 at 0xc8833514 (scull_read) is enabled on cpu 1
[1]kdb> go
命令bp在函數scull_read 開始處設置了一個斷點,接着,命令go退出調試器,重啓內核運行。內核下一次進入函數scull_read 時暫停運行。產生以下的狀態:
Entering kdb (0xc3108000) on processor 0 due to Breakpoint @ 0xc8833515 Instruction(i) breakpoint #0 at 0xc8833514 scull_read+0x1: movl %esp,%ebp [0]kdb>
kdb當前scull_read斷點位置。可用命令bt查看堆棧跟蹤記錄,檢查函數調用層次樹,方法以下:
[0]kdb> bt EBP EIP Function(args) 0xc3109c5c 0xc8833515 scull_read+0x1 0xc3109fbc 0xfc458b10 scull_read+0x33c255fc( 0x3, 0x803ad78, 0x1000, 0x1000, 0x804ad78) 0xbffffc88 0xc010bec0 system_call [0]kdb>
再可用命令mds顯示指定內存的數據,如:查詢 scull_devices 指針的值方法以下:
[0]kdb> mds scull_devices 1
c8836104: c4c125c0 ....
上面命令查看指針scull_devices所指位置的一個雙字(4個字節)數據,表示設備結構數組的起始地址爲c4c125c0。再用mds查看設備結構的數據,方法以下:
[0]kdb> mds c4c125c0
c4c125c0: c3785000 ....
c4c125c4: 00000000 ....
c4c125c8: 00000fa0 ....
c4c125cc: 000003e8 ....
c4c125d0: 0000009a ....
c4c125d4: 00000000 ....
c4c125d8: 00000000 ....
c4c125dc: 00000001 ....
上面8行分別對應結構Scull_Dev的8個成員。再與數據結構Scull_Dev的定義相對照,可知這8個數據的含義。
還可使用命令mm修改數據。例如:將結構Scull_Dev的某一成員值設置爲0x50,方法以下:
[0]kdb> mm c4c125d0 0x50
0xc4c125d0 = 0x50
調試器GNU gdb主要用於調試用戶級程序,經過串口線或網絡將兩臺計算機以主機/目標機(host machine/target machine)方式鏈接時,gdb還可用於調試linux內核。這種方式須要給內核打進包含kgdb驅動程序在內的補丁。
kgdb是Linux內核的源代碼級調試器,與gdb配合使用能夠調試Linux內核。在Linux內核的kgdb配合下,內核開發者能夠用相似於調試應用程序的方式經過gdb調試內核,能夠方便以使用gdb的命令在內核代碼放置斷點、單步調試內核和觀察內核變量值等。
kgdb進行源碼級內核調試的原理圖如圖1所示。在兩臺計算中機,一臺用做開發計算機,稱爲主機或開發機;一臺用做測試計算機,稱爲目標機或測試機。兩臺計算機可經過串行線或以太網進行通訊。內核在測試機上調試,gdb在開發機上運行,gdb經過串行線用null modem與調試的內核通訊。兩臺計算機也可使用一臺計算機上的兩個虛擬機進行替代。
圖1 kgdb進行源碼級內核調試原理圖
目前,kgdb支持i386, x86_64, ppc, arm, mips和ia64等處理器構架,開發機和測試機可用串行線或以太網進行鏈接通訊。
kgdb補丁將下面的內容加入到內核代碼中:
●gdb stub - gdb stub("樹樁")是調試器的核心,它處理來自開發機上gdb的請求。當測試機運行了帯有kgdb的內核時,gdb stub控制測試機中全部的處理器。
●對出錯處理例程的修改- 當一個不指望的錯誤發生時,內核將控制傳遞給kgdb調試器。不含有kgdb的內核在出現不可預測錯誤時會崩潰(panic),經過對出錯處理的修改,kgdb容許開發者分析不可預測的出錯。
●串行通訊-該部件經過內核的串行驅動程序,爲內核中的stub提供接口,負責在串行鏈接線上發送和接收數據,還負責處理開發機上gdb發送的處理控制斷點請求。
下面說明創建kdbg聯機調試的步驟:
(1)軟件創建和應用kgdb補丁
1)下載Linux內核源代碼:linux-2.6.15.5.tar.bz2。
2)下載與內核版本對應的kgdb補丁:linux-2.6.15.5-kgdb-2.4.tar.bz2。
3)解壓縮軟件包,方法以下:
cd ${BASE_DIR}
tar -jxvf linux-2.6.15.5.tar.bz2
cd ${BASE_DIR}/linux-2.6.15.5
tar -jxvf linux-2.6.15.5-kgdb-2.4.tar.bz2
在${BASE_DIR}/linux-2.6.15.5目錄中,給Linux內核打kgdb補丁,方法以下:
patch -p1 < ${BASE_DIR}/linux-2.6.15.5-kgdb-2.4/core-lite.patch
patch -p1 < ${BASE_DIR}/linux-2.6.15.5-kgdb-2.4/i386.patch
(2)在開發機上編譯內核
1)在${BASE_DIR}/linux-2.6.15.5/Makefile,設置EXTRAVERSION = -kgdb。
2)運行命令make xconfig或make oldconfig,出現如圖2所示的內核配置界面。在配置界面中,爲目標機硬件選擇合適的選項;在"Kernel hacking"條目下,選擇kgdb的選項。
圖2 在Linux內核配置界面中與kgdb相關的配置選項
3)運行make bzImage編譯內核。
4)將編譯的內核從開發機上傳送到目標機上,拷貝內核映像${BASE_DIR}/linux-2.6.15.5/arch/i386/boot/bzImage到目標機/boot/vmlinuz-2.6.15.5-kgdb。而後,再拷貝映射文件${BASE_DIR}/linux-2.6.15.5/System.map到目標機/boot/System.map-2.6.15.5-kgdb。再以下創建符號連接:
ln -s /boot/vmlinuz-2.6.15.5-kgdb /boot/vmlinuz
ln -s /boot/System.map-2.6.15.5-kgdb /boot/System.map
5)在目標機上編輯文件/boot/grub/grub.conf, 在該文件加入含有kgdb的內核條目,方法以下:
title Linux-2.6.15.5-kgdb
root (hd0,0)
kernel /boot/vmlinuz-2.6.15.5-kgdb ro root=/dev/hda1 kgdbwait
(3)在開發機上開始調試會話
1)在啓動目標機後,它將等待開發機鏈接,顯示下面的消息:
Waiting for connection from remote gdb...
2)用命令cd ${BASE_DIR}/linux-2.6.15.5進入目錄linux-2.6.15.5目錄。
3)用root用戶登陸設置調試會話波特率,方法以下:
<root#> gdb ./vmlinux
(gdb) set remotebaud 115200
(gdb) target remote /dev/ttyS0
Remote debugging using /dev/ttyS0
breakpoint () at kernel/kgdb.c:1212
1212 atomic_set(&kgdb_setting_breakpoint, 0);
warning: shared library handler failed to enable breakpoint
(gdb)
4)在開發機上輸入調試命令
此時,gdb已鏈接到目標機上的內核,目標機上的內核正等待接收命令進行測試。輸入命令(gdb) c(表示繼續運行)時,目標機系統正常啓動,在配置內核時,若是開發機選擇了經過gdb輸出控制檯消息,則控制檯log消息會從gdb上顯示。
由gdb鏈接到測試內核,若是測試內核發生內核崩潰(kernel panic),它將首先將控制權轉移給gdb,以讓gdb分析崩潰緣由。
(4)使用kgdb以太網接口
kgdb還可能經過以太網接口調試內核,用以太網接口創建鏈接的步驟說明以下:
1)添加下面的行到grub條目中:
kgdboe=@10.0.0.6/,@10.0.0.3/ (that's kgdboe=@LOCAL-IP/,@REMOTE-IP/) # Sample grub.conf which will by default boot the kgdb enabled kernel title Linux-2.6.15.5-kgdb(eth) root (hd0,0) kernel /boot/vmlinuz-2.6.15.5-kgdb ro root=/dev/hda1 kgdboe=@10.0.0.6/,@10.0.0.3/ console=ttyS0,115200
2)接着在gdb中用下面的命令開始調試會話:
(gdb) ./vmlinux
(gdb) target remote udp:HOSTNAME:6443
內核可加載模塊的調試具備其特殊性,內核模塊中各段的地址在模塊加載進內核後才最終肯定的,開發機的gdb沒法獲得各類符號地址信息。所以,用戶須要使用特殊版本的gdb,能夠檢測到內核模塊的裝載和卸載。另外,還須要將內核模塊的符號裝載到gdb中,這樣,gdb才能解析到符號。
(1)準備檢測內核模塊裝載和卸載代碼的gdb派生版本
此步驟在開發機上完成。
安裝在開發機上的gdb應含有內核模塊調試特徵,用戶須要安裝含有檢測內核模塊裝載和卸載代碼的gdb派生版本,該版本gdb派生於標準的gdb。用戶能夠從網址http://kgdb.linsyssoft.com/downloads.htm下載gdb-6.4-kgdb-2.4.tar.bz2,而後,編譯安裝生成gdb派生版本。或者下載gdbmod-2.4.bz2,解壓縮後獲得可執行的gdb派生版本。
在測試機上不須要特別的安裝,內核模塊能夠出如今根文件系統或一個ramdisk中。
(2)裝載內核模塊符號到gdb中
此步驟在開發機上完成。
在開發機上,用戶應裝載內核模塊的符號到gdb,讓gdb調試時能夠解析到二進制代碼對應的符號。
首先,內核模塊編譯時應打開調試信息。而後,用下面方法設置內核、調試接口和定位內核模塊位置:
#cd /usr/src/linux 2.6.13
#gdbmod 2.4 vmlinux (gdb) target remote /dev/ttyS0 Remote debugging using /dev/ttyS0 breakpoint () at gdbstub.c:1153 1153 } (gdb)set solib search path /usr/linux 2.6.13/drivers/net
一旦kgdb通知一個內核模塊裝載時,gdb必須能定位模塊文件。所以,用戶須要用命令"set solib-search-path"設置內核模塊文件所在的路徑。
(3)插入內核模塊到內核
插入內核模塊到內核,方法 以下:
# insmod mymodule.ko
到此,已裝載了內核模塊符號,內核模塊能夠像正常的內核代碼同樣調試了。
樣例:調試內核模塊test
1)編寫內核模塊
首先,在開發機上編寫簡單的內核模塊test,代碼以下:
void test_func() { printk("test_func\n"); printk("aaaaaaaaaaa\n"); } int test_init() { printk("test_init_module\n"); return 0; } void test_exit() { printk("test_cleanup_module\n"); } module_init(test_init); module_exit(test_exit);
2)編譯安裝內核模塊
接着,編譯內核模塊,並將內核模塊拷貝到測試機上。方法以下:
#cd /root/mymodule
#gcc -D__KERNEL__ -DMODULE -I/usr/src/linux-2.6.15/kernel/include -O -Wall -g -c -o test.ko test.c #scp test.ko root@192.168.1.130:/root
3)開始調試
裝載內核符號到gdb中,設置內核模塊所在路徑,方法以下:
# gdbmod vmlinux
(gdb) set solib-search-path /root/mymodule
執行命令rmt,進入測試機調試,方法以下:
(gdb) rmt breakpoint () at kgdbstub.c:1005 1005 atomic_set(&kgdb_setting_breakpoint, 0);
在內核模塊初始化處設置斷點。查內核源碼可知,內核模塊初始化函數init在module.c文件函數sys_init_module函數中的mod->init處調用,對應行號爲2168(根據不一樣版本的內核,行號可能不一樣)。設置斷點方法以下:
(gdb) b module.c:2168 Breakpoint 1 at 0xc011cd83: file module.c, line 2168.
讓測試機上的內核繼續運行,方法以下:
(gdb) c Continuing. [New Thread 1352] [Switching to Thread 1352]
測試機用命令"insmod test.ko"執行插入內核模塊操做時,開發機會在斷點處被暫停,暫停時顯示以下
Breakpoint 1, sys_init_module (name_user=0xc03401bc "\001", mod_user=0x80904d8) at module.c:2168 2168 ret = mod->init();
用step命令進入內核模塊test的函數init,方法以下:
(gdb) step test_init () at test.c:12 12 printk("test_init_module\n"); (gdb) n 15 } (gdb)
對內核模塊的非init函數調試時,因爲測試機上已插入模塊,模塊的符號也已加載,只須要直接須要調試的代碼處設置斷點。如:在函數test_func處設置斷點的方法以下:
(gdb)bt test_func
用gdb調試內核類型於調試應用程序進程,kgdb支持gdb的執行控制命令、棧跟蹤和線程分析等。但kgdb不支持watchpoint,kgdb經過gdb宏來執行watchpoint。
調試內核的命令說明以下:
(1)中止內核執行
用戶在gdb終端中按下Ctrl + C鍵,gdb將發送中止消息給kgdb stub,kgdb stub控制內核的運行,並與gdb通訊。
(2)繼續內核運行
gdb命令"(gdb) c"告訴kgdb stub繼續內核運行,直到遇到一個斷點,或者gdb執行Ctrl + C,或其餘緣由,內核運行才停頓下來。
(3)斷點
gdb斷點(Breakpoints)用於在一個函數或代碼行處暫停內核運行,設置斷點命令如:"(gdb) b module.c:2168"。
(4)進入代碼
使用命令"(gdb) step"進入一個函數或在暫停後執行下一個程序行;使用命令"(gdb) next"跳過一個函數執行下一個程序行或暫停後執行下一個程序;
(5)棧跟蹤(Stack Trace)
使用命令"(gdb) bt"或"(gdb) backtrace"顯示程序棧,它顯示了調用函數的層次列表,代表了函數的調用函數。該命令還打印調解函數的參數值。
例如,運行命令backtrace的樣例列出以下:
(gdb) backtrace #0 breakpoint () at gdbstub.c:1160 #1 0xc0188b6c in gdb_interrupt (irq=3, dev_id=0x0, regs=0xc02c9f9c) at gdbserial.c:143 #2 0xc0108809 in handle_IRQ_event (irq=3, regs=0xc02c9f9c, action=0xc12fd200)3 0xc0108a0d in do_IRQ (regs={ebx = 1072672288, ecx = 0, edx = 1070825472, esi = 1070825472, edi = 1072672288, ebp = 1070817328, eax = 0, xds = 1072693224, xes = 1072693224, orig_eax = 253, eip = 1072672241, xcs = 16, eflags = 582, esp = 1070817308, xss = 1072672126}) at irq.c:621 #4 0xc0106e04 in ret_from_intr () at af_packet.c:1878 #5 0xc0105282 in cpu_idle () at process.c:135 #6 0xc02ca91f in start_kernel () at init/main.c:599 #7 0xc01001cf in L6 () at af_packet.c:1878 Cannot access memory at address 0x8e000
除非棧幀數做爲命令backtrace的參數外,gdb僅在棧跟蹤走出了可訪問地址空間時才中止打印棧的信息。上面例子中,函數調用層次次序從上到下爲:ret_from_intr, do_IRQ, handle_IRQ_event, gdb_interrupt。
放置一個斷點在函數ext2_readlink,並訪問一個符號連接,以便運行到該斷點。 設置斷點方法以下:
(gdb) br ext2_readlink Breakpoint 2 at 0xc0158a05: file symlink.c, line 25. (gdb) c Continuing.
在測試機上運行命令"ls -l /boot/vmlinuz"顯示一個符號連接。在測試機上,內核會運行到上述斷點處,並暫停。而後,將斷點信息傳回開發機。在開發機上,用戶能夠查看棧或運行其餘調試命令。例如:運行棧跟蹤命令,顯示的結果列出以下:
Breakpoint 2, ext2_readlink (dentry=0xc763c6c0, buffer=0xbfffed84 "\214\005",buflen=4096) at symlink.c:25 25 char *s = (char *)dentry >d_inode >u.ext2_i.i_data; (gdb) bt #0 ext2_readlink (dentry=0xc763c6c0, buffer=0xbfffed84 "\214\005",buflen=4096) at symlink.c:25 #1 0xc013b027 in sys_readlink (path=0xbfffff77 "/boot/vmlinuz", buf=0xbfffed84 "\214\005", bufsiz=4096) at stat.c:262 #2 0xc0106d83 in system_call () at af_packet.c:1878 #3 0x804aec8 in ?? () at af_packet.c:1878 #4 0x8049697 in ?? () at af_packet.c:1878 #5 0x400349cb in ?? () at af_packet.c:1878
上述棧跟蹤中,gdb打印一些無效的棧幀(#3~#5),這是由於gdb不知道在哪裏中止棧跟蹤,能夠忽略這些無效的棧幀。
系統調用readlink在函數system_call進入內核,該函數顯示在af_packet.c中,這是不對的,由於對於彙編語言文件的函數,gdb不能指出正常的代碼行。但gdb能夠正確處理在C語言文件中內聯彙編代碼。更多的調用層次是:sys_readlink和ext2_readlink。
在調試完後,用戶可用"刪除"命令和"繼續"命令刪除斷點,並繼續內核的運行,方法以下:
(gdb) delete Delete all breakpoints? (y or n) y (gdb) c Continuing.
(6)內聯函數
使用gdb棧跟蹤命令一般足夠找出一個函數的調用層次關係。但當其中一個棧幀在擴展的內聯函數中,或者從一個內聯函數訪問另外一個內聯函數時,棧跟蹤命令是不夠用的,棧跟蹤僅顯示在內聯函數中的源代碼文件名和語句的行號,經過查看外面的函數,這可能知道調用的內聯函數,但若是調用了兩次內聯函數,它就不知道是哪一個內聯函數了。
下面的處理流程可用來找出內聯函數信息:
在棧跟蹤中,gdb還與函數名一塊兒顯示代碼地址,在調用了一個內聯函數的語句中,gdb顯示了這些代碼調用和被調用的地址。腳本disasfun.sh可用來反彙編,源代碼從vmlinux文件引用一個內核函數。文件vmlinux含有內核函數的絕對地址,所以,在彙編代碼中看見的地址是在內存中的地址。
下面是一個樣例。
配置內核時,kgdb應打開線程分析(CONFIG_KGDB_THREAD),gdb應鏈接到目標內核。
用"Ctrl+C"中斷內核,放置一個斷點在函數__down處,並繼續運行,方法以下:
Program received signal SIGTRAP, Trace/breakpoint trap.
breakpoint () at gdbstub.c:1160 1160 } (gdb) break __down Breakpoint 1 at 0xc0105a43: file semaphore.c, line 62. (gdb) c Continuing.
爲了讓程序運行到斷點處,在目標機上運行"man lilo"。程序會運行到斷點,gdb會進入命令行模式。輸入棧跟蹤命令,顯示以下:
Breakpoint 1, __down (sem=0xc7393f90) at semaphore.c:62 62 add_wait_queue_exclusive(&sem->wait, &wait); (gdb) backtrace #0 __down (sem=0xc7393f90) at semaphore.c:62 #1 0xc0105c70 in __down_failed () at af_packet.c:1878 #2 0xc011433b in do_fork (clone_flags=16657, stack_start=3221199556, regs=0xc7393fc4, stack_size=0) at /mnt/work/build/old-pc/linux-2.4.6-kgdb/include/asm/semaphore.h:120 #3 0xc010594b in sys_vfork (regs={ebx = 1074823660, ecx = 1074180970, edx = 1074823660, esi = -1073767732, edi = 134744856, ebp = -1073767712, eax = 190, xds = 43, xes = 43, orig_eax = 190, eip = 1074437320, xcs = 35, eflags = 518, esp = -1073767740, xss = 43}) at process.c:719
在函數sys_vfork中行號顯示爲719,這與文件process.c中的行號一致,查看該文件可獲得確認,方法以下:
(gdb) list process.c:719 714 * do not have enough call-clobbered registers to hold all 715 * the information you need. 716 */ 717 asmlinkage int sys_vfork(struct pt_regs regs) 718 { 719 return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, ®s, 0); 720 } 721 722 /* 723 * sys_execve() executes a new program.
就像gdb顯示的同樣,函數sys_vfork調用函數do_fork,再看棧跟蹤顯示的第2幀,gdb顯示它在文件semaphore.h中的行號是120,顯示的行號雖然沒有用可是正確的。查看該文件可獲得確認,方法以下:
(gdb) list semaphore.h:118 113 */ 114 static inline void down(struct semaphore * sem) 115 { 116 #if WAITQUEUE_DEBUG 117 CHECK_MAGIC(sem->__magic); 118 #endif 119 120 __asm__ __volatile__( <----- 121 "# atomic down operation\n\t" 122 LOCK "decl %0\n\t" /* --sem->count */
上述代碼中,在箭頭所指示語句處,獲得的信息僅是它在do_fork的一個擴展的內聯函數down中。gdb還打印了在do_fork中從下一個被調用的函數開始代碼的絕對地址:0xc011433b。這裏咱們用腳本disasfun找出該地址所對應的代碼行。命令"disasfun vmlinux do_fork"輸出的部分結果顯示以下:
if ((clone_flags & CLONE_VFORK) && (retval > 0)) c011431d: 8b 7d 08 mov 0x8(%ebp),%edi c0114320: f7 c7 00 40 00 00 test $0x4000,%edi c0114326: 74 13 je c011433b <do_fork+0x707> c0114328: 83 7d d4 00 cmpl $0x0,0xffffffd4(%ebp) c011432c: 7e 0d jle c011433b <do_fork+0x707> #if WAITQUEUE_DEBUG CHECK_MAGIC(sem->__magic); #endif __asm__ __volatile__( c011432e: 8b 4d d0 mov 0xffffffd0(%ebp),%ecx c0114331: f0 ff 4d ec lock decl 0xffffffec(%ebp) c0114335: 0f 88 68 95 13 00 js c024d8a3 <stext_lock+0x7bf> down(&sem); return retval; c011433b: 8b 45 d4 mov 0xffffffd4(%ebp),%eax <----- c011433e: e9 8d 00 00 00 jmp c01143d0 <do_fork+0x79c> Looking at the code in fork.c we know where above code is: fork_out: if ((clone_flags & CLONE_VFORK) && (retval > 0)) down(&sem)
(7)線程分析
gdb具備分析應用程序線程的特徵,它提供了應用程序建立的線程的列表,它容許開發者查看其中任何一個線程。gdb的特徵可用來與kgdb一塊兒查看內核線程。gdb能提供內核中全部線程的列表。開發者可指定一個線程進行分析。像backtrace,info regi這樣的gdb命令接着能夠顯示指定線程上下文的信息。
應用程序建立的全部線程分享同一地址空間,類似地,全部內核線程共享內核地址空間。每一個內核線程的用戶地址空間可能不一樣,所以,gdb線程可較好地分析內核代碼和駐留在內核空間的數據結構。
gdb info給出了關於gdb線程分析方面更多的信息。下面列出一個內核線程分析的樣例:
gdb命令"info threads"給出了內核線程的列表,顯示以下:
(gdb) info thr 21 thread 516 schedule_timeout (timeout=2147483647) at sched.c:411 20 thread 515 schedule_timeout (timeout=2147483647) at sched.c:411 19 thread 514 schedule_timeout (timeout=2147483647) at sched.c:411 18 thread 513 schedule_timeout (timeout=2147483647) at sched.c:411 17 thread 512 schedule_timeout (timeout=2147483647) at sched.c:411 16 thread 511 schedule_timeout (timeout=2147483647) at sched.c:411 15 thread 438 schedule_timeout (timeout=2147483647) at sched.c:411 14 thread 420 schedule_timeout (timeout=-1013981316) at sched.c:439 13 thread 406 schedule_timeout (timeout=-1013629060) at sched.c:439 12 thread 392 do_syslog (type=2, buf=0x804dc20 "run/utmp", len=4095) at printk.c:182 11 thread 383 schedule_timeout (timeout=2147483647) at sched.c:411 10 thread 328 schedule_timeout (timeout=2147483647) at sched.c:411 9 thread 270 schedule_timeout (timeout=-1011908724) at sched.c:439 8 thread 8 interruptible_sleep_on (q=0xc02c8848) at sched.c:814 7 thread 6 schedule_timeout (timeout=-1055490112) at sched.c:439 6 thread 5 interruptible_sleep_on (q=0xc02b74b4) at sched.c:814 5 thread 4 kswapd (unused=0x0) at vmscan.c:736 4 thread 3 ksoftirqd (__bind_cpu=0x0) at softirq.c:387 3 thread 2 context_thread (startup=0xc02e93c8) at context.c:101 2 thread 1 schedule_timeout (timeout=-1055703292) at sched.c:439 * 1 thread 0 breakpoint () at gdbstub.c:1159 (gdb)
如上所顯示,gdb爲每一個線程設定在gdb中惟一的id,當gdb內部引用一個線程時,可使用這個id。例如:線程7(PID 7)具備gdb id 8。爲了分析內核線程8 ,咱們指定線程9給gdb。gdb接着切換到該線程中,準備作更多的分析。
下面是分析線程的命令顯示:
(gdb) thr 9 [Switching to thread 9 (thread 270)] #0 schedule_timeout (timeout=-1011908724) at sched.c:439 439 del_timer_sync(&timer); (gdb) bt #0 schedule_timeout (timeout=-1011908724) at sched.c:439 #1 0xc0113f36 in interruptible_sleep_on_timeout (q=0xc11601f0, timeout=134) at sched.c:824 #2 0xc019e77c in rtl8139_thread (data=0xc1160000) at 8139too.c:1559 #3 0xc010564b in kernel_thread (fn=0x70617773, arg=0x6361635f, flags=1767859560) at process.c:491 #4 0x19 in uhci_hcd_cleanup () at uhci.c:3052 #5 0x313330 in ?? () at af_packet.c:1891 Cannot access memory at address 0x31494350 (gdb) info regi eax 0xc38fdf7c -1013981316 ecx 0x86 134 edx 0xc0339f9c -1070358628 ebx 0x40f13 266003 esp 0xc3af7f74 0xc3af7f74 ebp 0xc3af7fa0 0xc3af7fa0 esi 0xc3af7f8c -1011908724 edi 0xc3af7fbc -1011908676 eip 0xc011346d 0xc011346d eflags 0x86 134 cs 0x10 16 ss 0x18 24 ds 0x18 24 es 0x18 24 fs 0xffff 65535 gs 0xffff 65535 fctrl 0x0 0 fstat 0x0 0 ftag 0x0 0 fiseg 0x0 0 fioff 0x0 0 foseg 0x0 0 fooff 0x0 0 ---Type <return> to continue, or q <return> to quit--- fop 0x0 0 (gdb) thr 7 [Switching to thread 7 (thread 6)] #0 schedule_timeout (timeout=-1055490112) at sched.c:439 439 del_timer_sync(&timer); (gdb) bt #0 schedule_timeout (timeout=-1055490112) at sched.c:439 #1 0xc0137ef2 in kupdate (startup=0xc02e9408) at buffer.c:2826 #2 0xc010564b in kernel_thread (fn=0xc3843a64, arg=0xc3843a68, flags=3280222828) at process.c:491 #3 0xc3843a60 in ?? () Cannot access memory at address 0x1f4 (gdb)
用戶可從http://sourceware.org/gdb/download/下載進程信息宏ps和psname,ps宏提供了運行在內核的線程的名字和ID。運行結果顯示以下:
(gdb) ps 0 swapper 1 init 2 keventd 3 ksoftirqd_C 4 kswapd 5 bdflush 6 kupdated 8 khubd 270 eth0 328 portmap 383 syslogd 392 klogd 406 atd 420 crond 438 inetd 511 mingetty 512 mingetty 513 mingetty 514 mingetty 515 mingetty 516 mingetty (gdb) The psname macro can be used to get name of a thread when it's id is known. (gdb) psname 8 8 khubd (gdb) psname 7 (gdb) (7)Watchpoints
(7)Watchpoints
kgdb stub使用x86處理器的調試特徵支持硬件斷點,這些斷點不須要代碼修改。它們使用調試寄存器。x86體系中的ia32處理器有4個硬件斷點可用。每一個硬件斷點能夠是下面三個類型之一:
執行斷點 當代碼在斷點地址執行時,觸發執行斷點。因爲硬件斷點有限,建議經過gdb break命令使用軟件斷點,除非可避免修改代碼。
寫斷點 當系統對在斷點地址的內存位置進行寫操做時,觸發一個寫斷點。寫斷點能夠放置可變長度的數據。寫斷點的長度指示爲觀察的數據類型長度,1表示爲字節數據,2表示爲2字節數據,3表示爲4字節數據。
訪問斷點 當系統讀或寫斷點地址的內存時,觸發一個訪問斷點。訪問斷點也有可變長度數據類型。
ia-32處理器不支持IO斷點。
由於gdb stub目前不使用gdb用於硬件斷點的協議,所以,它經過gdb宏訪問硬件斷點。硬件斷點的gdb宏說明以下:
1)hwebrk – 放置一個執行斷點。
用法:hwebrk breakpointno address
2)hwwbrk – 放置一個寫斷點。
用法:hwwbrk breakpointno length address
3)hwabrk – 放置一個訪問斷點。
用法:hwabrk breakpointno length address
4)hwrmbrk – 刪除一個斷點
用法:hwrmbrk breakpointno
5)exinfo – 告訴是否有一個軟件或硬件斷點發生。若是硬件斷點發生,打印硬件斷點的序號。
這些命令要求的參數說明以下:
breakpointno – 0~3
length - 1~3
address - 16進制內存位置(沒有0x),如:c015e9bc
用戶模式Linux(User Mode Linux,UML)不一樣於其餘Linux虛擬化項目,UML儘可能將它本身做爲一個普通的程序。UML與其餘虛擬化系統相比,優勢說明以下:
良好的速度
UML編譯成本地機器的代碼,像主機上的其餘已編譯應用程序同樣運行。它比在軟件上應用整個硬件構架的虛擬機快得多。另外一方面,UML不須要考慮依賴於特定CPU的虛擬化系統的硬件特異性。
獲益於Liunx更新
每次Linux的改進,UML自動獲得這些功能,虛擬化系統並不必定能從更新中獲益。
彈性編碼
內核須要與硬件或虛擬硬件交互,但UML可將交互看做其餘方式。例如:能夠將這些交互轉換成共享的庫,其餘程序能夠在使用時鏈接該庫。它還可做爲其餘應用程序的子shell啓動,能任何其餘程序的stin/stdout使用。
可移植性
UML未來能夠移植到x86 Windows, PowerPC Linux, x86 BSD或其餘系統上運行。
從Linux2.6.9版本起,用戶模式Linux(User mode Linux,UML)已隨Linux內核源代碼一塊兒發佈,它存放於arch/um目錄下。編譯好UML的內核以後,可直接用gdb運行編譯好的內核並進行調試。
用戶模式Linux(User mode Linux,UML)將Linux內核的一部分做爲用戶空間的進程運行,稱爲客戶機內核。UML運行在基於Linux系統調用接口所實現的虛擬機。UML運行的方式如圖1所示。UML像其餘應用程序同樣與一個"真實"的Linux內核(稱爲"主機內核")交互。應用程序還可運行在UML中,就像運行在一個正常的Linux內核下。
圖1 UML在Linux系統中運行的位置
使用UML的優勢列出以下:
若是UML崩潰,主機內核還將運行無缺。
能夠用非root用戶運行UML。
能夠像正常進程同樣調試UML。
在不中斷任何操做下與內核進行交互。
用UML做爲測試新應用程序的"沙箱",用於測試可能有傷害的程序。
能夠用UML安全地開發內核。
能夠同時運行不一樣的發佈版本。
因爲UML基於以Linux系統調用接口實現的虛擬機,UML沒法訪問主機的硬件設備。所以,UML不適合於調試與硬件相關的驅動程序。
(1)獲取源代碼
從http://www.kernel.org/下載linux-2.6.24.tar.bz2,解壓縮源代碼,方法以下:
host% bunzip2 linux-2.6.24.tar.bz2
host% tar xf linux-2.6.24.tar
host% cd linux-2.6.24
(2)配置UML模式內核
若是使用缺省配置,那麼,方法以下:
host% make defconfig ARCH=um
若是運行配置界面,方法以下:
host% make menuconfig ARCH=um
若是不使用缺省配置defconfig,那麼,內核編譯將使用主機的配置文件,該配置文件在主機/boot目錄下。對於UML模式內核來講,這是不對的,它將編譯產生缺少重要的驅動程序和不能啓動的UML。
以編譯UML時,每一個make命令應加上選項"ARCH=um",或者設置環境變量"export ARCH=um"。
當再次配置時,能夠先運行下面的命令清除全部原來編譯產生的影響:
host% make mrproper
host% make mrproper ARCH=um
內核提供了配置選項用於內核調試,這些選項大部分在配置界面的kernel hacking菜單項中。通常須要選取CONFIG_DEBUG_INFO選項,以使編譯的內核包含調試信息。
(3)編譯UML模式內核
編譯內核的方法以下:
host% make ARCH=um
當編譯完成時,系統將產生名爲"linux"的UML二進制。查看方法以下:
host% ls -l linux
-rwxrwxr-x 2 jdike jdike 18941274 Apr 7 15:18 linux
因爲UML加入了調試符號,UML模式內核變得很大,刪除這些符號將會大大縮小內核的大小,變爲與標準內核接近的UML二進制。
如今,用戶能夠啓動新的UML模式內核了。
(4)UML的工具
使用UML和管理UML的工具說明以下:
UMLd – 用於建立UML實例、管理實例啓動/關閉的後臺程序。
umlmgr –用於管理正運行的UML實例的前臺工具程序。
UML Builder – 編譯根文件系統映像(用於UML模式操做系統安裝)。
uml switch2 用於後臺傳輸的用戶空間虛擬切換。
VNUML – 基於XML的語言,定義和啓動基於UML的虛擬網絡場景。
UMLazi – 配置和運行基於虛擬機的UML的管理工具。
vmon – 運行和監管多個UML虛擬機的輕量級工具,用Python 書寫。
umvs – umvs是用C++和Bash腳本寫的工具,用於管理UML實例。該應用程序的目的是簡化UML的配置和管理。它使用了模板,使得編寫不一樣的UML配置更容易。
MLN - MLN (My Linux Network) 是一個perl程序,用於從配置文件建立UML系統的完整網絡,使得虛擬網絡的配置和管理更容易。MLN基於它的描述和簡單的編程語言編譯和配置文件系統模板,並用一種組織方式存儲它們。它還產生每一個虛擬主機的啓動和中止腳本,在一個網絡內啓動和中止單個虛擬機。MLN能夠一次使用幾個獨立的網絡、項目,甚至還能夠將它們鏈接在一塊兒。
Marionnet – 一個徹底的虛擬網絡實驗,基於UML,帶有用戶友好的圖形界面。
(1)啓動UML
爲了運行UML實例,用戶須要運行Linux操做系統主機和帶有本身文件系統的UML客戶機。用戶能夠從http://uml.nagafix.co.uk/下載UML(如:kernel)和客戶機文件系統(如:root_fs),運行UML實例的方法以下:
$ ./kernel ubda= root_fs mem=128M
上述命令中,參數mem指定虛擬機的內存大小;參數ubda表示根文件系統root_fs做爲虛擬機第一個塊設備,虛擬機用/dev/udba表示虛擬機的第一個塊設備,與Linux主機系統的第一個物理塊設備/dev/sda相似。
用戶還能夠本身建立虛擬塊設備,例如:創建交換分區並在UML上使用它的方法以下:
$ dd if=/dev/zero of=swap bs=1M count=128
$ ./kernel ubda= root_fs ubdb=swap mem=128M
上述命令,建立了128M的交換分區,做爲第二個塊設備ubdb,接着,啓動UML模式內核,用ubdb做爲它的交換分區。
(2)登陸
預打包的文件系統有一個帶有"root"密碼的root賬戶,還有一個帶有"user"密碼的user賬戶。用戶登陸後能夠進入虛擬機。預打包的文件系統已安裝了各類命令和實用程序,用戶還可容易地添加工具或程序。
還有一些其餘登陸方法,說明以下:
在虛擬終端上登陸
每一個已配置(設備存在於/dev,而且/etc/inittab在上面運行了一個getty)的虛擬終端有它本身的xterm。.
經過串行線登陸
在啓動輸出中,找到相似下面的一行:
serial line 0 assigned pty /dev/ptyp1
粘貼用戶喜好的終端程序到相應的tty,如:minicom,方法以下:
host% minicom -o -p /dev/ttyp1
經過網絡登陸
若是網絡正運行,用戶可用telnet鏈接到虛擬機。
虛擬機運行後,用戶可像通常Linux同樣運行各類shell命令和應用程序。
能夠粘附UML串行線和控制檯到多個類型的主機I/O通道,經過命令行指定,用戶能夠粘附它們到主機ptys, ttys, 文件描述子和端口。經常使用鏈接方法說明以下:
讓UML控制檯出如今不用的主機控制檯上。
將兩個虛擬機鏈接在一塊兒,一個粘到pty,另外一個粘附到相應的tty。
建立可從網絡訪問的虛擬,粘附虛擬機的控制檯到主機的一個端口。
(1)指定設備
用選項"con"或"ssl"(分別表明控制檯和串行線)指定設備。例如:若是用戶想用3號控制檯或10號串行線交互,命令行選項分別爲"con3"和"ssl10"。
例如:指定pty給每一個串行線的樣例選項列出以下:
ssl=pty ssl0=tty:/dev/tty0 ssl1=tty:/dev/tty1
(2)指定通道
能夠粘附UML設備到多個不一樣類型的通道,每一個類型有不一樣的指定方法,分別說明以下:
僞終端爲:device=pty,pts終端爲:device=pts
UML分配空閒的主機僞終端給。用戶能夠經過粘附終端程序到相應的tty訪問僞終端,方法以下:
screen /dev/pts/n
screen /dev/ttyxx
minicom -o -p /dev/ttyxx #minicom彷佛不能處理pts設備
kermit #啓動它,打開設備,而後鏈接設備
終端爲:device=tty:tty設備文件
UML將粘附設備到指定的tty,例如:一個樣例選項列出以下:
con1=tty:/dev/tty3
上面語句將粘附UML的控制檯1到主機的/dev/tty3。若是用戶指定的tty是tty/pty對的slave端,則相應的pty必須已打開。
xterms爲:device=xterm
UML將運行一個xterm,而且將設備粘附到xterm。
端口爲:device=port:端口號
上述選項將粘附UML設備到指定的主機端口。例如:粘附控制檯1到主機的端口9000,方法以下:
con1=port:9000
粘附全部串行線到主機端口,方法以下:
ssl=port:9000
用戶能夠經過telnet到該端口來訪問這些設備,每一個激活的telnet會話獲得不一樣的設備,若是有比粘附到端口的UML設備多的telnet鏈接到一個端口,格外的telnet會話將阻塞正存在的telnet斷線,或直到其餘設備變爲激活(如:經過在/etc/inittab中設置激活)。
已存在的文件描述子:device=文件描述子
若是用戶在UML命令行中創建了一個文件描述子,他能夠粘附UML設備到文件描述子。這最經常使用於在指定全部其餘控制檯後將主控制檯放回到stdin和stdout上。方法以下:
con0=fd:0,fd:1 con=pts
null設備:device=null
與"none"選項相比,上述選項容許打開設備,但讀將阻塞,而且寫將成功,但數據會被丟掉。
無設備:device=none
上述選項將引發設備消失。若是你正使用devfs,設備將不出如今/dev下。若是設備出現,嘗試打開它將返回錯誤-ENODEV。
用戶還能夠指定不一樣的輸入和輸出通道給一個設備,最經常使用的用途是重粘附主控制到stdin和stdout。例如:一個樣例選項列出以下:
ssl3=tty:/dev/tty2,xterm
上述詩句將引發在主機/dev/tty3上的串行線3接受輸入,顯示輸出在xterm上。
若是用戶決定將主控制檯從stdin/stdout移開,初始的啓動輸出將出如今用戶正運行UML所在的終端。然而,一旦控制檯驅動程序已初始化,啓動及隨後的輸出將出如今控制檯0所在的地方。
UML實例能夠用網絡訪問主機、本地網絡上的其餘機器和網絡的其餘部分。新的輔助程序uml_net進行主機創建時須要root權限。
當前UML虛擬機有5種傳輸類型用於與其餘主機交換包,分別是:ethertap,TUN/TAP, Multicast,交換機後臺(switch daemon),slip,slirp和pcap。
TUN/TAP, ethertap, slip和slirp傳輸容許UML實例與主機交換包。它們可定向到主機或主機可扮做路由器提供對其餘物理或虛擬機的訪問。
pcap傳輸是一個綜合的僅讀接口,用libpcap二進制從主機上的接口收集包並過濾包。這對於構建預配置的交通監管器或sniffer來講,是有用的。
後臺和多播傳輸提供了徹底虛擬的網絡絡其餘虛擬機器。該網絡徹底從物理網絡斷開,除非某一個虛擬機扮做網關。
如何選擇這些主機傳輸類型 '用戶可根據用途進行選擇,選擇方法說明以下:
ethertap – 若是用戶想對主機網絡進行訪問,而且運行以2.2之前版本時,使用它。
TUN/TAP – 若是用戶想訪問主機網絡,可以使用它。TUN/TAP 運行在2.4之後的版本,比ethertap 更有效率。TUN/TAP 傳輸還能使用預置的設備,避免對uml_net輔助程序進行setuid操做。
Multicast – 若是用戶指望創建一個純虛擬網絡,而且僅想創建UML,就使用它。
交換機後臺 – 若是用戶想創建一個純虛擬網絡,而且不介意爲了獲得較好執行效率而創建後臺,就使用它。
slip – 沒有特殊理由不要運行slip後端,除非ethertap和TUN/TAP不可用。
slirp – 若是用戶在主機上沒有root權限對創建網絡進行訪問,或者若是用戶不想分配對UML分配IP時,使用它。
pcap – 對實際的網絡鏈接沒有太多用途,但用於主機上監管網絡交通頗有用。
(1)網絡創建通用步驟
首先,用戶必須已在UML中打開虛擬網絡。若是運行下載的預編譯的內核,則已打開虛擬網絡。若是用戶本身編譯內核,則在配置界面上的"Network device support"菜單中,打開"Network device support"和三種傳輸選項。
下一步是提供網絡設備給虛擬機,經過在內核命令行中進行描述,格式以下:
eth <n> = <transport> , <transport args>
例如:一個虛擬以太網設備能夠以下粘附到一個主機ethertap上:
eth0=ethertap,tap0,fe:fd:0:0:0:1,192.168.0.254
上述語句在虛擬機內部創建eth0,粘附它本身到主機/dev/tap0,指定一個以太網地址,並指定給主機tap0接口一個IP地址。
一旦用戶決定如何創建設備後,就能夠啓動UML、登陸、配置設備的UML側,並設置對外界的路由。此後,UML就能夠與網絡任何其餘機器(物理或虛擬的)通訊。
(2)用戶空間後臺
從http://www.user-mode-linux.org/cvs/tools/下載工具uml_net和uml_switch,編譯並安裝。uml_switch是在UML系統之間管理虛擬網絡的後臺,而不用鏈接到主機系統的網絡。uml_switch將在UNIX域的socket上監聽鏈接,並在鏈接到UNIX域的客戶端之間轉發包。
(3)指定以太網地址
TUN/TAP, ethertap和daemon接口容許用戶給虛擬以太網設備指定硬件地址。但一般不須要指定硬件地址。若是命令行沒有指定硬件地址,它將提供地址爲fe:fd:nn:nn:nn:nn,其中,nn.nn.nn.nn是設備IP地址。這種方法一般足夠保證有惟一的硬件地址。
(4)UML接口創建
一旦用命令行描述網絡設備,用戶在啓動UML和登陸後,第一件事應是創建接口,方法以下:
UML# ifconfig ethn ip-address up
此時,用戶應能夠ping通主機。爲了能查看網絡,用戶設置缺省的路由爲到達主機,方法以下:
UML# route add default gw host ip
例如:主機IP爲192.168.0.4,設置路由方法以下:
UML# route add default gw 192.168.0.4
注意:若是UML不能與物理以太網上其餘主機通訊,多是由於網絡路由自動創建,能夠運行"route –n"查看路由,結果相似以下:
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168.0.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
掩碼不是255.255.255.255,所以,就使用到用戶主機的路由替換它,方法以下:
UML# route del -net 192.168.0.0 dev eth0 netmask 255.255.255.0
UML# route add -host 192.168.0.4 dev eth0
添加缺省的路由到主機,將容許UML與用戶以太網上任何機器交換包。
(5)多播
在多個UML之間創建一個虛擬網絡的最簡單方法是使用多播傳輸。用戶的系統必須在內核中打開多播(multicast),而且在主機上必須有一個多播能力的網絡設備。一般它是eth0。
爲了使用多播,運行兩個UML,命令行帶有"eth0=mcast"選項。登陸後,用戶在每一個虛擬機上用不一樣的IP地址配置以太網設備,方法以下:
UML1# ifconfig eth0 192.168.0.254
UML2# ifconfig eth0 192.168.0.253
這兩個虛擬機應能相互通訊。
傳輸設置的整個命令行選項列出以下:
ethn=mcast,ethernet address,multicast address,multicast port,ttl
(6)TUN/TAP和uml_net輔助程序
TUN/TAP驅動程序實現了虛擬網卡的功能,TUN 表示虛擬的是點對點設備,TAP表示虛擬的是以太網設備,這兩種設備針對網絡包實施不一樣的封裝。利用TUN/TAP驅動,能夠將tcp/ip協議棧處理好的網絡分包傳給任何一個使用TUN/TAP驅動的進程,由進程從新處理後再發到物理鏈路中。
TUN/TAP是與主機交換包的較好機制,主機創建TUN/TAP較簡單的方法是使用uml_net輔助程序,它包括插入tun.o內核模塊、配置設備、創建轉發IP、路由和代理ARP。
若是在設備的主機側指定了IP地址,uml_net將在主機上作全部的創建工做。粘附設備到TUN/TAP設備的命令行格式列出以下:
eth <n> =tuntap,,, <host IP address>
例如:下面參數將粘附UML的eth0到下一個可用的tap設備,指定IP地址192.168.0.254麼tap設備的主機側,並指定一個基於IP地址的以太網地址。
eth0=tuntap,,,192.168.0.254
(7)帶有預配置的tap設備的TUN/TAP
若是用戶沒有更好的uml_net,能夠預先創建TUN/TAP。步驟以下:
用工具tunctl建立tap設備,方法以下:
host# tunctl -u uid
上述命令中,uid是用戶的ID或UML將運行登陸的用戶名。
配置設備IP地址,方法以下:
host# ifconfig tap0 192.168.0.254 up
創建路由和ARP,方法以下:
host# bash -c 'echo 1 > /proc/sys/net/ipv4/ip_forward'
host# route add -host 192.168.0.253 dev tap0
host# bash -c 'echo 1 > /proc/sys/net/ipv4/conf/tap0/proxy_arp'
host# arp -Ds 192.168.0.253 eth0 pub
注意:這個配置沒有重啓機時失效,每次主機啓動時,應從新設置它。最好的方法是用一個小應用程序,每次啓動時,讀出配置文件從新創建設置的配置。
使用網橋
爲了避免使用2個IP地址和ARP,還可經過對UML使用網橋提供對用戶LAN直接訪問,方法以下:
host# brctl addbr br0
host# ifconfig eth0 0.0.0.0 promisc up
host# ifconfig tap0 0.0.0.0 promisc up
host# ifconfig br0 192.168.0.1 netmask 255.255.255.0 up
host# brctl stp br0 off
host# brctl setfd br0 1
host# brctl sethello br0 1
host# brctl addif br0 eth0
host# brctl addif br0 tap0
注意:用戶應該用eth0的IP地址經過ifconfig創建"br0"。
運行UML
一旦設備創建好後,運行UML,命令格式爲: eth0=tuntap,devicename,例如:一個樣例列出以下:
eth0=tuntap,tap0
若是用戶再也不使用tap設置,能夠用下面命令刪除它:
host# tunctl -d tap device
最後,tunctl有一個"-b"(用於簡捷模式)切換,僅輸出它所建立的tap設備的名字。它很適合於被一個腳本使用,方法以下:
host# TAP=`tunctl -u 1000 -b`
(8)交換機後臺
交換機後臺uml_switch之前稱爲uml_router,它提供了建立整個虛擬網絡的機制。缺省下,它不提供對主機網絡的鏈接。
首先,用戶須要運行uml_switch,無參數運行時,表示它將監聽缺省的unix域socket。使用選項"-unix socket"可指定不一樣的socket,"-hub"可將交換機後臺變爲集線器(Hub)。若是用戶指望交換機後臺鏈接到主機網絡(容許UML訪問經過主機訪問外部的網絡),可以使用選項"-tap tap0"。
uml_switch還可做爲後臺運行,方法以下:
host% uml_switch [ options ] < /dev/null > /dev/null
內核命令行交換機的通用命令行格式列出以下:
ethn=daemon,ethernet address,socket type,socket
一般只須要指定參數"daemon",其餘使用缺省參數,若是用戶運行沒有參數的交換機後臺,在同一臺機器上使用選項"eth0=daemon"運行UML,etho驅動程序會直接粘附它本身到交換機後臺。參數socket爲unix域socket的文件名,用於uml_switch和UML之間網絡通訊。
(9)Slirp
slirp一般使用外部程序/usr/bin/slirp,僅經過主機提供IP網絡鏈接。它相似於防火牆的IP假裝,躍然傳輸有用戶空間進行,而不是由內核進行。slirp不在主機上創建任何接口或改變路由。slirp在主機上不須要root權限或運行setuid。
slirp命令行的通用格式爲:
ethn=slirp,ethernet address,slirp path
在UML上,用戶應使用沒有網關IP的etho設置缺省路由,方法以下:
UML# route add default dev eth0
slirp提供了UML可以使用的大量有用IP地址,如:10.0.2.3,是DNS服務器的一個別名,定義在主機/etc/resolv.conf中,或者它是slirp的選項"dns"中給定的IP地址。
(10)pcap
pcap對網絡上傳輸的數據包進行截獲和過濾。經過命令行或pcap傳輸粘附到UML以太網設備uml_mconsole工具,語法格式以下:
ethn=pcap,host interface,filter expression,option1,option2
其中,expression和option一、option2是可選的。
這個接口是主機上用戶想嗅探(sniff)的任何網絡設備,過濾器表達式(filter expression)與工具tcpdump使用的同樣,option1爲"promisc"或"nopromisc",控制pcap是否將主機接口設爲"promiscuous"(混雜)模式;option2爲"optimize "或"nooptimize",表示是否使用pcap表達式優化器。
一個設置pcap的樣例列出以下:
eth0=pcap,eth0,tcp
eth1=pcap,eth0,!tcp
上述語句將引發在主機eth0上的UML eth0將全部的tcp發出,而且在主機eth0上的UML eth1將發出全部非tcp包。
(11)用戶創建主機
主機上的網絡設備須要配置IP地址,還須要用值爲1484的mtu配置tap設備。slip設置還須要配置點到點(pointopoint)地址,方法以下:
host# ifconfig tap0 arp mtu 1484 192.168.0.251 up
host# ifconfig sl0 192.168.0.251 pointopoint 192.168.0.250 up
若是正創建tap設備,就將路由設置到UML IP。方法以下:
UML# route add -host 192.168.0.250 gw 192.168.0.251
爲了容許網絡上其餘主機看見這個虛擬機,化理ARP設置以下:
host# arp -Ds 192.168.0.250 eth0 pub
最後,將主機設置到路由包,方法以下:
host# echo 1 > /proc/sys/net/ipv4/ip_forward
在虛擬機間共享文件系統的方法是使用ubd(UML Block Device)塊設備驅動程序的寫拷貝(copy-on-write,COW)分層能力實現。COW支持在僅讀的共享設備上分層讀寫私有設備。一個虛擬機的寫數據存儲在它的私有設備上,而讀來自任一請求塊有效的設備。若是請求的塊有效,讀取私有設備,若是無效,就讀取共享設備。
用這種方法,數據大部分在多個虛擬機間共享,每一個虛擬機有多個小文件用於存放虛擬機所作的修改。當大量UML從一個大的根文件系統啓動時,這將節約大量磁盤空間。它還提供執行性能,由於主機可用較小的內存緩存共享數據,主機的內存而不是硬盤提供UML硬盤請求服務。
可經過簡單地加COW文件的名字到合適的ubd,實現加一個COW層到存在的塊設備文件。方法以下:
ubd0=root_fs_cow,root_fs_debian_22
上述語句中,"root_fs_cow"是私有的COW文件,"root_fs_debian_22"是存在的共享文件系統。COW文件沒必要要存在,若是它不存在,驅動程序將建立並初始化它。一旦COW文件已初始化,可在以命令行中使用它,方法以下:
ubd0=root_fs_cow
後備文件(backing file)的名字存在COW文件頭中,所以在命令行中繼續指定它將是多餘的。
COW文件是稀疏的,所以它的長度不一樣於硬盤的實際使用長度。能夠用命令"ls –ls"查看硬盤的實際消耗,用"ls –l"查看COW文件和後備文件(backing file)的長度,方法以下:
host% ls -l cow.debian debian2.2
-rw-r--r-- 1 jdike jdike 492504064 Aug 6 21:16 cow.debian
-rwxrw-rw- 1 jdike jdike 537919488 Aug 6 20:42 debian2.2
host% ls -ls cow.debian debian2.2
880 -rw-r--r-- 1 jdike jdike 492504064 Aug 6 21:16 cow.debian
525832 -rwxrw-rw- 1 jdike jdike 537919488 Aug 6 20:42 debian2.2
從上述顯示結構,用戶會發現COW文件實際硬盤消耗小於1M,面不是492M。
一旦文件系統用做一個COW文件的僅讀後備文件,不要直接從它啓動或修改它。這樣,會使使用經的任何COW文件失效。後備文件在建立時它的修改時間mtime和大小size存放在COW文件頭中,它們必須相匹配。若是不匹配,驅動程序將拒絕使用COW文件。
若是用戶手動地改變後備文件或COW頭,將獲得一個崩潰的文件系統。
操做COW文件的方法說明以下:
(1)刪除後備文件
因爲UML存放後備文件名和它的修改時間mtime在COW頭中,若是用戶刪除後文件文件,這些信息將變成無效的。所以,刪除後備文件的步驟以下:
用保護時間戳的方式刪除文件。一般,使用"-p"選項。拷貝操做命令"cp –a"中的"-a"隱含了"-p"。
經過啓動UML更新COW頭,命令行指定COW文件和新的後備文件的位置,方法以下:
ubda=COW file,新的後備文件位置
UML將注意到命令行和COW頭之間的不匹配,檢查新後文件路徑的大小和修改時間mtime,並更新COW頭。
若是當用戶刪除後備文件時忘記保留時間戳,用戶可手動整理mtime,方法以下:
host% mtime=UML認定的修改時間mtime; \
touch --date="`date -d 1970-01-01\ UTC\ $mtime\ seconds`" 後備文件
注意若是對真正修改過而不是剛刪除的後備文件進行上述操做,那麼將會文件崩潰,用戶將丟失文件系統。
(2)uml_moo :將COW文件與它的後備文件融合
依賴於用戶如何使用UML和COW設備,系統可能建議每隔一段時間融合COW文件中的變化到後備文件中。用戶能夠用工具uml_moo完成該操做,方法以下:
host% uml_moo COW file new backing file
因爲信息已在COW文件頭中,所以,沒必要指定後備文件。
uml_moo在缺省下建立一個新的後備文件,它還有一個破壞性的融合選項,直接將融合COW文件到它當前的後備文件。當後備文件僅有一個COW文件與它相關時,該選項頗有用。若是多個COW與一個後備文件相關,融合選項"-d"將使全部其餘的COW無效。可是,若是硬盤空間不夠時,使用融合選項"-d"很方便快捷,方法以下:
host% uml_moo -d COW file
(3)uml_mkcow :建立新COW文件
正常建立COW文件的方法是以UML命令行中指定一個不存在的COW文件,讓UML建立COW文件。可是,用戶有時想建立一個COW文件,但不想啓動UML。此時,可使用uml_mkcow工具。方法以下:
host% uml_mkcow 新COW文件 存在的後備文件
若是用戶想銷燬一個存在的COW文件,能夠加"-f"選項強制重寫舊的COW文件,方法以下:
host% uml_mkcow -f 存在的COW文件 存在的後備文件
若是根文件系統硬盤空間不夠大,或者想使用不一樣於ext2的文件系統,用戶就可能想建立和掛接新的UML文件系統,用戶能夠用以下方法建立UML的根文件系統:
(1)建立文件系統的文件
使用命令dd建立一個合適尺寸的空文件,用戶能夠建立稀疏文件,該文件直到實際使用時才分配硬盤空間。例如:下面的命令建立一個100M填滿0的稀疏文件:
host% dd if=/dev/zero of=new_filesystem seek=100 count=1 bs=1M
(2)指定文件給一個UML設備
在UML命令行上加入下面的選項:
ubdd=new_filesystem
上述命令中,ubdd應確保沒被使用。
(3)建立和掛接文件系統
建立和掛接文件系統方法以下:
host# mkreiserfs /dev/ubdd
UML# mount /dev/ubdd /mnt
若是用戶在UML中想訪問主機上的文件,用戶可將主機看成獨立的機器,可使用nfs從主機掛接目錄,或者用scp和rcp拷貝文件到虛擬機,由於UML運行在主機上,它能象其餘進程同樣訪問這些文件,並使它們在虛擬機內部可用,而不須要使用網絡。
還可使用hostfs虛擬文件系統,用戶經過它能夠掛接一個主機目錄到UML文件系統,並像在主機上同樣訪問該目錄中的文件。
(1)使用hostfs
首先,確認虛擬機內部是否有hostfs可用,方法以下:
UML# cat /proc/filesystems
若是沒有列出hostfs,則須要重編譯內核,配置hostfs,將它編譯成一個內核模塊,並用"insmod"插入該內核模塊。
掛接hostfs文件系統,例如:將hostfs掛接到虛擬機的/mnt/host下,方法以下:
UML# mount none /mnt/host -t hostfs
若是用戶不想掛接主機的root目錄,他能夠用"-o"選項指定掛接的子目錄。例如:掛接主機的/home到虛擬機的/mnt/home,方法以下:
UML# mount none /mnt/home -t hostfs -o /home
(2)hostfs命令行選項
在UML命令行選項可以使用hostfs選項,用來指定多個hostfs掛接到一個主機目錄或阻止hostfs用戶從主機上銷燬數據,方法以下:
hostfs=directory,options
當前可用的選項是"append",用來阻止全部的文件在追加方式打開,並不容許刪除文件。
(3)hostfs做爲根文件系統
還能夠經過hostfs從主機上的目錄而不是在一個文件中的標準文件系統啓動UML。最簡單的方法是用loop掛接一個存在的root_fs文件,方法以下:
host# mount root_fs uml_root_dir -o loop
用戶須要將/etc/fstab中的文件類型改變爲"hostfs",fstab中的該行列出以下:
none / hostfs defaults 1 1
接着用戶能夠用chown將目錄中root擁有的全部文件改變爲用戶擁有,方法以下:
host# find . -uid 0 -exec chown user {} \;
若是用戶不想用上面的命令改變文件屬主,用戶能夠用root身份運行UML。
接着,確保UML內核編譯進hostfs,而不是之內核模塊方式包含hostfs。那麼,加入下面的命令行運行UML:
root=/dev/root rootflags=/path/to/uml/root rootfstype=hostfs
加入上述選項後,UML應該像正常的同樣啓動。
(4)編譯hostfs
若是hostfs不在內核中,用戶須要編譯hostfs,用戶能夠將它編譯進內核或內核模塊。用戶在內核配置界面上選項hostfs,並編譯和安裝內核。
由於UML運行爲正常的Linux進程,用戶能夠用gdb像調試其餘進程同樣調試內核,稍微不一樣的是:由於內核的線程已用系統調用ptrace進行攔截跟蹤,所以,gdb不能ptrace它們。UML已加入瞭解決此問題的機制。
爲了調試內核,用戶須要從源代碼編譯,確保打開CONFIG_DEBUGSYM和CONFIG_PT_PROXY配置選項。它們分別用來確保編譯內核帶有"-g"選項和打開ptrace代理,以便gdb能與UML一塊兒工做調試內核。
(1)在gdb下啓動內核
用戶能夠在命令行中放入"debug"選項,在啓動UML時將內核放在gdb的控制之下。用戶能夠獲得一個運行gdb的xterm,內核將送一些命令到gdb,停在"start_kernel"處,用戶能夠輸入"next", "step"或"cont"運行內核。
(2)檢查睡眠的進程
並不是每一個bug在當前運行的進程中,有時候,當進程在信號量上或其餘相似緣由死鎖時,本來不該該掛起的進程在內核中掛起。這種狀況下,用戶在gdb中用"Ctrl+C"時,獲得一個跟蹤棧,用戶將能夠看見到不相關的空閒線程。
用戶本想看到的是不該該睡眠的進程的棧,爲了看到睡眠的進程,用戶能夠在主機上用命令ps獲得該進程的主機進程id。
用戶將gdb與當前線程分離,方法以下:
(UML gdb) det
而後將gdb粘附到用戶感興趣的線程上,方法以下:
(UML gdb) att <host pid>
查看該線程的棧,方法以下:
(UML gdb) bt
(3)在UML上運行ddd
ddd能夠工做於UML,用戶能夠主機上運行ddd,它給gdb提供了圖形界面。運行ddd的步驟以下:
啓動ddd,方法以下:
host% ddd linux
獲得gdb的pid
用命令ps能夠獲得ddd啓動的gdb的pid。
運行UML
在運行UML的命令行中加上選項"debug=parent gdb-pid=<pid>",啓動並登陸UML。
在ddd的gdb命令行中輸入"att 1",gdb顯示以下:
0xa013dc51 in __kill ()
(gdb)
在gdb中輸入"c",UML將繼續運行,用戶可接着像調試其餘進程同樣調試了。
(4)調試內核模塊
gdb已支持調試動態裝載入進程的代碼,這須要在UML下調試內核模塊。調試內核模塊有些複雜,用戶須要告訴gdb裝入UML的對象文件名以及它在內存中的位置。接着,它能讀符號表,並從裝載地址指出全部的符號。
當用戶在rmmod內核模塊後重裝載它時,可獲得更多信息。用戶必須告訴gdb忘記全部它的符號,包括主UML的符號,接着再裝載回全部的符號。
用戶可使用腳本umlgdb進行內核模塊的重裝載和讀取它的符號表。用戶還能夠手動進行一步步處理完成符號表的獲取工做。下面分別說明這兩種方法。
1)運行腳本umlgdb調試內核模塊
運行腳本umlgd較容易獲取內核模塊的符號表。
首先,用戶應告訴內核模塊所在的位置,在腳本中有一個列表相似以下:
set MODULE_PATHS {
"fat" "/usr/src/uml/linux-2.6.18/fs/fat/fat.ko"
"isofs" "/usr/src/uml/linux-2.6.18/fs/isofs/isofs.ko"
"minix" "/usr/src/uml/linux-2.6.18/fs/minix/minix.ko"
}
用戶將上述列表改成將調試的內核模塊的路徑,接着,從UML的頂層目錄運行該腳本,顯示以下:
Start UML as: ./linux <kernel switches> debug gdb-pid=21903
GNU gdb 5.0rh-5 Red Hat Linux 7.1
Copyright 2001 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux"...
(gdb) b sys_init_module
Breakpoint 1 at 0xa0011923: file module.c, line 349.
(gdb) att 1
在用戶運行UML後,用戶只須要在"att 1"按回車,並繼續執行它。方法以下:
Attaching to program: /home/jdike/linux/2.4/um/./linux, process 1
0xa00f4221 in __kill ()
(UML gdb) c
Continuing.
此時,當用戶用insmod插入內核模塊,顯示列出以下:
Breakpoint 1, sys_init_module (name_user=0x805abb0 "hostfs",
mod_user=0x8070e00) at module.c:349
349 char *name, *n_name, *name_tmp = NULL;
(UML gdb) finish
Run till exit from #0 sys_init_module (name_user=0x805abb0 "hostfs",
mod_user=0x8070e00) at module.c:349
0xa00e2e23 in execute_syscall (r=0xa8140284) at syscall_kern.c:411
411 else res = EXECUTE_SYSCALL(syscall, regs);
Value returned is $1 = 0
(UML gdb)
p/x (int)module_list + module_list->size_of_struct
$2 = 0xa9021054
(UML gdb) symbol-file ./linux
Load new symbol table from "./linux" ' (y or n) y
Reading symbols from ./linux...
done.
(UML gdb)
add-symbol-file /home/jdike/linux/2.4/um/arch/um/fs/hostfs/hostfs.o 0xa9021054
add symbol table from file "/home/jdike/linux/2.4/um/arch/um/fs/hostfs/hostfs.o" at
.text_addr = 0xa9021054
(y or n) y
Reading symbols from /home/jdike/linux/2.4/um/arch/um/fs/hostfs/hostfs.o...
done.
(UML gdb) p *module_list
$1 = {size_of_struct = 84, next = 0xa0178720, name = 0xa9022de0 "hostfs",
size = 9016, uc = {usecount = {counter = 0}, pad = 0}, flags = 1,
nsyms = 57, ndeps = 0, syms = 0xa9023170, deps = 0x0, refs = 0x0,
init = 0xa90221f0 <init_hostfs>, cleanup = 0xa902222c <exit_hostfs>,
ex_table_start = 0x0, ex_table_end = 0x0, persist_start = 0x0,
persist_end = 0x0, can_unload = 0, runsize = 0, kallsyms_start = 0x0,
kallsyms_end = 0x0,
archdata_start = 0x1b855 <Address 0x1b855 out of bounds>,
archdata_end = 0xe5890000 <Address 0xe5890000 out of bounds>,
kernel_data = 0xf689c35d <Address 0xf689c35d out of bounds>}
>> Finished loading symbols for hostfs ...
(2)手動調試內核模塊
在調試器中啓動內核,並用insmod或modprobe裝載內核模塊。在gdb中執行下面命令:
(UML gdb) p module_list
這是已裝載進內核的內核模塊列表,一般用戶指望的內核模塊在module_lis中。若是不在,就進入下一個連接,查看name域,直到找到用戶調試的內核模塊。獲取該結構的地址,並加上module.size_of_struct值,gdb可幫助獲取該值,方法以下:
(UML gdb) printf "%#x\n", (int)module_list module_list->size_of_struct
從內核模塊開始處的偏移偶爾會改變,所以,應檢查init和cleanup的地址,方法以下:
(UML gdb) add-symbol-file /path/to/module/on/host that_address
若是斷點不在正確的位置或不工做等 ,用戶能夠查看內核模塊結構,init和cleanup域應該相似以下:
init = 0x588066b0 <init_hostfs>, cleanup = 0x588066c0 <exit_hostfs>
若是名字正確,但它們有偏移,那麼,用戶應該將偏移加到add-symbol-file所在地址上。
當用戶想裝載內核模塊的新版本時,須要讓gdb刪除舊內核模塊的全部符號。方法以下:
(UML gdb) symbol-file
接着,從內核二進制重裝載符號,方法以下:
(UML gdb) symbol-file /path/to/kernel
而後,重複上面的裝載符號過程。還須要重打開斷點。
(5)粘附gdb到內核
若是用戶尚未在gdb下運行內核,用戶能夠經過給跟蹤線程發送一個SIGUSR1,用於之後粘附gdb到內核。控制檯第一行的輸出鑑別它的id,顯示相似以下:
tracing thread pid = 20093
發送信號的方法以下:
host% kill -USR1 20093
上述命令運行後,用戶將可看見帶有gdb運行的xterm。
若是用戶已將mconsole(UML的控制檯)編譯進UML,那麼可用mconsole客戶端啓動gdb,方法以下:
(mconsole) (mconsole) config gdb=xterm
上述命令運行後,用戶將可看見帶有gdb運行的xterm。
(6)使用可替換的調試器
UML支持粘附到一個已運行的調試器,而不是啓動gdb自己。當gdb是一些UI的子進程(如:emacs或ddd)時,這將是有用的。它還被用於在UML上運行非gdb的調試器。下面是一個使用strace做爲可替代調試器的例子。
用戶須要獲得調試器的pid,並將pid用"gdb-pid=<pid>"選項與"debug"選項一塊兒傳遞。
若是用戶在UI下使用gdb,那麼,應告訴UML"att 1",那麼,UI將粘附到UML。
下面以替換調試器strace爲例,用戶能夠用strace調試實際的內核,方法以下:
在shell中運行下述命令
host%
sh -c 'echo pid=$$; echo -n hit return; read x; exec strace -p 1 -o strace.out'
用"debug"和"gdb-pid=<pid>"運行UML。
strace輸出將出如今輸出文件中。
注意:運行下面的命令,結果不一樣於前面命令。
host% strace ./linux
上述命令將僅strace主UML線程,跟蹤的線程不作任何實際的內核操做。它僅標識出虛擬機。而使用上述的strce將顯示虛擬機低層的活動狀況。
在代碼裏面老能看到 BUG_ON() , WARN_ON() 這樣的宏 , 相似 咱們平常編程裏面的斷言(assert) 。
在include/asm-generic/bug.h
#ifdef CONFIG_BUG
#ifdef CONFIG_GENERIC_BUG #ifndef __ASSEMBLY__ struct bug_entry { unsigned long bug_addr; #ifdef CONFIG_DEBUG_BUGVERBOSE const char *file; unsigned short line; #endif unsigned short flags; }; #endif /* __ASSEMBLY__ */ #define BUGFLAG_WARNING (1<<0) #endif /* CONFIG_GENERIC_BUG */ #ifndef HAVE_ARCH_BUG #define BUG() do { \ printk("BUG: failure at %s:%d/%s()!\n", __FILE__, __LINE__, __FUNCTION__); \ panic("BUG!"); \ } while (0) #endif #ifndef HAVE_ARCH_BUG_ON #define BUG_ON(condition) do { if (unlikely(condition� BUG(); } while(0) #endif #ifndef __WARN #ifndef __ASSEMBLY__ extern void warn_on_slowpath(const char *file, const int line); #define WANT_WARN_ON_SLOWPATH #endif #define __WARN() warn_on_slowpath(__FILE__, __LINE__) #endif #ifndef WARN_ON #define WARN_ON(condition) ({ \ int __ret_warn_on = !!(condition); \ if (unlikely(__ret_warn_on� \ __WARN(); \ unlikely(__ret_warn_on); \ }) #endif #else /* !CONFIG_BUG */ #ifndef HAVE_ARCH_BUG #define BUG() #endif #ifndef HAVE_ARCH_BUG_ON #define BUG_ON(condition) do { if (condition) ; } while(0) #endif #ifndef HAVE_ARCH_WARN_ON #define WARN_ON(condition) ({ \ int __ret_warn_on = !!(condition); \ unlikely(__ret_warn_on); \ }) #endif #endif #define WARN_ON_ONCE(condition) ({ \ static int __warned; \ int __ret_warn_once = !!(condition); \ \ if (unlikely(__ret_warn_once� \ if (WARN_ON(!__warned� \ __warned = 1; \ unlikely(__ret_warn_once); \ }) #ifdef CONFIG_SMP # define WARN_ON_SMP(x) WARN_ON(x) #else # define WARN_ON_SMP(x) do { } while (0) #endif
鎖驗證器
內核鎖驗證器(Kernel lock validator)能夠在死鎖發生前檢測到死鎖,即便是不多發生的死鎖。它將每一個自旋鎖與一個鍵值相關,類似的鎖僅處理一次。加鎖時,查看全部已獲取的鎖,並確信在其餘上下文中沒有已獲取的鎖,在新獲取鎖以後被獲取。解鎖時,確信正被解開的鎖在已獲取鎖的頂部。
.
Validate spinlocks vs interrupts behavior.
當加鎖動態發生時,鎖驗證器映射全部加鎖規則,該檢測由內核的spinlocks、rwlocks、mutexes和rwsems等鎖機制觸發。無論什麼時候鎖合法性檢測器子系統檢測到一個新加鎖場景,它檢查新規則是否違反正存在的規則集,若是新規則與正存在的規則集一致,則加入新規則,內核正常運行。若是新規則可能建立一個死鎖場景,那麼這種建立死鎖的條件會被打印出來。
當判斷加鎖的有效性時,全部可能的"死鎖場景"會被考慮到:假定任意數量的CPU、任意的中斷上下文和任務上下文羣、運行全部正存在的加鎖場景的任意組合。在一個典型系統中,這意味着有成千上萬個獨立的場景。這就是爲何稱它爲"加鎖正確性"驗證器,對於全部被觀察的規則來講,鎖驗證器用數學的肯定性證實死鎖不可能發生,假定鎖驗證器實現自己正確,而且它內部的數據結構不會被其餘內核子系統弄壞。
還有,驗證器的屬性"全部可能的場景"也使查找變得複雜,特別是多CPU、多上下文競爭比單個上下文規則複雜得多,
爲了增長驗證器的效率,不是將每一個鎖實例進行映射,而是映射每一個鎖類型。例如:內核中全部的結構inode對象有inode->inotify_mutex,若是緩存了10000個inode,將會有10000個鎖對象。但->inotify_mutex是單個鎖類型,全部->inotify_mutex發生的加鎖活動都納入單個鎖類型。
Lock-class
驗證器操做的基本對象是鎖類Lock-class,一個鎖類是一組鎖,邏輯上有一樣的加鎖規則,儘管鎖可能有多個實例。例如:在結構inode中的一個鎖是一個類,而每一個節點有它本身的鎖類實例。
驗證器跟蹤鎖類的狀態和不一樣鎖類之間的依賴性。驗證器維護一個有關狀態和依賴性是否正確的滾動證據。
不像一個鎖實例,鎖類lock-class它自己從不消失:當lock-class註冊使用後,全部隨後鎖類的使用都會被附加到該lock-class上。