1. 程式人生 > >Linux庫檔案詳解

Linux庫檔案詳解

源自:

https://www.cnblogs.com/yangg518/p/5842651.html

轉自:

http://www.cppblog.com/deane/articles/165216.html

http://blog.sciencenet.cn/blog-1225851-904348.html

http://www.pchou.info/linux/2016/07/17/linux-libraries.html

一、基本概念

1.1什麼是庫

在windows平臺和linux平臺下都大量存在著庫。

本質上來說庫是一種可執行程式碼的二進位制形式,可以被作業系統載入記憶體執行。

由於windows和linux的平臺不同(主要是編譯器、彙編器和聯結器的不同),因此二者庫的二進位制是不相容的。

本文僅限於介紹linux下的庫。

1.2庫的種類

linux下的庫有兩種:靜態庫和共享庫(動態庫)。

二者的不同點在於程式碼被載入的時刻不同。

靜態庫的程式碼在編譯過程中已經被載入可執行程式,因此體積較大。

共享庫的程式碼是在可執行程式執行時才載入記憶體的,在編譯過程中僅簡單的引用,因此程式碼體積較小。

1.3庫存在的意義

庫是別人寫好的現有的,成熟的,可以複用的程式碼,你可以使用但要記得遵守許可協議。

現實中每個程式都要依賴很多基礎的底層庫,不可能每個人的程式碼都從零開始,因此庫的存在意義非同尋常。

共享庫的好處是,不同的應用程式如果呼叫相同的庫,那麼在記憶體裡只需要有一份該共享庫的例項。

1.4庫檔案是如何產生的在linux下

靜態庫的字尾是.a,它的產生分兩步

Step 1.由原始檔編譯生成一堆.o,每個.o裡都包含這個編譯單元的符號表

Step 2.ar命令將很多.o轉換成.a,成為靜態庫

動態庫的字尾是.so,它由gcc加特定引數編譯產生。

具體方法參見後文例項。

1.5庫檔案是如何命名的,有沒有什麼規範

在linux下,庫檔案一般放在/usr/lib和/lib下,

靜態庫的名字一般為libxxxx.a,其中xxxx是該lib的名稱

動態庫的名字一般為libxxxx.so.major.minor,xxxx是該lib的名稱,major是主版本號, minor是副版本號

1.6如何知道一個可執行程式依賴哪些庫

ldd命令可以檢視一個可執行程式依賴的共享庫,

例如# ldd /bin/lnlibc.so.6

=> /lib/libc.so.6 (0×40021000)/lib/ld-linux.so.2

=> /lib/ld- linux.so.2 (0×40000000)

可以看到ln命令依賴於libc庫和ld-linux庫

1.7可執行程式在執行的時候如何定位共享庫檔案

當系統載入可執行程式碼時候,能夠知道其所依賴的庫的名字,但是還需要知道絕對路徑。

此時就需要系統動態載入器(dynamic linker/loader)

對於elf格式的可執行程式,是由ld-linux.so*來完成的,它先後搜尋elf檔案的 DT_RPATH段—環境變數LD_LIBRARY_PATH—/etc/ld.so.cache檔案列表—/lib/,/usr/lib目錄找到庫檔案後將其載入記憶體

如:export LD_LIBRARY_PATH=’pwd’

將當前檔案目錄新增為共享目錄

1.8在新安裝一個庫之後如何讓系統能夠找到他

如果安裝在/lib或者/usr/lib下,那麼ld預設能夠找到,無需其他操作。

如果安裝在其他目錄,需要將其新增到/etc/ld.so.cache檔案中,步驟如下

1.編輯/etc/ld.so.conf檔案,加入庫檔案所在目錄的路徑

2.執行ldconfig,該命令會重建/etc/ld.so.cache檔案

二、用gcc生成靜態和動態連結庫的示例

我們通常把一些公用函式製作成函式庫,供其它程式使用。

函式庫分為靜態庫和動態庫兩種。

靜態庫在程式編譯時會被連線到目的碼中,程式執行時將不再需要該靜態庫。

動態庫在程式編譯時並不會被連線到目的碼中,而是在程式執行是才被載入,因此在程式執行時還需要動態庫存在。

