2017-06-19 2 views
0

Ich verwende AddressSanitizer für alle meine Projekte, um Speicherlecks, Heapbeschädigungen usw. zu erkennen. Beim Laden einer dynamischen Bibliothek zur Laufzeit über dlopen, Die Ausgabe von AddressSanitizer lässt viel zu wünschen übrig. Ich habe ein einfaches Testprogramm geschrieben, um das Problem zu veranschaulichen. Der Code selbst ist nicht interessant, einfach zwei Bibliotheken, eine zur Kompilierzeit über -l verlinkte, die andere zur Laufzeit mit dlopen geladen. Der Vollständigkeit halber hier der Code ich für den Test verwendet:AddressSanitizer und Laden von dynamischen Bibliotheken zur Laufzeit -> (<unbekanntes Modul>)

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

ich den Code oben mit den folgenden Befehlen zusammengestellt:

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 

Wenn ich das Programm laufen lasse, erhalte ich die folgende Ausgabe (ich muss export LD_LIBRARY_PATH vorher um libasan zu finden):

$ 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). 

Während die Lecks festgestellt werden, scheint AddressSanitizer nicht in der Lage zu sein Modulnamen, Funktionsnamen und Zeilennummern der lösen Bibliothek, die über dlopen geladen wird (Drucken (< unbekanntes Modul>)), während zum Kompilieren verknüpfte Bibliotheken einwandfrei funktionieren. Meine Frage ist:

Ist es möglich, dies mit einigen Compiler-Schalter zu beheben, oder gibt es keine Möglichkeit, weitere Informationen mit AddressSanitizer zu erhalten, wenn es Bibliotheken mit dlopen geladen kommt? Offensichtlich kann llvm-symbolizer gefunden werden, oder es würde keine Zeilennummern für die andere Bibliothek geben. Ausführen des Programms mit

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

führt nicht zu einem anderen Ausgang. Ich habe das Programm stattdessen mit g ++ kompiliert, aber die Ausgabe blieb gleich. Ich habe auch die Ausgabe über asan_symbolize.py weitergeleitet, aber nichts hat sich geändert. Ich weiß nicht, wo ich als nächstes hinschauen soll. Gibt es einen fundamentalen Fehler in meinem Denken? Ich bin kein Spezialist, wenn es um das dynamische Laden von Bibliotheken geht.

Antwort

1

Ich habe wirklich abgeschnitten, wenn es darum geht, solche Probleme in dynamisch geladenen Bibliotheken zu verfolgen, aber ich lasse Bibliothek Entladecode für Testzwecke einfach weg, so dass Symbole für Sanitizer (und Valgrind) immer noch verfügbar sind, wenn Programm beendet wird. Dies kann jedoch zu einer falschen Lecksuche führen, da die von dlopen zugewiesenen Mitarbeiter nicht freigegeben werden.

Und es scheint, dass es keine richtige Lösung für dieses Problem gibt, da technisch nach dem Entladen der Bibliothek nichts verhindert, dass eine andere Bibliothek an der gleichen Adresse geladen wird.

+0

Danke, nachdem ich das Entladen der Bibliothek deaktiviert habe, habe ich die richtige Ausgabe bekommen. Es ist eine Schande, dass es nicht funktioniert, wenn Sie die Bibliothek entladen, wie Sie sollten. Ich schätze, ich werde Ihren Ansatz verfolgen und einfach das Entladen der Bibliothek bei der Suche nach Speicherlecks deaktivieren. – kamshi

1

Das ist ein bekannter Fehler in ASan (siehe Issue 89). Es ist schon eine Weile her, aber scheint niemand motiviert zu sein, es zu beheben.

Verwandte Themen