庫的創建與使用

 當一段代碼在一個程序中被多次使用的時候,我們可以把他寫成函數來調用,當一類代碼被多個程序重複使用的時候,我們就可以將其組建成一個庫,來實現對這類代碼的重複使用。
在對庫進行修改的時候應該考慮其兼容性,也就是說,依賴於就的庫的軟件在將庫更新後,這個軟件還是可以使用的,不能因爲庫的更新導致軟件的運行失敗,否則的話,如果這個庫非常的重要,那後果將會是災難性的。那就意味着軟件要重寫。聽別人說,linux的libc5過渡到libc6的時候是多麼的麻煩,因爲以來libc5的軟件不能運行在libc6的庫上,導致許多軟件需要重新編譯甚至重寫。但是,這不能說,庫不能被修改,而是說,在對庫的修改和升級的時候要注意兼容。
對於庫的命名,這裏有些約定,但很簡單,首先,所有的庫都要以lib開頭,gcc就依賴於這個約定,當你使用-l這個選項的時候,就會被默認的加上lib這個字符串。還有,以.a結尾表示靜態庫,以.so結尾的表示是共享庫。另外還有就是編號的約定,一般格式爲:庫名.主版本號.次版本號.補丁級別號。對於編號的增加,約定是:若庫達到了不能和前一個版本兼容了,就要升級主版本號,如果僅是增加了新的功能,但有和以前的版本兼容的話,那就是升級次版本號,若只是修正錯誤,則只需要升級補丁的級別。還有一類很特殊的庫,他們以_p或者_g來結尾,通常,以_g結尾的庫是調試庫,編入了特殊的符號和功能,能夠增加採用這個庫的程序的調試能力,_p表示代碼剖析庫,他們包含的代碼和符號能夠進行復雜的代碼剖析和性能分析。記住,當你使用這些特殊的庫完成了程序的調試的時候,就要用,正常的庫重新將軟件進行編譯。

