Я использую AddressSanitizer для всех своих проектов, чтобы обнаруживать утечки памяти, повреждения кучи и т. д. Однако при загрузке динамической библиотеки во время выполнения через dlopen вывод AddressSanitizer оставляет желать лучшего. Я написал простую тестовую программу, чтобы проиллюстрировать проблему. Сам код не интересен, просто две библиотеки, одна подключается во время компиляции через -l, другая загружается во время выполнения с помощью dlopen. Для полноты, вот код, который я использовал для тестирования:
// ----------------------------------------------------------------------------
// dllHelper.hpp
#pragma once
#include <string>
#include <sstream>
#include <iostream>
#include <errno.h>
#include <dlfcn.h>
// Generic helper definitions for shared library support
#if defined WIN32
#define MY_DLL_EXPORT __declspec(dllexport)
#define MY_DLL_IMPORT __declspec(dllimport)
#define MY_DLL_LOCAL
#define MY_DLL_INTERNAL
#else
#if __GNUC__ >= 4
#define MY_DLL_EXPORT __attribute__ ((visibility ("default")))
#define MY_DLL_IMPORT __attribute__ ((visibility ("default")))
#define MY_DLL_LOCAL __attribute__ ((visibility ("hidden")))
#define MY_DLL_INTERNAL __attribute__ ((visibility ("internal")))
#else
#define MY_DLL_IMPORT
#define MY_DLL_EXPORT
#define MY_DLL_LOCAL
#define MY_DLL_INTERNAL
#endif
#endif
void* loadLibrary(const std::string& filename) {
void* module = dlopen(filename.c_str(), RTLD_NOW | RTLD_GLOBAL);
if(module == nullptr) {
char* error = dlerror();
std::stringstream stream;
stream << "Error trying to load the library. Filename: " << filename << " Error: " << error;
std::cout << stream.str() << std::endl;
}
return module;
}
void unloadLibrary(void* module) {
dlerror(); //clear all errors
int result = dlclose(module);
if(result != 0) {
char* error = dlerror();
std::stringstream stream;
stream << "Error trying to free the library. Error code: " << error;
std::cout << stream.str() << std::endl;
}
}
void* loadFunction(void* module, const std::string& functionName) {
if(!module) {
std::cerr << "Invalid module" << std::endl;
return nullptr;
}
dlerror(); //clear all errors
#ifdef __GNUC__
__extension__
#endif
void* result = dlsym(module, functionName.c_str());
char* error;
if((error = dlerror()) != nullptr) {
std::stringstream stream;
stream << "Error trying to get address of function \"" << functionName << "\" from the library. Error code: " << error;
std::cout << stream.str() << std::endl;
}
return result;
}
// ----------------------------------------------------------------------------
// testLib.hpp
#pragma once
#include "dllHelper.hpp"
#ifdef TESTLIB
#define TESTLIB_EXPORT MY_DLL_EXPORT
#else
#define TESTLIB_EXPORT MY_DLL_IMPORT
#endif
namespace TestLib {
// will be linked at compile time
class TESTLIB_EXPORT LeakerTestLib {
public:
void leak();
};
}
// ----------------------------------------------------------------------------
// testLib.cpp
#include "testLib.hpp"
namespace TestLib {
void LeakerTestLib::leak() {
volatile char* myLeak = new char[10];
(void)myLeak;
}
}
// ----------------------------------------------------------------------------
// testLibRuntime.hpp
#pragma once
#include "dllHelper.hpp"
#ifdef TESTLIBRUNTIME
#define TESTLIBRUNTIME_EXPORT MY_DLL_EXPORT
#else
#define TESTLIBRUNTIME_EXPORT MY_DLL_IMPORT
#endif
namespace TestLibRuntime {
// will be loaded via dlopen at runtime
class TESTLIBRUNTIME_EXPORT LeakerTestLib {
public:
void leak();
};
}
extern "C" {
TestLibRuntime::LeakerTestLib* TESTLIBRUNTIME_EXPORT createInstance();
void TESTLIBRUNTIME_EXPORT freeInstance(TestLibRuntime::LeakerTestLib* instance);
void TESTLIBRUNTIME_EXPORT performLeak(TestLibRuntime::LeakerTestLib* instance);
}
// ----------------------------------------------------------------------------
// testLibRuntime.cpp
#include "testLibRuntime.hpp"
namespace TestLibRuntime {
void LeakerTestLib::leak() {
volatile char* myLeak = new char[10];
(void)myLeak;
}
extern "C" {
LeakerTestLib* createInstance() {
return new LeakerTestLib();
}
void freeInstance(LeakerTestLib* instance) {
delete instance;
}
void performLeak(LeakerTestLib* instance) {
if(instance) {
instance->leak();
}
}
}
}
// ----------------------------------------------------------------------------
// main.cpp
#include "testLib.hpp"
#include "testLibRuntime.hpp"
#define LEAK_TESTLIB
#define LEAK_TESTLIBRUNTIME
int main(int argc, char** argv) {
#ifdef LEAK_TESTLIBRUNTIME
void* testLibRuntimeModule = loadLibrary("libtestLibRuntime.so");
if(!testLibRuntimeModule) {
return -1;
}
TestLibRuntime::LeakerTestLib* testLibRuntime = nullptr;
auto createInstance = (TestLibRuntime::LeakerTestLib * (*)())loadFunction(testLibRuntimeModule, "createInstance");
if(!createInstance) {
return -1;
}
auto freeInstance = (void(*)(TestLibRuntime::LeakerTestLib*))loadFunction(testLibRuntimeModule, "freeInstance");
if(!freeInstance) {
return -1;
}
auto performLeak = (void(*)(TestLibRuntime::LeakerTestLib*))loadFunction(testLibRuntimeModule, "performLeak");
if(!performLeak) {
return -1;
}
testLibRuntime = createInstance();
performLeak(testLibRuntime);
freeInstance(testLibRuntime);
#endif
#ifdef LEAK_TESTLIB
TestLib::LeakerTestLib testLib;
testLib.leak();
#endif
#ifdef LEAK_TESTLIBRUNTIME
unloadLibrary(testLibRuntimeModule);
#endif
return 0;
}
Я скомпилировал приведенный выше код со следующими командами:
clang++ -std=c++11 -O0 -g -ggdb -Wl,-undefined -Wl,dynamic_lookup -fsanitize=address -fsanitize-recover=address -fno-omit-frame-pointer -fsanitize-address-use-after-scope -DTESTLIB -shared -fPIC -o libtestLib.so testLib.cpp -ldl -shared-libasan
clang++ -std=c++11 -O0 -g -ggdb -Wl,-undefined -Wl,dynamic_lookup -fsanitize=address -fsanitize-recover=address -fno-omit-frame-pointer -fsanitize-address-use-after-scope -DTESTLIBRUNTIME -shared -fPIC -o libtestLibRuntime.so testLibRuntime.cpp -ldl -shared-libasan
clang++ -std=c++11 -O0 -g -ggdb -Wl,-undefined -Wl,dynamic_lookup -fsanitize=address -fsanitize-recover=address -fno-omit-frame-pointer -fsanitize-address-use-after-scope -o leak main.cpp -ldl -L./ -ltestLib -shared-libasan
Когда я запускаю программу, я получаю следующий вывод (я должен заранее экспортировать LD_LIBRARY_PATH, чтобы найти libasan):
$ export LD_LIBRARY_PATH=/usr/lib/clang/4.0.0/lib/linux/:./
$ ./leak
=================================================================
==4210==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 10 byte(s) in 1 object(s) allocated from:
#0 0x7fb665a210f0 in operator new[](unsigned long) (/usr/lib/clang/4.0.0/lib/linux/libclang_rt.asan-x86_64.so+0x10e0f0)
#1 0x7fb66550d58a in TestLib::LeakerTestLib::leak() /home/jae/projects/clang_memcheck/testLib.cpp:6:29
#2 0x402978 in main /home/jae/projects/clang_memcheck/main.cpp:37:13
#3 0x7fb6648d4439 in __libc_start_main (/usr/lib/libc.so.6+0x20439)
Direct leak of 10 byte(s) in 1 object(s) allocated from:
#0 0x7fb665a210f0 in operator new[](unsigned long) (/usr/lib/clang/4.0.0/lib/linux/libclang_rt.asan-x86_64.so+0x10e0f0)
#1 0x7fb6617fd6da (<unknown module>)
#2 0x7fb6617fd75f (<unknown module>)
#3 0x402954 in main /home/jae/projects/clang_memcheck/main.cpp:31:5
#4 0x7fb6648d4439 in __libc_start_main (/usr/lib/libc.so.6+0x20439)
SUMMARY: AddressSanitizer: 20 byte(s) leaked in 2 allocation(s).
Хотя утечки обнаружены, AddressSanitizer, похоже, не может разрешить имя модуля, имена функций и номера строк библиотеки, которая загружается через dlopen (вместо этого печатает ( ‹ unknown module > )), в то время как библиотеки, связанные во время компиляции, работают безупречно. Мой вопрос:
Можно ли исправить это с помощью некоторых параметров компилятора или нет возможности получить дополнительную информацию с помощью AddressSanitizer, когда речь идет о библиотеках, загруженных с помощью dlopen? Очевидно, что llvm-symbolizer можно найти, иначе не было бы номеров строк для другой библиотеки. Запуск программы с
ASAN_OPTIONS=symbolize=1 ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer ./leak
не приводит к другому результату. Вместо этого я скомпилировал программу с помощью g++, но вывод остался прежним. Я также передал вывод через asan_symbolize.py, но ничего не изменилось. Я не знаю, где искать дальше. Есть ли фундаментальная ошибка в моем мышлении? Я не специалист по динамической загрузке библиотек.