2011-02-23 5 views
7

배경 :이 동적 라이브러리 로딩 코드가 gcc에서 작동하는 이유는 무엇입니까?

나는 윈도우를 통해 C++ GNU/리눅스 응용 프로그램을 포팅하는 골치 아픈 작업과 자신을 발견했습니다. 이 애플리케이션이 수행하는 작업 중 하나는 특정 경로에서 공유 라이브러리를 검색 한 다음 posix dlopen() 및 dlsym() 호출을 사용하여 동적으로 클래스를로드하는 것입니다. 우리는 내가 여기로 가지 않을 것 인이 방법으로 로딩을하는 매우 좋은 이유가 있습니다.

문제점 : 동적 dlsym을() 또는 GetProcAddress를 가진 C++ 컴파일러에 의해 생성 된 심볼을 발견

()는 그것들은 통근 "C"링크 블록을 사용하여 unmangled되어야한다. 예 :

#include <list> 
#include <string> 

using std::list; 
using std::string; 

extern "C" { 

    list<string> get_list() 
    { 
     list<string> myList; 
     myList.push_back("list object"); 
     return myList; 
    } 

} 

이 코드는 Linux 및 Windows의 수많은 컴파일러에서 완벽하게 유효하며 컴파일되고 실행됩니다. 그러나 "반환 ​​형식이 유효하지 않으므로"MSVC로 컴파일되지 않습니다. 우리가 왔어요 해결 방법은 대신 목록 개체의 목록에 대한 포인터를 반환하는 기능을 변경하는 것입니다 : 나는 GNU/리눅스 로더에 대한 최적의 해결책을 찾기 위해 노력했습니다

#include <list> 
#include <string> 

using std::list; 
using std::string; 

extern "C" { 

    list<string>* get_list() 
    { 
     list<string>* myList = new list<string>(); 
     myList->push_back("ptr to list"); 
     return myList; 
    } 

} 

그 새 함수와 이전 레거시 함수 프로토 타입 모두에서 작동하거나 더 이상 사용되지 않는 함수가 발견 될 때이를 감지하고 경고를 내 보냅니다. 이전 라이브러리를 사용하려고 시도했을 때 코드가 segfault 된 경우 사용자에게 불편할 수 있습니다. 내 독창적 인 아이디어는 get_list를 호출하는 동안 SIGSEGV 신호 처리기를 설정하는 것이 었습니다. (저는 이것이 이상하다는 것을 알고 있습니다 - 더 좋은 아이디어에 열려 있습니다). 그래서 오래된 라이브러리를 로딩하는 것이 이전 함수 원형 (목록 객체를 반환)을 사용하여 라이브러리를 실행하고 (목록에 대한 포인터를 기대하는) 새로운 로딩 코드를 통해 라이브러리를 실행했다는 사실을 확인하고 놀랍게도이를 확인했습니다. 방금 일했습니다. 내가 가지고있는 질문은 인 이유는 무엇입니까?

아래 로딩 코드는 위에 나열된 두 가지 기능 프로토 타입 모두에서 작동합니다. Fedora 12, RedHat 5.5 및 RedHawk 5.1에서 gcc 버전 4.1.2 및 4.4.4를 사용하여 작동 함을 확인했습니다. g ++를 -shared 및 -fPIC과 함께 사용하여 라이브러리를 컴파일하면 실행 파일을 dl (-ldl)과 연결해야합니다.

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

using std::list; 
using std::string; 

int main(int argc, char **argv) 
{ 
    void *handle; 
    list<string>* (*getList)(void); 
    char *error; 

    handle = dlopen("library path", RTLD_LAZY); 
    if (!handle) 
    { 
     fprintf(stderr, "%s\n", dlerror()); 
     exit(EXIT_FAILURE); 
    } 

    dlerror(); 

    *(void **) (&getList) = dlsym(handle, "get_list"); 

    if ((error = dlerror()) != NULL) 
    { 
     printf("%s\n", error); 
     exit(EXIT_FAILURE); 
    } 

    list<string>* libList = (*getList)(); 

    for(list<string>::iterator iter = libList->begin(); 
      iter != libList->end(); iter++) 
    { 
     printf("\t%s\n", iter->c_str()); 
    } 

    dlclose(handle); 

    exit(EXIT_SUCCESS); 
} 
+3

운이 좋았 기 때문입니다. 나는 당신이 더 복잡한 프로그램으로 이런 종류의 것을 시도한다면, 당신은 박살난 스택이나 유사한 것의 효과를보기 시작할 것이라고 생각합니다. – aschepler

+0

게시 한 코드는 단순화되었습니다. 실제 응용 프로그램은 약 100k 줄의 코드이며, 모두 작동하는 꽤 광범위한 테스트 케이스를 실행했습니다. 나는 동의하지만,이 경우 GCC와 몇 가지 변칙이 없으면 이것이 작동하지 않아야한다. – bckohan

+1

'그들이 얽매여 져야한다 '는 것이 확실한지는 모르겠다. 맹 글링 된 이름에 대해 dlsym()을 요청하면 올바로 찾을 수 없습니다. –

답변

4

aschepler가 말하기를 운이 좋았 기 때문입니다.

gcc (및 대부분의 다른 컴파일러)에서 x86 및 x64 용으로 사용 된 ABI는 '숨겨진'포인터 arg을 함수는 포인터를 공간으로 사용하여 반환 값을 저장 한 다음 포인터 자체를 반환합니다. 그래서 발신자가 (아마도 스택)은 '갑'의 공간을 할당하고에서 통과 할 것으로 예상된다 형태

struct foo func(...) 

의 함수

struct foo *func(..., struct foo *) 

대략 equivlant 것을 밝혀 그것에 대한 포인터.

이렇게하면 (구조체를 반환 할 것으로 예상되는) 함수를 호출하고 포인터를 반환하는 함수 포인터를 통해 함수를 호출하면 작동하는 것처럼 보일 수 있습니다. if 여분의 arg (호출자가 남긴 임의의 레지스터 내용)에 대한 가비지 비트가 writable 어딘가를 가리 키기 때문에 호출 된 함수는 행복하게 반환 값을 쓰고 그 포인터를 반환하므로 호출 된 코드가 다시 돌아올 것입니다 그것은 예상하고있는 구조체에 대한 유효한 포인터와 비슷합니다. 따라서 코드는 표면적으로 작동하는 것처럼 보일 수 있지만, 나중에 실제로 중요한 임의의 메모리가 손상 될 수 있습니다.