有關System.map文件的信息好象很缺少。其實它一點也不神祕,而且在整個事情當中它並不象看上去那麼得重要。可是因爲缺少必要的文檔說明,使其顯得比較神祕。它就象耳垂,咱們每一個人都有,但殊不知道是幹什麼用的。本網頁就是用來講明這個問題的。linux
注意,我並不會是百分之一百正確的。例如,一個系統極可能沒有/proc文件系統支持,可是大多數系統確定有。這裏我假定你是「隨大流的」,並有一個典型配置的系統。ios
某些有關內核出錯(oops)的闡述來自於Alessandro Rubini的「Linux設備驅動程序」 一書,我是從其中學到大部份內核編程知識的。編程
什麼是符號(Symbols)?併發
在編程中,一個符號(symbol)是一個程序的建立塊:它是一個變量名或一個函數名。 正如你本身編制的程序同樣,內核具備各類符號也是不該該感到驚奇的。固然,區別在 於內核是一很是複雜的代碼塊,而且含有許多、許多的全局符號。less
內核符號表(Kernel Symbol Table)是什麼東西?ide
內核並不使用符號名。它是經過變量或函數的地址(指針)來使用變量或函數的,而 不是使用size_t BytesRead,內核更喜歡使用(例如)c0343f20來引用 這個變量。函數
而另外一方面,人們並不喜歡象c0343f20這樣的名字。咱們跟喜歡使用象 size_t BytesRead這樣的表示。一般,這並不會帶來什麼問題。內核主要 是用C語言寫成的,因此在咱們編程時編譯器/鏈接程序容許咱們使用符號名,而且使 內核在運行時使用地址表示。這樣你們都滿意了。oop
然而,存在一種狀況,此時咱們須要知道一個符號的地址(或者一個地址對應的 符號)。這是經過符號表來作到的,與gdb可以從一個地址給出函數名(或者給出一個 函數名的地址)的狀況很類似。符號表是全部符號及其對應地址的一個列表。這裏是 一個符號表例子:指針
c03441a0 B dmi_broken c03441a4 B is_sony_vaio_laptop c03441c0 b dmi_ident c0344200 b pci_bios_present c0344204 b pirq_table c0344208 b pirq_router c034420c b pirq_router_dev c0344220 b ascii_buffer c0344224 b ascii_buf_bytes
你能夠看出名稱爲dmi_broken的變量位於內核地址c03441a0處。調試
什麼是System.map文件?
有兩個文件是用做符號表的:
這裏,你如今能夠知道System.map文件是幹什麼用的了。
每當你編譯一個新內核時,各類符號名的地址定會變化。
/proc/ksyms 是一個 "proc文件" 而且是在內核啓動時建立的。實際上 它不是一個真實的文件;它只是內核數據的簡單表示形式,呈現出象一個磁盤文件似 的。若是你不相信我,那麼就試試找出/proc/ksyms的文件大小來。所以, 對於當前運行的內核來講,它老是正確的..
然而,System.map倒是文件系統上的一個真實文件。當你編譯一個新內核時,你原 來的System.map中的符號信息就不正確了。隨着每次內核的編譯,就會產生一個新的 System.map文件,而且須要用該文件取代原來的文件。
什麼是一個Oops?
在本身編制的程序中最多見的出錯狀況是什麼?是段出錯(segfault),信號11。
Linux內核中最多見的bug是什麼?也是段出錯。除此,正如你想象的那樣,段出 錯的問題是很是複雜的,並且也是很是嚴重的。當內核引用了一個無效指針時,並不 稱其爲段出錯 -- 而被稱爲"oops"。一個oops代表內核存在一個bug,應該老是提出 報告並修正該bug。
請注意,一個oops與一個段出錯並非一回事。你的程序並不能從段出錯中恢復 過來,當出現一個oops時,並不意味着內核確定處於不穩定的狀態。Linux內核是很是 健壯的;一個oops可能僅殺死了當前進程,並使餘下的內核處於一個良好的、穩定的 狀態。
一個oops並不是是內核死循環(panic)。在內核調用了panic()函數後,內核就不能 繼續運行了;此時系統就處於停頓狀態而且必須重啓。若是系統中關鍵部分遭到破壞 那麼一個oops也可能會致使內核進入死循環(panic)。例如,設備驅動程序中 出現的oops就幾乎不會致使系統進行死循環。
當出現一個oops時,系統就會顯示出用於調試問題的相關信息,好比全部CPU寄存器 中的內容以及頁描述符表的位置等,尤爲會象下面那樣打印出EIP(指令指針)的內容:
EIP: 0010:[<00000000>] Call Trace: []
一個Oops與System.map文件有什麼關係呢?
我想你也會認爲EIP和Call Trace所給出的信息並很少,可是重要 的是,對於內核開發人員來講這些信息也是不夠的。因爲一個符號並無固定的地址, c010b860能夠指向任何地方。
爲了幫助咱們使用oops含糊的輸出,Linux使用了一個稱爲klogd(內核日誌後臺程序)的 後臺程序,klogd會截取內核oops而且使用syslogd將其記錄下來,並將某些象c010b860 的信息轉換成咱們能夠識別和使用的信息。換句話說,klogd是一個內核消息記錄器(logger), 它能夠進行名字-地址之間的解析。一旦klogd開始轉換內核消息,它就使用手頭的記錄器, 將整個系統的消息記錄下來,一般是使用syslogd記錄器。
爲了進行名字-地址解析,klogd就要用到System.map文件。我想你如今 知道一個oops與System.map的關係了。
深刻說明: 其實klogd會執行兩類地址解析活動。
Klogd動態轉換
假設你加載了一個產生oops的內核模塊。因而就會產生一個oops消息,klogd就會截獲它,並發現該oops發生在d00cf810處。因爲該地址屬於動態加載模塊,所以在System.map文件中沒有對應條目。klogd將會在其中尋找並會毫無所獲,因而判定是一個可加載模塊產生了oops。此時klogd就會向內核查詢該可加載模塊輸出的符號。即便該模塊的編制者沒有輸出其符號,klogd也起碼會知道是哪一個模塊產生了oops,這總比對一個oops一無所知要好。
還有其它的軟件會使用System.map,我將在後面做一說明。
System.map應該位於什麼地方?
System.map應該位於使用它的軟件可以尋找到的地方,也就是說,klogd會在什麼地方尋找它。在系統啓動時,若是沒有以一個參數的形式爲klogd給出System.map的位置,則klogd將會在三個地方搜尋System.map。依次爲:
System.map 一樣也含有版本信息,而且klogd可以智能化地搜索正確的map文件。例如,假設你正在運行內核2.4.18而且相應的map文件位於/boot/System.map。如今你在目錄/usr/src/linux中編譯一個新內核2.5.1。在編譯期間,文件 /usr/src/linux/System.map就會被建立。當你啓動該新內核時,klogd將首先查詢 /boot/System.map,確認它不是啓動內核正確的map文件,就會查詢 /usr/src/linux/System.map, 肯定該文件是啓動內核正確的map文件並開始讀取其中的符號信息。
幾個注意點:
有一些驅動程序將使用System.map來解析符號(由於它們與內核頭鏈接而非glibc庫等),若是沒有System.map文件,它們將不能正確地工做。這與一個模塊因爲內核版本不匹配而沒有獲得加載是兩碼事。模塊加載是與內核版本有關,而與即便是同一版本內核其符號表也會變化的編譯後內核無關。
還有誰使用了System.map?
不要認爲System.map文件僅對內核oops有用。儘管內核自己實際上不使用System.map,其它程序,象klogd,lsof,
satan# strace lsof 2>&1 1> /dev/null | grep System
readlink("/proc/22711/fd/4", "/boot/System.map-2.4.18", 4095) = 23
ps,
satan# strace ps 2>&1 1> /dev/null | grep System
open("/boot/System.map-2.4.18", O_RDONLY|O_NONBLOCK|O_NOCTTY) = 6
以及其它許多軟件,象dosemu,須要有一個正確的System.map文件。
若是我沒有一個好的System.map,會發生什麼問題?
假設你在同一臺機器上有多個內核。則每一個內核都須要一個獨立的 System.map文件!若是所啓動的內核沒有對應的System.map文件,那麼你將按期地看到這樣一條信息:
System.map does not match actual kernel (System.map與實際內核不匹配)
不是一個致命錯誤,可是每當你執行ps ax時都會惱人地出現。有些軟件,好比dosemu,可能不會正常工做。最後,當出現一個內核oops時,klogd或ksymoops的輸出可能會不可靠。
我如何對上述狀況進行補救?
方法是將你全部的System.map文件放在目錄/boot下,並使用內核版本號從新對它們進行命名。假設你有如下多個內核:
那麼,只需對應各內核版本對map文件進行更名,並放在/boot下,如:
/boot/System.map-2.2.14
/boot/System.map-2.2.13
若是你有同一個內核的兩個拷貝怎麼辦?例如:
最佳解決方案將是全部軟件可以查找下列文件:
/boot/System.map-2.2.14
/boot/System.map-2.2.14.nosound
可是說實在的,我並不知道這是不是最佳狀況。我曾經見到搜尋"System.map-kernelversion",可是對於搜索"System.map-kernelversion.othertext"的狀況呢? 我不太清楚。此時我所能作的就是利用這樣一個事實:/usr/src/linux是標準map文件的搜索路徑,因此你的map文件將放在:
你也可使用符號鏈接:
System.map-2.2.14 System.map-2.2.14.sound System.map -> System.map-2.2.14.sound