1. 程式人生 > >小心兩個共享庫共用同一個靜態庫

小心兩個共享庫共用同一個靜態庫



問1:如果測試中的全域性變數global_var是個帶構造和析構的類物件,會如何?(答案在最後
問2:如果使用-fPIE替代-fPIC編譯連結,會是什麼結果了?


位置無關程式碼(PIC)對常量和函式入口地址的操作都是採用基於基暫存器(base register)BASE+ 偏移量的相對地址的定址方式,即使程式被裝載到記憶體中的不同地址(即 BASE值不同),而偏移量是不變的,所以程式仍然可以找到正確的入口地址或者常量。

為何要小心?原因是在使用dlopen動態載入共享庫時,如果靜態庫中包含有全域性變數,可能會出現名同地址不同的全域性變數。
解決辦法:總是使用RTLD_GLOBAL載入共享庫,而不是RTLD_

LOCAL。以下是測試程式:

Makefile

  1. # test shared libraries use static a same static library
  2. # the global variables defined at static library have the same address
  3. all: x libshared_lib1.so libshared_lib2.so
  4. x: x.cpp #libstatic_lib.a #libshared_lib1.so #libshared_lib2.so
  5. g++ -g -o [email protected] $^ -ldl
  6. libstatic_lib.a: static_lib.h static_lib.cpp
  7. g++ -g -fPIC -c static_lib.cpp -I.
  8. ar cr [email protected] static_lib.o
  9. libshared_lib1.so: shared_lib1.cpp libstatic_lib.a
  10. g++ -g -fPIC -shared -o [email protected] $^ -I.
  11. libshared_lib2.so: shared_lib2.cpp libstatic_lib.a
  12. g++ -g -fPIC -shared -o [email protected] $^ -I.
  13. clean:
  14. rm -f static_lib.o libstatic_lib.a
  15. rm -f shared_lib1.o libshared_lib1.so
  16. rm -f shared_lib2.o libshared_lib2.so
  17. rm -f x

測試程式x.cpp
  1. #include <dlfcn.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. extern void call_foo(const char* name, int load_flag);
  5. int main()
  6. {
  7.         int flag = RTLD_GLOBAL|RTLD_NOW;  // 如果是RTLD_GLOBAL則靜態庫中定義的全域性變數在共享庫中名同地址也同
  8.         //int flag = RTLD_LOCAL|RTLD_NOW;  // 如果是RTLD_LOCAL則靜態庫中定義的全域性變數在共享庫中名同地址不同
  9.         call_foo("./libshared_lib1.so", flag);
  10.         call_foo("./libshared_lib2.so", flag);
  11.         return 0;
  12. }
  13. // RTLD_NOW
  14. // RTLD_LAZY
  15. // RTLD_GLOBAL
  16. // RTLD_LOCAL
  17. void call_foo(const char* name, int load_flag)
  18. {
  19.         char *error;
  20.         void (*foo)();
  21.         void* handle = dlopen(name, load_flag);
  22.         if (NULL == handle)
  23.         {
  24.                 fprintf (stderr, "%s\n", dlerror());
  25.                 exit(1);
  26.         }
  27.         dlerror(); /* Clear any existing error */
  28.         *(void **) (&foo) = dlsym(handle, "foo");
  29.         if ((error = dlerror()) != NULL)
  30.         {
  31.                 fprintf (stderr, "%s\n", error);
  32.                 exit(1);
  33.         }
  34.         (*foo)();
  35. }

靜態庫標頭檔案static_lib.h
  1. extern int global_var;

靜態庫實現檔案static_lib.cpp
  1. #include <stdio.h>
  2. int global_var = 2013;

第1個共享庫實現檔案shared_lib1.cpp 
  1. #include "static_lib.h"
  2. #include <stdio.h>
  3. extern "C" void foo()
  4. {
  5.         global_var = 1111;
  6.         printf("%p 1-> %d\n", &global_var, global_var);
  7. }

第2個共享庫實現檔案shared_lib2.cpp
  1. #include "static_lib.h"
  2. #include <stdio.h>
  3. extern "C" void foo()
  4. {
  5.         printf("%p 2-> %d\n", &global_var, global_var);
  6. }

測試環境:
x86_64 x86_64 GNU/Linux 2.6.16

附:
1)如果你想覆蓋系統呼叫,可以使用LD_PRELOAD或/etc/ld.so.preload,也可進一步瞭解RTLD_NEXT;
2)靜態庫順序關係:假設X.a依賴Z.a,則順序為X.a Z.a,亦即被依賴的排在後面,否則連結時會報某些符號找不到(詳細請參見:連結靜態庫的順序問題)。

