2011-06-23 54 views
11

在Android上使用動態加載API(<dlfcn.h>dlopen()dlclose()等)時出現問題。 我正在使用NDK獨立工具鏈(版本8)來編譯應用程序和庫。 Android版本是2.2.1 Froyo。在Android平臺上使用dlclose(...)時出現分段錯誤

這是簡單共享庫的源代碼。

#include <stdio.h> 

int iii = 0; 
int *ptr = NULL; 

__attribute__((constructor)) 
static void init() 
{ 
    iii = 653; 
} 

__attribute__((destructor)) 
static void cleanup() 
{ 
} 

int aaa(int i) 
{ 
    printf("aaa %d\n", iii); 
} 

這是使用上述庫的程序源代碼。

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

int main() 
{ 
    void *handle; 
    typedef int (*func)(int); 
    func bbb; 

    printf("start...\n"); 

    handle = dlopen("/data/testt/test.so", RTLD_LAZY); 
    if (!handle) 
    { 
     return 0; 
    } 

    bbb = (func)dlsym(handle, "aaa"); 
    if (bbb == NULL) 
    { 
     return 0; 
    } 

    bbb(1); 

    dlclose(handle); 
    printf("exit...\n"); 

    return 0; 
} 

有了這些來源的一切工作正常,但當我嘗試使用一些STL函數或類,程序崩潰與分段故障,則main()函數退出,例如,當使用這個時共享庫的源代碼。

#include <iostream> 

using namespace std; 

int iii = 0; 
int *ptr = NULL; 

__attribute__((constructor)) 
static void init() 
{ 
    iii = 653; 
} 

__attribute__((destructor)) 
static void cleanup() 
{ 
} 

int aaa(int i) 
{ 
    cout << iii << endl; 
} 

利用該代碼,所述程序期間main()功能出口與後段故障或崩潰。 我已經嘗試了幾個測試,發現以下結果。

  1. 沒有使用STL,一切工作正常。
  2. 當使用STL並且最後不要調用dlclose()時,一切工作正常。
  3. 我試圖編譯各種編譯標誌,如-fno-use-cxa-atexit-fuse-cxa-atexit,結果是一樣的。

我的代碼使用STL時出了什麼問題?

+0

+1格式良好的問題;) –

+0

STL標頭是否在標頭的文件中?你可以把它只是對cpp文件? (所以STL不會在接口中。)定義和聲明是分開的嗎? – Naszta

+0

我想你是在談論aaa(...)函數,如果是,那麼聲明和定義是在不同的文件。定義頭文件是'#ifdef __cplusplus extern「C」 #endif int aaa(int i);' –

回答

-2

你應該使用的extern「C」來聲明你的功能AAA()

+1

它在適當的頭文件中完成,我沒有在問題中添加該文件的內容。 –

-1

您需要-fpic編譯爲正在使用dlopen()dlclose()應用程序編譯器標誌。你也應該嘗試通過dlerror()進行錯誤處理,並且可能檢查你的函數指針的賦值是否有效,即使它不是NULL,函數指針可能指向初始化時無效的東西,dlsym()不保證在android上返回NULL找不到符號。請參閱與符合posix的東西相反的android文檔,並非所有內容都符合android的posix標準。

0

我有一個普遍的反感調用dlclose()。問題在於,您必須確保在未映射共享庫後沒有任何操作會嘗試執行代碼,否則會出現分段錯誤。

最常見的失敗方法是創建一個對象,其中定義了析構函數或調用共享庫中定義的代碼。如果對象在dlclose()之後仍然存在,則在刪除對象時,您的應用程序將崩潰。

如果你看看logcat,你應該看到一個調試器堆棧跟蹤。如果你可以用arm-eabi-addr2line工具來解碼,你應該能夠確定它是否在析構函數中,如果是,則確定哪個類。或者,取出崩潰地址,去掉高12位,並將其用作爲dlclose() d的庫中的偏移量,並嘗試找出該地址處的代碼。

7

看起來像我找到了錯誤的原因。我曾嘗試另一個例子與下面的源文件: 下面是簡單的類的源代碼: myclass.h

class MyClass 
{ 
public: 
    MyClass(); 
    ~MyClass(); 
    void Set(); 
    void Show(); 
private: 
    int *pArray; 
}; 

myclass.cpp

#include <stdio.h> 
#include <stdlib.h> 
#include "myclass.h" 

MyClass::MyClass() 
{ 
    pArray = (int *)malloc(sizeof(int) * 5); 
} 

