diff --git a/CMakeLists.txt b/CMakeLists.txt index 0cdde70..7ff93df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,93 +1,98 @@ if (CMAKE_VERSION VERSION_LESS "2.8.12") cmake_minimum_required(VERSION 2.8.9) set(HEAPTRACK_BUILD_GUI OFF) else() cmake_minimum_required(VERSION 2.8.12) endif() project(heaptrack) enable_testing() if(NOT CMAKE_BUILD_TYPE) message(STATUS "Setting build type to 'RelWithDebInfo' as none was specified.") set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build." FORCE) endif() set(HEAPTRACK_VERSION_MAJOR 1) set(HEAPTRACK_VERSION_MINOR 0) set(HEAPTRACK_VERSION_PATCH 0) set(HEAPTRACK_LIB_VERSION 1.0.0) set(HEAPTRACK_LIB_SOVERSION 1) set(HEAPTRACK_FILE_FORMAT_VERSION 2) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) find_package(Libunwind REQUIRED) find_package(Boost 1.41.0 REQUIRED COMPONENTS iostreams program_options) find_package(Threads REQUIRED) find_package(ZLIB REQUIRED) include(FeatureSummary) option( HEAPTRACK_BUILD_GUI "Disable this option to skip building the Qt5 / KF5 based GUI for heaptrack." On ) if(HEAPTRACK_BUILD_GUI) find_package(Qt5 5.2.0 NO_MODULE OPTIONAL_COMPONENTS Widgets) find_package(ECM 1.0.0 NO_MODULE) if(Qt5_FOUND AND ECM_FOUND) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) find_package(KF5 COMPONENTS CoreAddons I18n ItemModels ThreadWeaver ConfigWidgets KIO) find_package(KChart "2.6.0") set_package_properties(KChart PROPERTIES TYPE RECOMMENDED PURPOSE "Required for the heaptrack_gui executable. Get it from the kdiagram module.") endif() endif() set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wpedantic") include (CheckCXXSourceCompiles) check_cxx_source_compiles( "#include #include thread_local int tls; int main() { return 0; }" HAVE_CXX11_SUPPORT) if (NOT HAVE_CXX11_SUPPORT) message(FATAL_ERROR "Your compiler is too old and does not support the required C++11 features.") endif() check_cxx_source_compiles( "#include #include #include #include int main() { return 0; }" HAVE_LINUX_HEADERS) if (NOT HAVE_LINUX_HEADERS) message(FATAL_ERROR "You are missing some Linux headers required to compile heaptrack.") endif() +# cfree() does not exist in glibc 2.26+. +# See: https://bugs.kde.org/show_bug.cgi?id=383889 +include(CheckSymbolExists) +check_symbol_exists(cfree malloc.h HAVE_CFREE) + set(BIN_INSTALL_DIR "bin") set(LIB_SUFFIX "" CACHE STRING "Define suffix of directory name (32/64)") set(LIB_INSTALL_DIR "lib${LIB_SUFFIX}") set(LIBEXEC_INSTALL_DIR "${LIB_INSTALL_DIR}/heaptrack/libexec") file(RELATIVE_PATH LIBEXEC_REL_PATH "${CMAKE_INSTALL_PREFIX}/${BIN_INSTALL_DIR}" "${CMAKE_INSTALL_PREFIX}/${LIBEXEC_INSTALL_DIR}") file(RELATIVE_PATH LIB_REL_PATH "${CMAKE_INSTALL_PREFIX}/${BIN_INSTALL_DIR}" "${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR}/heaptrack") add_subdirectory(3rdparty) add_subdirectory(src) add_subdirectory(tests) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/track/heaptrack_inject.cpp b/src/track/heaptrack_inject.cpp index 93508e0..c24a266 100644 --- a/src/track/heaptrack_inject.cpp +++ b/src/track/heaptrack_inject.cpp @@ -1,297 +1,292 @@ /* * Copyright 2014-2017 Milian Wolff * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "libheaptrack.h" +#include "util/config.h" #include #include #include #include #include #include /** * @file heaptrack_inject.cpp * * @brief Experimental support for symbol overloading after runtime injection. */ #if __WORDSIZE == 64 #define ELF_R_SYM(i) ELF64_R_SYM(i) #elif __WORDSIZE == 32 #define ELF_R_SYM(i) ELF32_R_SYM(i) #else #error unsupported word size #endif -#if defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || defined(__USE_MISC) -#define HAVE_CFREE 1 -#else -#define HAVE_CFREE 0 -#endif - namespace { namespace Elf { using Addr = ElfW(Addr); using Dyn = ElfW(Dyn); using Rela = ElfW(Rela); using Sym = ElfW(Sym); using Sxword = ElfW(Sxword); using Xword = ElfW(Xword); } void overwrite_symbols() noexcept; namespace hooks { struct malloc { static constexpr auto name = "malloc"; static constexpr auto original = &::malloc; static void* hook(size_t size) noexcept { auto ptr = original(size); heaptrack_malloc(ptr, size); return ptr; } }; struct free { static constexpr auto name = "free"; static constexpr auto original = &::free; static void hook(void* ptr) noexcept { heaptrack_free(ptr); original(ptr); } }; struct realloc { static constexpr auto name = "realloc"; static constexpr auto original = &::realloc; static void* hook(void* ptr, size_t size) noexcept { auto ret = original(ptr, size); heaptrack_realloc(ptr, size, ret); return ret; } }; struct calloc { static constexpr auto name = "calloc"; static constexpr auto original = &::calloc; static void* hook(size_t num, size_t size) noexcept { auto ptr = original(num, size); heaptrack_malloc(ptr, num * size); return ptr; } }; #if HAVE_CFREE struct cfree { static constexpr auto name = "cfree"; static constexpr auto original = &::cfree; static void hook(void* ptr) noexcept { heaptrack_free(ptr); original(ptr); } }; #endif struct dlopen { static constexpr auto name = "dlopen"; static constexpr auto original = &::dlopen; static void* hook(const char* filename, int flag) noexcept { auto ret = original(filename, flag); if (ret) { heaptrack_invalidate_module_cache(); overwrite_symbols(); } return ret; } }; struct dlclose { static constexpr auto name = "dlclose"; static constexpr auto original = &::dlclose; static int hook(void* handle) noexcept { auto ret = original(handle); if (!ret) { heaptrack_invalidate_module_cache(); } return ret; } }; struct posix_memalign { static constexpr auto name = "posix_memalign"; static constexpr auto original = &::posix_memalign; static int hook(void** memptr, size_t alignment, size_t size) noexcept { auto ret = original(memptr, alignment, size); if (!ret) { heaptrack_malloc(*memptr, size); } return ret; } }; template bool hook(const char* symname, Elf::Addr addr, bool restore) { static_assert(std::is_convertible::value, "hook is not compatible to original function"); if (strcmp(Hook::name, symname) != 0) { return false; } // try to make the page read/write accessible, which is hackish // but apparently required for some shared libraries auto page = reinterpret_cast(addr & ~(0x1000 - 1)); mprotect(page, 0x1000, PROT_READ | PROT_WRITE); // now write to the address auto typedAddr = reinterpret_cast::type*>(addr); if (restore) { // restore the original address on shutdown *typedAddr = Hook::original; } else { // now actually inject our hook *typedAddr = &Hook::hook; } return true; } void apply(const char* symname, Elf::Addr addr, bool restore) { // TODO: use std::apply once we can rely on C++17 hook(symname, addr, restore) || hook(symname, addr, restore) || hook(symname, addr, restore) || hook(symname, addr, restore) #if HAVE_CFREE || hook(symname, addr, restore) #endif || hook(symname, addr, restore) || hook(symname, addr, restore) || hook(symname, addr, restore); } } template struct elftable { T* table = nullptr; Elf::Xword size = {}; bool consume(const Elf::Dyn* dyn) noexcept { if (dyn->d_tag == AddrTag) { table = reinterpret_cast(dyn->d_un.d_ptr); return true; } else if (dyn->d_tag == SizeTag) { size = dyn->d_un.d_val; return true; } return false; } }; using elf_string_table = elftable; using elf_jmprel_table = elftable; using elf_symbol_table = elftable; void try_overwrite_symbols(const Elf::Dyn* dyn, const Elf::Addr base, const bool restore) noexcept { elf_symbol_table symbols; elf_jmprel_table jmprels; elf_string_table strings; // initialize the elf tables for (; dyn->d_tag != DT_NULL; ++dyn) { symbols.consume(dyn) || jmprels.consume(dyn) || strings.consume(dyn); } // find symbols to overwrite const auto rela_end = reinterpret_cast(reinterpret_cast(jmprels.table) + jmprels.size); for (auto rela = jmprels.table; rela < rela_end; rela++) { const auto index = ELF_R_SYM(rela->r_info); const char* symname = strings.table + symbols.table[index].st_name; auto addr = rela->r_offset + base; hooks::apply(symname, addr, restore); } } int iterate_phdrs(dl_phdr_info* info, size_t /*size*/, void* data) noexcept { if (strstr(info->dlpi_name, "/libheaptrack_inject.so")) { // prevent infinite recursion: do not overwrite our own symbols return 0; } else if (strstr(info->dlpi_name, "/ld-linux")) { // prevent strange crashes due to overwriting the free symbol in ld-linux return 0; } for (auto phdr = info->dlpi_phdr, end = phdr + info->dlpi_phnum; phdr != end; ++phdr) { if (phdr->p_type == PT_DYNAMIC) { try_overwrite_symbols(reinterpret_cast(phdr->p_vaddr + info->dlpi_addr), info->dlpi_addr, data != nullptr); } } return 0; } void overwrite_symbols() noexcept { dl_iterate_phdr(&iterate_phdrs, nullptr); } } extern "C" { void heaptrack_inject(const char* outputFileName) noexcept { heaptrack_init(outputFileName, []() { overwrite_symbols(); }, [](FILE* out) { fprintf(out, "A\n"); }, []() { bool do_shutdown = true; dl_iterate_phdr(&iterate_phdrs, &do_shutdown); }); } } diff --git a/src/track/heaptrack_preload.cpp b/src/track/heaptrack_preload.cpp index d8dde24..b77774a 100644 --- a/src/track/heaptrack_preload.cpp +++ b/src/track/heaptrack_preload.cpp @@ -1,322 +1,318 @@ /* * Copyright 2014-2017 Milian Wolff * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "libheaptrack.h" +#include "util/config.h" #include #include #include #include #include #include using namespace std; #if defined(_ISOC11_SOURCE) # define HAVE_ALIGNED_ALLOC 1 #else # define HAVE_ALIGNED_ALLOC 0 #endif -#if defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || defined(__USE_MISC) -# define HAVE_CFREE 1 -#else -# define HAVE_CFREE 0 -#endif extern "C" { __attribute__((weak)) void __libc_freeres(); } namespace __gnu_cxx { __attribute__((weak)) extern void __freeres(); } namespace { namespace hooks { template struct hook { Signature original = nullptr; void init() noexcept { auto ret = dlsym(RTLD_NEXT, Base::identifier); if (!ret) { fprintf(stderr, "Could not find original function %s\n", Base::identifier); abort(); } original = reinterpret_cast(ret); } template auto operator()(Args... args) const noexcept -> decltype(original(args...)) { return original(args...); } explicit operator bool() const noexcept { return original; } }; #define HOOK(name) \ struct name##_t : public hook \ { \ static constexpr const char* identifier = #name; \ } name HOOK(malloc); HOOK(free); HOOK(calloc); #if HAVE_CFREE HOOK(cfree); #endif HOOK(realloc); HOOK(posix_memalign); HOOK(valloc); #if HAVE_ALIGNED_ALLOC HOOK(aligned_alloc); #endif HOOK(dlopen); HOOK(dlclose); /** * Dummy implementation, since the call to dlsym from findReal triggers a call * to calloc. * * This is only called at startup and will eventually be replaced by the * "proper" calloc implementation. */ void* dummy_calloc(size_t num, size_t size) noexcept { const size_t MAX_SIZE = 1024; static char* buf[MAX_SIZE]; static size_t offset = 0; if (!offset) { memset(buf, 0, MAX_SIZE); } size_t oldOffset = offset; offset += num * size; if (offset >= MAX_SIZE) { fprintf(stderr, "failed to initialize, dummy calloc buf size exhausted: " "%zu requested, %zu available\n", offset, MAX_SIZE); abort(); } return buf + oldOffset; } void init() { atexit([]() { // free internal libstdc++ resources // see also Valgrind's `--run-cxx-freeres` option if (&__gnu_cxx::__freeres) { __gnu_cxx::__freeres(); } // free internal libc resources, cf: https://bugs.kde.org/show_bug.cgi?id=378765 // see also Valgrind's `--run-libc-freeres` option if (&__libc_freeres) { __libc_freeres(); } }); heaptrack_init(getenv("DUMP_HEAPTRACK_OUTPUT"), [] { hooks::calloc.original = &dummy_calloc; hooks::calloc.init(); hooks::dlopen.init(); hooks::dlclose.init(); hooks::malloc.init(); hooks::free.init(); hooks::calloc.init(); #if HAVE_CFREE hooks::cfree.init(); #endif hooks::realloc.init(); hooks::posix_memalign.init(); hooks::valloc.init(); #if HAVE_ALIGNED_ALLOC hooks::aligned_alloc.init(); #endif // cleanup environment to prevent tracing of child apps unsetenv("LD_PRELOAD"); unsetenv("DUMP_HEAPTRACK_OUTPUT"); }, nullptr, nullptr); } } } extern "C" { /// TODO: memalign, pvalloc, ...? void* malloc(size_t size) noexcept { if (!hooks::malloc) { hooks::init(); } void* ptr = hooks::malloc(size); heaptrack_malloc(ptr, size); return ptr; } void free(void* ptr) noexcept { if (!hooks::free) { hooks::init(); } // call handler before handing over the real free implementation // to ensure the ptr is not reused in-between and thus the output // stays consistent heaptrack_free(ptr); hooks::free(ptr); } void* realloc(void* ptr, size_t size) noexcept { if (!hooks::realloc) { hooks::init(); } void* ret = hooks::realloc(ptr, size); if (ret) { heaptrack_realloc(ptr, size, ret); } return ret; } void* calloc(size_t num, size_t size) noexcept { if (!hooks::calloc) { hooks::init(); } void* ret = hooks::calloc(num, size); if (ret) { heaptrack_malloc(ret, num * size); } return ret; } #if HAVE_CFREE void cfree(void* ptr) noexcept { if (!hooks::cfree) { hooks::init(); } // call handler before handing over the real free implementation // to ensure the ptr is not reused in-between and thus the output // stays consistent if (ptr) { heaptrack_free(ptr); } hooks::cfree(ptr); } #endif int posix_memalign(void** memptr, size_t alignment, size_t size) noexcept { if (!hooks::posix_memalign) { hooks::init(); } int ret = hooks::posix_memalign(memptr, alignment, size); if (!ret) { heaptrack_malloc(*memptr, size); } return ret; } #if HAVE_ALIGNED_ALLOC void* aligned_alloc(size_t alignment, size_t size) noexcept { if (!hooks::aligned_alloc) { hooks::init(); } void* ret = hooks::aligned_alloc(alignment, size); if (ret) { heaptrack_malloc(ret, size); } return ret; } #endif void* valloc(size_t size) noexcept { if (!hooks::valloc) { hooks::init(); } void* ret = hooks::valloc(size); if (ret) { heaptrack_malloc(ret, size); } return ret; } void* dlopen(const char* filename, int flag) noexcept { if (!hooks::dlopen) { hooks::init(); } void* ret = hooks::dlopen(filename, flag); if (ret) { heaptrack_invalidate_module_cache(); } return ret; } int dlclose(void* handle) noexcept { if (!hooks::dlclose) { hooks::init(); } int ret = hooks::dlclose(handle); if (!ret) { heaptrack_invalidate_module_cache(); } return ret; } } diff --git a/src/util/config.h.cmake b/src/util/config.h.cmake index 7bad362..861c1f7 100644 --- a/src/util/config.h.cmake +++ b/src/util/config.h.cmake @@ -1,32 +1,36 @@ /* * Copyright 2014-2017 Milian Wolff * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef HEAPTRACK_CONFIG_H #define HEAPTRACK_CONFIG_H #define HEAPTRACK_VERSION_STRING "@HEAPTRACK_VERSION_MAJOR@.@HEAPTRACK_VERSION_MINOR@.@HEAPTRACK_VERSION_PATCH@" #define HEAPTRACK_VERSION_MAJOR @HEAPTRACK_VERSION_MAJOR@ #define HEAPTRACK_VERSION_MINOR @HEAPTRACK_VERSION_MINOR@ #define HEAPTRACK_VERSION_PATCH @HEAPTRACK_VERSION_PATCH@ #define HEAPTRACK_VERSION ((HEAPTRACK_VERSION_MAJOR<<16)|(HEAPTRACK_VERSION_MINOR<<8)|(HEAPTRACK_VERSION_PATCH)) #define HEAPTRACK_FILE_FORMAT_VERSION @HEAPTRACK_FILE_FORMAT_VERSION@ #define HEAPTRACK_DEBUG_BUILD @HEAPTRACK_DEBUG_BUILD@ +// cfree() does not exist in glibc 2.26+. +// See: https://bugs.kde.org/show_bug.cgi?id=383889 +#cmakedefine01 HAVE_CFREE + #endif // HEAPTRACK_CONFIG_H diff --git a/tests/manual/CMakeLists.txt b/tests/manual/CMakeLists.txt index fe9602b..8996201 100644 --- a/tests/manual/CMakeLists.txt +++ b/tests/manual/CMakeLists.txt @@ -1,28 +1,32 @@ set(CMAKE_BUILD_TYPE Debug) add_executable(test_c test.c) add_executable(test_cpp test.cpp) +set_target_properties(test_cpp PROPERTIES + INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR}/../../src/ +) + add_executable(threaded threaded.cpp) target_link_libraries(threaded ${CMAKE_THREAD_LIBS_INIT}) add_executable(callgraph callgraph.cpp) add_library(testlib SHARED lib.cpp) add_executable(test_lib test_lib.cpp) target_link_libraries(test_lib testlib) add_executable(test_aggregation test_aggregation.cpp) add_executable(signals signals.cpp) target_link_libraries(signals ${CMAKE_THREAD_LIBS_INIT}) add_executable(libc_leaks libc_leaks.c) add_executable(peak peak.c) set_target_properties(peak PROPERTIES COMPILE_FLAGS "-g3 -O0" ) set(CMAKE_BUILD_TYPE RelWithDebInfo) add_executable(inlining inlining.cpp) diff --git a/tests/manual/test.cpp b/tests/manual/test.cpp index a77dbd7..4c7b0d2 100644 --- a/tests/manual/test.cpp +++ b/tests/manual/test.cpp @@ -1,109 +1,115 @@ /* * Copyright 2014-2017 Milian Wolff * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include +#include "util/config.h" + #if defined(_ISOC11_SOURCE) # define HAVE_ALIGNED_ALLOC 1 #else # define HAVE_ALIGNED_ALLOC 0 #endif struct Foo { Foo() : i(new int) { } ~Foo() { delete i; } int* i; }; void asdf() { int* i = new int; printf("i in asdf: %p\n", (void*)i); } void bar() { asdf(); } void laaa() { bar(); } void split() { Foo f; asdf(); bar(); laaa(); } static Foo foo; int main() { Foo* f = new Foo; printf("new Foo: %p\n", (void*)f); delete f; char* c = new char[1000]; printf("new char[]: %p\n", (void*)c); delete[] c; void* buf = malloc(100); printf("malloc: %p\n", buf); buf = realloc(buf, 200); printf("realloc: %p\n", buf); free(buf); buf = calloc(5, 5); printf("calloc: %p\n", buf); +#if HAVE_CFREE cfree(buf); +#else + free(buf); +#endif #if HAVE_ALIGNED_ALLOC buf = aligned_alloc(16, 160); printf("aligned_alloc: %p\n", buf); free(buf); #endif buf = valloc(32); printf("valloc: %p\n", buf); free(buf); posix_memalign(&buf, 16, 64); printf("posix_memalign: %p\n", buf); free(buf); for (int i = 0; i < 10; ++i) { laaa(); } laaa(); split(); return 0; }