答:結果是即使以RTLD_GLOBAL方式載入,都會出現兩次構造和析構呼叫,如果是RTLD_GLOBAL方式,地址仍然相同,也就是同一個物件執行了兩次構造和析構,後果當然是非常危險。執行測試程式碼x.zip即可得到驗證。

段表(Section Table) 一個描述檔案中各個段的陣列
.code/.text 程式碼段
.data 段儲存的是那些已經初始化了的全域性靜態變數和區域性靜態變數
.rodata/.rodata1 段存放的是隻讀資料,一般是程式裡面的只讀變數(如const修飾的變數)和字串常量
.bss 段存放的是未初始化的全域性變數和區域性靜態變數
.plt/.got 段動態連結的跳轉表和全域性入口表
.symtab 符號表(Symbol Table)
.strtab 字串表(String Table),用於儲存ELF檔案中用到的各種字串
.init/.fini 程式初始化與終結程式碼段
.note 額外的編譯器資訊。比如程式的公司名、釋出版本號等
.line 除錯時的行號表,即原始碼行號與編譯後指令的對應表
.hash 符號雜湊表
.dynamic 動態連結資訊
.debug 除錯資訊
.comment 存放的是編譯器版本資訊,比如字串:”GCC: (GNU) 4.2.0”
自定義段 GCC提供了一個擴充套件機制,使得程式設計師可以指定變數所處的段:
1.__attribute__((section("FOO"))) int global = 42;
2.__attribute__((section("BAR"))) void foo()
{
}
在全域性變數或函式之前加上"__attribute__((section("name")))"屬性就可以把相應的變數或函式放到以"name"作為段名的段中。

  • 如果被依賴的不是靜態庫,而是共享庫,則無論何種方式都不存在問題
  • 為何即使RTLD_GLOBAL載入,也會執行兩次構造和析構?原因是兩個共享庫存在相同的程式碼段,如果被依賴的是共享庫,則不存在這個問題


  1. -Wl的使用
  2. -Wl表示後面的引數傳遞給連結器,其中l是linker的意思。
  3. 連結時指定共享庫的搜尋路徑(類似於設定LD_LIBRARY_PATH):
  4. -Wl,-rpath=/usr/local/abc:/data/abc
  5. 以上也可以分開寫:
  6. -Wl,-rpath=/usr/local/abc -Wl,-rpath=/data/abc
  7. 部分庫連結它的靜態庫,部分庫連結它的共享庫:
  8. -Wl,-static -lb -Wl,-call_shared -la -lz
  9. 指定連結器:
  10. -Wl,-dynamic-linker /lib/ld-linux.so.2 -e _so_start
  11. 指定匯出的符號:
  12. -Wl,--export-dynamic,--version-script,exports.lds
  13. exports.lds的格式可以為:
  14. {
  15. global:
  16. foo;
  17. };
  18. 指定共享庫的soname:
  19. -Wl,--export-dynamic,--version-script,exports.lds,-soname=libqhttpd.so
  20. -rpath 增加共享庫搜尋路徑
  21. --retain-symbols-file表示不丟棄未定義的符號和需要重定位的符號
  22. --export-dynamic 建立一個動態連線的可執行程式時, 把所有的符號加到動態符號表中

FROM: http://blog.chinaunix.net/uid-20682147-id-3760647.html