什么是庫文件?
庫文件是事先編譯好的方法的合集。比如:我們提前寫好一些數據公式的實現,將其打包成庫文件,以后使用只需要庫文件就可以,不需要重新編寫。
Linux系統中:
1.靜態庫的擴展名為.a;
2.動態庫的擴展名為.so;
源代碼到可執行程序的轉換時需要經歷如下圖所示的過程:
1.編譯是指把用高級語言編寫的程序轉換成相應處理器的匯編語言程序的過程。
2.匯編是從匯編語言程序生成目標系統的二進制代碼(機器代碼)的過程。
3.鏈接是指將匯編生成的多段機器代碼組合成一個可執行程序。
通過編譯和匯編過程,每一個源文件將生成一個目標文件。連接器的作用就是將這些目標文件組合起來,組合的過程包括了代碼段、數據段等部分的合並,以及添加相應的文件頭。
最后得到的可執行文件如何作用的:
ELF文件格式包括三種主要的類型:可執行文件、可重定向文件、共享庫。
1.可執行文件(應用程序)
可執行文件包含了代碼和數據,是可以直接運行的程序。
2.可重定向文件(*.o)
可重定向文件又稱為目標文件,它包含了代碼和數據(這些數據是和其他重定位文件和共享的object文件一起連接時使用的)。
.o文件參與程序的連接(創建一個程序)和程序的執行(運行一個程序),它提供了一個方便有效的方法來用並行的視角看待文件的內容,這些.o文件的活動可以反映出不同的需要。
Linux下,我們可以用gcc -c編譯源文件時可將其編譯成*.o格式。
3.共享文件(*.so)
也稱為動態庫文件,它包含了代碼和數據(這些數據是在連接時候被連接器ld和運行時動態連接器使用的)。動態連接器可能稱為ld.so.1,libc.so.1或者 ld-linux.so.1。我的CentOS6.0系統中該文件為:/lib/ld-2.12.so
庫是一種可執行代碼的二進制格式,能夠被載入到內存中執行,庫分靜態庫和動態庫兩種:
靜態庫:這類庫的名字一般是libxxx.a,xxx為庫的名字。利用靜態函數庫編譯成的文件比較大,因為整個函數庫的所有數據都會被整合進目標代碼中,他的優點就顯而易見了,即編譯后的執行程序不需要外部的函數庫支持,因為所有使用的函數都已經被編譯進去了。當然這也會成為他的缺點,因為如果靜態函數庫改變了,那么你的程序必須重新編譯。
動態庫:這類庫的名字一般是libxxx.M.N.so,同樣的xxx為庫的名字,M是庫的主版本號,N是庫的副版本號。當然也可以不要版本號,但名字必須有。相對於靜態函數庫,動態函數庫在編譯的時候並沒有被編譯進目標代碼中,你的程序執行到相關函數時才調用該函數庫里的相應函數,因此動態函數庫所產生的可執行文件比較小。由於函數庫沒有被整合進你的程序,而是程序運行時動態的申請並調用,所以程序的運行環境中必須提供相應的庫。動態函數庫的改變並不影響你的程序,所以動態函數庫的升級比較方便。linux系統有幾個重要的目錄存放相應的函數庫,如/lib /usr/lib。
當要使用靜態的程序庫時,連接器會找出程序所需的函數,然后將它們拷貝到執行文件,由於這種拷貝是完整的,所以一旦連接成功,靜態程序庫也就不再需要了。然而,對動態庫而言,就不是這樣。動態庫會在執行程序內留下一個標記指明當程序執行時,首先必須載入這個庫。由於動態庫節省空間,linux下進行連接的缺省操作是首先連接動態庫,也就是說,如果同時存在靜態和動態庫,不特別指定的話,將與動態庫相連接。
制作靜態鏈接庫:
1.准備兩個源碼文件st1.cpp和st2.cpp,用它們來制作庫libmytest.a
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ cat st1.cpp
#include <iostream>
using namespace std;
void display1()
{
cout<<"This is my first static library!!!"<<endl;
}
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ cat st2.cpp
#include <iostream>
using namespace std;
void display2()
{
cout<<"This is my second static library"<<endl;
}
2.把兩個源碼文件生成目標文件
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ g++ -c st1.cpp st2.cpp
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ ll
總用量 24
drwxrwxr-x 2 xzj xzj 4096 7月 16 15:39 ./
drwxrwxr-x 7 xzj xzj 4096 7月 16 15:26 ../
-rw-rw-r-- 1 xzj xzj 115 7月 16 15:35 st1.cpp
-rw-rw-r-- 1 xzj xzj 2680 7月 16 15:39 st1.o
-rw-rw-r-- 1 xzj xzj 113 7月 16 15:35 st2.cpp
-rw-rw-r-- 1 xzj xzj 2680 7月 16 15:39 st2.o
3.使用ar -rsv libmytest.a st1.o st2.o制作靜態庫
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ ar -rsv libmytest.a st1.o st2.o
ar: 正在創建 libmytest.a
a - st1.o
a - st2.o
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ ll
總用量 32
drwxrwxr-x 2 xzj xzj 4096 7月 16 15:42 ./
drwxrwxr-x 7 xzj xzj 4096 7月 16 15:26 ../
-rw-rw-r-- 1 xzj xzj 5586 7月 16 15:42 libmytest.a
-rw-rw-r-- 1 xzj xzj 115 7月 16 15:35 st1.cpp
-rw-rw-r-- 1 xzj xzj 2680 7月 16 15:39 st1.o
-rw-rw-r-- 1 xzj xzj 113 7月 16 15:35 st2.cpp
-rw-rw-r-- 1 xzj xzj 2680 7月 16 15:39 st2.o
用file命令查看其屬性,發現它確實是歸檔壓縮文件
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ file libmytest.a
libmytest.a: current ar archive
用ar -t libmytest.a可以查看一個靜態庫包含了那些obj文件:
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ ar -t libmytest.a
st1.o
st2.o
4.寫個測試程序來調用庫libmytest.a中所提供的兩個接口display1()和display2()。
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ cat main.cpp
void display1();
void display2();
int main()
{
display1();
display2();
return 0;
}
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ g++ -o run main.cpp -L./ -lmytest
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ ll
總用量 48
drwxrwxr-x 2 xzj xzj 4096 7月 16 15:54 ./
drwxrwxr-x 7 xzj xzj 4096 7月 16 15:26 ../
-rw-rw-r-- 1 xzj xzj 5586 7月 16 15:42 libmytest.a
-rw-rw-r-- 1 xzj xzj 95 7月 16 15:53 main.cpp
-rwxrwxr-x 1 xzj xzj 9424 7月 16 15:54 run*
-rw-rw-r-- 1 xzj xzj 115 7月 16 15:35 st1.cpp
-rw-rw-r-- 1 xzj xzj 2680 7月 16 15:39 st1.o
-rw-rw-r-- 1 xzj xzj 113 7月 16 15:35 st2.cpp
-rw-rw-r-- 1 xzj xzj 2680 7月 16 15:39 st2.o
結果調用成功:
xzj@xzj-VirtualBox:~/development_test/static_lib_test$ ./run
This is my first static library!!!
This is my second static library
制作動態庫
靜態庫*.a文件的存在主要是為了支持較老的a.out格式的可執行文件而存在的。目前用的最多的要數動態庫了。
動態庫的后綴為*.so。在Linux發行版中大多數的動態庫基本都位於/usr/lib和/lib目錄下。在開發和使用我們自己動態庫之前,請容許我先落里羅嗦的跟大家嘮叨嘮叨Linux下和動態庫相關的事兒吧。
有時候當我們的應用程序無法運行時,它會提示我們說它找不到什么樣的庫,或者哪個庫的版本又不合它胃口了等等之類的話。那么應用程序它是怎么知道需要哪些庫的呢?我們前面已幾個學了個很棒的命令ldd,用就是用來查看一個文件到底依賴了那些so庫文件。
Linux系統中動態鏈接庫的配置文件一般在/etc/ld.so.conf文件內,它里面存放的內容是可以被Linux共享的動態聯庫所在的目錄的名字。我的系統中,該文件的內容如下:
xzj@xzj-VirtualBox:/etc$ cat /etc/ld.so.conf
include /etc/ld.so.conf.d/*.conf
然后/etc/ld.so.conf.d/目錄下存放了很多*.conf文件,如下:
xzj@xzj-VirtualBox:/etc$ ls /etc/ld.so.conf.d/
fakeroot-x86_64-linux-gnu.conf libc.conf x86_64-linux-gnu_GL.conf
i386-linux-gnu.conf x86_64-linux-gnu.conf zz_i386-biarch-compat.conf
i386-linux-gnu_GL.conf x86_64-linux-gnu_EGL.conf zz_x32-biarch-compat.conf
其中每個conf文件代表了一種應用的庫配置內容,以libc為例:
xzj@xzj-VirtualBox:/etc$ cat /etc/ld.so.conf.d/libc.conf
# libc default configuration
/usr/local/lib
在/etc目錄下還存在一個名叫ld.so.cache的文件。從名字來看,我們知道它肯定是動態鏈接庫的什么緩存文件。
xzj@xzj-VirtualBox:/etc$ ls -l |grep ld.so.cache
-rw-r--r-- 1 root root 125054 7月 16 09:09 ld.so.cache
為了使得動態鏈接庫可以被系統使用,當我們修改了/etc/ld.so.conf或/etc/ld.so.conf.d/目錄下的任何文件,或者往那些目錄下拷貝了新的動態鏈接庫文件時,都需要運行一個很重要的命令:ldconfig,該命令位於/sbin目錄下,主要的用途就是負責搜索/lib和/usr/lib,以及配置文件/etc/ld.so.conf里所列的目錄下搜索可用的動態鏈接庫文件,然后創建處動態加載程序/lib/ld-linux.so.2所需要的連接和(默認)緩存文件/etc/ld.so.cache(此文件里保存着已經排好序的動態鏈接庫名字列表)。
也就是說:當用戶在某個目錄下面創建或拷貝了一個動態鏈接庫,若想使其被系統共享,可以執行一下"ldconfig目錄名"這個命令。此命令的功能在於讓ldconfig將指定目錄下的動態鏈接庫被系統共享起來,即:在緩存文件/etc/ld.so.cache中追加進指定目錄下的共享庫。請注意:如果此目錄不在/lib,/usr/lib及/etc/ld.so.conf文件所列的目錄里面,則再次單獨運行ldconfig時,此目錄下的動態鏈接庫可能不被系統共享了。單獨運行ldconfig時,它只會搜索/lib、/usr/lib以及在/etc/ld.so.conf文件里所列的目錄,用它們來重建/etc/ld.so.cache。
因此,等會兒我們自己開發的共享庫就可以將其拷貝到/lib、/etc/lib目錄里,又或者修改/etc/ld.so.conf文件將我們自己的庫路徑添加到該文件中,再執行ldconfig命令。
動態庫的實戰搞起來
我們有一個頭文件my_so_test.h和三個源文件test_hubei.cpp、test_wuhan.cpp和test_xiaogan.cpp,將他們制作成一個名為libtest.so的動態鏈接庫文件:
頭文件:
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ cat my_so_test.h
#ifndef MY_SO_TEST_H
#define MY_SO_TEST_H
void test_hubei();
void test_wuhan();
void test_xiaogan();
#endif
三個源文件:
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ cat test_hubei.cpp
#include "my_so_test.h"
#include <iostream>
using namespace std;
void test_hubei()
{
cout<<"歡迎來到湖北"<<endl;
}
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ cat test_wuhan.cpp
#include "my_so_test.h"
#include <iostream>
using namespace std;
void test_wuhan()
{
cout<<"歡迎來到武漢"<<endl;
}
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ cat test_wuhan.cpp
#include "my_so_test.h"
#include <iostream>
using namespace std;
void test_wuhan()
{
cout<<"歡迎來到武漢"<<endl;
}
生產.so文件的方法:
方法一:
1、先生成目標.o文件:
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ g++ -c *.cpp
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ ll
總用量 36
drwxrwxr-x 2 xzj xzj 4096 7月 16 17:21 ./
drwxrwxr-x 8 xzj xzj 4096 7月 16 16:26 ../
-rw-rw-r-- 1 xzj xzj 108 7月 16 17:14 my_so_test.h
-rw-rw-r-- 1 xzj xzj 125 7月 16 17:18 test_hubei.cpp
-rw-rw-r-- 1 xzj xzj 2664 7月 16 17:21 test_hubei.o
-rw-rw-r-- 1 xzj xzj 125 7月 16 17:19 test_wuhan.cpp
-rw-rw-r-- 1 xzj xzj 2664 7月 16 17:21 test_wuhan.o
-rw-rw-r-- 1 xzj xzj 127 7月 16 17:20 test_xiaogan.cpp
-rw-rw-r-- 1 xzj xzj 2672 7月 16 17:21 test_xiaogan.o
2、再生成so文件:
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ g++ -shared -fPCI -o libcity.so test_hubei.o test_wuhan.o test_xiaogan.o
g++: error: unrecognized command line option ‘-fPCI’
出現了錯誤,我使用了第二種方法。
方法二:
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ g++ test_hubei.cpp test_wuhan.cpp test_xiaogan.cpp -fPIC -shared -o libtest.so
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ ll
總用量 48
drwxrwxr-x 2 xzj xzj 4096 7月 16 17:37 ./
drwxrwxr-x 8 xzj xzj 4096 7月 16 16:26 ../
-rwxrwxr-x 1 xzj xzj 9048 7月 16 17:37 libtest.so*
-rw-rw-r-- 1 xzj xzj 108 7月 16 17:14 my_so_test.h
-rw-rw-r-- 1 xzj xzj 125 7月 16 17:18 test_hubei.cpp
-rw-rw-r-- 1 xzj xzj 2664 7月 16 17:21 test_hubei.o
-rw-rw-r-- 1 xzj xzj 125 7月 16 17:19 test_wuhan.cpp
-rw-rw-r-- 1 xzj xzj 2664 7月 16 17:21 test_wuhan.o
-rw-rw-r-- 1 xzj xzj 127 7月 16 17:20 test_xiaogan.cpp
-rw-rw-r-- 1 xzj xzj 2672 7月 16 17:21 test_xiaogan.o
動態鏈接庫的使用有兩種方法:既可以在運行時對其進行動態鏈接,又可以動態加載在程序中是用它們
+++動態庫的使用+++
用法一:動態鏈接。
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ g++ main.cpp -o run -L. -ltest
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ ll
總用量 64
drwxrwxr-x 2 xzj xzj 4096 7月 16 18:14 ./
drwxrwxr-x 8 xzj xzj 4096 7月 16 16:26 ../
-rwxrwxr-x 1 xzj xzj 9048 7月 16 17:37 libtest.so*
-rw-rw-r-- 1 xzj xzj 130 7月 16 17:55 main.cpp
-rw-rw-r-- 1 xzj xzj 108 7月 16 17:14 my_so_test.h
-rwxrwxr-x 1 xzj xzj 8688 7月 16 18:14 run*
-rw-rw-r-- 1 xzj xzj 125 7月 16 17:18 test_hubei.cpp
-rw-rw-r-- 1 xzj xzj 2664 7月 16 17:21 test_hubei.o
-rw-rw-r-- 1 xzj xzj 125 7月 16 17:19 test_wuhan.cpp
-rw-rw-r-- 1 xzj xzj 2664 7月 16 17:21 test_wuhan.o
-rw-rw-r-- 1 xzj xzj 127 7月 16 17:20 test_xiaogan.cpp
-rw-rw-r-- 1 xzj xzj 2672 7月 16 17:21 test_xiaogan.o
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ LD_LIBRARY_PATH=. ./run
歡迎來到湖北
歡迎來到武漢
歡迎來到孝感
將main.cpp與libtest.so鏈接成一個可執行文件main。命令如下:
$ g++ main.cpp -o run -L. -ltest
測試可執行程序main是否已經鏈接的動態庫libtest.so,如果列出了libtest.so,那么就說明正常鏈接了。可以執行以下命令:
$ ldd run
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ ldd run
linux-vdso.so.1 => (0x00007ffe08fc7000)
libtest.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f685d1a2000)
/lib64/ld-linux-x86-64.so.2 (0x00007f685d56c000)
如果你直接執行可執行文件run的話就會出現以下錯誤:
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ ./run
./run: error while loading shared libraries: libtest.so: cannot open shared object file: No such file or directory
這里我們注意,ldd的輸出表示我們的libtest.so動態庫沒有找到。因為我們的libtest.so既不在/etc/ld.so.cache里,又不在/lib、/usr/lib或/etc/ld.so.conf所指定的任何一個目錄中
解決辦法:
方法一:LD_LIBRARY_PATH=. 可執行文件
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ LD_LIBRARY_PATH=. ./run
歡迎來到湖北
歡迎來到武漢
歡迎來到孝感
方法二:如果你在開發一款軟件,或者給自己的系統DIY一個非常有用的功能模塊,那么建議你將libtest.so拷貝到/lib、/usr/lib目錄下,
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ sudo cp libtest.so /lib/
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ cd /lib/
xzj@xzj-VirtualBox:/lib$ ls
apparmor hdparm ld-linux.so.2 modules terminfo
brltty i386-linux-gnu libtest.so recovery-mode udev
cpp ifupdown linux-sound-base resolvconf ufw
crda init lsb systemd x86_64-linux-gnu
firmware klibc-k3La8MUnuzHQ0_kG8hokcGAC0PA.so modprobe.d sysvinit xtables
之后在使用ldd命令來看是否使用動態庫時,可以看到成功了
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ ldd run
linux-vdso.so.1 => (0x00007ffe8b3fe000)
libtest.so => /lib/libtest.so (0x00007fe6af1f2000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe6aee28000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fe6aeaa6000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe6af3f4000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fe6ae79d000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fe6ae587000)
直接運行可執行文件時,就成功了!
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ ./run
歡迎來到湖北
歡迎來到武漢
歡迎來到孝感
方法三:動態加載。
動態加載是非常靈活的,它依賴於一套Linux提供的標准API來完成。在源程序里,你可以很自如的運用API來加載、使用、釋放so庫資源。以下函數在代碼中使用需要包含頭文件:dlfcn.h
函數原型 | 說明 |
---|---|
const char *dlerror(void) | 當動態鏈接庫操作函數執行失敗時,dlerror可以返回出錯信息,返回值為NULL時表示操作函數執行成功。 |
void *dlopen(const char *filename, int flag) | 用於打開指定名字(filename)的動態鏈接庫,並返回操作句柄。調用失敗時,將返回NULL值,否則返回的是操作句柄。 |
void *dlsym(void *handle, char *symbol) | 根據動態鏈接庫操作句柄(handle)與符號(symbol),返回符號對應的函數的執行代碼地址。由此地址,可以帶參數執行相應的函數。 |
int dlclose (void *handle) | 用於關閉指定句柄的動態鏈接庫,只有當此動態鏈接庫的使用計數為0時,才會真正被系統卸載。2.2在程序中使用動態鏈接庫函數。 |
dlsym(void *handle, char *symbol)
filename:如果名字不以“/”開頭,則非絕對路徑名,將按下列先后順序查找該文件。
(1)用戶環境變量中的LD_LIBRARY_PATH的值;
(2)動態鏈接緩沖文件/etc/ld.so.cache
(3)目錄/lib,/usr/lib
flag表示在什么時候解決未定義的符號(調用)。取值有兩個:
1) RTLD_LAZY : 表明在動態鏈接庫的函數代碼執行時解決。
2) RTLD_NOW :表明在dlopen返回前就解決所有未定義的符號,一旦未解決,dlopen將返回錯誤。
dlsym(void *handle, char *symbol)
dlsym()的用法一般如下:
void(add)(int x,int y); /說明一下要調用的動態函數add */
add=dlsym("xxx.so","add"); /* 打開xxx.so共享庫,取add函數地址 */
add(89,369); /* 帶兩個參數89和369調用add函數 */
代碼搞起
#include "my_so_test.h"
#include <stdio.h>
#include <dlfcn.h>
#include <cstdlib>
extern "C"
{
void (*fn)(void);
}
int main(int argc, char const *argv[])
{
void *handle = dlopen("./libtest.so",RTLD_LAZY);
/*const char *err = dlerror();
if(err !=NULL){
perror("could not open shared object!");
}*/
if (NULL != handle) {
/* code */
printf("nihao\n");
}
fn = (void (*)(void))dlsym(handle,"test_hubei");
fn();
dlclose(handle);
return 0;
}
執行結果:
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ g++ test_hubei.cpp test_wuhan.cpp test_xiaogan.cpp -fPIC -shared -o libtest.so
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ g++ -o mmain_run mmain.cpp -rdynamic -ldl
xzj@xzj-VirtualBox:~/development_test/dynamic_lib_test$ ./mmain_run
nihao
歡迎來到湖北!
歡迎來到武漢!!
歡迎來到孝感!
每次修改源文件,一定要重新生成動態文件.so,否則一直在用之前生產的文件。不利於調式出現的錯誤!
3、編譯參數
-shared 該選項指定生成動態連接庫(讓連接器生成T類型的導出符號表,有時候也生成弱連接W類型的導出符號),不用該標志外部程序無法連接。相當於一個可執行文件
-fPIC:表示編譯為位置獨立的代碼,不用此選項的話編譯后的代碼是位置相關的所以動態載入時是通過代碼拷貝的方式來滿足不同進程的需要,而不能達到真正代碼段共享的目的。
-L.:表示要連接的庫在當前目錄中
-ltest:編譯器查找動態連接庫時有隱含的命名規則,即在給出的名字前面加上lib,后面加上.so來確定庫的名稱
LD_LIBRARY_PATH:這個環境變量指示動態連接器可以裝載動態庫的路徑。
4、注意的問題
調用動態庫的時候有幾個問題會經常碰到,有時,明明已經將庫的頭文件所在目錄 通過 “-I” include進來了,庫所在文件通過 “-L”參數引導,並指定了“-l”的庫名,但通過ldd命令察看時,就是死活找不到你指定鏈接的so文件,這時你要作的就是通過修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件來指定動態庫的目錄。通常這樣做就可以解決庫無法鏈接的問題了。
在生成動態庫時的參數-fPIC
-fPIC 作用於編譯階段,告訴編譯器產生與位置無關代碼(Position-Independent Code),
則產生的代碼中,沒有絕對地址,全部使用相對地址,故而代碼可以被加載器加載到內存的任意
位置,都可以正確的執行。這正是共享庫所要求的,共享庫被加載時,在內存的位置不是固定的。
gcc -shared -fPIC -o 1.so 1.c
這里有一個-fPIC參數
PIC就是position independent code
PIC使.so文件的代碼段變為真正意義上的共享
如果不加-fPIC,則加載.so文件的代碼段時,代碼段引用的數據對象需要重定位, 重定位會修改代碼段的內容,這就造成每個使用這個.so文件代碼段的進程在內核里都會生成這個.so文件代碼段的copy.每個copy都不一樣,取決於 這個.so文件代碼段和數據段內存映射的位置.
不加fPIC編譯出來的so,是要再加載時根據加載到的位置再次重定位的.(因為它里面的代碼並不是位置無關代碼)
如果被多個應用程序共同使用,那么它們必須每個程序維護一份so的代碼副本了.(因為so被每個程序加載的位置都不同,顯然這些重定位后的代碼也不同,當然不能共享)
我們總是用fPIC來生成so,也從來不用fPIC來生成a.
fPIC與動態鏈接可以說基本沒有關系,libc.so一樣可以不用fPIC編譯,只是這樣的so必須要在加載到用戶程序的地址空間時重定向所有表目.
PIC原理與意義
載入時重定位的缺點:
(1)動態庫的代碼段不能在進程間共享:多個進程加載同一個動態庫到各自不同的地址空間,導致代碼段需要不同的重定位,所以最終每個引用該動態庫的進程擁有一份該動態庫代碼段的不同拷貝。
(2)代碼段必須是可寫的,增加了被攻擊風險。
為了解決載入時重定位的問題,引入了PIC的概念,即位置無關代碼。
PIC實現原理:
(1)GOT:在動態庫的數據段增加GOT(Global Offset Table),該表的每一項是符號到地址的絕對映射。由於代碼段到數據段的偏移是固定的,因此可以在編譯時確定代碼段中的某個符號到GOT特定項之間的偏移。這樣,代碼段中的符號偏移就可以在編譯時確定了,在加載時也無需修改代碼段的內容,只需要填寫位於數據段的GOT的所有項的符號的絕對地址就完成了。因為數據段本來就是進程間不共享,每個進程獨立的一份,因此GOT的設計完全解決了以上兩個問題,從而達到兩個目的:1,代碼段可以在多進程間共享;2,代碼段是只讀的。
(2)PLT:PLT是 Program Linkage Table 的縮寫,即程序鏈接表,PLT的出現是為了延時定位的目的。一個動態庫中的函數往往要遠多於全局變量,並且被調用的函數往往少於定義的函數。GOT中包含了該動態庫中的所有的全局變量的映射,並且在連接器加載時解析所有的全局變量的地址。如果用同樣的方式去處理函數調用符號,則開銷會非常大。因此在代碼段設計了一個PLT表,每一項其實是個代碼段,用於執行如下邏輯:首次訪問時,解析參數和向GOT填寫函數地址,后續訪問直接訪問GOT中的函數地址。如此達到了延時定位的目的。
因此,一個PIC的動態庫中,對全局變量使用GOT來映射,對函數調用使用PLT+GOT來映射,從而達到共享庫代碼段復用,代碼段安全訪問的目的。而這些就是 PIC 的意義。
在可執行程序中動態庫和靜態庫的區別?靜態鏈接、動態鏈接?各自有什么缺點?
參考鏈接:
Linux系統中“動態庫”和“靜態庫”那點事兒
gcc編譯參數-fPIC
gcc/g++編譯選項:-fPIC