새 교환원에게 전화가 오지 않는 이유는 무엇입니까?
-
20-08-2019 - |
문제
나는 동적으로 로드된 라이브러리(dlopen 등으로 로드됨)가 실제로 호출 프로그램에 정의된 이러한 연산자가 아닌 자체의 새로운 삭제 연산자를 사용하는지 확인하고 싶었습니다.그래서 다음 library.cpp를 작성했습니다.
#include <exception>
#include <new>
#include <cstdlib>
#include <cstdio>
#include "base.hpp"
void* operator new(size_t size) {
std::printf("New of library called\n");
void *p=std::malloc(size);
if (p == 0) // did malloc succeed?
throw std::bad_alloc(); // ANSI/ISO compliant behavior
return p;
}
void operator delete(void* p) {
std::printf("Delete of library called\n");
std::free(p);
}
class Derived : public Base {
public:
Derived() : Base(10) { }
};
extern "C" {
Base* create() {
return new Derived;
}
void destroy(Base* p) {
delete p;
}
}
그리고 그것을 컴파일
g++ -g -Wall -fPIC -shared library.cpp -o library.so
또는 고용된 러시아인이 시도해 보라고 제안한 대로(그러나 결국 아무것도 바뀌지 않았습니다)
g++ -g -Wall -fPIC -shared -Wl,-Bsymbolic library.cpp -o library.so
Base 클래스는 int 값과 이 값을 얻기 위한 함수 get_value()만 보유하고 있습니다.그 후 나는 다음과 같이 client.cpp를 작성했습니다.
#include <exception>
#include <new>
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <dlfcn.h>
#include "base.hpp"
void* operator new(size_t size) {
std::printf("New of client called\n");
void *p=std::malloc(size);
if (p == 0) // did malloc succeed?
throw std::bad_alloc(); // ANSI/ISO compliant behavior
return p;
}
void operator delete(void* p) {
std::printf("Delete of client called\n");
std::free(p);
}
typedef Base* create_module_t();
typedef void destroy_module_t(Base *);
int main() {
void* handle = dlopen("./library.so",
RTLD_LAZY);
if (handle == NULL) {
std::cout << dlerror() << std::endl;
return 1;
}
create_module_t* create_module = NULL;
void* func = dlsym(handle, "create");
if (func == NULL) {
std::cout << dlerror() << std::endl;
return 1;
} else create_module = (create_module_t *)func;
destroy_module_t* destroy_module = NULL;
func = dlsym(handle, "destroy");
if (func == NULL) {
std::cout << dlerror() << std::endl;
return 1;
} else destroy_module = (destroy_module_t *)func;
Base* a = create_module();
std::cout << "Value: " << a->get_value() << std::endl;
destroy_module(a);
return 0;
}
그리고 그것을 컴파일
g++ -Wall -g -o client -ldl client.cpp
클라이언트를 실행하면 "새 클라이언트 호출"과 "클라이언트 삭제 호출"만 표시됩니다.Employed Russian가 제안한 것과 같은 라이브러리에 대해 컴파일러 스위치 -Bsymbolic을 사용하더라도 마찬가지입니다.
지금:무엇이 잘못되었나요?나는 공유 라이브러리가 자체적인 신규/삭제를 사용하고 있으므로 팩토리 옆에 라이브러리 코드에서 소멸자 삭제를 생성하도록 제공해야 한다고 생각했습니다.
보충 질문:destroy(Base* p) 기능이 필요한 이유는 무엇입니까?이 함수가 클라이언트의 삭제 연산자만 호출하는 경우 혼자서도 수행할 수 있습니다. 즉, 마지막 줄 옆의 destroy_module(a) 대신 "delete a"를 수행할 수도 있습니다.
내가 찾은 답변:라이브러리는 신규/삭제-연산자 쌍을 제공할 수도 있습니다.따라서 라이브러리의 새 항목을 먼저 사용하고 나중에 클라이언트의 삭제 항목을 사용하면 아마도 함정에 빠질 수 있습니다.안타깝게도 지금까지 내 라이브러리가 자신의 새 라이브러리 또는 삭제된 라이브러리를 사용하는 것을 본 적이 없습니다...따라서 원래 질문에는 여전히 답변이 없습니다.
보충:저는 Linux 플랫폼만을 언급하고 있습니다.
편집하다:중요한 부분은 Employed Russian's Answer에 대한 설명에 있습니다.그래서 저는 간단히 말해서 주요 단서를 제공하고 있습니다.이런 식으로 gcc를 호출하면
g++ -Wall -g -fPIC -shared library.cpp -o library.so -Wl,-Bsymbolic
라이브러리는 자체적인 신규/삭제 연산자를 사용합니다.그렇지 않으면 결과
g++ -Wall -g -fPIC -shared library.cpp -o library.so
호출 프로그램의 신규/삭제 연산자를 사용하는 라이브러리에서.러시아어 고용자에게 감사드립니다!
해결책
문제는 대부분의 경우 UNIX
플랫폼(다른 플랫폼과 달리 Win32
그리고 AIX
) 기본적으로 모든 기호 참조는 첫 번째 런타임 로더에 표시되는 기호 정의.
정의한다면 'operator new'
메인에 a.out
, 모든 것이 해당 정의에 바인딩됩니다(Neil Butterworth의 예에서 볼 수 있듯이). a.out
최초의 이미지 런타임 로더 검색입니다.
다음에 로드되는 라이브러리에서 정의하는 경우 libC.so
(또는 libstdc++.so
당신이 사용하는 경우 GCC
), 그러면 정의가 사용되지 않습니다.당신이 이래로 dlopen()
프로그램이 시작된 후 도서관을 방문하세요. libC
해당 시점에 이미 로드되었으며 라이브러리는 런타임 로더가 검색할 마지막 라이브러리입니다.그래서 당신은 잃습니다.
~에 ELF
플랫폼에서는 다음을 사용하여 기본 동작을 변경할 수 있습니다. -Bsymbolic
.에서 man ld
리눅스에서:
-Bsymbolic
When creating a shared library, bind references to global symbols
to the definition within the shared library, if any. Normally, it
is possible for a program linked against a shared library to override
the definition within the shared library. This option is only meaningful
on ELF platforms which support shared libraries.
참고하세요 -Bsymbolic
컴파일러 플래그가 아닌 링커 플래그입니다.사용하는 경우 g++
, 다음과 같이 플래그를 링커에 전달해야 합니다.
g++ -fPIC -shared library.cpp -o library.so -Wl,-Bsymbolic
다른 팁
다음 코드는 예상대로 작동합니다.동적 라이브러리 코드가 귀하가 제공한 신규/삭제를 사용할 것으로 예상하십니까?내 생각엔 당신이 실망할 것 같아요.
#include <memory>
#include <cstdio>
#include <cstdlib>
using namespace std;;
void* operator new(size_t size) {
std::printf("New...\n");
void *p=std::malloc(size);
if (p == 0) // did malloc succeed?
throw std::bad_alloc(); // ANSI/ISO compliant behavior
return p;
}
void operator delete(void* p) {
std::printf("Delete...\n");
std::free(p);
}
int main() {
int * p = new int(42);
delete p;
}
RTLD_DEEPBIND를 살펴보세요.
문제는 C++의 연산자 오버로딩이 링크 타임이 아닌 컴파일 타임 기능이라는 것입니다.DLL은 오버로드된 new()에 대한 지식 없이 컴파일되었으므로 올바르게 작동하지 않습니다.
또 다른 가능성은 귀하의 플랫폼에서 링크와 함께 작동하지만(예에서와 같이) DLL이 실행 파일의 기호를 확인하지 않는다는 것입니다.