[zhuan]動態鏈接庫中的.symtab和.dynsym


http://blog.csdn.net/beyond702/article/details/50979340

原文如下:

shared library (.so)

"Program Library Howto-Shared Libraries"是很好的材料, 下面的內容多是據此整理的.

定義:
Shared libraries are libraries that are loaded by programs when they start.
使用shared library(共享庫)會有很多好處, 比如軟件升級, 不難想象.

命名約定:
1. soname: 每個共享庫都有一個soname, 形式為"libNAME.so.x", 其中x是版本號. 如"libc.so.6".
2. real name: 真正的庫文件, 一般形式為"soname.y[.z]", 即"libName.so.x.y[.z]", 其中y是minor number, z是release number, 是可選的. 如"libattr.so.1.1.0".
3. linker name: compiler用來請求庫時使用的名字, 一般是沒有版本號的soname.

放置位置 & load/preload:
共享庫一般放在一些約定的目錄下, 如/usr/lib/, /usr/local/lib, /lib/等. 這其實是遵循FHS的, 比如/usr/local/lib下放置的一般是用戶開發的庫.
在啟動程序時, program loader(ld-Linux.so.x)會找到並加載程序需要的共享庫, loader查找的路徑一般就是上述的幾個目錄, 這些目錄在/etc/ld.so.conf文件中配置.
如果只想覆蓋共享庫的某幾個函數, 保持其余函數不變, 則可以將共享庫名字和函數名字輸入到/etc/ld.so.preload中, 這里面定義的規則會覆蓋標准規則.

cache arrangement & ldconfig
實際上, 在啟動程序時再去搜尋所需的共享庫不是高效做法, 所以loader使用了cache. ldconfig的作用就是讀取文件/etc/ld.so.conf, 在各個庫目錄中, 對共享庫設置合適的symbolic link(使得遵守命名約定), 然后寫入某種數據到/etc/ld.so.cache, 這個文件再今后就被其他程序使用, 從而大幅提升了共享庫的查找速度.
所以在每加入/移除一個共享庫, 或者修改了/etc/ld.so.conf(即修改庫目錄)的時候, 最要運行ldconfig.

創建共享庫
step1. 編譯出object files, 需要使用-fPIC-fpic flag. fPIC和fpic的區別是, 前者生成的文件更大, 不過具有更好的平台無關性, 后者恰好相反. 這說明前者為了platform-independence做了更多工作.
step2. 用-Wl向linker傳遞參數. 如: "gcc -shared -Wl,-soname,libmystuff.so.1 -o libmystuff.so.1.0.1 a.o b.o -lc".
step3. 把共享庫拷貝到約定的某個目錄下即可, 如/usr/local/lib.
step4. ldconfig -n /path/to/lib.

elf

elf的內容參考"elf & libelf, elftoolchain", 它是一種格式,也是一種規范, 可以用libelf寫程序去操作它, 可以用objdump、nm和readelf去讀取elf文件的內容.

symbols

我也已經熟悉共享庫了, 我知道ldconfig的作用, 我知道常用的庫放置目錄, 我知道ltrace, ldd可以用來幫助確認某程序和某些共享庫的關聯關系是否正確.
所以, 如果沒有symbols這一節, 本篇文章存在的意義不大.

"Inside ELF Symbol Tables"是絕佳的資料, 當然正如很多網文一樣, 它僅是幫助理解, 而不涉及很深的細節. 細節標准什么的還是要看書和文檔了, 這方面很不錯的書籍就是校友的<程序員的自我修養>了.

查看elf規范, 你必然可以看到symtab和dynsym, 如"ELF-64 Object File Format"中"4.Sections"就列出了標准的sections, .symtab和.dynsym就是其中之二.
實際上, 我們知道機器可執行的是machine code, 而我們使用的高級語言編程, 並不是利用晦澀的機器碼, 而是用human-readable的變量名, 函數名等, 這些名字就是symbolic name. 編譯器在編譯時收集symbol信息, 並儲存在object file的.symtab和.dynsym中. symbols是linkerdebugger所必需的信息, 如果沒有symbols, 試想debugger如何能展示給用戶調試信息了? 如果沒有symbol, 而只有地址(相對於object file的offset), linker如何能夠鏈接多個object file了?
對於linker和symbol, 我們可以做個小實驗:

