歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux基礎知識 >> linux動態庫編譯和使用詳細剖析

linux動態庫編譯和使用詳細剖析

日期:2017/3/2 17:15:37   编辑:Linux基礎知識

引言

重點講述linux上使用gcc編譯動態庫的一些操作.並且對其深入的案例分析.
最後介紹一下動態庫插件技術, 讓代碼向後兼容.關於linux上使用gcc基礎編譯,

預編譯,編譯,生成機械碼最後鏈接輸出可執行文件流程參照下面.

  gcc編譯流程

而本文重點是分析動態庫相關的知識點. 首先看需要用到的測試素材

heoo.h

#ifndef _H_HEOO
#define _H_HEOO

/*
 * 測試接口,得到key內容
 *      : 返回key的字符串
 */
extern const char* getkey(void);

/*
 * 測試接口,得到value內容
 * arg      : 傳入的參數
 *          : 返回得到的結果
 */
extern void* getvalue(void* arg);

#endif // !_H_HEOO

heoo-getkey.c

#include "heoo.h"

/*
 * 測試接口,得到key內容
 *      : 返回key的字符串
 */
const char*
getkey(void) {
     return "heoo-getkey.c getkey";
}

heoo-getvalue.c

#include "heoo.h"
#include <stdio.h>

/*
 * 測試接口,得到value內容
 * arg      : 傳入的參數
 *          : 返回得到的結果
 */
const void* 
getvalue(void* arg) {
    const char* key = "heoo-getvalue.c getvalue";
    printf("%s - %s\n", key, (void*)arg);
    return key;
}

heoo.c

#include "heoo.h"
#include <stdio.h>

/*
 * 測試接口,得到key內容
 *      : 返回key的字符串
 */
const char* 
getkey(void) {
    return "heoo.c getkey";
}

/*
 * 測試接口,得到value內容
 * arg      : 傳入的參數
 *          : 返回得到的結果
 */
const void* 
getvalue(void* arg) {
    const char* key = "heoo.c getvalue";
    printf("%s - %s\n", key, (char*)arg);
    return key;
}

main.c

#include <stdio.h>
#include "heoo.h"

// 測試邏輯主函數
int main(int argc, char* argv[]) {
    // 簡單的打印數據
    printf("getkey => %s\n", getkey());
    getvalue(NULL);
    return 0;
}

到這裡也許感覺有點臃腫, 但是理解為什麼是必要的. 會讓你對於動態庫高度高上0.01毫米的.哈哈.

先讓上面代碼跑起來.

gcc -g -Wall -o main.out main.c heoo.c

測試結果如下

測試完成,那就開始靜態庫到動態庫擴展之旅.

前言

從靜態庫說起來

首先參照下面編譯語句

gcc -c -o heoo-getkey.o heoo-getkey.c
gcc -c -o heoo-getvalue.o heoo-getvalue.c

對於靜態庫創建本質就是打包. 所以用linux上一個 ar創建靜態庫壓縮命令.詳細用法可以看

  ar詳細用法參照 http://blog.163.com/xychenbaihu@yeah/blog/static/132229655201121093917552/

那麼我們開始制作靜態庫

ar rcs libheoo.a heoo-getvalue.o heoo-getkey.o

那麼我們采用靜態庫執行編譯上面main.c 函數

gcc -g -Wall -o main.out main.c -L. -lheoo

運行的截圖如下

運行一切正常. 對於靜態庫編譯 簡單說明一下. ar 後面的 rcs表示 替換創建和添加索引. 具體的看上面的網址.

後面gcc中 -L表示查找庫的目錄, -l表示搜索的 libheoo庫. 還有其它的-I表示查找頭文件地址, -D表示添加全局宏.......

對於上面靜態庫編譯還有一種方式如下

gcc -g -Wall -o main.out main.c libheoo.a

執行結果也是一樣的.可以將 *.a 理解成多個 *.o合體.

好到這裡前言就說完了.那我們開始說正題動態庫了.

正文

動態庫的構建和使用

動態庫構建命名如下,仍然以heoo.c heoo.h 為例

gcc -shared -fPIC  -o libheoo.so heoo.c

開始編譯代碼 先介紹一種最簡單的有點類似上面靜態庫最後一種方式.

gcc -g -Wall -o main.out main.c ./libheoo.so

這裡是顯式編譯. 結果如下

對於 上面編譯 動態庫的時候如果 直接使用 libheoo.so. 例如

gcc -g -Wall -o main.out main.c libheoo.so

如果沒有配置動態庫路徑, 查找動態庫路徑會出問題. 這裡就不復現了(因為我把環境調好了). 會面會給出解決辦法.

下面說libheoo.so 標准的使用方式

gcc -g -Wall -o main.out main.c -L. -lheoo

運行結果如下

上面是個常見錯誤, 系統找不見動態庫在那. 需要配置一下, 再編譯參照如下