對庫的操作:nm, ar, ldd, ldconfig
    nm命令
        nm命令用來列出目標文件或者二進制文件所有的符號,他可以用來查看程序到底調用了什麼函數,還能用來查看,某一個庫中有沒有我們所需要的函數。
        nm [option(s)] [file(s)]
        當你不指定文件的時候,nm就會尋找但前目錄現有沒有a.out文件,若有,則會將該文件作爲要查看的文件,否則他會提示
            nm: 'a.out': No such file
        例如:
            #include <stdio.h>

            int i = 5;     
            char s;
            const C = '0';

            void fun(void)
            {
                    printf("hello");
            }
        gcc -c 1.c後將會生成一個1.o的文件。然後我們利用nm工具來查看。
            nm 1.o
            00000000 R C
            00000000 T fun
            00000000 D i  /*有一點不明白就是如果i被初始化爲0,則他顯示的是B,爲初始化的*/
                       U printf
            00000001 C s
        其中的大寫字母表示符號的種類,下表給出了他們的意思:
            A  The symbol’s value is absolute, and will not be changed by further linking.
            B  The symbol is in the uninitialized data section (known as BSS)
            C  The symbol is common
            D  The symbol is in the initialized data section.
            G  The symbol is in an initialized data section for small objects.
            I       The symbol is an indirect reference to another symbol
            N   The symbol is a debugging symbol
            R   The symbol is in a read only data section
            S   The symbol is  in  an  uninitialized  data  section  for  small objects
            T   The symbol is in the text (code) section
            U   The symbol is undefined.
            V   The  symbol  is  a  weak object.
            W   The  symbol  is  a  weak  symbol that has not been specifically tagged as a weak object symbol.
            -   The symbol is a stabs symbol in an a.out object file
        還有幾個比較常用的選項:
            -A  列出符號名的同時列出他來自那個文件。
            -a  列出所有符號,包括調試符號
            -l  列出對應的行號
            -n  根據符號的地址來排序,默認是按名稱來排的
            -u  只列出未定義的符號(同--undefined-only,反--defined-only)
    ar命令
        ar命令使用來建立或修改備份文件,或是從備份文件中抽取文件,當然,也可以把他用在庫的創建與修改上,他可以把多個文件按照一定的組織結構組成一個備份文件,而且所有的文件即使在備份文件中,仍然保存着其原來的權限與屬性。組成他的文件稱爲他的member。
        ar [emulation options] [-]{dmpqrstx}[abcfilNoPsSuvV] [member-name] [count] archive-file file...
            ar -M [<mri-script]
        常用的選項:
        d   從庫中刪除模塊
        m   在一個庫中移動成員
        p   顯示庫中指定的成員到標準輸出中。
        q   快速追加,增加模塊到庫的結尾處,並不見查是否替換
        r   在庫中插入模塊
        t   顯示庫的模塊的清單
        x   提取庫中的成員,若不指定,則提取所有的成員
        a   在庫中一個已存在的成員的後面添加一個文件
        b   在庫中一個已存在的成員的前面添加一個文件
        c   創建一個庫,不論庫是否已經存在
        f   在庫中截斷指定的名字
        i   類似b
        N   與count參數一起使用,在庫中,有多個相同文件名的時候,指定提取或輸出的個數。
        o   當提取成員時,保留成員的原始數據
        P   進行文件明匹配時使用全路徑名
        s   寫入一個目標文件索引到庫中或者更新一個存在的目標文件索引。
        S   不創建目標文件索引
        u   ?
        例如:我們有如下幾個程序:
        /*File 1.c*/
        #include "test.h"
        #include <stdio.h>

        void p1(void)
        {
                printf("We are in p1 function\n");
        }
        /*File 2.c*/
        #include "test.h"
        #include <stdio.h>
   
        void p2(void)
        {
                printf("We are in p2 function\n");
        }
        /*File test.h*/
        #ifndef _TEST_
        #define _TEST_

        void p1(void);
        void p2(void);

        #endif
        /*File 3.c*/
        #include "test.h"
        #include <stdio.h>

        int main(void)
        {
                printf("We are in main function\n");
                p1();
                p2();
                return 0;
        }
        他們之間的關係是:3.c掉用了1.c和2.c中的p1,p2函數,首先要編譯1.c 2.c:
        $gcc -c 1.c
        $gcc -c 2.c
        然後建立一個庫,將產生的1.o 2.o放到庫裏面去:
        $ar -rcs libtest.a 1.o 2.o
        這就行了,然後使用gcc編譯並連接3.c
        $gcc 3.c -static -L. -l test
        我們使用-static是爲了將libtest.a和他連接起來,執行:
        $./a.out
        We are in main function
        We are in p1 function
        We are in p2 function
        很明顯,程序發的運行結果說明了,我們已經使用libtest.a這個庫中的p1,p2函數。
    ldd命令
        將會列出爲使程序正常運行所需要的共享庫。
            ldd [options] file
            常用的選項,
                -d  執行重定位,並報告所有丟失的函數
                -r  執行對函數和數據對象的重定位,並報告丟失的任何函數或數據
    ldconfig命令:
        他是一個動態連接庫的管理命令,能使動態連接庫爲系統所共享,他的工作是,在默認搜索目錄也就是/lib和/usr/lib還有動態庫配置文件/etc/ld.so.conf內所記錄的目錄中,搜索動態連接庫,來創建動態裝入程序ld.so所需的連接和緩存文件(/etc/ld.so.cache(保存已排序的動態連接庫名)),程序連接時首先在cache中找,之後在到ld.so.conf找詳細的路徑。
    共享庫的編寫
        創建共享庫的辦法,和創建靜態庫的辦法有點不同,我們仍然拿上面的程序當作例子:
        1.編譯1.o 2.o,注意,這裏有不同的地方,再用gcc編譯是,要用上-fPIC的選項,這能產生無關的代碼,並能被加載到任何地址。
        $gcc -fPIC -c 1.c
        $gcc -fPIC -c 2.c
        2.用ar創建庫,注意,這個時候的文件名有所改變
        ar -rcs libtest.so 1.o 2.o
        3.使用gcc的-shared的選項和-soname選項,用-Wl選項把參數傳遞連接器ld,使用-l選項顯示的鏈接C庫,以保證得到所需的啓動(startup)代碼,從而避免程序在使用不同的,可能不兼容版本的C庫的系統上不能運行。
        $ gcc -g -shared -Wl,-soname,libtest.so -o libtest.so.1.0.0 1.o 2.o -lc
        之後會產生一個libtest.so.1.0.0文件,
        我們只是爲了測試並不想 把他安裝到系統上面,所以要爲其建立連接:(用於soname)
        $ ln -s libtest.so.1.0.0 libtest.so.1
        另一個是鏈接程序在使用-l test連接到libtest使用的:
        $ ln -s libtest.so.1.0.0 libtest.so
        爲了使用剛纔建立的共享庫,我們需要將libtest.so拷貝到/usr/lib的目錄下面:
        $cp libtest.so /usr/lib
        然夠編譯3.c:
        $gcc 3.c -l test
        $./a.out
        We are in main function
        We are in p1 function
        We are in p2 function  
        還有一種方法是把他的路徑放到/etc/ld.so.conf文件中,然後以root的身份運行一下ldconfig命令,要說的是,ldconfig在/sbin中。
    dl接口
        加載共享對象:
            void    *   dlopen  (const char *filename, int flag);
        他會以flag指定的模式加載filename指定的對象,若filename是NULL,則dlopen打開當前執行的文件,如果是一個絕對的路徑名,dlopen就會打開那個文件,如果僅僅是一個文件名,則dlopen會以下面給定的順序搜索下列目錄,查找文件$LD_ELF_LIBRARY_PATH:
            $LD_LIBRARY_PATH, /etc/ld.so.cache, /usr/lib/, /lib
        參數flag:
            RTLD_LAZY : 來自被加載的對象的符號在被調用時解析
            RTLD_NOW : 來自被加載的對象的所有符號在函數dlopen返回前解析
            如果他們的其中一個 | RTDL_GLOBAL 就會導致導出所有的符號,就像他們被直接鏈接一樣;
        另外,當dlopen成功返回時,返回的是一個句柄,否則返回NULL;
        使用共享對象:
            void *dlsym(void *handle, const char *symbol);
        dlsym在handle中搜索symbol,失敗返回NULL;
    錯誤檢查
            char *dlerror(void);
        返回描述最近的發生錯誤的字符串,沒錯時返回NULL;
    卸載共享對象:
        int dlclose(void *handle);
    順便說一下,他們所在的頭文件是dlfcn.h
    例如:
        #include <stdio.h>
        #include <dlfcn.h>

        int main(void)
        {
                void * handle;
                void (*pfun)(void);
                char *err;
       
                printf("we are in function main\n");
                if (NULL == (handle = dlopen("./libtest.so",RTLD_NOW)))
                {
                        printf("Failed open libteat\n");
                        return 1;
                }
                dlerror();
                pfun = dlsym(handle,"p1");
                printf("%s\n",((err = dlerror()) == NULL) ? "get p1" : "err");
                (*pfun)();
                pfun = dlsym(handle,"p2");
                printf("%s\n",((err = dlerror()) == NULL) ? "get p2" : "err"); 
                (*pfun)();
                dlclose(handle);
                return 0;
        }
    將其保存爲4.c後:
    $gcc -Wall 4.c -l dl
    $./a.out
    we are in function main
    get p1
    We are in p1 function
    get p2
    We are in p2 function
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章