// 編寫一個簡單的 a.c
$ cat a.c
void func(void)
{
        printf("call func()\n");
}

$ nm a.o
00000000 T func
         U puts

// 編寫一個簡單的 main.c
$ cat main.c
#include <stdio.h>
extern void func(void);
int main(
{
        func();
        return 0;
}

$ nm main.o
         U func
         00000000 T main

// 正常情況下
$ gcc main.o a.o -o main
$ ./main
call func()

// 為了驗證symbol對於linker來說是必需品, 我做如下操作
$ file a.o
a.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
$ strip a.o
$ file a.o
a.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), stripped
$ gcc main.o a.o -o main
main.o: In function `main':
/home/xan/lab/main.c:7: undefined reference to `func'
collect2: ld returned 1 exit status

這個小實驗證實了symbols對於linker的重要性, 同時使用file看出"not stripped"->"stripped"的變化, 說的就是去除了symbols信息.

現在假使我們生成了最后的可執行文件(當然是elf格式了), 那么這個elf中是否包含symbols呢? 其中又是否需要symbols呢?
不妨先下結論: 一般地, 生成的可執行文件都是包含symbols, 不過這些信息不是程序執行所必需的, 可以通過strip(Discard symbols from object files)去除.
同樣可以做個小實驗:

// 仍用上面實驗的代碼
$ file main
main: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped
$ ./main
call func()
$ strip main
$ file main
main: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped
$ ./main
call func()
$ 

這個小實驗證實了symbols對於可執行文件來說不是必需的, 這是因為可執行的代碼都是machine code, 只需要address信息, 無需symbol信息.

對於elf和symbols, 還是好理解的啦. 就是我elf文件中留了一席之地給你放symbols, compiler在生成elf時會往其中填充. debugger/nm/readelf等可以來讀取. 不過這些symbols不是程序執行必需的, 所以完全可以去除, 只不過去除之后, debugger就讀不到信息了.

而對於共享庫來說, 情況略復雜些了. 我們來特別說明.

共享庫和symbols

在繼續下去之前, 先來看兩個事實.

$ ldd /bin/ls
        linux-gate.so.1 =>  (0xb7711000)
        libselinux.so.1 => /lib/libselinux.so.1 (0xb76e5000)
        librt.so.1 => /lib/i686/cmov/librt.so.1 (0xb76dc000)
        libacl.so.1 => /lib/libacl.so.1 (0xb76d4000)
        libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb758d000)
        libdl.so.2 => /lib/i686/cmov/libdl.so.2 (0xb7589000)
        /lib/ld-linux.so.2 (0xb7712000)
        libpthread.so.0 => /lib/i686/cmov/libpthread.so.0 (0xb7570000)
        libattr.so.1 => /lib/libattr.so.1 (0xb756b000)
$ nm /lib/i686/cmov/libc.so.6 
nm: /lib/i686/cmov/libc.so.6: no symbols    --> libattr, libacl也一樣, 都顯示"no symbols"

// 而libpthread有一大串
$ nm /lib/i686/cmov/libpthread.so.0 | tail
        U twalk@@GLIBC_2.0
        U uname@@GLIBC_2.0
        U unlink@@GLIBC_2.0
0000c930 t unwind_cleanup
0000c970 t unwind_stop
0000cfd0 W vfork
0000de90 W wait
0000df50 W waitpid
0000c140 t walker
0000d020 W write

這僅僅是因為有些庫做了strip, 而其他庫沒做strip而已? 還是說對於某些共享庫來說, symbols也是必需的?
目前不知答案, 分析下去.

前面提到.symtab和.dynsym兩個不同的symbol table, 它們有什么區別?
.dynsym是.symtab的一個子集, 大家都有疑問, 為什么要兩個信息重合的結構?
需要先了解allocable/non-allocable ELF section, ELF文件包含一些sections(如code和data)是在運行時需要的, 這些sections被稱為allocable; 而其他一些sections僅僅是linker,debugger等工具需要, 在運行時並不需要, 這些sections被稱為non-allocable的. 當linker構建ELF文件時, 它把allocable的數據放到一個地方, 將non-allocable的數據放到其他地方. 當OS加載ELF文件時, 僅僅allocable的數據被映射到內存, non-allocable的數據仍靜靜地呆在文件里不被處理. strip就是用來移除某些non-allocable sections的.
.symtab包含大量linker,debugger需要的數據, 但並不為runtime必需, 它是non-allocable的; .dynsym包含.symtab的一個子集, 比如共享庫所需要在runtime加載的函數對應的symbols, 它世allocable的.

