diff options
-rw-r--r-- | include/pdb/address.hpp | 4 | ||||
-rw-r--r-- | include/pdb/call_stack.hpp | 5 | ||||
-rw-r--r-- | test/test_lib.cpp | 14 | ||||
-rw-r--r-- | test/test_lib.hpp | 1 | ||||
-rw-r--r-- | test/unit_tests/CMakeLists.txt | 2 | ||||
-rw-r--r-- | test/unit_tests/call_stack.cpp | 79 | ||||
-rw-r--r-- | test/unit_tests/dbghelp.cpp | 22 | ||||
-rw-r--r-- | test/unit_tests/fixtures.hpp | 82 |
8 files changed, 165 insertions, 44 deletions
diff --git a/include/pdb/address.hpp b/include/pdb/address.hpp index fa692ad..f7a7239 100644 --- a/include/pdb/address.hpp +++ b/include/pdb/address.hpp @@ -20,6 +20,10 @@ inline std::string format_address(Address address) { return oss.str(); } +inline std::string format_address(void* address) { + return format_address(reinterpret_cast<Address>(address)); +} + inline bool parse_address(Address& dest, const std::string& src) { std::istringstream iss{src}; iss >> std::hex; diff --git a/include/pdb/call_stack.hpp b/include/pdb/call_stack.hpp index f0e13af..ef4c119 100644 --- a/include/pdb/call_stack.hpp +++ b/include/pdb/call_stack.hpp @@ -43,6 +43,11 @@ public: const std::array<Address, max_length> frames; const std::size_t length; + const Address* begin() const { return frames.data(); } + const Address* cbegin() const { return begin(); } + const Address* end() const { return begin() + length; } + const Address* cend() const { return end(); } + private: CallStack() = default; }; diff --git a/test/test_lib.cpp b/test/test_lib.cpp index 63e51ad..711d4fd 100644 --- a/test/test_lib.cpp +++ b/test/test_lib.cpp @@ -18,22 +18,26 @@ void do_print_call_stack() { call_stack.dump(boost::nowide::cout, dbghelp); } +void do_throw_call_stack() { + throw pdb::CallStack::capture(); +} + } // namespace volatile int var = 42; void baz(F f) { - boost::nowide::cout << "baz\n"; + boost::nowide::cout << "baz " << pdb::format_address(&baz) << '\n'; f(); } void bar(F f) { - boost::nowide::cout << "bar\n"; + boost::nowide::cout << "bar " << pdb::format_address(&bar) << '\n'; baz(f); } void foo(F f) { - boost::nowide::cout << "foo\n"; + boost::nowide::cout << "foo " << pdb::format_address(&foo) << '\n'; bar(f); } @@ -41,4 +45,8 @@ void print_call_stack() { foo(&do_print_call_stack); } +void throw_call_stack() { + foo(&do_throw_call_stack); +} + } // namespace test_ns diff --git a/test/test_lib.hpp b/test/test_lib.hpp index cf7b841..bc488dc 100644 --- a/test/test_lib.hpp +++ b/test/test_lib.hpp @@ -15,5 +15,6 @@ TEST_LIB_API void bar(F); TEST_LIB_API void baz(F); TEST_LIB_API void print_call_stack(); +TEST_LIB_API void throw_call_stack(); } // namespace test_ns diff --git a/test/unit_tests/CMakeLists.txt b/test/unit_tests/CMakeLists.txt index 6b5eca6..1241061 100644 --- a/test/unit_tests/CMakeLists.txt +++ b/test/unit_tests/CMakeLists.txt @@ -1,6 +1,6 @@ find_package(Boost REQUIRED COMPONENTS filesystem unit_test_framework) -add_executable(unit_tests main.cpp dbghelp.cpp error.cpp) +add_executable(unit_tests main.cpp call_stack.cpp dbghelp.cpp error.cpp) target_link_libraries(unit_tests PRIVATE pdb_repo test_lib) target_link_libraries(unit_tests PRIVATE Boost::disable_autolinking Boost::filesystem Boost::nowide Boost::unit_test_framework) diff --git a/test/unit_tests/call_stack.cpp b/test/unit_tests/call_stack.cpp new file mode 100644 index 0000000..481ab6e --- /dev/null +++ b/test/unit_tests/call_stack.cpp @@ -0,0 +1,79 @@ +#include "fixtures.hpp" + +#include <pdb/all.hpp> +#include <test_lib.hpp> + +#include <boost/test/unit_test.hpp> + +#include <algorithm> +#include <sstream> +#include <string> +#include <vector> + +BOOST_FIXTURE_TEST_SUITE(call_stack_tests, CurrentProcess) + +BOOST_AUTO_TEST_CASE(call_stack) { + try { + test_ns::throw_call_stack(); + } catch (const pdb::CallStack& call_stack) { + // First, check that the call stack have been caught. + BOOST_TEST(true, "Caught the call stack"); + + // Debug output: + std::vector<std::string> pretty; + pretty.reserve(call_stack.length); + + BOOST_TEST_MESSAGE("Call stack:"); + for (const auto& addr : call_stack) { + pretty.emplace_back(call_stack.pretty_print_address(dbghelp, addr)); + BOOST_TEST_MESSAGE('\t' << pdb::format_address(addr) << ' ' << pretty.back()); + } + + // Second, resolve the symbols: + std::vector<pdb::SymbolInfo> symbols; + symbols.reserve(call_stack.length); + + BOOST_TEST_MESSAGE("Resolved symbols:"); + for (const auto& addr : call_stack) { + symbols.emplace_back(dbghelp.resolve_symbol(addr)); + const auto& symbol = symbols.back(); + BOOST_TEST_MESSAGE('\t' << pdb::format_address(symbol.get_offline_address()) << ' ' + << symbol.get_name()); + } + + { + // Third, check that the expected function frames are in the call stack. + const auto expected = expected_function_addresses(); + + const auto check = [&](pdb::Address addr) { + for (const auto& symbol : symbols) + if (symbol.get_offline_address() == addr) + return true; + return false; + }; + for (const auto& addr : expected) { + BOOST_TEST(check(addr), "Function frame captured: " << pdb::format_address(addr)); + } + } + + { + // Fourth, check that the expected function names are in the call stack. + const auto expected = expected_functions_full(); + + const auto check = [&](const std::string& name) { + for (const auto& actual : pretty) + if (actual.find(name) != std::string::npos) + return true; + return false; + }; + for (const auto& name : expected) { + BOOST_TEST(check(name), "Function frame captured: " << name); + } + } + return; + } + // We must catch the call stack. + BOOST_TEST(false, "Didn't catch the call stack"); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/unit_tests/dbghelp.cpp b/test/unit_tests/dbghelp.cpp index 4346bf7..bd2d300 100644 --- a/test/unit_tests/dbghelp.cpp +++ b/test/unit_tests/dbghelp.cpp @@ -1,8 +1,6 @@ #include "fixtures.hpp" -#include "paths.hpp" #include <pdb/all.hpp> -#include <test_lib.hpp> #include <boost/test/unit_test.hpp> @@ -10,15 +8,7 @@ #include <string> #include <vector> -namespace { - -void throw_call_stack() { - throw pdb::CallStack::capture(); -} - -} // namespace - -BOOST_FIXTURE_TEST_SUITE(dbghelp_tests, DbgHelpWithSymbols) +BOOST_FIXTURE_TEST_SUITE(enum_symbols_tests, PostMortem) BOOST_AUTO_TEST_CASE(enum_symbols) { // Symbols can be enumerated, and all the expected symbols are there. @@ -44,14 +34,4 @@ BOOST_AUTO_TEST_CASE(enum_symbols) { } } -BOOST_AUTO_TEST_CASE(call_stack) { - try { - test_ns::foo(&throw_call_stack); - } catch (const pdb::CallStack& call_stack) { - BOOST_TEST(true, "Caught the call stack"); - return; - } - BOOST_TEST(false, "Didn't catch the call stack"); -} - BOOST_AUTO_TEST_SUITE_END() diff --git a/test/unit_tests/fixtures.hpp b/test/unit_tests/fixtures.hpp index eb7dcc0..fadd393 100644 --- a/test/unit_tests/fixtures.hpp +++ b/test/unit_tests/fixtures.hpp @@ -3,6 +3,7 @@ #include "paths.hpp" #include <pdb/all.hpp> +#include <test_lib.hpp> #include <boost/filesystem.hpp> #include <boost/test/unit_test.hpp> @@ -12,19 +13,6 @@ #include <unordered_set> #include <utility> -class DbgHelp { -public: - DbgHelp() : dbghelp{pdb::DbgHelp::post_mortem()} { BOOST_TEST_MESSAGE("Initializing DbgHelp"); } - - ~DbgHelp() { BOOST_TEST_MESSAGE("Cleaning up DbgHelp"); } - - const pdb::DbgHelp dbghelp; - -private: - DbgHelp(const DbgHelp&) = delete; - DbgHelp& operator=(const DbgHelp&) = delete; -}; - template <typename T> using Set = std::unordered_set<T>; @@ -34,9 +22,18 @@ Set<T> join(Set<T>&& xs, Set<T>&& ys) { return std::move(xs); } -class DbgHelpWithSymbols : public DbgHelp { +class DbgHelp { public: - DbgHelpWithSymbols() { load_test_lib_pdb(); } + DbgHelp(pdb::DbgHelp&& dbghelp) : dbghelp{std::move(dbghelp)} {} + + ~DbgHelp() { BOOST_TEST_MESSAGE("Cleaning up DbgHelp"); } + + const pdb::DbgHelp dbghelp; + + static const std::string& get_module_name() { + static const std::string name{"test_lib"}; + return name; + } static const std::string& get_namespace() { static const std::string name{"test_ns"}; @@ -44,16 +41,41 @@ public: } typedef Set<std::string> SymbolList; + typedef Set<pdb::Address> AddressList; + + static AddressList expected_function_addresses() { + return cast({&test_ns::foo, &test_ns::bar, &test_ns::baz}); + } static SymbolList expected_functions() { return make_qualified({"foo", "bar", "baz"}); } + static SymbolList expected_functions_full() { return add_module(expected_functions()); } + static SymbolList expected_variables() { return make_qualified({"var"}); } static SymbolList expected_symbols() { return join(expected_functions(), expected_variables()); } +protected: + static pdb::DbgHelp init_dbghelp(bool current_process) { + BOOST_TEST_MESSAGE("Initializing DbgHelp"); + if (current_process) { + return pdb::DbgHelp::current_process(); + } else { + return pdb::DbgHelp::post_mortem(); + } + } + private: + static AddressList cast(Set<void*>&& fs) { + AddressList addresses; + for (auto&& f : fs) { + addresses.emplace(reinterpret_cast<pdb::Address>(f)); + } + return addresses; + } + static SymbolList make_qualified(SymbolList&& plain) { SymbolList qualified; for (auto&& name : plain) { @@ -62,13 +84,35 @@ private: return qualified; } - void load_test_lib_pdb() { - const auto pdb_path = get_test_lib_pdb_path().string(); + static SymbolList add_module(SymbolList&& plain) { + SymbolList full; + for (auto&& name : plain) { + full.emplace(get_module_name() + "!" + std::move(name)); + } + return full; + } + + DbgHelp(const DbgHelp&) = delete; + DbgHelp& operator=(const DbgHelp&) = delete; +}; + +class PostMortem : public DbgHelp { +public: + PostMortem() : DbgHelp{init_dbghelp(false)} { load_module_pdb(); } + +private: + void load_module_pdb() { + const auto pdb_path = get_module_pdb_path().string(); BOOST_TEST_MESSAGE("Loading PDB: " << pdb_path); dbghelp.load_pdb(pdb_path); } - static boost::filesystem::path get_test_lib_pdb_path() { - return Paths::get().exe_dir / "test_lib.pdb"; + static boost::filesystem::path get_module_pdb_path() { + return Paths::get().exe_dir / (get_module_name() + ".pdb"); } }; + +class CurrentProcess : public DbgHelp { +public: + CurrentProcess() : DbgHelp{init_dbghelp(true)} {} +}; |