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