因此, 得到答案:
1. strip移除的應是.symtab.
2. nm讀取的應是.symtab: 上面發現的libattr等nm結果為空, libpthread nm結果非空應是正常的. 3. 共享庫包含的.dynsym是runtime必需的, 是allocable的.

可做驗證, 期望的結果為:
1. strip libpthread, ls依然能夠工作.
2. strip libpthread, nm libpthread得到結果為空.
3. 可以通過設置nm options, 或使用readelf讀出.dynsym的內容.

$ sudo strip /lib/i686/cmov/libpthread-2.11.3.so
$ file /lib/i686/cmov/libpthread-2.11.3.so
/lib/i686/cmov/libpthread-2.11.3.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped
$ ls
...(輸出正確結果)

$ nm /lib/i686/cmov/libpthread-2.11.3.so
nm: /lib/i686/cmov/libpthread-2.11.3.so: no symbols

$ readelf -s /lib/i686/cmov/libpthread-2.11.3.so | tail
322: 0000b6a0   292 FUNC    GLOBAL DEFAULT   13 __pthread_clock_gettime@@GLIBC_PRIVATE
323: 0000ec30    46 FUNC    GLOBAL DEFAULT   13 pthread_mutex_consistent_@@GLIBC_2.4
324: 0000b3a0    50 FUNC    GLOBAL DEFAULT   13 pthread_testcancel@@GLIBC_2.0
325: 0000d6b0   111 FUNC    WEAK   DEFAULT   13 fsync@@GLIBC_2.0
326: 0000d1f0   180 FUNC    WEAK   DEFAULT   13 fcntl@@GLIBC_2.0
327: 0000dde0   176 FUNC    WEAK   DEFAULT   13 tcdrain@@GLIBC_2.0
328: 00009390     7 FUNC    GLOBAL DEFAULT   13 pthread_mutexattr_destroy@@GLIBC_2.0
329: 00006de0    23 FUNC    GLOBAL DEFAULT   13 pthread_yield@@GLIBC_2.2
330: 000077c0   259 FUNC    GLOBAL DEFAULT   13 pthread_mutex_init@@GLIBC_2.0
331: 000093c0    49 FUNC    GLOBAL DEFAULT   13 pthread_mutexattr_setpsha@@GLIBC_2.2

$ readelf -s /lib/libattr.so.1.1.0 | tail
48: 00002f50    50 FUNC    GLOBAL DEFAULT   13 lremovexattr@@ATTR_1.0
49: 00003010    57 FUNC    GLOBAL DEFAULT   13 llistxattr@@ATTR_1.0
50: 00002ae0    50 FUNC    GLOBAL DEFAULT   13 attr_copy_check_permissio@@ATTR_1.1
51: 00001b50   259 FUNC    GLOBAL DEFAULT   13 attr_set@@ATTR_1.0
52: 00002b20  1002 FUNC    GLOBAL DEFAULT   13 attr_copy_action
53: 000031f0    71 FUNC    GLOBAL DEFAULT   13 setxattr@@ATTR_1.0
54: 00001380   543 FUNC    GLOBAL DEFAULT   13 attr_list@@ATTR_1.2
55: 000030d0    64 FUNC    GLOBAL DEFAULT   13 lgetxattr@@ATTR_1.0
56: 00002fd0    57 FUNC    GLOBAL DEFAULT   13 flistxattr@@ATTR_1.0
57: 00002f10    50 FUNC    GLOBAL DEFAULT   13 fremovexattr@@ATTR_1.0

至此, 對symbols和共享庫,ELF的關系的了解告一段落.

more

既然已經說到共享庫(shared library), 不妨稍微提一下動態裝載庫(Dynamically Loaded Libraries), 共享庫是在程序startup時被加載, 而DLL(注意區別於windows下的概念)則是在程序運行過程中顯式被加載, 實際上就是調用dlopen,dlsym等接口顯式地打開共享庫, 顯示地查找庫中的symbol, 然后找到對應的代碼去執行.

How To Write Shared Libraries by Ulrich Drepper.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM