1. 程式人生 > >System.map檔案的作用解析

System.map檔案的作用解析

有關System.map檔案的資訊好象很缺乏。其實它一點也不神祕,並且在整個事情當中它並不象看上去那麼得重要。但是由於缺乏必要的文件說明,使其顯得比較神祕。它就象耳垂,我們每個人都有,但卻不知道是幹什麼用的。本網頁就是用來說明這個問題的。

注意,我並不會是百分之一百正確的。例如,一個系統很可能沒有/proc檔案系統支援,但是大多數系統肯定有。這裡我假定你是“隨大流的”,並有一個典型配置的系統。

某些有關核心出錯(oops)的闡述來自於Alessandro Rubini的“Linux裝置驅動程式” 一書,我是從其中學到大部分核心程式設計知識的。

什麼是符號(Symbols)?

在程式設計中,一個符號(symbol)是一個程式的建立塊:它是一個變數名或一個函式名。 正如你自己編制的程式一樣,核心具有各種符號也是不應該感到驚奇的。當然,區別在 於核心是一非常複雜的程式碼塊,並且含有許多、許多的全域性符號。

核心符號表(Kernel Symbol Table)是什麼東西?

核心並不使用符號名。它是通過變數或函式的地址(指標)來使用變數或函式的,而 不是使用size_t BytesRead,核心更喜歡使用(例如)c0343f20來引用 這個變數。

而另一方面,人們並不喜歡象c0343f20這樣的名字。我們跟喜歡使用象 size_t BytesRead這樣的表示。通常,這並不會帶來什麼問題。核心主要 是用C語言寫成的,所以在我們程式設計時編譯器/連線程式允許我們使用符號名,並且使 核心在執行時使用地址表示。這樣大家都滿意了。

然而,存在一種情況,此時我們需要知道一個符號的地址(或者一個地址對應的 符號)。這是通過符號表來做到的,與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檔案?

有兩個檔案是用作符號表的:

  1. /proc/ksyms
  2. 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會執行兩類地址解析活動。

  • 靜態轉換,將使用System.map檔案。
  • 動態轉換,該方式用於可載入模組,不使用System.map,因此與本討論沒有關係,但我仍然對其加以簡單說明。

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。依次為:

  1. /boot/System.map
  2. /System.map
  3. /usr/src/linux/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檔案並開始讀取其中的符號資訊。

幾個注意點:

  • 在2.5.x系列核心的某個版本,Linux核心會開始untar成linux-version,而非只是linux (請舉手表決 -- 有多少人一直等待著這樣做?)。我不知道klogd是否已經修改為在/usr/src/linux-version/System.map中搜索。TODO:檢視klogd原始碼。
  • 線上手冊上對此也沒有完整描述,請看:
    strace -f /sbin/klogd | grep 'System.map'
    31208 open("/boot/System.map-2.4.18", O_RDONLY|O_LARGEFILE) = 2顯然,不僅klogd在三個搜尋目錄中尋找正確版本的map檔案,klogd也同樣知道尋找名字為 "System.map" 後加"-核心版本",象 System.map-2.4.18. 這是klogd未公開的特性。

有一些驅動程式將使用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下,並使用核心版本號重新對它們進行命名。假設你有以下多個核心:

  • /boot/vmlinuz-2.2.14
  • /boot/vmlinuz-2.2.13

那麼,只需對應各核心版本對map檔案進行改名,並放在/boot下,如:

/boot/System.map-2.2.14
/boot/System.map-2.2.13

如果你有同一個核心的兩個拷貝怎麼辦?例如:

  • /boot/vmlinuz-2.2.14
  • /boot/vmlinuz-2.2.14.nosound

最佳解決方案將是所有軟體能夠查詢下列檔案:

/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檔案將放在:

  • /boot/System.map-2.2.14
  • /usr/src/linux/System.map (對於nosound版本)

你也可以使用符號連線:

System.map-2.2.14
System.map-2.2.14.sound
System.map -> Syst