export LD_LIBRARY_PATH="$LD_LIBRARY_PATH;./"
gcc -g -Wall -o main.out main.c -L. -lheoo

上面第一句話是在當前會話層. 添加庫查找路徑,包含當前文件目錄.這個會話層關閉了就失效了. Linux上shell確實很重要. 現在執行結果

到這裡動態庫的也都完畢了. 一切正常.

一個奇巧淫技

問: gcc -l 鏈接一個庫的時候,但是庫中存在同名的靜態庫和動態庫. 會鏈接到那個庫?

通過上面的那麼多測試應該知道是動態庫吧,因為使用動態庫會報錯.使用靜態庫沒有事.

那麼問題來了, 我想使用靜態庫怎麼辦.

-static

上面gcc 選項可以幫助我們強制鏈接靜態庫!

動態庫的顯示使用

到這裡基本上是重頭戲了. 扯一點,這些知識點在window也一樣知識環境變了,設置變了.鏈接編譯顯式加載都有的. 下面是重新操作的代碼.

heooso.c

#include <stdio.h>
#include <dlfcn.h>

#define _STR_PATH "./libheoo.so"

// 顯示調用動態庫, 需要 -ldl 鏈接程序庫
int main(int argc, char* argv[]) {
    const char* (*getkey)(void);
    const void* (*getvalue)(void* arg); 
    /*  
     * 對於dlopen 函數第二個參數
     * RTLD_NOW:將共享庫中的所有函數加載到內存
     * RTLD_LAZY:會推後共享庫中的函數的加載操作,直到調用dlsym()時方加載某函數
     */
    void* handle = dlopen(_STR_PATH, RTLD_LAZY);
    // 下面得到錯誤信息,是一種小心的變成方式,每次都檢測一下錯誤是否存在
    const char* err = dlerror();

    if(!handle || err) {
        fprintf(stderr, "dlopen " _STR_PATH " no open! err = %s\n", err);
        return -1; 
    }   
    
    getkey = dlsym(handle, "getkey");
    if((err = dlerror())){
        fprintf(stderr, "getkey err = %s\n", err);
        dlclose(handle);
        return -2; 
    }   
    puts(getkey());

    //這種顯式調用dll代碼,很不安全代碼注入太簡單了
    getvalue = dlsym(handle, "getvalue");
    if((err = dlerror())){
        fprintf(stderr, "getvalue err = %s\n", err);
        dlclose(handle);
        return -3; 
    }   
    puts(getvalue(NULL));

    dlclose(handle);
    return 0;
}

編譯代碼

gcc -g -Wall -o heooso.out heooso.c -ldl

測試結果截圖如下

運行一切正常. 功能是實現了.但是大家千萬別這麼用.否則還是比較危險的.也是一種編程思路吧.後面

後記會寫一個向後兼容的插件機制. 大家可以觀摩一下. 方便更深入的了解Linux系統開發.算是一個簡易的

Linux運用插件技術的小項目吧.

後記

  錯誤是難免的,歡迎吐槽. 最後獻上一個linux上如何通過動態庫運行時加載插件的案例.麻雀雖小,五髒俱全.

Makefile

CC = gcc 
DEBUG = -g -Wall
LIB = -ldl
RUNSO = $(CC) -fPIC -shared -o $@ $^
RUN = $(CC) $(DEBUG) -o $@ $^

#總的任務
all:libheoo.so libheootwo.so libheoothree.so main.out
    

#簡單lib%.so生成
libheoo.so:heoo.c
    $(RUNSO)
libheootwo.so:heootwo.c
    $(RUNSO)
libheoothree.so:heoothree.c
    $(RUNSO)

#生成的主要內容
main.out:main.c
    $(RUN) $(LIB)

# 簡單的清除操作 make clean
.PHONY:clean
clean:
    rm -rf *.so *.s *.i *.o *.out *~ ; ls -hl

heoo.h

#ifndef _H_HEOO
#define _H_HEOO

/*
 * 測試接口,得到key內容 
 *      : 返回key的字符串
 */
extern const char* getkey(void);

/*
 * 測試接口,得到value內容
 * arg      : 傳入的參數
 *          : 返回得到的結果
 */
extern const void* getvalue(void* arg);

#endif // !_H_HEOO

heootwo.c

#include "heoo.h"
#include <stdio.h>

/*
 * 測試接口,得到key內容 
 *      : 返回key的字符串
 */
const char*  
getkey(void) {
    return "heootwo.c getkey";
}

/*
 * 測試接口,得到value內容
 * arg      : 傳入的參數
 *          : 返回得到的結果
 */
const void* 
getvalue(void* arg) {
    const char* key = "heootwo.c getvalue";
    printf("%s - %s\n", key, (char*)arg);
    return key;
}

heoothree.c

#include "heoo.h"
#include <stdio.h>

