1. 程式人生 > >轉:System.map 解析

轉: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/kallsyms
       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:[]
   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 -> System.map-2.2.14.sound

【轉:http://edu.codepub.com/2011/0323/30291.php

第二部分:System.map的格式

本文參考 binutils文件。

system.map內容格式為:線性地址 型別 符號

具體內容如下: 
00100000 A phys_startup_32
c0100000 T startup_32
c0100000 A _text                             注:表示核心程式碼第一個位元組的地址
c01000c6 t checkCPUtype
c0100147 t is486
c010014e t is386
c0100199 t L6
c010019b t check_x87
c01001c2 t setup_idt
c01001df t rp_sidt
c01001ec t ignore_int
c0100220 t rest_init
c0100220 T stext
c0100220 T _stext
c0100252 t do_pre_smp_initcalls
c0100257 t run_init_process
c0100283 t init
c01003a8 t try_name
c0100529 T name_to_dev_t
c0100790 t calibrate_delay_direct

c02f95d7 T register_kretprobe
c02f96cd T unregister_kretprobe
c02f9760 t .text.lock.kprobes
c02f97b0 T __kprobes_text_end
c02f9abe t iret_exc
c02fa1af A _etext                              注:核心程式碼結束的位置,之後為核心初始化的資料
c02fa1b0 A __start___ex_table
c02facc0 A __stop___ex_table
c02fb000 r __func__.12
c02fb000 A __start_rodata
c02fb00c r __func__.13
c02fb020 r __func__.2
c02fb02c r __func__.3
c02fb040 R linux_banner
c02fb0bb r __func__.0
c02fb0c7 r __func__.1
c02fb0e0 r __func__.0
c02fb0ec r __func__.1
c02fb100 r border
c02fb160 r cplens

c03e1b08 D ip_statistics
c03e1b10 D tcp_statistics
c03e1b18 D udp_statistics
c03e1b20 D icmp_statistics
c03e1b28 D net_statistics
c03e1b30 d fn_hash_kmem
c03e1b34 d fn_alias_kmem
c03e1b38 d xfrm_dst_cache
c03e1b3c d secpath_cachep
c03e1b40 A _edata                       注:核心初始化資料結束,之後為未初始化資料
c03e2000 D init_thread_union
c03e4000 A __init_begin
c03e4000 t no_halt
c03e4000 T _sinittext
c03e400d t mca_pentium
c03e401d t no_387
c03e4033 t check_fpu

c043a058 b pfkey_table
c043a05c b pfkey_table_lock
c043a05c b pfkey_table_users
c043a060 b pfkey_socks_nr
c043a064 b acqseq.4
c043a068 b acqseq_lock.5
c043a068 A __bss_stop
c043a068 A _end                         注:核心未初始化資料結束
c043b000 A pg0
ffffe400 A __kernel_vsyscall
ffffe410 A SYSENTER_RETURN
ffffe420 A __kernel_sigreturn
ffffe440 A __kernel_rt_sigreturn

符號型別.

小寫字母表示區域性; 大寫字母表示全域性(外部). 

The symbol’s value is absolute, and will not be changed by further linking.


The symbol is in the uninitialized data section (known as BSS).


The symbol is common. Common symbols are uninitialized data. When linking, multiple common symbols may appear with the same name. If the symbol is defined anywhere, the common symbols are treated as undefined references. For more details on common symbols, see the discussion of -warn-common in Linker options.


The symbol is in the initialized data section.


The symbol is in an initialized data section for small objects. Some object file formats permit more efficient access to small data objects, such as a global int variable as opposed to a large global array.


The symbol is an indirect reference to another symbol. This is a GNU extension to the a.out object file format which is rarely used.


The symbol is a debugging symbol.


The symbol is in a read only data section.


The symbol is in an uninitialized data section for small objects.


The symbol is in the text (code) section.


The symbol is undefined.


The symbol is a weak object. When a weak defined symbol is linked with a normal defined symbol, the normal defined symbol is used with no error. When a weak undefined symbol is linked and the symbol is not defined, the value of the weak symbol becomes zero with no error.


The symbol is a weak symbol that has not been specifically tagged as a weak object symbol. When a weak defined symbol is linked with a normal defined symbol, the normal defined symbol is used with no error. When a weak undefined symbol is linked and the symbol is not defined, the value of the weak symbol becomes zero with no error.


