AddressSanitizer并在运行时加载动态库 - >(

问题描述:

为所有我的项目使用AddressSanitizer以检测内存泄漏,堆损坏等。但是,当通过dlopen在运行时加载动态库时, AddressSanitizer的输出结果很不理想。我写了一个简单的测试程序来说明问题。代码本身并不有趣,只有两个库,一个在编译时通过-l链接,另一个在运行时用dlopen加载。为了完整起见,这里的代码我用来测试:AddressSanitizer并在运行时加载动态库 - >(<unknown module>)

// ---------------------------------------------------------------------------- 
// 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加载(打印(<未知模块>)),而在编译时连接的库完美地工作。我的问题是:

是否有可能使用某些编译器开关解决此问题,或者当使用dlopen加载库时,是否无法使用AddressSanitizer获取更多信息?显然llvm-symbolizer可以被找到,或者不会有其他库的行号。运行程序

ASAN_OPTIONS=symbolize=1 ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer ./leak 

不会导致不同的输出。我用g ++编译程序,但输出保持不变。我还通过asan_symbolize.py管道输出,但没有任何改变。我不知道下一步该往哪里看。我的想法有没有根本的错误?当涉及到动态加载库时,我不是专家。

当我们在动态加载的库中跟踪这些问题时,我一直在偷工减料,但为了测试目的,我只是省略了库卸载代码,因此程序终止时,符号仍然可用于sanitizer(和valgrind)。尽管这样做可能会导致一些错误的泄漏检测,因为dlopen分配的人员不会被释放。

而且似乎没有适当的解决方案,因为技术上在卸载库之后没有任何功能阻止另一个库被加载到相同的地址。

+0

谢谢你,我禁用卸载库后,我得到了正确的输出。当你像卸载应用程序库时,它不起作用是件耻辱。猜猜我会采取你的方法,并简单地禁用库卸载时搜索内存泄漏。 – kamshi

这是ASan中的一个已知错误(请参阅Issue 89)。它已经存在了一段时间,但似乎没有人有动力修复它。