/* Copyright (C) 2020 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // note: this is more of an on-demand display of the stack trace than // self-test of it. // TODO: compare against known-good result? // problem: results may differ by compiler (e.g. due to differing STL) #include "lib/self_test.h" #include #include #include #include #include #include "lib/bits.h" #include "lib/code_annotation.h" #include "lib/sysdep/os/win/win.h" // HWND #include "lib/sysdep/sysdep.h" #include "lib/sysdep/os/win/wdbg_sym.h" #include "lib/external_libraries/dbghelp.h" static void* callers[100]; static size_t numCallers; static Status OnFrame(const _tagSTACKFRAME64* frame, uintptr_t UNUSED(cbData)) { callers[numCallers++] = (void*)frame->AddrPC.Offset; return INFO::OK; } #pragma optimize("", off) #pragma warning(disable:4748) // /GS can not protect [..] from local buffer overrun because optimizations are disabled // (these must be outside of TestWdbgSym so that we can simply // search for the function's name as a substring within the ILT // decorated name (which omits the :: scope resolution operator, // while debug_ResolveSymbol for the function does not) __declspec(noinline) static void Func1() { CONTEXT context; UNUSED2(debug_CaptureContext(&context)); wdbg_sym_WalkStack(OnFrame, 0, context); } __declspec(noinline) static void Func2() { Func1(); } __declspec(noinline) static void Func3() { Func2(); } class TestWdbgSym : public CxxTest::TestSuite { // m_test_array might get inlined and that messes // with the test so in order to protect the stack // trace we have to prevent it. __declspec(noinline) static void m_test_array() { struct Small { int i1; int i2; }; struct Large { double d1; double d2; double d3; double d4; }; Large large_array_of_large_structs[8] = { { 0.0,0.0,0.0,0.0 } }; Large small_array_of_large_structs[2] = { { 0.0,0.0,0.0,0.0 } }; Small large_array_of_small_structs[8] = { { 1,2 } }; Small small_array_of_small_structs[2] = { { 1,2 } }; int ints[] = { 1,2,3,4,5 }; wchar_t chars[] = { 'w','c','h','a','r','s',0 }; wchar_t many_wchars[1024]; memset(many_wchars, 'a', sizeof(many_wchars)); debug_printf("\n(dumping stack frames may result in access violations...)\n"); debug_printf(" locals: %.0d %.0c %.0c %.0g %.0g %.0d %.0d\n", ints[0], chars[0], many_wchars[0], large_array_of_large_structs[0].d1, small_array_of_large_structs[0].d1, large_array_of_small_structs[0].i1, small_array_of_small_structs[0].i1); // note: we don't want any kind of dialog to be raised, because // this test now always runs. therefore, just make sure a decent // amount of text (not just "(failed)" error messages) was produced. CACHE_ALIGNED(u8) context[DEBUG_CONTEXT_SIZE]; TS_ASSERT_EQUALS(debug_CaptureContext(context), INFO::OK); const wchar_t* text = debug_BuildErrorMessage(L"dummy", 0, 0, 0, context, L"m_test_array"); TS_ASSERT(wcslen(text) > 500); debug_printf("(done dumping stack frames)\n"); } // also used by test_stl as an element type struct Nested { int nested_member; struct Nested* self_ptr; }; static void m_test_udt() { Nested nested = { 123 }; nested.self_ptr = &nested; typedef struct { u8 s1; u8 s2; char s3; } Small; Small small__ = { 0x55, 0xaa, -1 }; UNUSED2(small__); struct Large { u8 large_member_u8; std::string large_member_string; double large_member_double; } large = { 0xff, "large struct string", 123456.0 }; UNUSED2(large); class Base { int base_int; std::wstring base_wstring; public: Base() : base_int(123), base_wstring(L"base wstring") { } }; class Derived : private Base { double derived_double; public: Derived() : derived_double(-1.0) { } } derived; m_test_array(); } // STL containers and their contents static void m_test_stl() { std::vector v_wstring; v_wstring.push_back(L"ws1"); v_wstring.push_back(L"ws2"); std::deque d_int; d_int.push_back(1); d_int.push_back(2); d_int.push_back(3); std::deque d_string; d_string.push_back("a"); d_string.push_back("b"); d_string.push_back("c"); std::list l_float; l_float.push_back(0.1f); l_float.push_back(0.2f); l_float.push_back(0.3f); l_float.push_back(0.4f); std::map m_string_int; m_string_int.insert(std::make_pair("s5", 5)); m_string_int.insert(std::make_pair("s6", 6)); m_string_int.insert(std::make_pair("s7", 7)); std::map m_int_string; m_int_string.insert(std::make_pair(1, "s1")); m_int_string.insert(std::make_pair(2, "s2")); m_int_string.insert(std::make_pair(3, "s3")); std::map m_int_int; m_int_int.insert(std::make_pair(1, 1)); m_int_int.insert(std::make_pair(2, 2)); m_int_int.insert(std::make_pair(3, 3)); std::set s_uintptr; s_uintptr.insert(0x123); s_uintptr.insert(0x456); // empty std::deque d_u8_empty; std::list l_nested_empty; std::map m_double_empty; std::multimap mm_int_empty; std::set s_uint_empty; std::multiset ms_char_empty; std::vector v_double_empty; std::queue q_double_empty; std::stack st_double_empty; std::string str_empty; std::wstring wstr_empty; m_test_udt(); // uninitialized std::deque d_u8_uninit; std::list l_nested_uninit; std::map m_double_uninit; std::multimap mm_int_uninit; std::set s_uint_uninit; std::multiset ms_char_uninit; std::vector v_double_uninit; std::queue q_double_uninit; std::stack st_double_uninit; std::string str_uninit; std::wstring wstr_uninit; } // also exercises all basic types because we need to display some values // anyway (to see at a glance whether symbol engine addrs are correct) static void m_test_addrs(int p_int, double p_double, char* p_pchar, uintptr_t p_uintptr) { size_t l_uint = 0x1234; bool l_bool = true; UNUSED2(l_bool); wchar_t l_wchars[] = L"wchar string"; enum TestEnum { VAL1=1, VAL2=2 } l_enum = VAL1; u8 l_u8s[] = { 1,2,3,4 }; void (*l_funcptr)(void) = m_test_stl; static double s_double = -2.718; static char s_chars[] = {'c','h','a','r','s',0}; static void (*s_funcptr)(int, double, char*, uintptr_t) = m_test_addrs; static void* s_ptr = (void*)(uintptr_t)0x87654321; static HDC s_hdc = (HDC)0xff0; #if 0 // output only needed when debugging debug_printf("\nTEST_ADDRS\n"); debug_printf("p_int addr=%p val=%d\n", &p_int, p_int); debug_printf("p_double addr=%p val=%g\n", &p_double, p_double); debug_printf("p_pchar addr=%p val=%s\n", &p_pchar, p_pchar); debug_printf("p_uintptr addr=%p val=%lu\n", &p_uintptr, p_uintptr); debug_printf("l_uint addr=%p val=%u\n", &l_uint, l_uint); debug_printf("l_wchars addr=%p val=%s\n", &l_wchars, utf8_from_wstring(l_wchars)); debug_printf("l_enum addr=%p val=%d\n", &l_enum, l_enum); debug_printf("l_u8s addr=%p val=%d\n", &l_u8s, l_u8s); debug_printf("l_funcptr addr=%p val=%p\n", &l_funcptr, l_funcptr); #else UNUSED2(p_uintptr); UNUSED2(p_pchar); UNUSED2(p_double); UNUSED2(p_int); UNUSED2(l_funcptr); UNUSED2(l_enum); UNUSED2(l_uint); UNUSED2(l_u8s); UNUSED2(l_wchars); #endif m_test_stl(); int uninit_int; UNUSED2(uninit_int); float uninit_float; UNUSED2(uninit_float); double uninit_double; UNUSED2(uninit_double); bool uninit_bool; UNUSED2(uninit_bool); HWND uninit_hwnd; UNUSED2(uninit_hwnd); } #pragma optimize("", on) public: void test_stack_trace() { m_test_addrs(123, 3.1415926535897932384626, "pchar string", 0xf00d); } void test_stack_walk() { Status ret; Func3(); TS_ASSERT(numCallers >= 3); size_t foundFunctionBits = 0; void* funcAddresses[3] = { (void*)&Func1, (void*)&Func2, (void*)&Func3 }; for(size_t idxCaller = 0; idxCaller < numCallers; idxCaller++) { wchar_t callerName[DEBUG_SYMBOL_CHARS]; ret = debug_ResolveSymbol(callers[idxCaller], callerName, 0, 0); TS_ASSERT_OK(ret); wchar_t funcName[DEBUG_SYMBOL_CHARS]; for(size_t idxFunc = 0; idxFunc < ARRAY_SIZE(funcAddresses); idxFunc++) { ret = debug_ResolveSymbol(funcAddresses[idxFunc], funcName, 0, 0); TS_ASSERT_OK(ret); if(wcsstr(funcName, callerName)) foundFunctionBits |= BIT(idxFunc); } } TS_ASSERT(foundFunctionBits == bit_mask(ARRAY_SIZE(funcAddresses))); } };