2011-06-23 39 views
11

Tôi gặp một số sự cố khi sử dụng API tải động (<dlfcn.h>: dlopen(), dlclose(), v.v.) trên Android. Tôi đang sử dụng chuỗi công cụ độc lập NDK (phiên bản 8) để biên dịch các ứng dụng và thư viện. Phiên bản Android là 2.2.1 Froyo.Lỗi phân đoạn khi sử dụng dlclose (...) trên nền tảng Android

Đây là mã nguồn của thư viện được chia sẻ đơn giản.

#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); 
} 

Đây là mã nguồn chương trình sử dụng thư viện được đề cập.

#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; 
} 

Với những nguồn mọi thứ đều hoạt động tốt, nhưng khi tôi cố gắng sử dụng một số chức năng STL hoặc các lớp học, các chương trình bị treo với một lỗi segmentation, khi chức năng main() lối thoát hiểm, ví dụ như khi sử dụng này mã nguồn cho thư viện được chia sẻ.

#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; 
} 

Với mã này, chương trình sẽ bị lỗi phân đoạn sau hoặc trong khi thoát chức năng main(). Tôi đã thử một vài thử nghiệm và tìm thấy các kết quả sau.

  1. Nếu không sử dụng STL, mọi thứ đều hoạt động tốt.
  2. Khi sử dụng STL và không gọi dlclose() ở cuối, mọi thứ đều hoạt động tốt.
  3. Tôi đã cố gắng biên dịch với các cờ biên dịch khác nhau như -fno-use-cxa-atexit hoặc -fuse-cxa-atexit, kết quả là như nhau.

Có gì sai trong mã của tôi sử dụng STL?

+0

+1 Câu hỏi được định dạng tốt;) –

+0

Tiêu đề STL có trong tiêu đề của tệp đó không? Bạn có thể lấy nó chỉ để các tập tin cpp? (Vì vậy, STL sẽ không được trong giao diện.) Có phải định nghĩa và khai báo được tách ra? – Naszta

+0

Tôi đoán bạn đang nói về hàm aaa (...), nếu có, thì khai báo và định nghĩa nằm trong các tệp khác nhau. Tệp tiêu đề định nghĩa là '#ifdef __cplusplus bên ngoài" C " #endif int aaa (int i);' –

Trả lời

-2

Bạn nên sử dụng extern "C" để khai báo bạn hoạt aaa()

+1

Nó được thực hiện trong tệp tiêu đề thích hợp, tôi không thêm nội dung của tệp đó vào câu hỏi. –

-1

Bạn cần phải biên dịch với -fpic như một lá cờ biên dịch cho các ứng dụng được sử dụng dlopen()dlclose(). Bạn cũng nên thử xử lý lỗi qua dlerror() và có lẽ kiểm tra xem việc gán con trỏ hàm của bạn có hợp lệ hay không, ngay cả khi nó không phải là NULL con trỏ hàm có thể trỏ đến thứ gì đó không hợp lệ từ khởi tạo, dlsym() không được bảo đảm trả về NULL trên android không thể tìm thấy một biểu tượng. Tham khảo tài liệu android phản đối với các công cụ tuân thủ posix, không phải mọi thứ đều tuân thủ posix trên Android.

0

Tôi có ác cảm chung khi gọi dlclose(). Vấn đề là bạn phải đảm bảo rằng không có gì sẽ cố gắng thực thi mã trong thư viện được chia sẻ sau khi nó đã được unmapped, hoặc bạn sẽ nhận được một lỗi phân đoạn.

Cách phổ biến nhất để thất bại là tạo đối tượng có số destructor được xác định trong hoặc mã cuộc gọi được xác định trong thư viện được chia sẻ. Nếu đối tượng vẫn tồn tại sau dlclose(), ứng dụng của bạn sẽ bị lỗi khi đối tượng bị xóa.