本文主要通過舉例來說明在Linux中如何建立靜態庫和動態庫,以及使用它們。

為了便於闡述,我們先做一部分準備工作。

2.1準備好測試程式碼hello.h、hello.c和main.c;

hello.h(見程式1)為該函式庫的標頭檔案。

hello.c(見程式2)是函式庫的源程式,其中包含公用函式hello,該函式將在螢幕上輸出”Hello XXX!”。

main.c(見程式3)為測試庫檔案的主程式,在主程式中呼叫了公用函式hello。

 程式1: hello.h

#ifndef HELLO_H 
#define HELLO_H 
  
void hello(const char *name); 
  
#endif

程式2:hello.c

#include <stdio.h> 
void hello(const char *name) { 

        printf(“Hello %s!\n”, name); 
}

程式3:main.c

#include “hello.h” 
 int main() 
 { 
     hello(“everyone”); 
     return 0; 
 }

2.2問題的提出

注意:這個時候,我們編譯好的hello.o是無法通過gcc –o 編譯的,這個道理非常簡單,

hello.c是一個沒有main函式的.c程式,因此不夠成一個完整的程式,如果使用gcc –o 編譯並連線它,GCC將報錯。

無論靜態庫,還是動態庫,都是由.o檔案建立的。因此,我們必須將源程式hello.c通過gcc先編譯成.o檔案。

這個時候我們有三種思路:

1)  通過編譯多個原始檔,直接將目的碼合成一個.o檔案。

2)  通過建立靜態連結庫libmyhello.a,使得main函式呼叫hello函式時可呼叫靜態連結庫。

3)  通過建立動態連結庫libmyhello.so,使得main函式呼叫hello函式時可呼叫靜態連結庫。

2.3思路一:編譯多個原始檔

在系統提示符下鍵入以下命令得到hello.o檔案。

# gcc -c hello.c

為什麼不使用gcc–o hello hello.cpp 這個道理我們之前已經說了,使用-c是什麼意思呢?這涉及到gcc 編譯選項的常識。

我們通常使用的gcc –o 是將.c原始檔編譯成為一個可執行的二進位制程式碼(-o選項其實是制定輸出檔案檔名,如果不加-c選項,gcc預設會編譯連線生成可執行檔案,檔案的名稱有-o選項指定),這包括呼叫作為GCC內的一部分真正的C編譯器(ccl),以及呼叫GNU C編譯器的輸出中實際可執行程式碼的外部GNU彙編器(as)和聯結器工具(ld)。

而gcc –c是使用GNU彙編器將原始檔轉化為目的碼之後就結束,在這種情況下,只調用了C編譯器(ccl)和彙編器(as),而聯結器(ld)並沒有被執行,所以輸出的目標檔案不會包含作為Linux程式在被裝載和執行時所必須的包含資訊,但它可以在以後被連線到一個程式。

我們執行ls命令看看是否生存了hello.o檔案。

# ls

hello.c hello.h hello.o main.c

在ls命令結果中,我們看到了hello.o檔案,本步操作完成。

同理編譯main

#gcc –c main.c

將兩個檔案連結成一個.o檔案。

#gcc –o hello hello.o main.o

執行

# ./hello

Hello everyone!

完成^ ^!

2.4思路二:靜態連結庫

下面我們先來看看如何建立靜態庫,以及使用它。

靜態庫檔名的命名規範是以lib為字首,緊接著跟靜態庫名,副檔名為.a。例如:我們將建立的靜態庫名為myhello,則靜態庫檔名就是libmyhello.a。在建立和使用靜態庫時,需要注意這點。建立靜態庫用ar命令。

在系統提示符下鍵入以下命令將建立靜態庫檔案libmyhello.a。

# ar rcs libmyhello.a hello.o

我們同樣執行ls命令檢視結果:

# ls

hello.c  hello.h hello.o libmyhello.a main.c

ls命令結果中有libmyhello.a。

靜態庫製作完了,如何使用它內部的函式呢?只需要在使用到這些公用函式的源程式中包含這些公用函式的原型宣告,然後在用gcc命令生成目標檔案時指明靜態庫名,gcc將會從靜態庫中將公用函式連線到目標檔案中。注意,gcc會在靜態庫名前加上字首lib,然後追加副檔名.a得到的靜態庫檔名來查詢靜態庫檔案,因此,我們在寫需要連線的庫時,只寫名字就可以,如libmyhello.a的庫,只寫:-lmyhello