The symbol is a stabs symbol in an a.out object file. In this case, the next values printed are the stabs other field, the stabs desc field, and the stab type. Stabs symbols are used to hold debugging information. For more information, see Stabs.


The symbol type is unknown, or object file format specific. 
 

# info binutils檢視更詳細的資訊

【轉:http://blog.csdn.net/gates84/archive/2006/11/27/1416514.aspx

 

看了一下Linux下的/boot/System.map,發現裡邊定義的東西還是比較有意思的
仔細的看了一下,寫一些東西:)

部分摘錄:
    00000000 A agp_frontend_cleanup
    c0127350 t kmem_slab_destroy
    c0127430 T kmem_cache_create
    c02a0ea0 R ide_pio_timings
    c02aee28 r usb_bandwidth_option
    c02c68bf ? __kstrtab_get_zeroed_page
    c02c68cf ? __kstrtab___free_pages
    c02cfe00 d real_mode_gdt_entries
    c02d028c D kstack_depth_to_print
    c03218c0 B irq_err_count
    c03218c4 b root_irq_dir

    其中每項有三部分組成:
    第三部分是專案的名稱
    第一部分是專案在核心中的偏移量;
    第二部分是專案的型別  各種型別的特徵程式碼的意義
        A:表示全域性函式或變數
            比如其中的agp_frontend_cleanup在原碼中的定義為:
            extern void agp_frontend_cleanup(void);
        B:表示 靜態/全域性/原子 變數|陣列
            比如上面的irq_err_count在原碼中的定義分別為:
            extern atomic_t irq_err_count;            //include\asm-i386\Hw_irq.h
            atomic_t irq_err_count;                //arch\i386\kernel\Irq.c
            static volatile unsigned long irq_err_count;    //arch\i386\kernel\Irq.c
        b:表示靜態結構體
            比如上面例子中的root_irq_dir,在原碼中的定義是:
            static struct proc_dir_entry * root_irq_dir;    //arch\i386\kernel\Irq.c
        D:表示已經定義過初值的變數
            比如上面例子中的kstack_depth_to_print,在原碼中的定義是:
            int kstack_depth_to_print = 24;            //arch\i386\kernel、Traps.c
        d:表示已經定義了初值的變數/陣列
        R:表示定義的常量[全域性]陣列
            比如上面例子中的ide_pio_timings,在原碼中的定義是:
            extern const ide_pio_timings_t ide_pio_timings[6]; //drivers\ide、Ide_modes.h
            又比如:c02a53f8 R sense_data_texts在原碼中的定義
            const struct {
                unsigned long asc_ascq;
                const char * const text;
                } sense_data_texts[] = {....}
        r:表示靜態常量定義
            比如上面例子中的usb_bandwidth_option,在原碼中的定義是:
            static const int usb_bandwidth_option = …    // drivers\usb\Usb.c
        T:表示全域性函式/函式指標
            比如上面例子中的kmem_cache_create,在原碼中的定義是:
            extern kmem_cache_t *kmem_cache_create(…)    // include\linux\Slab.h
        t:表示靜態函式或定義了初值的靜態變數/陣列
            比如上面例子中的usb_bandwidth_option在原碼中的定義是:
            static const int usb_bandwidth_option = 1    // drivers\usb\Usb.c
        ?:在原碼中沒有找到,你知道嗎?告訴我吧:)

 

 

         每次編譯核心時符號的地址均不同,因而編譯核心的同時都會生成一個相應的System.map檔案。在System.map檔案中,地址與符號之間的字母代表符號的型別。這裡的型別定義與nm中型別的定義是一樣的。下面我們便羅列出這些型別。

A

絕對

B或b

未初始化的資料段(BSS)

D或d

初始化的資料段

G或g

小物件的初始化資料段(全域性的)

i

DLL相關的段

N

除錯用符號

p

堆疊的展開段(stack unwind section)

R或r

只讀資料段

S或s

小物件的未初始化資料段

T或t

程式碼段

U

未定義

V或v

弱物件(weak object)

W或w

沒有標籤的弱物件(weak object)

-

a.out目標檔案中的樁符號(stabs symbol)

?

未知符號