1. 程式人生 > >LINUX靜態庫與動態庫符號衝突問題分析與解決

LINUX靜態庫與動態庫符號衝突問題分析與解決

1. 問題重現模型

為了重現問題並去掉無關干擾細節,我們將構建一個最簡單的可執行模組和依賴模組的關係鏈,程式依賴模型如下:
這裡寫圖片描述

1.1 解釋

(1)有一個名為RTSP的第三方庫提供了公共介面RTSP_OPEN,RTSP可以編譯為靜態庫libRTSP_STATIC.a也可以編譯為動態庫libRTSP_SHARED.so。
(2)基於RTSP庫封裝了一個名為STREAM的庫,該庫以動態庫libSTREAM.so的形式提供使用。STREAM庫提供了1個名為STREAM_OPEN的介面,該介面在內部使用RTSP庫提供的公共介面RTSP_OPEN。
(3)使用者程式呼叫了STREAM庫的STREAM_OPEN介面。
(4)整個依賴鏈關係為使用者程式依賴STREAM庫,STREAM庫依賴RTSP庫。

1.2 程式碼

為了便於看到實驗效果,RTSP庫的靜態庫(.a)和動態庫(.so)分別使用不同的原始檔進行編譯,但是他們提供相同的介面。(現實情況是使用同一套程式碼生成靜態庫和動態庫,此處只是為了方便看到實驗效果)。
(1)RTSP庫程式碼如下:
1)rtsp.h

#ifndef __RTSP_H__
#define __RTSP_H__
int RTSP_OPEN();
int RTSP_CLOSE();
int RTSP_PARSE();
#endif

2)rtsp_static.c

#include "stdio.h"

int RTSP_OPEN()
{
    printf
("[%s:%s:%d]\n", __FILE__, __FUNCTION__, __LINE__); return 0; } int RTSP_CLOSE() { printf("[%s:%s:%d]\n", __FILE__, __FUNCTION__, __LINE__); return 0; } int RTSP_PARSE() { printf("[%s:%s:%d]\n", __FILE__, __FUNCTION__, __LINE__); return 0; }

3)rtsp_shared.c

#include "stdio.h"

int
RTSP_OPEN() { printf("[%s:%s:%d]\n", __FILE__, __FUNCTION__, __LINE__); return 0; } int RTSP_CLOSE() { printf("[%s:%s:%d]\n", __FILE__, __FUNCTION__, __LINE__); return 0; } int RTSP_PARSE() { printf("[%s:%s:%d]\n", __FILE__, __FUNCTION__, __LINE__); return 0; }

(2)STREAM庫程式碼如下:
1)stream.h

#ifndef __STREAM_H__
#define __STREAM_H__
int STREAM_OPEN();
#endif

2)stream.c

#include "rtsp.h"
int STREAM_OPEN()
{
    RTSP_OPEN();
    return 0;
}

(3)使用者程式程式碼如下:
1)test_stream.c:

#include "stream.h"
int main(int argc, char *argv[])
{
    STREAM_OPEN();
    while(1) sleep(1000);
    return 0;
}

1.3 編譯

各模組編譯語句如下:
(1)libRTSP_SHARED.so

gcc -g -fPIC -shared rtsp_shared.c -o 
libRTSP_SHARED.so

(2)libRTSP_STATIC.a

gcc -c -g -fPIC rtsp_static.c -o rtsp_static.o
ar crv libRTSP_STATIC.a rtsp_static.o

(3)libSTREAM.so
STREAM庫使用RTSP靜態庫,請注意STREAM庫的編譯方法,非常重要,後面解決符號衝突問題會修改此編譯語句!

gcc -g -fPIC -shared stream.c -o libSTREAM.so -L./ -lRTSP_STATIC

(4)使用者程式的編譯放在下一節分析,因為使用者程式的編譯方法不同將導致使用者程式執行結果不同,使用者程式有可能呼叫到RTSP靜態庫中的RTSP_OPEN介面,也有可能呼叫到RTSP動態庫中的RTSP_OPEN介面。

2. 問題重現分析

現在我們再來看下上述程式碼的程式依賴模型圖:
這裡寫圖片描述

2.1 情景分析

STREAM庫在內部使用了第三方RTSP庫提供的公共介面,如果使用者程式也直接使用了RTSP庫會出現什麼情況?請看下面的情景分析。
(1)情況1
使用者程式使用如下語句編譯:

gcc -g test_stream.c -o test_stream -L./ -lSTREAM