在程式3:main.c中,我們包含了靜態庫的標頭檔案hello.h,然後在主程式main中直接呼叫公用函式hello。下面先生成目標程式hello,然後執行hello程式看看結果如何。

# gcc -o hello main.c -static -L. -lmyhello

# ./hello

Hello everyone!

我們刪除靜態庫檔案試試公用函式hello是否真的連線到目標檔案 hello中了。

# rm libmyhello.a

rm: remove regular file `libmyhello.a’? y

# ./hello

Hello everyone!

程式照常執行,靜態庫中的公用函式已經連線到目標檔案中了。

靜態連結庫的一個缺點是,如果我們同時運行了許多程式,並且它們使用了同一個庫函式,這樣,在記憶體中會大量拷貝同一庫函式。這樣,就會浪費很多珍貴的記憶體和儲存空間。使用了共享連結庫的Linux就可以避免這個問題。

共享函式庫和靜態函式在同一個地方,只是字尾有所不同。比如,在一個典型的Linux系統,標準的共享數序函式庫是/usr/lib/libm.so。

當一個程式使用共享函式庫時,在連線階段並不把函式程式碼連線進來,而只是連結函式的一個引用。當最終的函式匯入記憶體開始真正執行時,函式引用被解析,共享函式庫的程式碼才真正匯入到記憶體中。這樣,共享連結庫的函式就可以被許多程式同時共享,並且只需儲存一次就可以了。共享函式庫的另一個優點是,它可以獨立更新,與呼叫它的函式毫不影響。

2.5思路三、動態連結庫(共享函式庫)

我們繼續看看如何在Linux中建立動態庫。我們還是從.o檔案開始。

動態庫檔名命名規範和靜態庫檔名命名規範類似,也是在動態庫名增加字首lib,但其副檔名為.so。例如:我們將建立的動態庫名為myhello,則動態庫檔名就是libmyhello.so。用gcc來建立動態庫。

在系統提示符下鍵入以下命令得到動態庫檔案libmyhello.so。

# gcc -shared -fPIC -o libmyhello.so hello.o

 “PIC”命令列標記告訴GCC產生的程式碼不要包含對函式和變數具體記憶體位置的引用,這是因為現在還無法知道使用該訊息程式碼的應用程式會將它連線到哪一段記憶體地址空間。這樣編譯出的hello.o可以被用於建立共享連結庫。建立共享連結庫只需要用GCC的”-shared”標記即可。

我們照樣使用ls命令看看動態庫檔案是否生成。

# ls

hello.cpp hello.h hello.o libmyhello.so main.cpp

呼叫動態連結庫編譯目標檔案。

在程式中使用動態庫和使用靜態庫完全一樣,也是在使用到這些公用函式的源程式中包含這些公用函式的原型宣告,然後在用gcc命令生成目標檔案時指明動態庫名進行編譯。我們先執行gcc命令生成目標檔案,再執行它看看結果。

如果直接用如下方法進行編譯,並連線:

# gcc -o hello main.c -L. -lmyhello

(使用”-lmyhello”標記來告訴GCC驅動程式在連線階段引用共享函式庫libmyhello.so。”-L.”標記告訴GCC函式庫可能位於當前目錄。否則GNU聯結器會查詢標準系統函式目錄:它先後搜尋1.elf檔案的 DT_RPATH段—2.環境變數LD_LIBRARY_PATH—3./etc/ld.so.cache檔案列表—4./lib/,/usr/lib目錄找到庫檔案後將其載入記憶體,但是我們生成的共享庫在當前資料夾下,並沒有加到上述的4個路徑的任何一箇中,因此,執行後會出現錯誤)

# ./hello

./hello: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory

#

錯誤提示,找不到動態庫檔案libmyhello.so。程式在執行時,會在/usr/lib和/lib等目錄中查詢需要的動態庫檔案。若找到,則載入動態庫,否則將提示類似上述錯誤而終止程式執行。有多種方法可以解決,

(1)我們將檔案 libmyhello.so複製到目錄/usr/lib中,再試試。

# mv libmyhello.so /usr/lib

# ./hello

成功!

(2)既然聯結器會搜尋LD_LIBRARY_PATH所指定的目錄,那麼我們可以將這個環境變數設定成當前目錄:

先執行:

export LD_LIBRARY_PATH=$(pwd)

再執行:

./hello

成功!

(3)

執行:  

ldconfig   /usr/zhsoft/lib     
       
      
  注:   當用戶在某個目錄下面建立或拷貝了一個動態連結庫,若想使其被系統共享,可以執行一下”ldconfig   目錄名”這個命令.此命令的功能在於讓ldconfig將指定目錄下的動態連結庫被系統共享起來,意即:在快取檔案/etc/ld.so.cache中追加進指定目錄下的共享庫.本例讓系統共享了/usr/zhsoft/lib目錄下的動態連結庫.該命令會重建/etc/ld.so.cache檔案

成功!

¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥

下面的這個錯誤我沒有遇到,不過也記錄下,給遇到的人:

 {  這步後我沒有成功,報錯內容如下:/hello: error while loading shared libraries: /usr/lib/libmyhello.so: cannot restore segment prot after reloc: Permission denied

google了一下,發現是SELinux搞的鬼,解決辦法有兩個:

1.
    chcon -t texrel_shlib_t   /usr/lib/libmyhello.so
    (chcon -t texrel_shlib_t “你不能share的庫的絕對路徑”)

2.
    #vi /etc/sysconfig/selinux file
    或者用
    #gedit /etc/sysconfig/selinux file
    修改SELINUX=disabled
    重啟

}

#

¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥

這也進一步說明了動態庫在程式執行時是需要的。

可以檢視程式執行時呼叫動態庫的過程:

# ldd hello
執行 test,可以看到它是如何呼叫動態庫中的函式的。
[[email protected] 20090505]$ ldd hello
        linux-gate.so.1 => (0x00110000)
        libmyhello.so => /usr/lib/libmyhello.so (0x00111000)
        libc.so.6 => /lib/libc.so.6 (0x00859000)
        /lib/ld-linux.so.2 (0x0083a000)

我們回過頭看看,發現使用靜態庫和使用動態庫編譯成目標程式使用的gcc命令完全一樣,
那當靜態庫和動態庫同名時,gcc命令會使用哪個庫檔案呢?抱著對問題必究到底的心情,
來試試看。

先刪除除.c和.h外的所有檔案,恢復成我們剛剛編輯完舉例程式狀態。

# rm -f hello hello.o /usr/lib/libmyhello.so

# ls

hello.c hello.h main.c

#

在來建立靜態庫檔案libmyhello.a和動態庫檔案libmyhello.so。

# gcc -c hello.c

# ar rcs libmyhello.a hello.o

# gcc -shared -fPCI -o libmyhello.so hello.o

# ls

hello.c hello.h hello.o libmyhello.a libmyhello.so main.c

#

通過上述最後一條ls命令,可以發現靜態庫檔案libmyhello.a和動態庫檔案libmyhello.so都已經生成,並都在當前目錄中。然後,我們執行gcc命令來使用函式庫myhello生成目標檔案hello,並執行程式 hello。

# gcc -o hello main.c -L. -lmyhello

# ./hello

./hello: error while loading shared libraries: libmyhello.so: cannot open shar
ed object file: No such file or directory

#

從程式hello執行的結果中很容易知道,當靜態庫和動態庫同名時, gcc命令將優先使用動態庫。

Note:

編譯引數解析


最主要的是GCC命令列的一個選項:
-shared 該選項指定生成動態連線庫(讓聯結器生成T型別的匯出符號表,有時候也生成弱連線W型別的匯出符號),不用該標誌外部程式無法連線。相當於一個可執行檔案
l -fPIC:表示編譯為位置獨立的程式碼,不用此選項的話編譯後的程式碼是位置相關的所以動態載入時是通過程式碼拷貝的方式來滿足不同程序的需要,而不能達到真正程式碼段共享的目的。
l -L.:表示要連線的庫在當前目錄中
l -ltest:編譯器查詢動態連線庫時有隱含的命名規則,即在給出的名字前面加上lib,後面加上.so來確定庫的名稱
l LD_LIBRARY_PATH:這個環境變數指示動態聯結器可以裝載動態庫的路徑。
l 當然如果有root許可權的話,可以修改/etc/ld.so.conf檔案,然後呼叫 /sbin/ldconfig來達到同樣的目的,不過如果沒有root許可權,那麼只能採用輸出LD_LIBRARY_PATH的方法了。

呼叫動態庫的時候有幾個問題會經常碰到,有時,明明已經將庫的標頭檔案所在目錄 通過 “-I” include進來了,庫所在檔案通過 “-L”引數引導,並指定了“-l”的庫名,但通過ldd命令察看時,就是死活找不到你指定連結的so檔案,這時你要作的就是通過修改 LD_LIBRARY_PATH或者/etc/ld.so.conf檔案來指定動態庫的目錄。通常這樣做就可以解決庫無法連結的問題了。


靜態庫連結時搜尋路徑順序:

1. ld會去找GCC命令中的引數-L

2. 再找gcc的環境變數LIBRARY_PATH

3. 再找內定目錄 /lib /usr/lib /usr/local/lib 這是當初compile gcc時寫在程式內的

動態連結時、執行時搜尋路徑順序:


1.  編譯目的碼時指定的動態庫搜尋路徑;

2.  環境變數LD_LIBRARY_PATH指定的動態庫搜尋路徑;

3.  配置檔案/etc/ld.so.conf中指定的動態庫搜尋路徑;

4. 預設的動態庫搜尋路徑/lib;

5. 預設的動態庫搜尋路徑/usr/lib。

有關環境變數:

LIBRARY_PATH環境變數:指定程式靜態連結庫檔案搜尋路徑

LD_LIBRARY_PATH環境變數:指定程式動態連結庫檔案搜尋路徑

相關推薦

Linux檔案

源自: https://www.cnblogs.com/yangg518/p/5842651.html 轉自: http://www.cppblog.com/deane/articles/165216.html http://blog.sciencenet.cn/blog-1225851-904348.ht

linux .so檔案

建立相應的符號連線: ln –s liberr.so.1.0.0 liberr.so.1; ln –s liberr.so.1.0.0 liberr.so; 在MAKEFILE中: [email protected]    表示規則中的目標檔案集。在模式規則中,如果有多個目標,那麼,"[email&

深入探討Linux靜態與動態(轉)

share 分享 命名 one .com 過程 程序 簡單介紹 mage 2.生成動態庫並使用 linux下編譯時通過 -shared 參數可以生成動態庫(.so)文件,如下 庫從本質上來說是一種可執行代碼的二進制格式,可以被載入內存中執行。庫分靜態庫和動態庫兩種。

Linux下oracle12c數據安裝

shadow follow glibc 格式化磁盤 www ech etc 12c 接收 簡介: oracle12c概述 oracle12c數據庫屬於關系型數據庫,采用C/S模式、支持SQL語言,穩定性、高性能、安全性優於其他官方網站: https://www.oracle

Linux(CentOS)開機自動掛載與fstab檔案

摘要: Linux中我們分完區,並做好檔案系統格式化,掛載(mount)之後才可以使用磁碟裝置。/etc/fstab是用來存放檔案系統的靜態資訊的檔案, 當系統啟動的時候,系統會自動地從這個檔案讀取資訊,並且會自動將此檔案中指定的檔案系統掛載到指定的目錄。 Linux中我們分完區,並做

Red Hat Enterprise Linux(RHEL)中yum的repo檔案

Yum(全稱為 Yellow dog Updater, Modified)是一個在Fedora和RedHat以及CentOS中的Shell前端軟體包管理器。基於RPM包管理,能夠從指定的伺服器自動下載RPM包並且安裝,可以自動處理依賴性關係,並且一次安裝所有依賴的軟體包,無須繁瑣地一次次下載、安裝。 使

Linux標頭檔案

標頭檔案主目錄include 標頭檔案目錄中總共有32個.h標頭檔案。其中主目錄下有13個,asm子目錄中有4個,Linux子目錄中有10個,sys子目錄中有5個。這些標頭檔案各自的功能如下,具體的作用和所包含的資訊請參見第14章。 <a.out.h>:a.out標頭檔案,定義了

linux /etc/resolv.conf /etc/hosts配置檔案

/etc/resolv.conf 該檔案是由域名解析器(resolver,一個根據主機名解析IP地址的庫)使用的配置檔案該檔案是DNS域名解析的配置檔案,它的格式很簡單,每行以一個關鍵字開頭,後接配置引數。resolv.conf的關鍵字主要有四個,分別是:nameserver  

Linux Makefile與Kconfig檔案

本文章介紹了makefile跟kconfig檔案,包括編譯過程與makefile編碼規則。    編譯過程:     我們在進行linux核心配置的時候經常會執行make menuconfig這個命令,然後螢幕上會出現以下介面: &n

linux 下的連結檔案

轉載來自:http://linux.chinaunix.net/techdoc/beginner/2009/08/12/1129972.shtml 轉載來自:(這個哥們加工了的,各種顏色,美化)http://www.cnblogs.com/li-hao/p/4107964.html

Linux /etc/profile檔案

linux /etc/profile檔案的改變會涉及到系統的環境,也就是有關Linux環境變數的東西,學習Linux要了解Linux profile檔案的相關原理,這裡對則以檔案進行具體分析。這裡修改會對所有使用者起作用。   1、Linux是一個多使用者的作業系統。每

linux檔案系統 /etc/resolv.conf 檔案

大家好,今天51開源給大家介紹一個在配置檔案,那就是/etc/resolv.conf。很多網友對此檔案的用處不太瞭解。其實並不複雜,它是DNS客戶機配置檔案,用於設定DNS伺服器的IP地址及DNS域名,還包含了主機的域名搜尋順序。該檔案是由域名解析器(resol

Linux --- SSH遠端登陸配置sshd_config檔案

ssh是linux遠端登入的安全協議,是 C/S 模式的架構,配置檔案分為伺服器端配置檔案 [/etc/ssh/sshd_config] 與客戶端配置檔案預設配置檔案[/etc/ssh/ssh_config] 使用者配置檔案[~/.ssh/config]  sshd_conf

linux中C語言標頭檔案

linux中C程式標頭檔案的種類 一類:#include<stdio.h> stdio.h檔案就在/usr/include目錄下 二類:#include<arpa/inet.h> arpa/是/usr/include目錄下的子目錄,inet.h其實是/usr/include

Linux 檔案

Linux下的壓縮解壓縮命令詳解及例項 例項:壓縮伺服器上當前目錄的內容為xxx.zip檔案 zip -r xxx.zip ./* 解壓zip檔案到當前目錄 unzip filename.zip ============================ 另:有些伺

linux 網路涉及的所有配置檔案

linux 網路涉及的所有配置檔案詳解 Linux 為 配 置 網 絡 提 供 了 許 多 工 具 , 其 中 有 圖 形 界 面 的 ( 如NetworkManager1)、也有偽圖形介面(如 system-config-network 2)的。雖然使用這些工具來配置網路會很方便,但是由於各個發行版本的

linux逆向分析之ELF檔案

前言 首先如果大家遇到ELF二進位制檔案的逆向首先考慮的可能就是通過IDA進行靜態逆向分析演算法,那麼我們首先就要了解ELF(Executable and Linking Format)的檔案格式。 ELF檔案格式主要分為以下幾類: 1. 可重定位檔案(Relocatable File),這類檔

linux,centos7.2,/etc/rsyslog.conf檔案

linux系統自動的log功能,目前由rsyslog服務代管,應該說rsyslog 是syslog 的升級版。 [[email protected] ~]# cat /etc/rsyslog.conf # rsyslog configuration f

Linux網路配置檔案

Redhat Linux的網路配置,基本上是通過修改幾個配置檔案來實現的,雖然也可以用ifconfig來設定IP,用route來配置預設閘道器,用hostname來配置主機名,但是重啟後會丟失。 Linux中網路相關的主要的幾個配置檔案為: /ect/hosts配置主機名(

關於MD5 32位和16位的區別以及linux /etc/shadow 檔案

有人說md5,128位,32位,16位,到底md5多長? md5的長度,預設為128bit,也就是128個0和1的二進位制串。 這樣表達是很不友好的。 所以將二進位制轉成了16進位制,每4個bit表示一個16進位制, 所以128/4 = 32 換成16進製表示後