linux 下有動態(tài)庫和靜態(tài)庫,動態(tài)庫以.so為擴(kuò)展名,靜態(tài)庫以.a為擴(kuò)展名息拜。二者都使用廣泛溉潭。本文主要講動態(tài)庫方面知識净响。
基本上每一個linux 程序都至少會有一個動態(tài)庫,查看某個程序使用了那些動態(tài)庫喳瓣,使用ldd命令查看
#?ldd?/bin/ls
linux-vdso.so.1?=>?(0x00007fff597ff000)
libselinux.so.1?=>?/lib64/libselinux.so.1?(0x00000036c2e00000)
librt.so.1?=>?/lib64/librt.so.1?(0x00000036c2200000)
libcap.so.2?=>?/lib64/libcap.so.2?(0x00000036c4a00000)
libacl.so.1?=>?/lib64/libacl.so.1?(0x00000036d0600000)
libc.so.6?=>?/lib64/libc.so.6?(0x00000036c1200000)
libdl.so.2?=>?/lib64/libdl.so.2?(0x00000036c1600000)
/lib64/ld-linux-x86-64.so.2?(0x00000036c0e00000)
libpthread.so.0?=>?/lib64/libpthread.so.0?(0x00000036c1a00000)
libattr.so.1?=>?/lib64/libattr.so.1?(0x00000036cf600000)
? ?這么多so馋贤,是的。使用ldd顯示的so畏陕,并不是所有so都是需要使用的配乓,下面舉個例子
main.cpp
#include?<stdio.h>
#include?<iostream>
#include?<string>
using namespace std;
int?main?()
{
cout?<<?"test"?<<?endl;
return 0;
}
使用缺省參數(shù)編譯結(jié)果
# g++?-o demo main.cpp
# ldd demo
linux-vdso.so.1?=>?(0x00007fffcd1ff000)
libstdc++.so.6?=>?/usr/lib64/libstdc++.so.6?(0x00007f4d02f69000)
libm.so.6?=>?/lib64/libm.so.6?(0x00000036c1e00000)
libgcc_s.so.1?=>?/lib64/libgcc_s.so.1?(0x00000036c7e00000)
libc.so.6?=>?/lib64/libc.so.6?(0x00000036c1200000)
/lib64/ld-linux-x86-64.so.2?(0x00000036c0e00000)
如果我鏈接一些so,但是程序并不用到這些so惠毁,又是什么情況呢犹芹,下面我加入鏈接壓縮庫,數(shù)學(xué)庫鞠绰,線程庫
# g++?-o demo?-lz?-lm?-lrt main.cpp
# ldd demo
linux-vdso.so.1?=>?(0x00007fff0f7fc000)
libz.so.1 => /lib64/libz.so.1 (0x00000036c2600000)
librt.so.1 => /lib64/librt.so.1 (0x00000036c2200000)
libstdc++.so.6?=>?/usr/lib64/libstdc++.so.6?(0x00007ff6ab70d000)
libm.so.6 => /lib64/libm.so.6 (0x00000036c1e00000)
libgcc_s.so.1?=>?/lib64/libgcc_s.so.1?(0x00000036c7e00000)
libc.so.6?=>?/lib64/libc.so.6?(0x00000036c1200000)
libpthread.so.0?=>?/lib64/libpthread.so.0?(0x00000036c1a00000)
/lib64/ld-linux-x86-64.so.2?(0x00000036c0e00000)
? 看看腰埂,雖然沒有用到,但是一樣有鏈接進(jìn)來蜈膨,那看看程序啟動時候有沒有去加載它們呢
# strace?./demo
execve("./demo",?["./demo"],?[/*?30 vars?*/])?=?0
...?=?0
open("/lib64/libz.so.1",?O_RDONLY)?=?3
...
close(3)?=?0
open("/lib64/librt.so.1",?O_RDONLY)?=?3
...
close(3)?=?0
open("/usr/lib64/libstdc++.so.6",?O_RDONLY)?=?3
...
close(3)?=?0
open("/lib64/libm.so.6",?O_RDONLY)?=?3
...
close(3)?=?0
open("/lib64/libgcc_s.so.1",?O_RDONLY)?=?3
...
close(3)?=?0
open("/lib64/libc.so.6",?O_RDONLY)?=?3
...
close(3)?=?0
open("/lib64/libpthread.so.0",?O_RDONLY)?=?3
...
close(3)?=?0
...
看屿笼,有加載,所以必定會影響進(jìn)程啟動速度翁巍,所以我們最后不要把無用的so編譯進(jìn)來驴一,這里會有什么影響呢?
? ?大家知不知道linux從程序(program或?qū)ο螅┳兂蛇M(jìn)程(process或進(jìn)程)灶壶,要經(jīng)過哪些步驟呢肝断,這里如果詳細(xì)的說,估計(jì)要另開一篇文章例朱。簡單的說分三步:
? ? 1孝情、fork進(jìn)程,在內(nèi)核創(chuàng)建進(jìn)程相關(guān)內(nèi)核項(xiàng)洒嗤,加載進(jìn)程可執(zhí)行文件箫荡;
? ? 2、查找依賴的so渔隶,一一加載映射虛擬地址
? ? 3羔挡、初始化程序變量。
? 可以看到间唉,第二步中dll依賴越多绞灼,進(jìn)程啟動越慢,并且發(fā)布程序的時候呈野,這些鏈接但沒有使用的so低矮,同樣要一起跟著發(fā)布,否則進(jìn)程啟動時候被冒,會失敗军掂,找不到對應(yīng)的so轮蜕。所以我們不能像上面那樣,把一些毫無意義的so鏈接進(jìn)來蝗锥,浪費(fèi)資源跃洛。但是開發(fā)人員寫makefile 一般有沒有那么細(xì)心,圖省事方便终议,那么有什么好的辦法呢汇竭。繼續(xù)看下去,下面會給你解決方法穴张。
先使用 ldd -u demo 查看不需要鏈接的so细燎,看下面,一面了然陆馁,無用的so全部暴露出來了吧
# ldd?-u demo
Unused direct dependencies:
/lib64/libz.so.1
/lib64/librt.so.1
/lib64/libm.so.6
/lib64/libgcc_s.so.1
使用-Wl,--as-needed 編譯選項(xiàng)
# g++?-Wl,--as-needed?-o demo?-lz?-lm?-lrt main.cpp
# ldd demo
linux-vdso.so.1?=>?(0x00007fffebfff000)
libstdc++.so.6?=>?/usr/lib64/libstdc++.so.6?(0x00007ff665c05000)
libc.so.6?=>?/lib64/libc.so.6?(0x00000036c1200000)
libm.so.6?=>?/lib64/libm.so.6?(0x00000036c1e00000)
/lib64/ld-linux-x86-64.so.2?(0x00000036c0e00000)
libgcc_s.so.1?=>?/lib64/libgcc_s.so.1?(0x00000036c7e00000)
# ldd?-u demo
Unused direct dependencies:
我們知道linux鏈接so有兩種途徑:顯示和隱式找颓。所謂顯示就是程序主動調(diào)用dlopen打開相關(guān)so;這里需要補(bǔ)充的是,如果使用顯示鏈接叮贩,上篇文章討論的那些問題都不存在击狮。首先,dlopen的so使用ldd是查看不到的。其次益老,使用dlopen打開的so并不是在進(jìn)程啟動時候加載映射的彪蓬,而是當(dāng)進(jìn)程運(yùn)行到調(diào)用dlopen代碼地方才加載該so,也就是說捺萌,如果每個進(jìn)程顯示鏈接a.so;但是如果發(fā)布該程序時候忘記附帶發(fā)布該a.so,程序仍然能夠正常啟動档冬,甚至如果運(yùn)行邏輯沒有觸發(fā)運(yùn)行到調(diào)用dlopen函數(shù)代碼地方。該程序還能正常運(yùn)行桃纯,即使沒有a.so.
??既然顯示加載這么多優(yōu)點(diǎn)酷誓,那么為什么實(shí)際生產(chǎn)中很少碼農(nóng)使用它呢,?主要原因還是起使用不是很方便,需要開發(fā)人員多寫不少代碼态坦。所以不被大多數(shù)碼農(nóng)使用盐数,還有一個重要原因應(yīng)該是能提前發(fā)現(xiàn)錯誤,在部署的時候就能發(fā)現(xiàn)缺少哪些so伞梯,而不是等到實(shí)際上限運(yùn)行的時候才發(fā)現(xiàn)缺東少西玫氢。
??下面舉個工作中最常碰到的問題,來引申出本篇內(nèi)容吧谜诫。
寫一個最簡單的so漾峡,?tmp.cpp
1.????int?test()
2.????{
3.??????return 20;
4.????}
??編譯=>鏈接=》運(yùn)行,?下面main.cpp?內(nèi)容請參見上一篇文章。
[stevenrao]$?g++ -fPIC -c tmp.cpp
[stevenrao]$?g++ -shared -o libtmp.so tmp.o
[stevenrao]$?mv libtmp.so /tmp/
[stevenrao]$?g++ -o demo -L/tmp -ltmp main.cpp
[stevenrao]$?./demo
./demo: error while loading shared libraries: libtmp.so: cannot open shared object file: No such file or directory
[stevenrao]$?ldd demo
linux-vdso.so.1 =>??(0x00007fff7fdc1000)
????????libtmp.so => not found
這個錯誤是最常見的錯誤了喻旷。運(yùn)行程序的時候找不到依賴的so生逸。一般人使用方法是修改LD_LIBRARY_PATH這個環(huán)境變量
? ?export?LD_LIBRARY_PATH=/tmp
[stevenrao]$?./demo
test
? ?這樣就OK了,?不過這樣export?只對當(dāng)前shell有效,當(dāng)另開一個shell時候,又要重新設(shè)置槽袄∥袄可以把export?LD_LIBRARY_PATH=/tmp?語句寫到?~/.bashrc中,這樣就對當(dāng)前用戶有效了掰伸,寫到/etc/bashrc中就對所有用戶有效了。
前面鏈接時候使用?-L/tmp/ -ltmp?是一種設(shè)置相對路徑方法怀估,還有一種絕對路徑鏈接方法狮鸭。
[stevenrao]$?g++ -o demo??/tmp/libtmp.so main.cpp
[stevenrao]$?./demo
??test
[stevenrao]$?ldd demo
????????linux-vdso.so.1 =>??(0x00007fff083ff000)
????????/tmp/libtmp.so (0x00007f53ed30f000)?
絕對路徑雖然申請?jiān)O(shè)置環(huán)境變量步驟,但是缺陷也是致命的多搀,這個so必須放在絕對路徑下歧蕉,不能放到其他地方,這樣給部署帶來很大麻煩康铭。所以應(yīng)該禁止使用絕對路徑鏈接so惯退。
搜索路徑分兩種,一種是鏈接時候的搜索路徑从藤,一種是運(yùn)行時期的搜索路徑催跪。像前面提到的?-L/tmp/?是屬于鏈接時期的搜索路徑,即給ld程序提供的編譯鏈接時候?qū)ふ覄討B(tài)庫路徑夷野;而LD_LIBRARY_PATH則既屬于鏈接期搜索路徑懊蒸,又屬于運(yùn)行時期的搜索路徑。
? ?這里需要介紹鏈-rpath鏈接選項(xiàng)悯搔,它是指定運(yùn)行時候都使用的搜索路徑骑丸。聰明的同學(xué)馬上就想到,運(yùn)行時搜索路徑,那它記錄在哪兒呢妒貌。也像.?LD_LIBRARY_PATH那樣通危,每部署一臺機(jī)器就需要配一下嗎。呵呵灌曙,不需要..,因?yàn)樗呀?jīng)被硬編碼到可執(zhí)行文件內(nèi)部了菊碟。看看下面演示
1.?? [stevenrao]?$g++?-o demo -L?/tmp/?-ltmp main.cpp
2.?? [stevenrao]?$./demo
3.?? ./demo:?error?while?loading shared libraries:?libtmp.so:?cannot?open?shared object?file:?No such?file?or directory
4.?? [stevenrao]?$g++?-o demo?-Wl,-rpath?/tmp/?-L/tmp/?-ltmp main.cpp
5.?? [stevenrao]?$?./demo
6.?? test
7.?? [stevenrao]?$readelf -d demo
8.?? ?
9.?? Dynamic section at offset 0xc58 contains 26 entries:
10.? ? Tag ? ? ? ?Type ? ? ? ? ? ? ? ? ? ? ? ? Name/Value
11.? ?0x0000000000000001 (NEEDED) ? ? ? ? ? ? Shared library: [libtmp.so]
12.? ?0x0000000000000001 (NEEDED) ? ? ? ? ? ? Shared library: [libstdc++.so.6]
13.? ?0x0000000000000001 (NEEDED) ? ? ? ? ? ? Shared library: [libm.so.6]
14.? ?0x0000000000000001 (NEEDED) ? ? ? ? ? ? Shared library: [libgcc_s.so.1]
15.? ?0x0000000000000001 (NEEDED) ? ? ? ? ? ? Shared library: [libc.so.6]
16.? ?0x000000000000000f (RPATH) ? ? ? ? ? ? ?Library rpath: [/tmp/]
17.? ?0x000000000000001d (RUNPATH) ? ? ? ? ? ?Library runpath: [/tmp/]
? ?看看是吧平匈,編譯到elf文件內(nèi)部了框沟,路徑和程序深深的耦合到一起