GCC制作動態庫導出符號表【轉】


轉自:https://blog.csdn.net/whb_fei/article/details/76974543

版權聲明:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/whb_fei/article/details/76974543

GCC制作動態鏈接庫時默認會將所有的函數及變量都導出到符號表,這里的函數及變量指的是沒有使用static修飾的,使用static修飾的函數及變量不會導出。正常情況下所有符號均導出是不會有問題的,但是有時會有問題,在下邊的例子中會說明。為了避免這種情況,就需要定制符號表,即僅僅將需要提供給其他模塊使用的接口或變量導出到符號表,本文就是介紹制作這樣的動態庫的方法。

一 問題示例

介紹制作方法前先舉例說明其必要性,有兩個動態庫libhello.so及libhello1.so,對應的源文件是hello.c及hello1.c,其源碼如下:

/* *hello.c源碼 */ int hello() { return 1; }

 

/* *hello1.c源碼 */ int hello() { return 10; }

 

/* *hello.h源碼 */ int hello();

 

將hello.c和hello1.c分別編譯成動態庫:

gcc -shared -o libhello.so hello.c gcc -shared -o libhello1.so hello1.c

 

測試文件源碼:

#include <stdio.h> #include "hello.h" int main() { printf(">>>%d\n", hello()); return 0; }

 

現在編譯測試文件,並且同時連接libhello.so和libhello1.so兩個文件:

gcc main.c -L. -lhello -lhello1 運行結果: >>>1

 

結果如下圖所示:
這里寫圖片描述
由上圖ldd查看結果可知,實際上只連接了libhello.so庫,現在交換編譯測試文件時連接庫的順序並運行:

gcc main.c -L. –lhello1 -lhello 運行結果: >>>10

 

結果如下圖所示:
這里寫圖片描述
由上圖ldd查看結果可知,實際上只連接了libhello1.so庫。
由此可知有相同函數時,調用先找到的庫中的函數實體。假設項目中有兩個模塊以庫的方式提供分別為A、B庫,而兩個庫中均有函數D並且函數格式一樣但功能不同,其中A庫中的D是模塊內部使用的,但是沒有用static修飾,這樣兩個庫的符號表中均有D函數符號,上層模塊調用了D函數接口,編譯時同時連接了A、B庫,則實際調用的D函數就有編譯鏈接時A在前還是B在前,如果B在前則不會有問題,但是如果A在前就會調用A中的D函數,功能就會出錯。
這種問題遇到了就很不好查找與排除,因為並非代碼問題,修改代碼解決不了問題,所以才有了本文描述的制作自己需要的庫,該庫僅僅暴露提供給外部模塊使用的接口符號表。

二 定制方法

2.1 前期准備

前期准備只是熟悉查看庫的符號表方法及相關介紹。查看符號表用的命令是readelf,選項是-s,如readelf –s libhello.so,查看的結果如圖所示:
這里寫圖片描述
(注:圖比較長,並未截完整,剩下的都是.symtab的內容)
從上圖可知,符號表有兩部分:.dynsyn和.symtab,這里只做簡單介紹,具體信息可自行查找。
.symtab和.dynsym兩個不同的symbol table,.dynsym用來保存與動態鏈接相關的導入導出符號,而 .symtab 則保存所有符號,包括 .dynsym 中的符號,.dynsym是.symtab的一個子集。
ELF文件包含一些sections(如code和data)是在運行時需要的, 這些sections被稱為allocable;而其他一些sections僅僅是linker、debugger等工具需要,在運行時並不需要,這些sections被稱為non-allocable。當linker構建ELF文件時,它把allocable的數據放到一個地方,將non-allocable的數據放到其他地方,當OS加載ELF文件時,僅僅allocable的數據被映射到內存,non-allocable的數據仍靜靜地呆在文件里不被處理。strip就是用來移除某些non-allocable sections的,移除后不影響使用。

2.2 制作動態庫

本文制作的動態庫的目的是控制暴露在符號表中的符號,使用的是gcc的visibility屬性,網上介紹還有其他方法,本文不做介紹。
制作動態庫的源碼hello.c如下:

/* *hello.c源碼 */ int call_count = 0; int hello_init() { call_count = 0; return 0; } int hello_call_count_add() { return ++call_count; } int hello_handle() { return hello_call_count_add(); } void hello_exit() { call_count = 0; }

 

/* *hello.h源碼 */ #ifndef __HELLO_H #define __HELLO_H int hello_init(); int hello_handle(); void hello_exit(); #endif

 

由hello.h頭文件可知hello_init()、hello_handle()和hello_exit()是對外接口,而hello_call_count_add()不是。先用常規方法制作libhello.so,查看符號表,此處僅查看.dynsyn部分,.symtab部分用strip命令處理掉。

gcc -shared -o libhello.so hello.c strip libhello.so readelf -s libhello.so

 

查看結果:
這里寫圖片描述
由圖可知,不僅所有的函數在符號表中,未用static修飾的變量call_count也在符號表中,這沒有達到目的,需要將hello_call_count_add()函數和變量call_count去掉。
要達到目的,需要用gcc的visibility屬性,將需要導出的符號用__attribute__ ((visibility (“default”)))修飾,並且gcc編譯時需要加入-fvisibility=hidden選項。
使用visibility屬性后的源碼:

#define DLL_PUBLIC __attribute__ ((visibility ("default"))) int call_count = 0; DLL_PUBLIC int hello_init() { call_count = 0; return 0; } int hello_call_count_add() { return ++call_count; } DLL_PUBLIC int hello_handle() { return hello_call_count_add(); } DLL_PUBLIC void hello_exit() { call_count = 0; }

 

編譯並用strip命令處理掉.symtab部分:

gcc -shared -fvisibility=hidden -o libhello.so hello.c strip libhello.so readelf -s libhello.so

 

結果如下:
這里寫圖片描述
從結果可知達到了目的,經驗證制作的庫可以正常使用。

2.3 整理

gcc在鏈接時設置-fvisibility=hidden,則不加 visibility聲明的都默認為hidden,即都是隱藏的, gcc默認設置-fvisibility=default,即全部可見。

三 總結

本文只介紹了linux下gcc的編譯選項,交叉編譯時選項參考GNU文檔How to use the new C++ visibility support部分,網址:http://gcc.gnu.org/wiki/Visibility

--------------------- 本文來自 我是菜鳥_我在學 的CSDN 博客 ,全文地址請點擊:https://blog.csdn.net/whb_fei/article/details/76974543?utm_source=copy 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM