diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 48f01e6..0975ee2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,6 +19,7 @@ jobs: ] symbols: [ CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE, + CPPTRACE_GET_SYMBOLS_WITH_LIBDL, CPPTRACE_GET_SYMBOLS_WITH_NOTHING, ] demangle: [ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3736e88..301afd6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,6 +21,7 @@ jobs: ] symbols: [ CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE, + CPPTRACE_GET_SYMBOLS_WITH_LIBDL, #CPPTRACE_GET_SYMBOLS_WITH_NOTHING, ] demangle: [ @@ -44,12 +45,13 @@ jobs: -D${{matrix.demangle}}=On \ -DCPPTRACE_BACKTRACE_PATH=/usr/lib/gcc/x86_64-linux-gnu/10/include/backtrace.h \ -DCPPTRACE_BUILD_TEST=On \ + $(if [ "${{matrix.symbols}}" = "CPPTRACE_GET_SYMBOLS_WITH_LIBDL" ]; then echo "-DCPPTRACE_BUILD_TEST_RDYNAMIC=On"; else echo ""; fi) \ -DBUILD_SHARED_LIBS=On make - name: test working-directory: build run: | - ./test | python3 ../test/test.py "${{matrix.compiler}}" + ./test | python3 ../test/test.py "${{matrix.compiler}}" "${{matrix.unwind}}" "${{matrix.symbols}}" "${{matrix.demangle}}" test-windows: runs-on: windows-2019 strategy: @@ -94,7 +96,7 @@ jobs: - name: test working-directory: build run: | - .\${{matrix.target}}\test.exe | python3 ..\test\test.py "${{matrix.compiler}}" + .\${{matrix.target}}\test.exe | python3 ..\test\test.py "${{matrix.compiler}}" "${{matrix.unwind}}" "${{matrix.symbols}}" "${{matrix.demangle}}" test-linux-full-or-auto: runs-on: ubuntu-22.04 diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e552fa..f2ffa34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,7 @@ option(CPPTRACE_DEMANGLE_WITH_CXXABI "" OFF) option(CPPTRACE_DEMANGLE_WITH_NOTHING "" OFF) option(CPPTRACE_BUILD_TEST "" OFF) +option(CPPTRACE_BUILD_TEST_RDYNAMIC "" OFF) set(CPPTRACE_BACKTRACE_PATH "" CACHE STRING "Path to backtrace.h, if the compiler doesn't already know it. Check /usr/lib/gcc/x86_64-linux-gnu/*/include.") set(CPPTRACE_HARD_MAX_FRAMES "" CACHE STRING "Hard limit on unwinding depth. Default is 100.") @@ -238,10 +239,12 @@ endif() if(CPPTRACE_GET_SYMBOLS_WITH_LIBDL) target_compile_definitions(cpptrace PUBLIC CPPTRACE_GET_SYMBOLS_WITH_LIBDL) + target_link_libraries(cpptrace PRIVATE dl) endif() if(CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE) target_compile_definitions(cpptrace PUBLIC CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE) + target_link_libraries(cpptrace PRIVATE dl) endif() if(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP) @@ -356,4 +359,7 @@ endif() if(CPPTRACE_BUILD_TEST) add_executable(test test/test.cpp) target_link_libraries(test PRIVATE cpptrace) + if(CPPTRACE_BUILD_TEST_RDYNAMIC) + set_property(TARGET test PROPERTY ENABLE_EXPORTS ON) + endif() endif() diff --git a/src/cpptrace.cpp b/src/cpptrace.cpp index 66bb704..c3631e6 100644 --- a/src/cpptrace.cpp +++ b/src/cpptrace.cpp @@ -16,12 +16,10 @@ namespace cpptrace { CPPTRACE_FORCE_NO_INLINE std::vector generate_trace() { std::vector frames = detail::capture_frames(1); - std::vector trace; detail::symbolizer symbolizer; - for(const auto frame : frames) { - auto entry = symbolizer.resolve_frame(frame); - entry.symbol = detail::demangle(entry.symbol); - trace.push_back(entry); + std::vector trace = symbolizer.resolve_frames(frames); + for(auto& frame : trace) { + frame.symbol = detail::demangle(frame.symbol); } return trace; } diff --git a/src/symbols/cpptrace_symbols.hpp b/src/symbols/cpptrace_symbols.hpp index 4115a9d..20189f8 100644 --- a/src/symbols/cpptrace_symbols.hpp +++ b/src/symbols/cpptrace_symbols.hpp @@ -14,7 +14,8 @@ namespace cpptrace { public: symbolizer(); ~symbolizer(); - stacktrace_frame resolve_frame(void* addr); + //stacktrace_frame resolve_frame(void* addr); + std::vector resolve_frames(const std::vector& frames); }; } } diff --git a/src/symbols/symbols_with_dbghelp.cpp b/src/symbols/symbols_with_dbghelp.cpp index 29ce5fd..b1bea71 100644 --- a/src/symbols/symbols_with_dbghelp.cpp +++ b/src/symbols/symbols_with_dbghelp.cpp @@ -392,8 +392,17 @@ namespace cpptrace { symbolizer::symbolizer() : pimpl{new impl} {} symbolizer::~symbolizer() = default; - stacktrace_frame symbolizer::resolve_frame(void* addr) { - return pimpl->resolve_frame(addr); + //stacktrace_frame symbolizer::resolve_frame(void* addr) { + // return pimpl->resolve_frame(addr); + //} + + std::vector symbolizer::resolve_frames(const std::vector& frames) { + std::vector trace; + trace.reserve(frames.size()); + for(const auto frame : frames) { + trace.push_back(pimpl->resolve_frame(frame)); + } + return trace; } } } diff --git a/src/symbols/symbols_with_dl.cpp b/src/symbols/symbols_with_dl.cpp index e69de29..fd28ee9 100644 --- a/src/symbols/symbols_with_dl.cpp +++ b/src/symbols/symbols_with_dl.cpp @@ -0,0 +1,55 @@ +#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL + +#include +#include "cpptrace_symbols.hpp" +#include "../platform/cpptrace_program_name.hpp" + +#include +#include + +#include + +namespace cpptrace { + namespace detail { + struct symbolizer::impl { + stacktrace_frame resolve_frame(void* addr) { + Dl_info info; + if(dladdr(addr, &info)) { + return { + reinterpret_cast(addr), + 0, + 0, + info.dli_fname ? info.dli_fname : "", + info.dli_sname ? info.dli_sname : "" + }; + } else { + return { + reinterpret_cast(addr), + 0, + 0, + "", + "" + }; + } + } + }; + + symbolizer::symbolizer() : pimpl{new impl} {} + symbolizer::~symbolizer() = default; + + //stacktrace_frame symbolizer::resolve_frame(void* addr) { + // return pimpl->resolve_frame(addr); + //} + + std::vector symbolizer::resolve_frames(const std::vector& frames) { + std::vector trace; + trace.reserve(frames.size()); + for(const auto frame : frames) { + trace.push_back(pimpl->resolve_frame(frame)); + } + return trace; + } + } +} + +#endif diff --git a/src/symbols/symbols_with_libbacktrace.cpp b/src/symbols/symbols_with_libbacktrace.cpp index 80efdac..6a6c4ea 100644 --- a/src/symbols/symbols_with_libbacktrace.cpp +++ b/src/symbols/symbols_with_libbacktrace.cpp @@ -80,8 +80,17 @@ namespace cpptrace { symbolizer::symbolizer() : pimpl{new impl} {} symbolizer::~symbolizer() = default; - stacktrace_frame symbolizer::resolve_frame(void* addr) { - return pimpl->resolve_frame(addr); + //stacktrace_frame symbolizer::resolve_frame(void* addr) { + // return pimpl->resolve_frame(addr); + //} + + std::vector symbolizer::resolve_frames(const std::vector& frames) { + std::vector trace; + trace.reserve(frames.size()); + for(const auto frame : frames) { + trace.push_back(pimpl->resolve_frame(frame)); + } + return trace; } } } diff --git a/src/symbols/symbols_with_nothing.cpp b/src/symbols/symbols_with_nothing.cpp index b618863..c95b798 100644 --- a/src/symbols/symbols_with_nothing.cpp +++ b/src/symbols/symbols_with_nothing.cpp @@ -11,14 +11,24 @@ namespace cpptrace { symbolizer::symbolizer() = default; symbolizer::~symbolizer() = default; - stacktrace_frame symbolizer::resolve_frame(void*) { - return { + // stacktrace_frame symbolizer::resolve_frame(void*) { + // return { + // 0, + // -1, + // -1, + // "", + // "", + // }; + // } + + std::vector symbolizer::resolve_frames(const std::vector& frames) { + return std::vector(frames.size(), { 0, -1, -1, "", - "", - }; + "" + }); } struct symbolizer::impl {}; diff --git a/test/expected/gcc.txt b/test/expected/gcc.txt deleted file mode 100644 index 6126803..0000000 --- a/test/expected/gcc.txt +++ /dev/null @@ -1,27 +0,0 @@ -test/test.cpp||18||trace() -test/test.cpp||34||foo(int) -test/test.cpp||38||foo(int) -test/test.cpp||38||foo(int) -test/test.cpp||38||foo(int) -test/test.cpp||38||foo(int) -test/test.cpp||38||foo(int) -test/test.cpp||38||foo(int) -test/test.cpp||38||foo(int) -test/test.cpp||38||foo(int) -test/test.cpp||38||foo(int) -test/test.cpp||38||foo(int) -test/test.cpp||46||void foo(int, int) -test/test.cpp||46||void foo(int, int, int) -test/test.cpp||46||void foo(int, int, int, int) -test/test.cpp||46||void foo(int, int, int, int, int) -test/test.cpp||46||void foo(int, int, int, int, int, int) -test/test.cpp||46||void foo(int, int, int, int, int, int, int) -test/test.cpp||46||void foo(int, int, int, int, int, int, int, int) -test/test.cpp||46||void foo(int, int, int, int, int, int, int, int, int) -test/test.cpp||46||void foo(int, int, int, int, int, int, int, int, int, int) -test/test.cpp||52||function_two(int, float) -test/test.cpp||58||function_one(int) -test/test.cpp||64||main -../csu/libc-start.c||308||__libc_start_main -||0||_start -||0|| \ No newline at end of file diff --git a/test/expected/linux.libdl.txt b/test/expected/linux.libdl.txt new file mode 100644 index 0000000..be44193 --- /dev/null +++ b/test/expected/linux.libdl.txt @@ -0,0 +1,26 @@ +./test||0||trace() +./test||0||foo(int) +./test||0||foo(int) +./test||0||foo(int) +./test||0||foo(int) +./test||0||foo(int) +./test||0||foo(int) +./test||0||foo(int) +./test||0||foo(int) +./test||0||foo(int) +./test||0||foo(int) +./test||0||foo(int) +./test||0||void foo(int, int) +./test||0||void foo(int, int, int) +./test||0||void foo(int, int, int, int) +./test||0||void foo(int, int, int, int, int) +./test||0||void foo(int, int, int, int, int, int) +./test||0||void foo(int, int, int, int, int, int, int) +./test||0||void foo(int, int, int, int, int, int, int, int) +./test||0||void foo(int, int, int, int, int, int, int, int, int) +./test||0||void foo(int, int, int, int, int, int, int, int, int, int) +./test||0||function_two(int, float) +./test||0||function_one(int) +./test||0||main +/lib/x86_64-linux-gnu/libc.so.6||0||__libc_start_main +./test||0||_start \ No newline at end of file diff --git a/test/expected/clang.txt b/test/expected/linux.txt similarity index 100% rename from test/expected/clang.txt rename to test/expected/linux.txt diff --git a/test/expected/windows_clang.txt b/test/expected/windows.txt similarity index 100% rename from test/expected/windows_clang.txt rename to test/expected/windows.txt diff --git a/test/expected/windows_msvc.txt b/test/expected/windows_msvc.txt deleted file mode 100644 index bf338e0..0000000 --- a/test/expected/windows_msvc.txt +++ /dev/null @@ -1,30 +0,0 @@ -test\test.cpp||18||trace() -test\test.cpp||35||foo(int) -test\test.cpp||39||foo(int) -test\test.cpp||39||foo(int) -test\test.cpp||39||foo(int) -test\test.cpp||39||foo(int) -test\test.cpp||39||foo(int) -test\test.cpp||39||foo(int) -test\test.cpp||39||foo(int) -test\test.cpp||39||foo(int) -test\test.cpp||39||foo(int) -test\test.cpp||39||foo(int) -test\test.cpp||47||foo(int, int) -test\test.cpp||47||foo(int, int, int) -test\test.cpp||47||foo(int, int, int, int) -test\test.cpp||47||foo(int, int, int, int, int) -test\test.cpp||47||foo(int, int, int, int, int, int) -test\test.cpp||47||foo(int, int, int, int, int, int, int) -test\test.cpp||47||foo(int, int, int, int, int, int, int, int) -test\test.cpp||47||foo(int, int, int, int, int, int, int, int, int) -test\test.cpp||47||foo(int, int, int, int, int, int, int, int, int, int) -test\test.cpp||53||function_two(int, float) -test\test.cpp||59||function_one(int) -test\test.cpp||65||main() -D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl||79||invoke_main() -D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl||288||__scrt_common_main_seh() -D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl||331||__scrt_common_main() -D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_main.cpp||17||mainCRTStartup(void*) -||-1||BaseThreadInitThunk -||-1||RtlUserThreadStart \ No newline at end of file diff --git a/test/test.py b/test/test.py index 5626104..e3213eb 100644 --- a/test/test.py +++ b/test/test.py @@ -1,23 +1,64 @@ import os import sys +from typing import List MAX_LINE_DIFF = 2 +def similarity(name: str, target: List[str]) -> int: + parts = name.split(".txt")[0].split(".") + c = 0 + for part in parts: + if part in target: + c += 1 + else: + return -1 + return c + def main(): - if len(sys.argv) != 2: - print("Expected one argument") + if len(sys.argv) < 2: + print("Expected at least one arg") sys.exit(1) - if sys.argv[1].startswith("gcc") or sys.argv[1].startswith("g++"): - name = "gcc" - elif sys.argv[1].startswith("clang"): - name = "clang" - elif sys.argv[1].startswith("cl"): - name = "msvc" - if os.name == "nt": - name = "windows_" + name + target = [] - with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "expected/", name + ".txt"), "r") as f: + if sys.argv[1].startswith("gcc") or sys.argv[1].startswith("g++"): + target.append("gcc") + elif sys.argv[1].startswith("clang"): + target.append("clang") + elif sys.argv[1].startswith("cl"): + target.append("msvc") + + if os.name == "nt": + target.append("windows") + else: + target.append("linux") + + other_configs = sys.argv[2:] + for config in other_configs: + assert "WITH_" in config + target.append(config.split("WITH_")[1].lower()) + + print(f"Searching for expected file best matching {target}") + + expected_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "expected/") + files = [f for f in os.listdir(expected_dir) if os.path.isfile(os.path.join(expected_dir, f))] + if len(files) == 0: + print(f"Error: No expected files to use (searching {expected_dir})", file=sys.stderr) + sys.exit(1) + files = list(map(lambda f: (f, similarity(f, target)), files)) + m = max(files, key=lambda entry: entry[1])[1] + if m <= 0: + print(f"Error: Could not find match for {target} in {files}", file=sys.stderr) + sys.exit(1) + files = [entry[0] for entry in files if entry[1] == m] + if len(files) > 1: + print(f"Error: Ambiguous expected file to use ({files})", file=sys.stderr) + sys.exit(1) + + file = files[0] + print(f"Reading from {file}") + + with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "expected/", file), "r") as f: expected = f.read() output = sys.stdin.read() @@ -49,8 +90,6 @@ def main(): break if errored: - #print("Test output:", file=sys.stderr) - #print(raw_output, file=sys.stderr) print("Test failed") sys.exit(1) else: