diff --git a/tests/auto/CMakeLists.txt b/tests/auto/CMakeLists.txt index 786476b..4d3277e 100644 --- a/tests/auto/CMakeLists.txt +++ b/tests/auto/CMakeLists.txt @@ -1,45 +1,57 @@ if (ECM_FOUND) include(ECMEnableSanitizers) endif() set(CMAKE_BUILD_TYPE Debug) +configure_file(tst_config.h.in ${CMAKE_CURRENT_BINARY_DIR}/tst_config.h @ONLY) include_directories( ${LIBUNWIND_INCLUDE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../../ ${CMAKE_CURRENT_SOURCE_DIR}/../../src ) add_definitions(-DCATCH_CONFIG_MAIN) add_executable(tst_trace tst_trace.cpp) target_link_libraries(tst_trace ${LIBUNWIND_LIBRARY}) add_test(NAME tst_trace COMMAND tst_trace) find_package(Boost 1.41.0 COMPONENTS system filesystem) if (${Boost_FILESYSTEM_FOUND} AND ${Boost_SYSTEM_FOUND}) include_directories( ${Boost_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}/../../src/ ${CMAKE_CURRENT_SOURCE_DIR}/../../src/track ) add_executable(tst_libheaptrack tst_libheaptrack.cpp ../../src/track/libheaptrack.cpp) target_link_libraries(tst_libheaptrack LINK_PRIVATE ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT} ${LIBUNWIND_LIBRARY} rt ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ) add_test(NAME tst_libheaptrack COMMAND tst_libheaptrack) add_executable(tst_io tst_io.cpp) target_link_libraries(tst_io ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ) add_test(NAME tst_io COMMAND tst_io) + + + add_executable(tst_inject tst_inject.cpp) + target_link_libraries(tst_inject + ${CMAKE_DL_LIBS} + ${Boost_SYSTEM_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ) + add_test(NAME tst_inject COMMAND tst_inject) + endif() diff --git a/tests/auto/tempfile.h b/tests/auto/tempfile.h index 3f744f4..54fdce5 100644 --- a/tests/auto/tempfile.h +++ b/tests/auto/tempfile.h @@ -1,58 +1,69 @@ /* * Copyright 2018 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 TEMPFILE_H #define TEMPFILE_H #include #include #include +#include +#include + struct TempFile { TempFile() : path(boost::filesystem::unique_path()) , fileName(path.native()) { } ~TempFile() { boost::filesystem::remove(path); close(); } bool open() { fd = ::open(fileName.c_str(), O_CREAT | O_CLOEXEC | O_RDWR, 0644); return fd != -1; } void close() { if (fd != -1) { ::close(fd); } } + std::string readContents() const + { + // open in binary mode to really read everything + // we want to ensure that the contents are really clean + std::ifstream ifs(fileName, std::ios::binary); + return {std::istreambuf_iterator(ifs), std::istreambuf_iterator()}; + } + const boost::filesystem::path path; const std::string fileName; int fd = -1; }; #endif diff --git a/tests/auto/tempfile.h b/tests/auto/tst_config.h.in similarity index 55% copy from tests/auto/tempfile.h copy to tests/auto/tst_config.h.in index 3f744f4..d768d95 100644 --- a/tests/auto/tempfile.h +++ b/tests/auto/tst_config.h.in @@ -1,58 +1,20 @@ /* * Copyright 2018 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 TEMPFILE_H -#define TEMPFILE_H - -#include -#include -#include - -struct TempFile -{ - TempFile() - : path(boost::filesystem::unique_path()) - , fileName(path.native()) - { - } - - ~TempFile() - { - boost::filesystem::remove(path); - close(); - } - - bool open() - { - fd = ::open(fileName.c_str(), O_CREAT | O_CLOEXEC | O_RDWR, 0644); - return fd != -1; - } - - void close() - { - if (fd != -1) { - ::close(fd); - } - } - - const boost::filesystem::path path; - const std::string fileName; - int fd = -1; -}; - -#endif +#define HEAPTRACK_LIB_DIR "@PROJECT_BINARY_DIR@/@LIB_INSTALL_DIR@/heaptrack" +#define HEAPTRACK_LIB_INJECT_SO HEAPTRACK_LIB_DIR "/libheaptrack_inject.so" diff --git a/tests/auto/tst_inject.cpp b/tests/auto/tst_inject.cpp new file mode 100644 index 0000000..15b74ce --- /dev/null +++ b/tests/auto/tst_inject.cpp @@ -0,0 +1,107 @@ +/* + * Copyright 2018 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 "3rdparty/catch.hpp" + +#include "tempfile.h" +#include "tst_config.h" + +#include + +#include + +static_assert(RTLD_NOW == 0x2, "RTLD_NOW needs to equal 0x2"); + +using heaptrack_inject_t = void (*)(const char*); +using heaptrack_stop_t = void (*)(); + +namespace { +template +T resolveSymbol(void* handle, const char* symbol) +{ + return reinterpret_cast(dlsym(handle, symbol)); +} + +heaptrack_inject_t resolveHeaptrackInject(void* handle) +{ + return resolveSymbol(handle, "heaptrack_inject"); +} + +heaptrack_stop_t resolveHeaptrackStop(void* handle) +{ + return resolveSymbol(handle, "heaptrack_stop"); +} + +template +void runInjectTest(Load load, Unload unload) +{ + REQUIRE(!resolveHeaptrackInject(RTLD_DEFAULT)); + REQUIRE(!resolveHeaptrackStop(RTLD_DEFAULT)); + + auto* handle = load(); + REQUIRE(handle); + + auto* heaptrack_inject = resolveHeaptrackInject(handle); + REQUIRE(heaptrack_inject); + + auto* heaptrack_stop = resolveHeaptrackStop(handle); + REQUIRE(heaptrack_stop); + + TempFile file; + + heaptrack_inject(file.fileName.c_str()); + + auto* p = malloc(100); + free(p); + + heaptrack_stop(); + + unload(handle); + + REQUIRE(!resolveHeaptrackInject(RTLD_DEFAULT)); + REQUIRE(!resolveHeaptrackStop(RTLD_DEFAULT)); + + const auto contents = file.readContents(); + REQUIRE(!contents.empty()); + REQUIRE(contents.find("\nA\n") != std::string::npos); +} +} + +TEST_CASE ("inject via dlopen", "[inject]") { + runInjectTest( + []() -> void* { + dlerror(); // clear error + auto* handle = dlopen(HEAPTRACK_LIB_INJECT_SO, RTLD_NOW); + if (!handle) { + std::cerr << "DLOPEN FAILED: " << dlerror() << std::endl; + } + return handle; + }, + [](void* handle) { dlclose(handle); }); +} + +extern "C" { +__attribute__((weak)) void* __libc_dlopen_mode(const char* filename, int flag); +__attribute__((weak)) int __libc_dlclose(void* handle); +} + +TEST_CASE ("inject via libc", "[inject]") { + REQUIRE(__libc_dlopen_mode); + runInjectTest([]() { return __libc_dlopen_mode(HEAPTRACK_LIB_INJECT_SO, 0x80000000 | 0x002); }, + [](void* handle) { __libc_dlclose(handle); }); +} diff --git a/tests/auto/tst_io.cpp b/tests/auto/tst_io.cpp index d4abd33..48abda6 100644 --- a/tests/auto/tst_io.cpp +++ b/tests/auto/tst_io.cpp @@ -1,183 +1,171 @@ /* * Copyright 2018 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 "3rdparty/catch.hpp" #include "util/linereader.h" #include "util/linewriter.h" #include "tempfile.h" -#include #include -#include using namespace std; -namespace { -string fileContents(const string& fileName) -{ - // open in binary mode to really read everything - // we want to ensure that the contents are really clean - ifstream ifs(fileName, ios::binary); - return {istreambuf_iterator(ifs), istreambuf_iterator()}; -} -} - TEST_CASE ("write data", "[write]") { TempFile file; REQUIRE(file.open()); LineWriter writer(file.fd); REQUIRE(writer.canWrite()); REQUIRE(writer.write("hello world\n")); REQUIRE(writer.write("%d %x\n", 42, 42)); REQUIRE(writer.writeHexLine('t', 0u, 0ul, 1u, 1ul, 15u, 15ul, 16u, 16ul)); REQUIRE(writer.writeHexLine('u', std::numeric_limits::max() - 1, std::numeric_limits::max())); REQUIRE(writer.writeHexLine('l', std::numeric_limits::max() - 1, std::numeric_limits::max())); - REQUIRE(fileContents(file.fileName).empty()); + REQUIRE(file.readContents().empty()); REQUIRE(writer.flush()); const string expectedContents = "hello world\n" "42 2a\n" "t 0 0 1 1 f f 10 10\n" "u fffffffe ffffffff\n" "l fffffffffffffffe ffffffffffffffff\n"; - REQUIRE(fileContents(file.fileName) == expectedContents); + REQUIRE(file.readContents() == expectedContents); } TEST_CASE ("buffered write", "[write]") { TempFile file; REQUIRE(file.open()); LineWriter writer(file.fd); REQUIRE(writer.canWrite()); string expectedContents; for (unsigned i = 0; i < 10000; ++i) { REQUIRE(writer.write("%d %x\n", 42, 42)); expectedContents += "42 2a\n"; } REQUIRE(expectedContents.size() > LineWriter::BUFFER_CAPACITY); REQUIRE(writer.flush()); - REQUIRE(fileContents(file.fileName) == expectedContents); + REQUIRE(file.readContents() == expectedContents); } TEST_CASE ("buffered writeHex", "[write]") { TempFile file; REQUIRE(file.open()); LineWriter writer(file.fd); REQUIRE(writer.canWrite()); string expectedContents; for (unsigned i = 0; i < 10000; ++i) { REQUIRE(writer.writeHexLine('t', 0x123u, 0x456u)); expectedContents += "t 123 456\n"; } REQUIRE(expectedContents.size() > LineWriter::BUFFER_CAPACITY); REQUIRE(writer.flush()); - REQUIRE(fileContents(file.fileName) == expectedContents); + REQUIRE(file.readContents() == expectedContents); } TEST_CASE ("write flush", "[write]") { TempFile file; REQUIRE(file.open()); LineWriter writer(file.fd); REQUIRE(writer.canWrite()); string data1(LineWriter::BUFFER_CAPACITY - 10, '#'); REQUIRE(writer.write(data1.c_str())); // not yet written - REQUIRE(fileContents(file.fileName).empty()); + REQUIRE(file.readContents().empty()); // NOTE: while this data would fit, // snprintf used by the writer tries to append a \0 too which doesn't fit string data2(10, '+'); REQUIRE(writer.write(data2.c_str())); // so the above flushes, but only the first chunk - REQUIRE(fileContents(file.fileName) == data1); + REQUIRE(file.readContents() == data1); writer.flush(); - REQUIRE(fileContents(file.fileName) == data1 + data2); + REQUIRE(file.readContents() == data1 + data2); } TEST_CASE ("read line 64bit", "[read]") { const string contents = "m /tmp/KDevelop-5.2.1-x86_64/usr/lib/libKF5Completion.so.5 7f48beedc00 0 36854 236858 2700\n"; stringstream stream(contents); LineReader reader; REQUIRE(reader.getLine(stream)); REQUIRE(reader.line() == "m /tmp/KDevelop-5.2.1-x86_64/usr/lib/libKF5Completion.so.5 7f48beedc00 0 36854 236858 2700"); REQUIRE(reader.mode() == 'm'); string module; REQUIRE(reader >> module); REQUIRE(module == "/tmp/KDevelop-5.2.1-x86_64/usr/lib/libKF5Completion.so.5"); for (uint64_t expected : {0x7f48beedc00ul, 0x0ul, 0x36854ul, 0x236858ul, 0x2700ul}) { uint64_t addr = 0; REQUIRE(reader >> addr); REQUIRE(addr == expected); } uint64_t x = 0; REQUIRE(!(reader >> x)); REQUIRE(!(reader >> module)); } TEST_CASE ("read line 32bit", "[read]") { const string contents = "t 4 3\n" "a 11c00 4\n" "+ 0\n"; stringstream stream(contents); LineReader reader; uint32_t idx = 0; REQUIRE(reader.getLine(stream)); REQUIRE(reader.line() == "t 4 3"); REQUIRE(reader.mode() == 't'); REQUIRE(reader >> idx); REQUIRE(idx == 0x4); REQUIRE(reader >> idx); REQUIRE(idx == 0x3); REQUIRE(!(reader >> idx)); REQUIRE(reader.getLine(stream)); REQUIRE(reader.line() == "a 11c00 4"); REQUIRE(reader.mode() == 'a'); REQUIRE(reader >> idx); REQUIRE(idx == 0x11c00); REQUIRE(reader >> idx); REQUIRE(idx == 0x4); REQUIRE(!(reader >> idx)); REQUIRE(reader.getLine(stream)); REQUIRE(reader.line() == "+ 0"); REQUIRE(reader.mode() == '+'); REQUIRE(reader >> idx); REQUIRE(idx == 0x0); REQUIRE(!(reader >> idx)); }