程式執行結果如下:
這裡寫圖片描述
可以看到使用者程式最終呼叫了RTSP靜態庫中的RTSP_OPEN介面。
分析:
通常情況下,使用者程式直接使用封裝的STREAM庫,STREAM庫隱含的使用了第三方RTSP庫的靜態庫,這個隱含關係對於使用者來說是不可見的。如果使用者程式不直接使用RTSP動態庫,一切都沒有問題。
情況1模組載入關係如下:
這裡寫圖片描述
圖中藍色部分代表使用者程式執行期間被載入到記憶體中的模組,從圖中可以看到,使用者程式執行時只存在RTSP靜態庫,因此RTSP_OPEN是唯一的。
(2)情況2
使用者程式使用如下語句編譯:

gcc -g test_stream.c -o test_stream -L./ -lRTSP_SHARED -lSTREAM

程式執行結果如下:
這裡寫圖片描述
可以看到使用者程式最終呼叫了RTSP動態庫中的RTSP_OPEN介面。
分析:
在這種情況下,使用者程式使用了STREAM庫,同時又直接連結了第三方RTSP庫的動態庫(編譯語句中的紅色部分)。因為STREAM庫是使用RTSP庫的靜態庫(libRTSP_STATIC.a)編譯的,現在又連結了RTSP庫的動態庫(libRTSP_SHARED.so),因此在使用者程式中會有兩個相同的RTSP_OPEN符號,載入器在載入應用程式並繫結符號時就要做出決議,到底是要使用靜態庫中的RTSP_OPEN符號還是動態庫中的RTSP_OPEN符號。
從執行結果上來看,上述的編譯語句編譯出來的使用者程式使用了動態庫中的RTSP_OPEN符號。
情況2模組載入關係如下:
這裡寫圖片描述
圖中藍色部分代表使用者程式執行期間被載入到記憶體中的模組,從圖中可以看到,使用者程式執行時同時存在RTSP靜態庫和RTSP動態庫,因此RTSP_OPEN具有二義性。
實際上,即使使用者使用了RTSP動態庫也不一定會導致使用者程式呼叫到動態庫中的RTSP_OPEN符號。例如,我們把上面的編譯語句改為下面的:
原來的使用者程式編譯語句:

gcc -g test_stream.c -o test_stream -L./ -lRTSP_SHARED –lSTREAM

修改的使用者程式編譯語句:

gcc -g test_stream.c -o test_stream -L./ -lSTREAM -lRTSP_SHARED

重新編譯編譯後執行使用者程式,輸出如下:

可以看到即使連結了RTSP動態庫,使用者程式最終還是呼叫了靜態庫中的RTSP_OPEN介面。
具體原因在此處暫時不展開,但是可以說明一點,如果使用者程式使用了RTSP動態庫可能會產生符號衝突問題,並且這個行為是STREAM庫提供者不能控制的!

3. 問題解決方案

3.1 解決方法

我們回過頭來看一下STREAM庫的編譯語句:

gcc -g -fPIC -shared stream.c -o libSTREAM.so -L./ -lRTSP_STATIC

再看一下libSTREAM.so的重定位資訊:
這裡寫圖片描述
可以發現RTSP_OPEN是一個動態繫結符號,所謂動態繫結符號就是編譯連結階段並不確定符號地址,符號地址的解析和繫結推遲到裝載階段。
因此解決問題的一種思路就是在編譯連結階段將使用的RTSP靜態庫中的符號地址確定下來。
解決的辦法就是在編譯libSTREAM.so的時候加上-Wl,-Bsymbolic編譯選項,該編譯選項的含義是在連結過程中優先使用本模組內部的符號。
原來的libSTREAM.so編譯語句:

gcc -g -fPIC -shared stream.c -o libSTREAM.so -L./ -lRTSP_STATIC

修改的libSTREAM.so編譯語句:

gcc -g -fPIC -shared stream.c -o libSTREAM.so -L./ -lRTSP_STATIC -Wl,-Bsymbolic

重新編譯libSTREAM.so後檢視重定位資訊:
這裡寫圖片描述
可以看到libSTREAM.so中的動態繫結符號中已經沒有RTSP_OPEN這個符號了。

3.2 驗證

重新編譯使用者程式並使用新的libSTREAM.so,驗證符號衝突問題是否解決:
(1)

gcc -g test_stream.c -o test_stream -L./ -lRTSP_SHARED –lSTREAM

執行結果:
這裡寫圖片描述
(2)

gcc -g test_stream.c -o test_stream -L./ -lSTREAM -lRTSP_SHARED

執行結果:
這裡寫圖片描述
從上面2個例子可以看出,使用者程式載入了動態庫libRTSP_SHARED.so,但是使用的都是RTSP靜態庫(libRTSP_STATIC.a)中提供的RTSP_OPEN介面,符號衝突問題已經解決。

3.3 其他

本文分析的情景是:有1個動態庫和1個靜態庫同時匯出了同名符號,使用者程式又同時使用了這兩個庫(隱式或顯示)從而導致的符號衝突問題。
本文提供的解決方案並不能解決兩個動態庫匯出了同名符號,使用者程式又同時使用了這兩個動態庫導致的符號衝突問題。