Nếu bạn nhìn vào logcat, bạn sẽ thấy một dấu vết stackgerd stack. Nếu bạn có thể giải mã bằng công cụ arm-eabi-addr2line, bạn sẽ có thể xác định xem nó có trong một destructor hay không, và nếu có, cho lớp nào.Ngoài ra, lấy địa chỉ sự cố, loại bỏ 12 bit cao, và sử dụng nó như là một bù đắp vào thư viện là dlclose() d và cố gắng tìm ra mã nào sống tại địa chỉ đó.

7

Có vẻ như tôi đã tìm thấy lý do của lỗi. Tôi đã thử một ví dụ khác với các file gốc sau: Đây là mã nguồn của lớp đơn giản: 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]); 
     } 
    } 
} 

Như bạn có thể nhìn thấy từ mã Tôi đã không sử dụng bất kỳ công cụ liên quan đến STL nào. Đây là tệp nguồn của thư viện hàm xuất. 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; 
} 

Và cuối cùng đây là mã nguồn của programm có sử dụng thư viện. 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; 
} 

Một lần nữa như bạn có thể thấy chương trình sử dụng thư viện cũng không sử dụng bất kỳ công cụ liên quan STL, nhưng sau khi chạy chương trình tôi có lỗi segmentation cùng trong main(...) thoát chức năng. Vì vậy, vấn đề không được kết nối với STL, và nó được ẩn ở một số nơi khác. Sau đó, sau một số nghiên cứu dài, tôi đã tìm thấy lỗi. Thông thường, destructors biến tĩnh C++ được gọi ngay trước khi thoát main(...), nếu chúng được xác định trong chương trình chính hoặc nếu chúng được xác định trong một số thư viện và bạn đang sử dụng nó, thì trình phá hủy phải được gọi ngay trước dlclose(...). Trên hệ điều hành Android tất cả trình phá hủy (được xác định trong chương trình chính hoặc trong một số thư viện bạn đang sử dụng) của biến tĩnh C++ được gọi trong khi thoát hàm main(...). Vậy điều gì xảy ra trong trường hợp của chúng ta? Chúng tôi có cls biến tĩnh C++ được xác định trong thư viện mà chúng tôi đang sử dụng. Sau đó, ngay trước khi thoát khỏi chức năng main(...), chúng tôi gọi hàm dlclose(...), do đó thư viện kết quả đã đóng và cls sẽ không hợp lệ. Nhưng con trỏ của cls được lưu trữ ở đâu đó và nó sẽ được gọi trong khi thoát khỏi hàm main(...) và vì tại thời điểm gọi nó không hợp lệ, chúng tôi nhận được lỗi phân đoạn. Vì vậy, giải pháp là không gọi dlclose(...) và mọi thứ sẽ ổn. Thật không may với giải pháp này, chúng tôi không thể sử dụng thuộc tính ((destructor)) để deinitializing của một cái gì đó chúng tôi muốn deinitialize, bởi vì nó được gọi là kết quả của cuộc gọi dlclose(...).

+2

Đã có nhiều bản sửa lỗi để xử lý destructor trong libc của Android theo thời gian, do đó, hành vi chính xác có thể sẽ phụ thuộc vào phiên bản Android cụ thể mà bạn đang sử dụng. Tôi khuyên bạn nên gửi một lỗi trên b.android.com, với mã mẫu, giải thích hành vi bạn đang xem và những gì bạn đang mong đợi. – fadden

0

Tôi gặp phải cùng một sự đau đầu trên Linux. Một công việc xung quanh, có thể chữa segfault của tôi là để đưa những dòng này trong file giống như main(), vì vậy dlclose rằng() được gọi sau khi lợi nhuận chính:

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

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

Nguyên nhân gốc rễ của segfault dlclose gây ra có thể rằng việc triển khai cụ thể dlclose() không làm sạch các biến toàn cầu bên trong đối tượng được chia sẻ.

Các vấn đề liên quan