MyClass::~MyClass() 
{ 
    free(pArray); 
    pArray = NULL; 
} 

void MyClass::Set() 
{ 
    if (pArray != NULL) 
    { 
     pArray[0] = 0; 
     pArray[1] = 1; 
     pArray[2] = 2; 
     pArray[3] = 3; 
     pArray[4] = 4; 
    } 
} 

void MyClass::Show() 
{ 
    if (pArray != NULL) 
    { 
     for (int i = 0; i < 5; i++) 
     { 
      printf("pArray[%d] = %d\n", i, pArray[i]); 
     } 
    } 
} 

正如你可以從代碼中看到我沒有使用任何STL相關的東西。 這裏是函數庫導出的源文件。 func.h

#ifdef __cplusplus 
extern "C" { 
#endif 

int SetBabe(int); 
int ShowBabe(int); 

#ifdef __cplusplus 
} 
#endif 

func.cpp

#include <stdio.h> 
#include "myclass.h" 
#include "func.h" 

MyClass cls; 

__attribute__((constructor)) 
static void init() 
{ 

} 

__attribute__((destructor)) 
static void cleanup() 
{ 

} 

int SetBabe(int i) 
{ 
    cls.Set(); 
    return i; 
} 

int ShowBabe(int i) 
{ 
    cls.Show(); 
    return i; 
} 

最後,這是使用該庫的PROGRAMM的源代碼。 main.cpp中

#include <dlfcn.h> 
#include <stdlib.h> 
#include <stdio.h> 
#include "../simple_lib/func.h" 

int main() 
{ 
    void *handle; 
    typedef int (*func)(int); 
    func bbb; 

    printf("start...\n"); 

    handle = dlopen("/data/testt/test.so", RTLD_LAZY); 
    if (!handle) 
    { 
     printf("%s\n", dlerror()); 
     return 0; 
    } 

    bbb = (func)dlsym(handle, "SetBabe"); 
    if (bbb == NULL) 
    { 
     printf("%s\n", dlerror()); 
     return 0; 
    } 
    bbb(1); 

    bbb = (func)dlsym(handle, "ShowBabe"); 
    if (bbb == NULL) 
    { 
     printf("%s\n", dlerror()); 
     return 0; 
    } 
    bbb(1); 

    dlclose(handle); 
    printf("exit...\n"); 

    return 0; 
} 

同樣,你可以看到使用該庫還程序不使用任何STL相關的東西,但在程序的運行後,我得到了在main(...)退出函數相同分段錯誤。所以這個問題與STL本身沒有關係,而且它隱藏在其他地方。然後經過長時間的研究,我發現了這個錯誤。 正常情況下,如果在主程序中定義了函數出口前立即調用靜態C++變量的destructors,或者如果它們在某個庫中定義並且正在使用它,則應該在dlclose(...)之前立即調用析構函數。 在Android操作系統全部靜態C++變量的析構函數(在主程序或某些庫中定義的)在main(...)函數出口處被調用。那麼我們的情況會發生什麼?我們有cls我們正在使用的庫中定義的靜態C++變量。然後在main(...)函數退出之前立即調用dlclose(...)函數,結果庫關閉,並且cls變得無效。但cls的指針存儲在某個地方,它的析構函數應該在main(...)函數退出時被調用,並且因爲在調用時它已經無效,所以我們得到了段錯誤。所以解決方案是不要撥打dlclose(...),一切都應該沒問題。不幸的是,通過這個解決方案,我們不能使用屬性((析構函數))來取消初始化我們想要初始化的東西,因爲它被稱爲dlclose(...)調用的結果。

+2

隨着時間的推移,Android的libc中的析構函數處理有很多修復,所以確切的行爲可能取決於您使用的Android的特定版本。我建議在b.android.com上提供一個bug,並附上示例代碼,解釋您看到的行爲以及您的期望。 – fadden

0

我在Linux上遇到了同樣的問題。一個變通辦法能解決我的段錯誤是把這些線路中的同一個文件中的main(),從而使dlclose()主返回之後被稱爲:

static void* handle = 0; 
void myDLClose(void) __attribute__ ((destructor)); 
void myDLClose(void) 
{ 
    dlclose(handle); 
} 

int main() 
{ 
    handle = dlopen(...); 
    /* ... real work ... */ 
    return 0; 
} 

dlclose引起段錯誤的根本原因可能是dlclose()的特定實現不清除共享對象內的全局變量。