/*
 * 測試接口,得到key內容 
 *      : 返回key的字符串
 */
const char*  
getkey(void) {
    return "heoothree.c getkey";
}

/*
 * 測試接口,得到value內容
 * arg      : 傳入的參數
 *          : 返回得到的結果
 */
const void* 
getvalue(void* arg) {
    const char* key = "heoothree.c getvalue";
    printf("%s - %s\n", key, (char*)arg);
    return key;
}

main.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <errno.h>
#include <unistd.h>
#include <dirent.h>
#include <dlfcn.h>

//塞入的句柄數
#define _INT_HND (3)
// 最多支持108個插件
#define _INT_LEN (108)
// 文件路徑最大長度
#define _INT_BUF (512)

// 處理dll,並且將返回的數據保存在a[_INT_HND]中, 這個數組長度必須是
bool dll_add(void* a[], const char* dllpath);
// 處理指定目錄得到結果塞入a中, nowpath為NULL表示當前目錄
int dll_new(void* a[][_INT_HND], int len, const char* nowpath);
// 釋放資源
void dll_del(void* a[][_INT_HND], int len);

/*
 * 動態加載機制
 */
int main(int argc, char* argv[]) {
    int idx, len, i;
    void* a[_INT_LEN][_INT_HND];
        
    // 當前目錄下,處理結果
    len = dll_new(a, _INT_LEN, NULL);
    if(len == 0){ 
        fprintf(stderr, "感謝使用,沒有發現合法插件內容!\n");
        exit(1);    
    }   

    //數據展示
    puts("------------------------------ 歡迎使用main插件 ----------------------------------");
    for(i=0; i<len; ++i){
        const char* (*getkey)(void) = a[i][1];
        printf("    %d   =>  %s\n", i, getkey());
    }   
    printf("    請輸入 待執行的 索引[0, %d)\n", len);
    if(scanf("%d", &idx)!=1 || idx<0 || idx >= len){
        puts("    fake 錯誤的命令,程序退出中!");
        goto __exit;
    }
    puts("   執行結果如下:");
    const void* (*getvalue)(void* arg) = a[idx][2];
    puts(getvalue(NULL));

__exit:
    puts("------------------------------ 謝謝使用main插件 ----------------------------------");
    dll_del(a, len);
    return 0;
}

// 處理dll,並且將返回的數據保存在a[_INT_HND]中, 這個數組長度必須是
bool
dll_add(void* a[], const char* dllpath) {
    const char* (*getkey)(void);
    const void* (*getvalue)(void* arg);

    void* handle = dlopen(dllpath, RTLD_LAZY);
    // 下面得到錯誤信息,是一種小心的變成方式,每次都檢測一下錯誤是否存在
    const char* err = dlerror();

    if(!handle || err) return false;

    getkey = dlsym(handle, "getkey");
    if((err = dlerror())){
        dlclose(handle);
        return false;
    }

    //這種顯式調用dll代碼,很不安全代碼注入太簡單了
    getvalue = dlsym(handle, "getvalue");
    if((err = dlerror())){
        dlclose(handle);
        return false;
    }
    // 句柄, key, value, 協議訂的
    a[0] = handle;
    a[1] = getkey;
    a[2] = getvalue;
    return true;
}

// 處理指定目錄得到結果塞入a中, nowpath為NULL表示當前目錄
int
dll_new(void* a[][_INT_HND], int len, const char* nowpath){
    int j = 0, rt;
    DIR* dir;
    struct dirent* ptr;
    char path[_INT_BUF];

    // 設置默認目錄
    if(!nowpath || !*nowpath) nowpath = ".";
    // 打開目錄信息
    if((dir = opendir(nowpath)) == NULL) {
        fprintf(stderr, "opendir open %s, error:%s\n", nowpath, strerror(errno));
        exit(-1);
    }

    //挨個讀取文件
    while(j<len && (ptr=readdir(dir))){
        //只處理文件,包含未知文件
        if(DT_BLK == ptr->d_type || DT_UNKNOWN == ptr->d_type){
            rt = snprintf(path, _INT_BUF, "%s/%s", nowpath, ptr->d_name);// 只有確實是 *.so 文件才去出去運行 
            if(rt>3&&rt < _INT_BUF&&path[rt-1]=='o'&&path[rt-2]=='s'&&path[rt-3]=='.') {
                // 添加數據 dao數組 a中
                if(dll_add(a[j], path))
                    ++j;
            }
        }
    }

    closedir(dir);
    return j;
}

// 釋放資源
void
dll_del(void* a[][_INT_HND], int len) {
    int i=-1;
    while(++i < len)
        dlclose(a[i][0]);
}

最後運行截圖

到這裡一個小demo就完工了. 關於Linux gcc上動態庫插件開發,剖析完畢.O(∩_∩)O哈哈~

Copyright © Linux教程網 All Rights Reserved