diff --git a/assets/addons/toon_shader/shaders/programs/scene/toon.frag b/assets/addons/toon_shader/shaders/programs/scene/toon.frag index dce4a6ef9..c4e6c9df4 100644 --- a/assets/addons/toon_shader/shaders/programs/scene/toon.frag +++ b/assets/addons/toon_shader/shaders/programs/scene/toon.frag @@ -133,15 +133,16 @@ void main() rimInfo.threshold = 0.1; rimInfo.color = get_mat_rim_color(); - vec2 texCoords = fs_in.vert_uv; + vec2 texCoords = get_vertex_uv(); vec4 baseColor = fetch_albedo_map(texCoords, get_instance_color()); vec4 color = baseColor; - mat3 normalMatrix = transpose(inverse(mat3(fs_in.M))); + mat3 normalMatrix = transpose(inverse(mat3(get_model_matrix()))); vec3 normal = get_normal_from_map(texCoords, get_mat_flags()); normal = normalize(normalMatrix * normal); - vec3 viewDir = normalize(u_renderSettings.posCam.xyz - fs_in.vert_pos_ws.xyz); + vec3 vertPos = get_vertex_position_ws(); + vec3 viewDir = normalize(u_renderSettings.posCam.xyz - vertPos); ShadingInfo shadingInfo; shadingInfo.surfaceNormal = normal; shadingInfo.viewDirection = viewDir; @@ -160,8 +161,8 @@ void main() for(uint i = SCENE_SPOT_LIGHT_BUFFER_START; i < SCENE_SPOT_LIGHT_BUFFER_END && visibleLightTileIndicesBuffer.data[tileStartOffset + i].index != -1; i++) { uint lightIndex = visibleLightTileIndicesBuffer.data[tileStartOffset + i].index; LightSourceData light = get_light_source(lightIndex); - float attenuation = calc_spot_light_attenuation(light, fs_in.vert_pos_ws.xyz); - vec3 lightDir = normalize(light.position.xyz - fs_in.vert_pos_ws.xyz); + float attenuation = calc_spot_light_attenuation(light, vertPos); + vec3 lightDir = normalize(light.position.xyz - vertPos); float shadowFactor = 1.0; if(CSPEC_ENABLE_DYNAMIC_SHADOWS == 1) shadowFactor = enableShadows ? get_spot_light_shadow_factor(lightIndex, true) : 1.0; @@ -172,11 +173,11 @@ void main() for(uint i = SCENE_POINT_LIGHT_BUFFER_START; i < SCENE_POINT_LIGHT_BUFFER_END && visibleLightTileIndicesBuffer.data[tileStartOffset + i].index != -1; i++) { uint lightIndex = visibleLightTileIndicesBuffer.data[tileStartOffset + i].index; LightSourceData light = get_light_source(lightIndex); - float attenuation = calc_point_light_attenuation(light, fs_in.vert_pos_ws.xyz); - vec3 lightDir = normalize(light.position.xyz - fs_in.vert_pos_ws.xyz); + float attenuation = calc_point_light_attenuation(light, vertPos); + vec3 lightDir = normalize(light.position.xyz - vertPos); float shadowFactor = 1.0; if(CSPEC_ENABLE_DYNAMIC_SHADOWS == 1) - shadowFactor = enableShadows ? get_point_light_shadow_factor(lightIndex, true, fs_in.vert_pos_ws.xyz) : 1.0; + shadowFactor = enableShadows ? get_point_light_shadow_factor(lightIndex, true, vertPos) : 1.0; calc_toon_blinn_phong_lighting(shadingInfo, specInfo, rimInfo, light, lightDir, attenuation, shadowFactor, normal, totalLightColor, totalSpecularColor, totalRimColor, lightIndex, enableShadows); } } @@ -214,12 +215,13 @@ void main() if(CSPEC_ENABLE_LIGHT_MAPS == 1) { // TODO: Lightmap mode should be determined by specialization constant to avoid if-condition overhead if(useLightmaps) { - vec4 colDirect = texture(u_lightMap, fs_in.vert_uv_lightmap.xy); + vec2 uv = get_vertex_uv_lightmap(); + vec4 colDirect = texture(u_lightMap, uv); float exposure = get_lightmap_exposure_pow(); if(is_indirect_light_map_enabled()) { - vec3 colIndirect = texture(u_lightMapIndirect, fs_in.vert_uv_lightmap.xy).rgb; + vec3 colIndirect = texture(u_lightMapIndirect, uv).rgb; if(is_directional_light_map_enabled()) { - vec3 dominantDir = texture(u_lightMapDominant, fs_in.vert_uv_lightmap.xy).rgb; + vec3 dominantDir = texture(u_lightMapDominant, uv).rgb; dominantDir = dominantDir * 2.0 - 1.0; dominantDir = normalize(dominantDir); diff --git a/assets/scripts/localization/de/texts/prompts.txt b/assets/scripts/localization/de/texts/prompts.txt new file mode 100644 index 000000000..5cc7ae304 --- /dev/null +++ b/assets/scripts/localization/de/texts/prompts.txt @@ -0,0 +1,14 @@ +prompt_button_abort = "Abbrechen" +prompt_button_cancel = "Abbrechen" +prompt_button_continue = "Fortfahren" +prompt_button_ignore = "Ignorieren" +prompt_button_no = "Nein" +prompt_button_ok = "OK" +prompt_button_retry = "Wiederholen" +prompt_button_try_again = "Erneut versuchen" +prompt_button_yes = "Ja" +prompt_crash = "Es ist ein schwerwiegender Fehler im Programm aufgetreten. Möchten Sie eine Diagnose-Datei speichern? Diese Datei enthält Informationen über Ihr System und den Zustand des Spiels zum Zeitpunkt des Absturzes und kann von einem Entwickler verwendet werden, um das zugrunde liegende Problem zu beheben." +prompt_crash_dump_archive_failed = "Fehler beim Speichern der Dump-Datei: {}" +prompt_crash_dump_generation_failed = "Fehler beim Speichern der Dump-Datei in '{}': Fehlercode {}" +prompt_crash_dump_saved = "Dump-Datei in '{}' gespeichert. Bitte senden Sie diese an einen Entwickler, zusammen mit einer Beschreibung dessen, was Sie getan haben, um den Fehler auszulösen." +prompt_fallback_to_opengl = "Wenn dieser Absturz weiterhin besteht, kann das Wechseln zur OpenGL-Rendering die Lösung sein. Möchten Sie das jetzt versuchen?" \ No newline at end of file diff --git a/assets/scripts/localization/en/texts/prompts.txt b/assets/scripts/localization/en/texts/prompts.txt new file mode 100644 index 000000000..98ad2bf42 --- /dev/null +++ b/assets/scripts/localization/en/texts/prompts.txt @@ -0,0 +1,14 @@ +prompt_button_abort = "Abort" +prompt_button_cancel = "Cancel" +prompt_button_continue = "Continue" +prompt_button_ignore = "Ignore" +prompt_button_no = "No" +prompt_button_ok = "Ok" +prompt_button_retry = "Retry" +prompt_button_try_again = "Try Again" +prompt_button_yes = "Yes" +prompt_crash = "A terminal error has occurred in the program. Would you like to save a diagnostic file? This file contains information about your system and the state of the game at the time of the crash and can be utilized by a developer to fix the underlying problem." +prompt_crash_dump_archive_failed = "Failed to save dump file: {}" +prompt_crash_dump_generation_failed = "Failed to save dump file to '{}': Error code {}" +prompt_crash_dump_saved = "Saved dump file to '{}'. Please send it to a developer, along with a description of what you did to trigger the error." +prompt_fallback_to_opengl = "If this crash persists, switching to OpenGL rendering may help resolve it. Would you like to try that now?" \ No newline at end of file diff --git a/assets/scripts/localization/es/texts/prompts.txt b/assets/scripts/localization/es/texts/prompts.txt new file mode 100644 index 000000000..dd9f73800 --- /dev/null +++ b/assets/scripts/localization/es/texts/prompts.txt @@ -0,0 +1,14 @@ +prompt_button_abort = "Abortar" +prompt_button_cancel = "Cancelar" +prompt_button_continue = "Continuar" +prompt_button_ignore = "Ignorar" +prompt_button_no = "No" +prompt_button_ok = "OK" +prompt_button_retry = "Reintentar" +prompt_button_try_again = "Intentar de nuevo" +prompt_button_yes = "Sí" +prompt_crash = "Se ha producido un error terminal en el programa. ¿Le gustaría guardar un archivo de diagnóstico? Este archivo contiene información sobre su sistema y el estado del juego en el momento del fallo, y puede ser utilizado por un desarrollador para solucionar el problema subyacente." +prompt_crash_dump_archive_failed = "No se pudo guardar el archivo de volcado: {}" +prompt_crash_dump_generation_failed = "No se pudo guardar el archivo de volcado en '{}': Código de error {}" +prompt_crash_dump_saved = "Archivo de volcado guardado en '{}'. Por favor, envíelo a un desarrollador junto con una descripción de lo que hizo para provocar el error." +prompt_fallback_to_opengl = "Si este error persiste, cambiar a renderizado con OpenGL podría ayudar a resolverlo. ¿Le gustaría intentarlo ahora?" \ No newline at end of file diff --git a/assets/scripts/localization/fr/texts/prompts.txt b/assets/scripts/localization/fr/texts/prompts.txt new file mode 100644 index 000000000..2d71c99b2 --- /dev/null +++ b/assets/scripts/localization/fr/texts/prompts.txt @@ -0,0 +1,14 @@ +prompt_button_abort = "Abandonner" +prompt_button_cancel = "Annuler" +prompt_button_continue = "Continuer" +prompt_button_ignore = "Ignorer" +prompt_button_no = "Non" +prompt_button_ok = "OK" +prompt_button_retry = "Réessayer" +prompt_button_try_again = "Réessayer" +prompt_button_yes = "Oui" +prompt_crash = "Une erreur fatale est survenue dans le programme. Souhaitez-vous enregistrer un fichier de diagnostic ? Ce fichier contient des informations sur votre système et l'état du jeu au moment du crash et peut être utilisé par un développeur pour résoudre le problème sous-jacent." +prompt_crash_dump_archive_failed = "Échec de l'enregistrement du fichier de vidage : {}" +prompt_crash_dump_generation_failed = "Échec de l'enregistrement du fichier de vidage dans '{}': Code d'erreur {}" +prompt_crash_dump_saved = "Fichier de vidage enregistré dans '{}'. Veuillez l'envoyer à un développeur, accompagné d'une description de ce que vous faisiez pour provoquer l'erreur." +prompt_fallback_to_opengl = "Si ce crash persiste, passer au rendu OpenGL pourrait aider à le résoudre. Voulez-vous essayer maintenant ?" \ No newline at end of file diff --git a/assets/scripts/localization/it/texts/prompts.txt b/assets/scripts/localization/it/texts/prompts.txt new file mode 100644 index 000000000..e767a5065 --- /dev/null +++ b/assets/scripts/localization/it/texts/prompts.txt @@ -0,0 +1,14 @@ +prompt_button_abort = "Interrompi" +prompt_button_cancel = "Annulla" +prompt_button_continue = "Continua" +prompt_button_ignore = "Ignora" +prompt_button_no = "No" +prompt_button_ok = "OK" +prompt_button_retry = "Riprova" +prompt_button_try_again = "Prova di nuovo" +prompt_button_yes = "Sì" +prompt_crash = "Si è verificato un errore fatale nel programma. Vuoi salvare un file diagnostico? Questo file contiene informazioni sul tuo sistema e lo stato del gioco al momento del crash e può essere utilizzato da uno sviluppatore per risolvere il problema sottostante." +prompt_crash_dump_archive_failed = "Impossibile salvare il file di dump: {}" +prompt_crash_dump_generation_failed = "Impossibile salvare il file di dump in '{}': Codice errore {}" +prompt_crash_dump_saved = "File di dump salvato in '{}'. Inviarlo a uno sviluppatore, insieme a una descrizione di ciò che hai fatto per provocare l'errore." +prompt_fallback_to_opengl = "Se questo crash persiste, il passaggio al rendering OpenGL potrebbe aiutare a risolverlo. Vuoi provare ora?" \ No newline at end of file diff --git a/assets/scripts/localization/jp/texts/prompts.txt b/assets/scripts/localization/jp/texts/prompts.txt new file mode 100644 index 000000000..a38ad0e31 --- /dev/null +++ b/assets/scripts/localization/jp/texts/prompts.txt @@ -0,0 +1,14 @@ +prompt_button_abort = "中止" +prompt_button_cancel = "キャンセル" +prompt_button_continue = "続行" +prompt_button_ignore = "無視" +prompt_button_no = "いいえ" +prompt_button_ok = "OK" +prompt_button_retry = "再試行" +prompt_button_try_again = "もう一度試す" +prompt_button_yes = "はい" +prompt_crash = "プログラムに致命的なエラーが発生しました。診断ファイルを保存しますか?このファイルには、クラッシュ時のシステム情報やゲームの状態が含まれており、開発者が問題を解決するのに役立てることができます。" +prompt_crash_dump_archive_failed = "ダンプファイルの保存に失敗しました: {}" +prompt_crash_dump_generation_failed = "'{}' にダンプファイルを保存できませんでした: エラーコード {}" +prompt_crash_dump_saved = "'{}' にダンプファイルを保存しました。このファイルと、エラーが発生するまでに行った操作の説明を開発者に送ってください。" +prompt_fallback_to_opengl = "このクラッシュが続く場合、OpenGL レンダリングに切り替えることで解決する可能性があります。今すぐ試してみますか?" \ No newline at end of file diff --git a/assets/scripts/localization/pl/texts/prompts.txt b/assets/scripts/localization/pl/texts/prompts.txt new file mode 100644 index 000000000..66397da56 --- /dev/null +++ b/assets/scripts/localization/pl/texts/prompts.txt @@ -0,0 +1,14 @@ +prompt_button_abort = "Przerwij" +prompt_button_cancel = "Anuluj" +prompt_button_continue = "Kontynuuj" +prompt_button_ignore = "Ignoruj" +prompt_button_no = "Nie" +prompt_button_ok = "OK" +prompt_button_retry = "Ponów próbę" +prompt_button_try_again = "Spróbuj ponownie" +prompt_button_yes = "Tak" +prompt_crash = "W programie wystąpił krytyczny błąd. Czy chcesz zapisać plik diagnostyczny? Plik ten zawiera informacje o twoim systemie i stanie gry w momencie awarii i może zostać wykorzystany przez programistę do rozwiązania problemu." +prompt_crash_dump_archive_failed = "Nie udało się zapisać pliku zrzutu: {}" +prompt_crash_dump_generation_failed = "Nie udało się zapisać pliku zrzutu w '{}': Kod błędu {}" +prompt_crash_dump_saved = "Zapisano plik zrzutu w '{}'. Wyślij go do programisty wraz z opisem, co zrobiłeś, aby wywołać błąd." +prompt_fallback_to_opengl = "Jeśli ten błąd będzie się powtarzał, przełączenie na renderowanie OpenGL może pomóc go rozwiązać. Czy chcesz spróbować teraz?" \ No newline at end of file diff --git a/assets/scripts/localization/zh-cn/texts/prompts.txt b/assets/scripts/localization/zh-cn/texts/prompts.txt new file mode 100644 index 000000000..80f4ada98 --- /dev/null +++ b/assets/scripts/localization/zh-cn/texts/prompts.txt @@ -0,0 +1,14 @@ +prompt_button_abort = "中止" +prompt_button_cancel = "取消" +prompt_button_continue = "继续" +prompt_button_ignore = "忽略" +prompt_button_no = "否" +prompt_button_ok = "确定" +prompt_button_retry = "重试" +prompt_button_try_again = "再试一次" +prompt_button_yes = "是" +prompt_crash = "程序发生了致命错误。是否要保存诊断文件?该文件包含您的系统信息以及游戏崩溃时的状态,可供开发人员用于修复潜在问题。" +prompt_crash_dump_archive_failed = "无法保存转储文件:{}" +prompt_crash_dump_generation_failed = "无法保存转储文件到'{}':错误代码 {}" +prompt_crash_dump_saved = "转储文件已保存到'{}'。请将其与您触发错误时的操作说明一并发送给开发人员。" +prompt_fallback_to_opengl = "如果此崩溃持续发生,切换到 OpenGL 渲染可能有助于解决问题。您想立即尝试吗?" \ No newline at end of file diff --git a/core/client/include/pragma/c_engine.h b/core/client/include/pragma/c_engine.h index 0cafcb4ff..0225b43d1 100644 --- a/core/client/include/pragma/c_engine.h +++ b/core/client/include/pragma/c_engine.h @@ -122,6 +122,8 @@ class DLLCLIENT CEngine : public Engine, public pragma::RenderContext { // If the input is an axis input, inOutState may change to represent actual button state bool GetInputButtonState(float axisInput, GLFW::Modifier mods, GLFW::KeyState &inOutState) const; + virtual void HandleOpenGLFallback() override; + void SetRenderResolution(std::optional resolution); Vector2i GetRenderResolution() const; diff --git a/core/client/src/c_engine.cpp b/core/client/src/c_engine.cpp index cac99071d..4d084ade2 100644 --- a/core/client/src/c_engine.cpp +++ b/core/client/src/c_engine.cpp @@ -24,6 +24,7 @@ namespace pragma::string { #include #include #include "pragma/console/engine_cvar.h" +#include "pragma/debug/debug_utils.hpp" #include "pragma/model/c_modelmanager.h" #include "pragma/networking/iclient.hpp" #include "pragma/networking/local_client.hpp" @@ -637,6 +638,23 @@ extern std::optional g_titleBarColor; extern std::optional g_borderColor; extern bool g_windowless; void register_game_shaders(); + +void CEngine::HandleOpenGLFallback() +{ + if(ustring::compare(GetRenderAPI(), std::string {"opengl"}, false)) + return; + auto *cl = static_cast(GetClientState()); + if(!cl) + return; + auto msg = Locale::GetText("prompt_fallback_to_opengl"); + if(pragma::debug::show_message_prompt(msg, pragma::debug::MessageBoxButtons::YesNo, util::get_program_name()) != pragma::debug::MessageBoxButton::Yes) + return; + cl->SetConVar("render_api", "opengl"); + SaveClientConfig(); + ShutDown(); + util::start_process(util::get_program_name().c_str()); +} + bool CEngine::Initialize(int argc, char *argv[]) { Engine::Initialize(argc, argv); diff --git a/core/shared/include/pragma/debug/debug_lua_zerobrane.hpp b/core/shared/include/pragma/debug/debug_lua_zerobrane.hpp deleted file mode 100644 index 751532a4e..000000000 --- a/core/shared/include/pragma/debug/debug_lua_zerobrane.hpp +++ /dev/null @@ -1,18 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2021 Silverlan */ - -#ifndef __DEBUG_LUA_ZEROBRANE_HPP__ -#define __DEBUG_LUA_ZEROBRANE_HPP__ - -#include "pragma/networkdefinitions.h" -#include -#include - -namespace debug { - DLLNETWORK void open_file_in_zerobrane(const std::string &fileName, uint32_t lineIdx); -}; - -#endif diff --git a/core/shared/include/pragma/debug/debug_utils.hpp b/core/shared/include/pragma/debug/debug_utils.hpp new file mode 100644 index 000000000..bb5754af8 --- /dev/null +++ b/core/shared/include/pragma/debug/debug_utils.hpp @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2021 Silverlan */ + +#ifndef __DEBUG_UTILS_HPP__ +#define __DEBUG_UTILS_HPP__ + +#include "pragma/networkdefinitions.h" +#include +#include +#include + +namespace pragma::debug { + enum class MessageBoxButton : uint32_t { + Ok = 0, + Cancel, + Abort, + Retry, + Ignore, + Yes, + No, + TryAgain, + Continue, + }; + enum class MessageBoxButtons : uint8_t { + Ok = 0, + OkCancel, + AbortRetryIgnore, + YesNoCancel, + YesNo, + RetryCancel, + CancelTryAgainContinue, + }; + DLLNETWORK void open_file_in_zerobrane(const std::string &fileName, uint32_t lineIdx); + DLLNETWORK std::optional show_message_prompt(const std::string &msg, MessageBoxButtons bts, std::optional title = {}); +#ifdef _WIN32 + DLLNETWORK bool is_module_in_callstack(struct _EXCEPTION_POINTERS *exp, const std::string &moduleName); +#endif +}; + +#endif diff --git a/core/shared/include/pragma/debug/mdump.h b/core/shared/include/pragma/debug/mdump.h index 11aa3e205..af5aceaea 100644 --- a/core/shared/include/pragma/debug/mdump.h +++ b/core/shared/include/pragma/debug/mdump.h @@ -8,8 +8,9 @@ #ifndef __MDUMP_H__ #define __MDUMP_H__ -#ifdef _WIN32 #include "pragma/definitions.h" + +#ifdef _WIN32 #if _MSC_VER < 1300 #define DECLSPEC_DEPRECATED // VC6: change this path to your Platform SDK headers @@ -21,15 +22,23 @@ // based on dbghelp.h typedef BOOL(WINAPI *MINIDUMPWRITEDUMP)(HANDLE hProcess, DWORD dwPid, HANDLE hFile, MINIDUMP_TYPE DumpType, CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam); - -class DLLNETWORK MiniDumper { - private: - static LPCSTR m_szAppName; - - static LONG WINAPI TopLevelFilter(struct _EXCEPTION_POINTERS *pExceptionInfo); - public: - MiniDumper(LPCSTR szAppName); -}; #endif +namespace pragma::debug { + class DLLNETWORK CrashHandler { + public: + CrashHandler(const std::string &appName); + ~CrashHandler(); + private: + bool GenerateCrashDump() const; + std::string m_appName; +#ifdef _WIN32 + struct _EXCEPTION_POINTERS *m_pExceptionInfo = nullptr; + std::optional GenerateMiniDump(std::string &outErr) const; + static LONG WINAPI TopLevelFilter(struct _EXCEPTION_POINTERS *pExceptionInfo); +#else + int m_sig = -1; +#endif + }; +}; #endif diff --git a/core/shared/include/pragma/engine.h b/core/shared/include/pragma/engine.h index 5a62c90eb..ece976cfb 100644 --- a/core/shared/include/pragma/engine.h +++ b/core/shared/include/pragma/engine.h @@ -144,6 +144,7 @@ class DLLNETWORK Engine : public CVarHandler, public CallbackHandler { virtual void Close(); virtual void Release(); virtual void ClearConsole(); + virtual void HandleOpenGLFallback() {}; void ClearCache(); void SetRunUpdaterOnClose(bool run); diff --git a/core/shared/include/pragma/engine_init.hpp b/core/shared/include/pragma/engine_init.hpp index 6ef2aa67d..b79a86916 100644 --- a/core/shared/include/pragma/engine_init.hpp +++ b/core/shared/include/pragma/engine_init.hpp @@ -14,10 +14,8 @@ template std::shared_ptr InitializeEngine(int argc, char *argv[]) { -#ifdef _WIN32 auto exe = engine_info::get_executable_name(); - MiniDumper dmp(exe.c_str()); -#endif + pragma::debug::CrashHandler dmp(exe); auto en = std::shared_ptr {new T {argc, argv}, [](T *p) { #ifdef _WIN32 if(std::uncaught_exceptions() > 0) { @@ -41,10 +39,8 @@ std::shared_ptr InitializeEngine(int argc, char *argv[]) inline DLLNETWORK std::shared_ptr InitializeServer(int argc, char *argv[]) { -#ifdef _WIN32 auto exe = engine_info::get_executable_name(); - MiniDumper dmp(exe.c_str()); -#endif + pragma::debug::CrashHandler dmp(exe); auto en = std::shared_ptr {new Engine {argc, argv}, [](Engine *p) { if(std::uncaught_exceptions() > 0) { // If we're stack unwinding due to an uncaught exception, diff --git a/core/shared/src/debug/StackWalker/StackWalker.cpp b/core/shared/src/debug/StackWalker/StackWalker.cpp new file mode 100644 index 000000000..ecb396177 --- /dev/null +++ b/core/shared/src/debug/StackWalker/StackWalker.cpp @@ -0,0 +1,1552 @@ +/********************************************************************** + * + * StackWalker.cpp + * https://github.com/JochenKalmbach/StackWalker + * + * Old location: http://stackwalker.codeplex.com/ + * + * + * History: + * 2005-07-27 v1 - First public release on http://www.codeproject.com/ + * http://www.codeproject.com/threads/StackWalker.asp + * 2005-07-28 v2 - Changed the params of the constructor and ShowCallstack + * (to simplify the usage) + * 2005-08-01 v3 - Changed to use 'CONTEXT_FULL' instead of CONTEXT_ALL + * (should also be enough) + * - Changed to compile correctly with the PSDK of VC7.0 + * (GetFileVersionInfoSizeA and GetFileVersionInfoA is wrongly defined: + * it uses LPSTR instead of LPCSTR as first parameter) + * - Added declarations to support VC5/6 without using 'dbghelp.h' + * - Added a 'pUserData' member to the ShowCallstack function and the + * PReadProcessMemoryRoutine declaration (to pass some user-defined data, + * which can be used in the readMemoryFunction-callback) + * 2005-08-02 v4 - OnSymInit now also outputs the OS-Version by default + * - Added example for doing an exception-callstack-walking in main.cpp + * (thanks to owillebo: http://www.codeproject.com/script/profile/whos_who.asp?id=536268) + * 2005-08-05 v5 - Removed most Lint (http://www.gimpel.com/) errors... thanks to Okko Willeboordse! + * 2008-08-04 v6 - Fixed Bug: Missing LEAK-end-tag + * http://www.codeproject.com/KB/applications/leakfinder.aspx?msg=2502890#xx2502890xx + * Fixed Bug: Compiled with "WIN32_LEAN_AND_MEAN" + * http://www.codeproject.com/KB/applications/leakfinder.aspx?msg=1824718#xx1824718xx + * Fixed Bug: Compiling with "/Wall" + * http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=2638243#xx2638243xx + * Fixed Bug: Now checking SymUseSymSrv + * http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=1388979#xx1388979xx + * Fixed Bug: Support for recursive function calls + * http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=1434538#xx1434538xx + * Fixed Bug: Missing FreeLibrary call in "GetModuleListTH32" + * http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=1326923#xx1326923xx + * Fixed Bug: SymDia is number 7, not 9! + * 2008-09-11 v7 For some (undocumented) reason, dbhelp.h is needing a packing of 8! + * Thanks to Teajay which reported the bug... + * http://www.codeproject.com/KB/applications/leakfinder.aspx?msg=2718933#xx2718933xx + * 2008-11-27 v8 Debugging Tools for Windows are now stored in a different directory + * Thanks to Luiz Salamon which reported this "bug"... + * http://www.codeproject.com/KB/threads/StackWalker.aspx?msg=2822736#xx2822736xx + * 2009-04-10 v9 License slightly corrected ( replaced) + * 2009-11-01 v10 Moved to http://stackwalker.codeplex.com/ + * 2009-11-02 v11 Now try to use IMAGEHLP_MODULE64_V3 if available + * 2010-04-15 v12 Added support for VS2010 RTM + * 2010-05-25 v13 Now using secure MyStrcCpy. Thanks to luke.simon: + * http://www.codeproject.com/KB/applications/leakfinder.aspx?msg=3477467#xx3477467xx + * 2013-01-07 v14 Runtime Check Error VS2010 Debug Builds fixed: + * http://stackwalker.codeplex.com/workitem/10511 + * + * + * LICENSE (http://www.opensource.org/licenses/bsd-license.php) + * + * Copyright (c) 2005-2013, Jochen Kalmbach + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of Jochen Kalmbach nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + **********************************************************************/ + +#if defined(_MSC_VER) + +#include "StackWalker.h" + +#include +#include +#include +#include +#include + +#pragma comment(lib, "version.lib") // for "VerQueryValue" + +#pragma warning(disable : 4826) +#if _MSC_VER >= 1900 +#pragma warning(disable : 4091) // For fix unnamed enums from DbgHelp.h +#endif + + +// If VC7 and later, then use the shipped 'dbghelp.h'-file +#pragma pack(push, 8) +#if _MSC_VER >= 1300 +#include +#else +// inline the important dbghelp.h-declarations... +typedef enum +{ + SymNone = 0, + SymCoff, + SymCv, + SymPdb, + SymExport, + SymDeferred, + SymSym, + SymDia, + SymVirtual, + NumSymTypes +} SYM_TYPE; +typedef struct _IMAGEHLP_LINE64 +{ + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_LINE64) + PVOID Key; // internal + DWORD LineNumber; // line number in file + PCHAR FileName; // full filename + DWORD64 Address; // first instruction of line +} IMAGEHLP_LINE64, *PIMAGEHLP_LINE64; +typedef struct _IMAGEHLP_MODULE64 +{ + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64) + DWORD64 BaseOfImage; // base load address of module + DWORD ImageSize; // virtual size of the loaded module + DWORD TimeDateStamp; // date/time stamp from pe header + DWORD CheckSum; // checksum from the pe header + DWORD NumSyms; // number of symbols in the symbol table + SYM_TYPE SymType; // type of symbols loaded + CHAR ModuleName[32]; // module name + CHAR ImageName[256]; // image name + CHAR LoadedImageName[256]; // symbol file name +} IMAGEHLP_MODULE64, *PIMAGEHLP_MODULE64; +typedef struct _IMAGEHLP_SYMBOL64 +{ + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_SYMBOL64) + DWORD64 Address; // virtual address including dll base address + DWORD Size; // estimated size of symbol, can be zero + DWORD Flags; // info about the symbols, see the SYMF defines + DWORD MaxNameLength; // maximum size of symbol name in 'Name' + CHAR Name[1]; // symbol name (null terminated string) +} IMAGEHLP_SYMBOL64, *PIMAGEHLP_SYMBOL64; +typedef enum +{ + AddrMode1616, + AddrMode1632, + AddrModeReal, + AddrModeFlat +} ADDRESS_MODE; +typedef struct _tagADDRESS64 +{ + DWORD64 Offset; + WORD Segment; + ADDRESS_MODE Mode; +} ADDRESS64, *LPADDRESS64; +typedef struct _KDHELP64 +{ + DWORD64 Thread; + DWORD ThCallbackStack; + DWORD ThCallbackBStore; + DWORD NextCallback; + DWORD FramePointer; + DWORD64 KiCallUserMode; + DWORD64 KeUserCallbackDispatcher; + DWORD64 SystemRangeStart; + DWORD64 Reserved[8]; +} KDHELP64, *PKDHELP64; +typedef struct _tagSTACKFRAME64 +{ + ADDRESS64 AddrPC; // program counter + ADDRESS64 AddrReturn; // return address + ADDRESS64 AddrFrame; // frame pointer + ADDRESS64 AddrStack; // stack pointer + ADDRESS64 AddrBStore; // backing store pointer + PVOID FuncTableEntry; // pointer to pdata/fpo or NULL + DWORD64 Params[4]; // possible arguments to the function + BOOL Far; // WOW far call + BOOL Virtual; // is this a virtual frame? + DWORD64 Reserved[3]; + KDHELP64 KdHelp; +} STACKFRAME64, *LPSTACKFRAME64; +typedef BOOL(__stdcall* PREAD_PROCESS_MEMORY_ROUTINE64)(HANDLE hProcess, + DWORD64 qwBaseAddress, + PVOID lpBuffer, + DWORD nSize, + LPDWORD lpNumberOfBytesRead); +typedef PVOID(__stdcall* PFUNCTION_TABLE_ACCESS_ROUTINE64)(HANDLE hProcess, DWORD64 AddrBase); +typedef DWORD64(__stdcall* PGET_MODULE_BASE_ROUTINE64)(HANDLE hProcess, DWORD64 Address); +typedef DWORD64(__stdcall* PTRANSLATE_ADDRESS_ROUTINE64)(HANDLE hProcess, + HANDLE hThread, + LPADDRESS64 lpaddr); + +// clang-format off +#define SYMOPT_CASE_INSENSITIVE 0x00000001 +#define SYMOPT_UNDNAME 0x00000002 +#define SYMOPT_DEFERRED_LOADS 0x00000004 +#define SYMOPT_NO_CPP 0x00000008 +#define SYMOPT_LOAD_LINES 0x00000010 +#define SYMOPT_OMAP_FIND_NEAREST 0x00000020 +#define SYMOPT_LOAD_ANYTHING 0x00000040 +#define SYMOPT_IGNORE_CVREC 0x00000080 +#define SYMOPT_NO_UNQUALIFIED_LOADS 0x00000100 +#define SYMOPT_FAIL_CRITICAL_ERRORS 0x00000200 +#define SYMOPT_EXACT_SYMBOLS 0x00000400 +#define SYMOPT_ALLOW_ABSOLUTE_SYMBOLS 0x00000800 +#define SYMOPT_IGNORE_NT_SYMPATH 0x00001000 +#define SYMOPT_INCLUDE_32BIT_MODULES 0x00002000 +#define SYMOPT_PUBLICS_ONLY 0x00004000 +#define SYMOPT_NO_PUBLICS 0x00008000 +#define SYMOPT_AUTO_PUBLICS 0x00010000 +#define SYMOPT_NO_IMAGE_SEARCH 0x00020000 +#define SYMOPT_SECURE 0x00040000 +#define SYMOPT_DEBUG 0x80000000 +#define UNDNAME_COMPLETE (0x0000) // Enable full undecoration +#define UNDNAME_NAME_ONLY (0x1000) // Crack only the name for primary declaration; +// clang-format on + +#endif // _MSC_VER < 1300 +#pragma pack(pop) + +// Some missing defines (for VC5/6): +#ifndef INVALID_FILE_ATTRIBUTES +#define INVALID_FILE_ATTRIBUTES ((DWORD)-1) +#endif + +// secure-CRT_functions are only available starting with VC8 +#if _MSC_VER < 1400 +#define strcpy_s(dst, len, src) strcpy(dst, src) +#define strncpy_s(dst, len, src, maxLen) strncpy(dst, len, src) +#define strcat_s(dst, len, src) strcat(dst, src) +#define _snprintf_s _snprintf +#define _tcscat_s _tcscat +#endif + +static void MyStrCpy(char* szDest, size_t nMaxDestSize, const char* szSrc) +{ + if (nMaxDestSize <= 0) + return; + strncpy_s(szDest, nMaxDestSize, szSrc, _TRUNCATE); + // INFO: _TRUNCATE will ensure that it is null-terminated; + // but with older compilers (<1400) it uses "strncpy" and this does not!) + szDest[nMaxDestSize - 1] = 0; +} // MyStrCpy + +// Normally it should be enough to use 'CONTEXT_FULL' (better would be 'CONTEXT_ALL') +#define USED_CONTEXT_FLAGS CONTEXT_FULL + +class StackWalkerInternal +{ +public: + StackWalkerInternal(StackWalker* parent, HANDLE hProcess, PCONTEXT ctx) + { + m_parent = parent; + m_hDbhHelp = NULL; + pSC = NULL; + m_hProcess = hProcess; + pSFTA = NULL; + pSGLFA = NULL; + pSGMB = NULL; + pSGMI = NULL; + pSGO = NULL; + pSGSFA = NULL; + pSI = NULL; + pSLM = NULL; + pSSO = NULL; + pSW = NULL; + pUDSN = NULL; + pSGSP = NULL; + m_ctx.ContextFlags = 0; + if (ctx != NULL) + m_ctx = *ctx; + } + + ~StackWalkerInternal() + { + if (pSC != NULL) + pSC(m_hProcess); // SymCleanup + if (m_hDbhHelp != NULL) + FreeLibrary(m_hDbhHelp); + m_hDbhHelp = NULL; + m_parent = NULL; + } + + BOOL Init(LPCSTR szSymPath) + { + if (m_parent == NULL) + return FALSE; + // Dynamically load the Entry-Points for dbghelp.dll: + // First try to load the newest one from + TCHAR szTemp[4096]; + // But before we do this, we first check if the ".local" file exists + if (GetModuleFileName(NULL, szTemp, 4096) > 0) + { + _tcscat_s(szTemp, _T(".local")); + if (GetFileAttributesA(szTemp) == INVALID_FILE_ATTRIBUTES) + { + // ".local" file does not exist, so we can try to load the dbghelp.dll from the "Debugging Tools for Windows" + // Ok, first try the new path according to the architecture: +#ifdef _M_IX86 + if ((m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0)) + { + _tcscat_s(szTemp, _T("\\Debugging Tools for Windows (x86)\\dbghelp.dll")); + // now check if the file exists: + if (GetFileAttributesA(szTemp) != INVALID_FILE_ATTRIBUTES) + { + m_hDbhHelp = LoadLibrary(szTemp); + } + } +#elif _M_X64 + if ((m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0)) + { + _tcscat_s(szTemp, _T("\\Debugging Tools for Windows (x64)\\dbghelp.dll")); + // now check if the file exists: + if (GetFileAttributesA(szTemp) != INVALID_FILE_ATTRIBUTES) + { + m_hDbhHelp = LoadLibrary(szTemp); + } + } +#elif _M_IA64 + if ((m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0)) + { + _tcscat_s(szTemp, _T("\\Debugging Tools for Windows (ia64)\\dbghelp.dll")); + // now check if the file exists: + if (GetFileAttributesA(szTemp) != INVALID_FILE_ATTRIBUTES) + { + m_hDbhHelp = LoadLibrary(szTemp); + } + } +#endif + // If still not found, try the old directories... + if ((m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0)) + { + _tcscat_s(szTemp, _T("\\Debugging Tools for Windows\\dbghelp.dll")); + // now check if the file exists: + if (GetFileAttributesA(szTemp) != INVALID_FILE_ATTRIBUTES) + { + m_hDbhHelp = LoadLibrary(szTemp); + } + } +#if defined _M_X64 || defined _M_IA64 + // Still not found? Then try to load the (old) 64-Bit version: + if ((m_hDbhHelp == NULL) && (GetEnvironmentVariable(_T("ProgramFiles"), szTemp, 4096) > 0)) + { + _tcscat_s(szTemp, _T("\\Debugging Tools for Windows 64-Bit\\dbghelp.dll")); + if (GetFileAttributesA(szTemp) != INVALID_FILE_ATTRIBUTES) + { + m_hDbhHelp = LoadLibrary(szTemp); + } + } +#endif + } + } + if (m_hDbhHelp == NULL) // if not already loaded, try to load a default-one + m_hDbhHelp = LoadLibrary(_T("dbghelp.dll")); + if (m_hDbhHelp == NULL) + return FALSE; + pSI = (tSI)GetProcAddress(m_hDbhHelp, "SymInitialize"); + pSC = (tSC)GetProcAddress(m_hDbhHelp, "SymCleanup"); + + pSW = (tSW)GetProcAddress(m_hDbhHelp, "StackWalk64"); + pSGO = (tSGO)GetProcAddress(m_hDbhHelp, "SymGetOptions"); + pSSO = (tSSO)GetProcAddress(m_hDbhHelp, "SymSetOptions"); + + pSFTA = (tSFTA)GetProcAddress(m_hDbhHelp, "SymFunctionTableAccess64"); + pSGLFA = (tSGLFA)GetProcAddress(m_hDbhHelp, "SymGetLineFromAddr64"); + pSGMB = (tSGMB)GetProcAddress(m_hDbhHelp, "SymGetModuleBase64"); + pSGMI = (tSGMI)GetProcAddress(m_hDbhHelp, "SymGetModuleInfo64"); + pSGSFA = (tSGSFA)GetProcAddress(m_hDbhHelp, "SymGetSymFromAddr64"); + pUDSN = (tUDSN)GetProcAddress(m_hDbhHelp, "UnDecorateSymbolName"); + pSLM = (tSLM)GetProcAddress(m_hDbhHelp, "SymLoadModule64"); + pSGSP = (tSGSP)GetProcAddress(m_hDbhHelp, "SymGetSearchPath"); + + if (pSC == NULL || pSFTA == NULL || pSGMB == NULL || pSGMI == NULL || pSGO == NULL || + pSGSFA == NULL || pSI == NULL || pSSO == NULL || pSW == NULL || pUDSN == NULL || + pSLM == NULL) + { + FreeLibrary(m_hDbhHelp); + m_hDbhHelp = NULL; + pSC = NULL; + return FALSE; + } + + // SymInitialize + if (this->pSI(m_hProcess, szSymPath, FALSE) == FALSE) + this->m_parent->OnDbgHelpErr("SymInitialize", GetLastError(), 0); + + DWORD symOptions = this->pSGO(); // SymGetOptions + symOptions |= SYMOPT_LOAD_LINES; + symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS; + //symOptions |= SYMOPT_NO_PROMPTS; + // SymSetOptions + symOptions = this->pSSO(symOptions); + + char buf[StackWalker::STACKWALK_MAX_NAMELEN] = {0}; + if (this->pSGSP != NULL) + { + if (this->pSGSP(m_hProcess, buf, StackWalker::STACKWALK_MAX_NAMELEN) == FALSE) + this->m_parent->OnDbgHelpErr("SymGetSearchPath", GetLastError(), 0); + } + char szUserName[1024] = {0}; + DWORD dwSize = 1024; + GetUserNameA(szUserName, &dwSize); + this->m_parent->OnSymInit(buf, symOptions, szUserName); + + return TRUE; + } + + StackWalker* m_parent; + + CONTEXT m_ctx; + HMODULE m_hDbhHelp; + HANDLE m_hProcess; + +#pragma pack(push, 8) + typedef struct _IMAGEHLP_MODULE64_V3 + { + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64) + DWORD64 BaseOfImage; // base load address of module + DWORD ImageSize; // virtual size of the loaded module + DWORD TimeDateStamp; // date/time stamp from pe header + DWORD CheckSum; // checksum from the pe header + DWORD NumSyms; // number of symbols in the symbol table + SYM_TYPE SymType; // type of symbols loaded + CHAR ModuleName[32]; // module name + CHAR ImageName[256]; // image name + CHAR LoadedImageName[256]; // symbol file name + // new elements: 07-Jun-2002 + CHAR LoadedPdbName[256]; // pdb file name + DWORD CVSig; // Signature of the CV record in the debug directories + CHAR CVData[MAX_PATH * 3]; // Contents of the CV record + DWORD PdbSig; // Signature of PDB + GUID PdbSig70; // Signature of PDB (VC 7 and up) + DWORD PdbAge; // DBI age of pdb + BOOL PdbUnmatched; // loaded an unmatched pdb + BOOL DbgUnmatched; // loaded an unmatched dbg + BOOL LineNumbers; // we have line number information + BOOL GlobalSymbols; // we have internal symbol information + BOOL TypeInfo; // we have type information + // new elements: 17-Dec-2003 + BOOL SourceIndexed; // pdb supports source server + BOOL Publics; // contains public symbols + } IMAGEHLP_MODULE64_V3, *PIMAGEHLP_MODULE64_V3; + + typedef struct _IMAGEHLP_MODULE64_V2 + { + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64) + DWORD64 BaseOfImage; // base load address of module + DWORD ImageSize; // virtual size of the loaded module + DWORD TimeDateStamp; // date/time stamp from pe header + DWORD CheckSum; // checksum from the pe header + DWORD NumSyms; // number of symbols in the symbol table + SYM_TYPE SymType; // type of symbols loaded + CHAR ModuleName[32]; // module name + CHAR ImageName[256]; // image name + CHAR LoadedImageName[256]; // symbol file name + } IMAGEHLP_MODULE64_V2, *PIMAGEHLP_MODULE64_V2; +#pragma pack(pop) + + // SymCleanup() + typedef BOOL(__stdcall* tSC)(IN HANDLE hProcess); + tSC pSC; + + // SymFunctionTableAccess64() + typedef PVOID(__stdcall* tSFTA)(HANDLE hProcess, DWORD64 AddrBase); + tSFTA pSFTA; + + // SymGetLineFromAddr64() + typedef BOOL(__stdcall* tSGLFA)(IN HANDLE hProcess, + IN DWORD64 dwAddr, + OUT PDWORD pdwDisplacement, + OUT PIMAGEHLP_LINE64 Line); + tSGLFA pSGLFA; + + // SymGetModuleBase64() + typedef DWORD64(__stdcall* tSGMB)(IN HANDLE hProcess, IN DWORD64 dwAddr); + tSGMB pSGMB; + + // SymGetModuleInfo64() + typedef BOOL(__stdcall* tSGMI)(IN HANDLE hProcess, + IN DWORD64 dwAddr, + OUT IMAGEHLP_MODULE64_V3* ModuleInfo); + tSGMI pSGMI; + + // SymGetOptions() + typedef DWORD(__stdcall* tSGO)(VOID); + tSGO pSGO; + + // SymGetSymFromAddr64() + typedef BOOL(__stdcall* tSGSFA)(IN HANDLE hProcess, + IN DWORD64 dwAddr, + OUT PDWORD64 pdwDisplacement, + OUT PIMAGEHLP_SYMBOL64 Symbol); + tSGSFA pSGSFA; + + // SymInitialize() + typedef BOOL(__stdcall* tSI)(IN HANDLE hProcess, IN LPCSTR UserSearchPath, IN BOOL fInvadeProcess); + tSI pSI; + + // SymLoadModule64() + typedef DWORD64(__stdcall* tSLM)(IN HANDLE hProcess, + IN HANDLE hFile, + IN LPCSTR ImageName, + IN LPCSTR ModuleName, + IN DWORD64 BaseOfDll, + IN DWORD SizeOfDll); + tSLM pSLM; + + // SymSetOptions() + typedef DWORD(__stdcall* tSSO)(IN DWORD SymOptions); + tSSO pSSO; + + // StackWalk64() + typedef BOOL(__stdcall* tSW)(DWORD MachineType, + HANDLE hProcess, + HANDLE hThread, + LPSTACKFRAME64 StackFrame, + PVOID ContextRecord, + PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, + PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, + PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, + PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress); + tSW pSW; + + // UnDecorateSymbolName() + typedef DWORD(__stdcall WINAPI* tUDSN)(PCSTR DecoratedName, + PSTR UnDecoratedName, + DWORD UndecoratedLength, + DWORD Flags); + tUDSN pUDSN; + + typedef BOOL(__stdcall WINAPI* tSGSP)(HANDLE hProcess, PSTR SearchPath, DWORD SearchPathLength); + tSGSP pSGSP; + +private: +// **************************************** ToolHelp32 ************************ +#define MAX_MODULE_NAME32 255 +#define TH32CS_SNAPMODULE 0x00000008 +#pragma pack(push, 8) + typedef struct tagMODULEENTRY32 + { + DWORD dwSize; + DWORD th32ModuleID; // This module + DWORD th32ProcessID; // owning process + DWORD GlblcntUsage; // Global usage count on the module + DWORD ProccntUsage; // Module usage count in th32ProcessID's context + BYTE* modBaseAddr; // Base address of module in th32ProcessID's context + DWORD modBaseSize; // Size in bytes of module starting at modBaseAddr + HMODULE hModule; // The hModule of this module in th32ProcessID's context + char szModule[MAX_MODULE_NAME32 + 1]; + char szExePath[MAX_PATH]; + } MODULEENTRY32; + typedef MODULEENTRY32* PMODULEENTRY32; + typedef MODULEENTRY32* LPMODULEENTRY32; +#pragma pack(pop) + + BOOL GetModuleListTH32(HANDLE hProcess, DWORD pid) + { + // CreateToolhelp32Snapshot() + typedef HANDLE(__stdcall * tCT32S)(DWORD dwFlags, DWORD th32ProcessID); + // Module32First() + typedef BOOL(__stdcall * tM32F)(HANDLE hSnapshot, LPMODULEENTRY32 lpme); + // Module32Next() + typedef BOOL(__stdcall * tM32N)(HANDLE hSnapshot, LPMODULEENTRY32 lpme); + + // try both dlls... + const TCHAR* dllname[] = {_T("kernel32.dll"), _T("tlhelp32.dll")}; + HINSTANCE hToolhelp = NULL; + tCT32S pCT32S = NULL; + tM32F pM32F = NULL; + tM32N pM32N = NULL; + + HANDLE hSnap; + MODULEENTRY32 me; + me.dwSize = sizeof(me); + BOOL keepGoing; + size_t i; + + for (i = 0; i < (sizeof(dllname) / sizeof(dllname[0])); i++) + { + hToolhelp = LoadLibrary(dllname[i]); + if (hToolhelp == NULL) + continue; + pCT32S = (tCT32S)GetProcAddress(hToolhelp, "CreateToolhelp32Snapshot"); + pM32F = (tM32F)GetProcAddress(hToolhelp, "Module32First"); + pM32N = (tM32N)GetProcAddress(hToolhelp, "Module32Next"); + if ((pCT32S != NULL) && (pM32F != NULL) && (pM32N != NULL)) + break; // found the functions! + FreeLibrary(hToolhelp); + hToolhelp = NULL; + } + + if (hToolhelp == NULL) + return FALSE; + + hSnap = pCT32S(TH32CS_SNAPMODULE, pid); + if (hSnap == (HANDLE)-1) + { + FreeLibrary(hToolhelp); + return FALSE; + } + + keepGoing = !!pM32F(hSnap, &me); + int cnt = 0; + while (keepGoing) + { + this->LoadModule(hProcess, me.szExePath, me.szModule, (DWORD64)me.modBaseAddr, + me.modBaseSize); + cnt++; + keepGoing = !!pM32N(hSnap, &me); + } + CloseHandle(hSnap); + FreeLibrary(hToolhelp); + if (cnt <= 0) + return FALSE; + return TRUE; + } // GetModuleListTH32 + + // **************************************** PSAPI ************************ + typedef struct _MODULEINFO + { + LPVOID lpBaseOfDll; + DWORD SizeOfImage; + LPVOID EntryPoint; + } MODULEINFO, *LPMODULEINFO; + + BOOL GetModuleListPSAPI(HANDLE hProcess) + { + // EnumProcessModules() + typedef BOOL(__stdcall * tEPM)(HANDLE hProcess, HMODULE * lphModule, DWORD cb, + LPDWORD lpcbNeeded); + // GetModuleFileNameEx() + typedef DWORD(__stdcall * tGMFNE)(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, + DWORD nSize); + // GetModuleBaseName() + typedef DWORD(__stdcall * tGMBN)(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, + DWORD nSize); + // GetModuleInformation() + typedef BOOL(__stdcall * tGMI)(HANDLE hProcess, HMODULE hModule, LPMODULEINFO pmi, DWORD nSize); + + HINSTANCE hPsapi; + tEPM pEPM; + tGMFNE pGMFNE; + tGMBN pGMBN; + tGMI pGMI; + + DWORD i; + //ModuleEntry e; + DWORD cbNeeded; + MODULEINFO mi; + HMODULE* hMods = NULL; + char* tt = NULL; + char* tt2 = NULL; + const SIZE_T TTBUFLEN = 8096; + int cnt = 0; + + hPsapi = LoadLibrary(_T("psapi.dll")); + if (hPsapi == NULL) + return FALSE; + + pEPM = (tEPM)GetProcAddress(hPsapi, "EnumProcessModules"); + pGMFNE = (tGMFNE)GetProcAddress(hPsapi, "GetModuleFileNameExA"); + pGMBN = (tGMFNE)GetProcAddress(hPsapi, "GetModuleBaseNameA"); + pGMI = (tGMI)GetProcAddress(hPsapi, "GetModuleInformation"); + if ((pEPM == NULL) || (pGMFNE == NULL) || (pGMBN == NULL) || (pGMI == NULL)) + { + // we couldn't find all functions + FreeLibrary(hPsapi); + return FALSE; + } + + hMods = (HMODULE*)malloc(sizeof(HMODULE) * (TTBUFLEN / sizeof(HMODULE))); + tt = (char*)malloc(sizeof(char) * TTBUFLEN); + tt2 = (char*)malloc(sizeof(char) * TTBUFLEN); + if ((hMods == NULL) || (tt == NULL) || (tt2 == NULL)) + goto cleanup; + + if (!pEPM(hProcess, hMods, TTBUFLEN, &cbNeeded)) + { + //_ftprintf(fLogFile, _T("%lu: EPM failed, GetLastError = %lu\n"), g_dwShowCount, gle ); + goto cleanup; + } + + if (cbNeeded > TTBUFLEN) + { + //_ftprintf(fLogFile, _T("%lu: More than %lu module handles. Huh?\n"), g_dwShowCount, lenof( hMods ) ); + goto cleanup; + } + + for (i = 0; i < cbNeeded / sizeof(hMods[0]); i++) + { + // base address, size + pGMI(hProcess, hMods[i], &mi, sizeof(mi)); + // image file name + tt[0] = 0; + pGMFNE(hProcess, hMods[i], tt, TTBUFLEN); + // module name + tt2[0] = 0; + pGMBN(hProcess, hMods[i], tt2, TTBUFLEN); + + DWORD dwRes = this->LoadModule(hProcess, tt, tt2, (DWORD64)mi.lpBaseOfDll, mi.SizeOfImage); + if (dwRes != ERROR_SUCCESS) + this->m_parent->OnDbgHelpErr("LoadModule", dwRes, 0); + cnt++; + } + + cleanup: + if (hPsapi != NULL) + FreeLibrary(hPsapi); + if (tt2 != NULL) + free(tt2); + if (tt != NULL) + free(tt); + if (hMods != NULL) + free(hMods); + + return cnt != 0; + } // GetModuleListPSAPI + + DWORD LoadModule(HANDLE hProcess, LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size) + { + CHAR* szImg = _strdup(img); + CHAR* szMod = _strdup(mod); + DWORD result = ERROR_SUCCESS; + if ((szImg == NULL) || (szMod == NULL)) + result = ERROR_NOT_ENOUGH_MEMORY; + else + { + if (pSLM(hProcess, 0, szImg, szMod, baseAddr, size) == 0) + result = GetLastError(); + } + ULONGLONG fileVersion = 0; + if ((m_parent != NULL) && (szImg != NULL)) + { + // try to retrieve the file-version: + if ((this->m_parent->m_options & StackWalker::RetrieveFileVersion) != 0) + { + VS_FIXEDFILEINFO* fInfo = NULL; + DWORD dwHandle; + DWORD dwSize = GetFileVersionInfoSizeA(szImg, &dwHandle); + if (dwSize > 0) + { + LPVOID vData = malloc(dwSize); + if (vData != NULL) + { + if (GetFileVersionInfoA(szImg, dwHandle, dwSize, vData) != 0) + { + UINT len; + TCHAR szSubBlock[] = _T("\\"); + if (VerQueryValue(vData, szSubBlock, (LPVOID*)&fInfo, &len) == 0) + fInfo = NULL; + else + { + fileVersion = + ((ULONGLONG)fInfo->dwFileVersionLS) + ((ULONGLONG)fInfo->dwFileVersionMS << 32); + } + } + free(vData); + } + } + } + + // Retrieve some additional-infos about the module + IMAGEHLP_MODULE64_V3 Module; + const char* szSymType = "-unknown-"; + if (this->GetModuleInfo(hProcess, baseAddr, &Module) != FALSE) + { + switch (Module.SymType) + { + case SymNone: + szSymType = "-nosymbols-"; + break; + case SymCoff: // 1 + szSymType = "COFF"; + break; + case SymCv: // 2 + szSymType = "CV"; + break; + case SymPdb: // 3 + szSymType = "PDB"; + break; + case SymExport: // 4 + szSymType = "-exported-"; + break; + case SymDeferred: // 5 + szSymType = "-deferred-"; + break; + case SymSym: // 6 + szSymType = "SYM"; + break; + case 7: // SymDia: + szSymType = "DIA"; + break; + case 8: //SymVirtual: + szSymType = "Virtual"; + break; + } + } + LPCSTR pdbName = Module.LoadedImageName; + if (Module.LoadedPdbName[0] != 0) + pdbName = Module.LoadedPdbName; + this->m_parent->OnLoadModule(img, mod, baseAddr, size, result, szSymType, pdbName, + fileVersion); + } + if (szImg != NULL) + free(szImg); + if (szMod != NULL) + free(szMod); + return result; + } + +public: + BOOL LoadModules(HANDLE hProcess, DWORD dwProcessId) + { + // first try toolhelp32 + if (GetModuleListTH32(hProcess, dwProcessId)) + return true; + // then try psapi + return GetModuleListPSAPI(hProcess); + } + + BOOL GetModuleInfo(HANDLE hProcess, DWORD64 baseAddr, IMAGEHLP_MODULE64_V3* pModuleInfo) + { + memset(pModuleInfo, 0, sizeof(IMAGEHLP_MODULE64_V3)); + if (this->pSGMI == NULL) + { + SetLastError(ERROR_DLL_INIT_FAILED); + return FALSE; + } + // First try to use the larger ModuleInfo-Structure + pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V3); + void* pData = malloc( + 4096); // reserve enough memory, so the bug in v6.3.5.1 does not lead to memory-overwrites... + if (pData == NULL) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return FALSE; + } + memcpy(pData, pModuleInfo, sizeof(IMAGEHLP_MODULE64_V3)); + static bool s_useV3Version = true; + if (s_useV3Version) + { + if (this->pSGMI(hProcess, baseAddr, (IMAGEHLP_MODULE64_V3*)pData) != FALSE) + { + // only copy as much memory as is reserved... + memcpy(pModuleInfo, pData, sizeof(IMAGEHLP_MODULE64_V3)); + pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V3); + free(pData); + return TRUE; + } + s_useV3Version = false; // to prevent unnecessary calls with the larger struct... + } + + // could not retrieve the bigger structure, try with the smaller one (as defined in VC7.1)... + pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V2); + memcpy(pData, pModuleInfo, sizeof(IMAGEHLP_MODULE64_V2)); + if (this->pSGMI(hProcess, baseAddr, (IMAGEHLP_MODULE64_V3*)pData) != FALSE) + { + // only copy as much memory as is reserved... + memcpy(pModuleInfo, pData, sizeof(IMAGEHLP_MODULE64_V2)); + pModuleInfo->SizeOfStruct = sizeof(IMAGEHLP_MODULE64_V2); + free(pData); + return TRUE; + } + free(pData); + SetLastError(ERROR_DLL_INIT_FAILED); + return FALSE; + } +}; + +// ############################################################# + +#if defined(_MSC_VER) && _MSC_VER >= 1400 && _MSC_VER < 1900 +extern "C" void* __cdecl _getptd(); +#endif +#if defined(_MSC_VER) && _MSC_VER >= 1900 +extern "C" void** __cdecl __current_exception_context(); +#endif + +static PCONTEXT get_current_exception_context() +{ + PCONTEXT * pctx = NULL; +#if defined(_MSC_VER) && _MSC_VER >= 1400 && _MSC_VER < 1900 + LPSTR ptd = (LPSTR)_getptd(); + if (ptd) + pctx = (PCONTEXT *)(ptd + (sizeof(void*) == 4 ? 0x8C : 0xF8)); +#endif +#if defined(_MSC_VER) && _MSC_VER >= 1900 + pctx = (PCONTEXT *)__current_exception_context(); +#endif + return pctx ? *pctx : NULL; +} + +bool StackWalker::Init(ExceptType extype, int options, LPCSTR szSymPath, DWORD dwProcessId, + HANDLE hProcess, PEXCEPTION_POINTERS exp) +{ + PCONTEXT ctx = NULL; + if (extype == AfterCatch) + ctx = get_current_exception_context(); + if (extype == AfterExcept && exp) + ctx = exp->ContextRecord; + this->m_options = options; + this->m_modulesLoaded = FALSE; + this->m_szSymPath = NULL; + this->m_MaxRecursionCount = 1000; + this->m_sw = NULL; + SetTargetProcess(dwProcessId, hProcess); + SetSymPath(szSymPath); + /* MSVC ignore std::nothrow specifier for `new` operator */ + LPVOID buf = malloc(sizeof(StackWalkerInternal)); + if (!buf) + return false; + memset(buf, 0, sizeof(StackWalkerInternal)); + this->m_sw = new(buf) StackWalkerInternal(this, this->m_hProcess, ctx); // placement new + return true; +} + +StackWalker::StackWalker(DWORD dwProcessId, HANDLE hProcess) +{ + Init(NonExcept, OptionsAll, NULL, dwProcessId, hProcess); +} + +StackWalker::StackWalker(int options, LPCSTR szSymPath, DWORD dwProcessId, HANDLE hProcess) +{ + Init(NonExcept, options, szSymPath, dwProcessId, hProcess); +} + +StackWalker::StackWalker(ExceptType extype, int options, PEXCEPTION_POINTERS exp) +{ + Init(extype, options, NULL, GetCurrentProcessId(), GetCurrentProcess(), exp); +} + +StackWalker::~StackWalker() +{ + SetSymPath(NULL); + if (m_sw != NULL) { + m_sw->~StackWalkerInternal(); // call the object's destructor + free(m_sw); + } + m_sw = NULL; +} + +bool StackWalker::SetSymPath(LPCSTR szSymPath) +{ + if (m_szSymPath) + free(m_szSymPath); + m_szSymPath = NULL; + if (szSymPath == NULL) + return true; + m_szSymPath = _strdup(szSymPath); + if (m_szSymPath) + m_options |= SymBuildPath; + return true; +} + +bool StackWalker::SetTargetProcess(DWORD dwProcessId, HANDLE hProcess) +{ + m_dwProcessId = dwProcessId; + m_hProcess = hProcess; + if (m_sw) + m_sw->m_hProcess = hProcess; + return true; +} + +PCONTEXT StackWalker::GetCurrentExceptionContext() +{ + return get_current_exception_context(); +} + +BOOL StackWalker::LoadModules() +{ + if (this->m_sw == NULL) + { + SetLastError(ERROR_DLL_INIT_FAILED); + return FALSE; + } + if (m_modulesLoaded != FALSE) + return TRUE; + + // Build the sym-path: + char* szSymPath = NULL; + if ((this->m_options & SymBuildPath) != 0) + { + const size_t nSymPathLen = 4096; + szSymPath = (char*)malloc(nSymPathLen); + if (szSymPath == NULL) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return FALSE; + } + szSymPath[0] = 0; + // Now first add the (optional) provided sympath: + if (this->m_szSymPath != NULL) + { + strcat_s(szSymPath, nSymPathLen, this->m_szSymPath); + strcat_s(szSymPath, nSymPathLen, ";"); + } + + strcat_s(szSymPath, nSymPathLen, ".;"); + + const size_t nTempLen = 1024; + char szTemp[nTempLen]; + // Now add the current directory: + if (GetCurrentDirectoryA(nTempLen, szTemp) > 0) + { + szTemp[nTempLen - 1] = 0; + strcat_s(szSymPath, nSymPathLen, szTemp); + strcat_s(szSymPath, nSymPathLen, ";"); + } + + // Now add the path for the main-module: + if (GetModuleFileNameA(NULL, szTemp, nTempLen) > 0) + { + szTemp[nTempLen - 1] = 0; + for (char* p = (szTemp + strlen(szTemp) - 1); p >= szTemp; --p) + { + // locate the rightmost path separator + if ((*p == '\\') || (*p == '/') || (*p == ':')) + { + *p = 0; + break; + } + } // for (search for path separator...) + if (strlen(szTemp) > 0) + { + strcat_s(szSymPath, nSymPathLen, szTemp); + strcat_s(szSymPath, nSymPathLen, ";"); + } + } + if (GetEnvironmentVariableA("_NT_SYMBOL_PATH", szTemp, nTempLen) > 0) + { + szTemp[nTempLen - 1] = 0; + strcat_s(szSymPath, nSymPathLen, szTemp); + strcat_s(szSymPath, nSymPathLen, ";"); + } + if (GetEnvironmentVariableA("_NT_ALTERNATE_SYMBOL_PATH", szTemp, nTempLen) > 0) + { + szTemp[nTempLen - 1] = 0; + strcat_s(szSymPath, nSymPathLen, szTemp); + strcat_s(szSymPath, nSymPathLen, ";"); + } + if (GetEnvironmentVariableA("SYSTEMROOT", szTemp, nTempLen) > 0) + { + szTemp[nTempLen - 1] = 0; + strcat_s(szSymPath, nSymPathLen, szTemp); + strcat_s(szSymPath, nSymPathLen, ";"); + // also add the "system32"-directory: + strcat_s(szTemp, nTempLen, "\\system32"); + strcat_s(szSymPath, nSymPathLen, szTemp); + strcat_s(szSymPath, nSymPathLen, ";"); + } + + if ((this->m_options & SymUseSymSrv) != 0) + { + if (GetEnvironmentVariableA("SYSTEMDRIVE", szTemp, nTempLen) > 0) + { + szTemp[nTempLen - 1] = 0; + strcat_s(szSymPath, nSymPathLen, "SRV*"); + strcat_s(szSymPath, nSymPathLen, szTemp); + strcat_s(szSymPath, nSymPathLen, "\\websymbols"); + strcat_s(szSymPath, nSymPathLen, "*https://msdl.microsoft.com/download/symbols;"); + } + else + strcat_s(szSymPath, nSymPathLen, + "SRV*c:\\websymbols*https://msdl.microsoft.com/download/symbols;"); + } + } // if SymBuildPath + + // First Init the whole stuff... + BOOL bRet = this->m_sw->Init(szSymPath); + if (szSymPath != NULL) + free(szSymPath); + szSymPath = NULL; + if (bRet == FALSE) + { + this->OnDbgHelpErr("Error while initializing dbghelp.dll", 0, 0); + SetLastError(ERROR_DLL_INIT_FAILED); + return FALSE; + } + + bRet = this->m_sw->LoadModules(this->m_hProcess, this->m_dwProcessId); + if (bRet != FALSE) + m_modulesLoaded = TRUE; + return bRet; +} + +// The following is used to pass the "userData"-Pointer to the user-provided readMemoryFunction +// This has to be done due to a problem with the "hProcess"-parameter in x64... +// Because this class is in no case multi-threading-enabled (because of the limitations +// of dbghelp.dll) it is "safe" to use a static-variable +static StackWalker::PReadProcessMemoryRoutine s_readMemoryFunction = NULL; +static LPVOID s_readMemoryFunction_UserData = NULL; + +BOOL StackWalker::ShowCallstack(HANDLE hThread, + const CONTEXT* context, + PReadProcessMemoryRoutine readMemoryFunction, + LPVOID pUserData) +{ + CONTEXT c; + CallstackEntry csEntry; + IMAGEHLP_SYMBOL64* pSym = NULL; + StackWalkerInternal::IMAGEHLP_MODULE64_V3 Module; + IMAGEHLP_LINE64 Line; + int frameNum; + bool bLastEntryCalled = true; + int curRecursionCount = 0; + + if (m_modulesLoaded == FALSE) + this->LoadModules(); // ignore the result... + + if (this->m_sw->m_hDbhHelp == NULL) + { + SetLastError(ERROR_DLL_INIT_FAILED); + return FALSE; + } + + s_readMemoryFunction = readMemoryFunction; + s_readMemoryFunction_UserData = pUserData; + + if (context == NULL) + { + // If no context is provided, capture the context + // See: https://stackwalker.codeplex.com/discussions/446958 +#if _WIN32_WINNT <= 0x0501 + // If we need to support XP, we need to use the "old way", because "GetThreadId" is not available! + if (hThread == GetCurrentThread()) +#else + if (GetThreadId(hThread) == GetCurrentThreadId()) +#endif + { + if (m_sw->m_ctx.ContextFlags != 0) + c = m_sw->m_ctx; // context taken at Init + else + GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, USED_CONTEXT_FLAGS); + } + else + { + SuspendThread(hThread); + memset(&c, 0, sizeof(CONTEXT)); + c.ContextFlags = USED_CONTEXT_FLAGS; + + // TODO: Detect if you want to get a thread context of a different process, which is running a different processor architecture... + // This does only work if we are x64 and the target process is x64 or x86; + // It cannot work, if this process is x64 and the target process is x64... this is not supported... + // See also: http://www.howzatt.demon.co.uk/articles/DebuggingInWin64.html + if (GetThreadContext(hThread, &c) == FALSE) + { + ResumeThread(hThread); + return FALSE; + } + } + } + else + c = *context; + + // init STACKFRAME for first call + STACKFRAME64 s; // in/out stackframe + memset(&s, 0, sizeof(s)); + DWORD imageType; +#ifdef _M_IX86 + // normally, call ImageNtHeader() and use machine info from PE header + imageType = IMAGE_FILE_MACHINE_I386; + s.AddrPC.Offset = c.Eip; + s.AddrPC.Mode = AddrModeFlat; + s.AddrFrame.Offset = c.Ebp; + s.AddrFrame.Mode = AddrModeFlat; + s.AddrStack.Offset = c.Esp; + s.AddrStack.Mode = AddrModeFlat; +#elif _M_X64 + imageType = IMAGE_FILE_MACHINE_AMD64; + s.AddrPC.Offset = c.Rip; + s.AddrPC.Mode = AddrModeFlat; + s.AddrFrame.Offset = c.Rsp; + s.AddrFrame.Mode = AddrModeFlat; + s.AddrStack.Offset = c.Rsp; + s.AddrStack.Mode = AddrModeFlat; +#elif _M_IA64 + imageType = IMAGE_FILE_MACHINE_IA64; + s.AddrPC.Offset = c.StIIP; + s.AddrPC.Mode = AddrModeFlat; + s.AddrFrame.Offset = c.IntSp; + s.AddrFrame.Mode = AddrModeFlat; + s.AddrBStore.Offset = c.RsBSP; + s.AddrBStore.Mode = AddrModeFlat; + s.AddrStack.Offset = c.IntSp; + s.AddrStack.Mode = AddrModeFlat; +#elif _M_ARM64 + imageType = IMAGE_FILE_MACHINE_ARM64; + s.AddrPC.Offset = c.Pc; + s.AddrPC.Mode = AddrModeFlat; + s.AddrFrame.Offset = c.Fp; + s.AddrFrame.Mode = AddrModeFlat; + s.AddrStack.Offset = c.Sp; + s.AddrStack.Mode = AddrModeFlat; +#else +#error "Platform not supported!" +#endif + + pSym = (IMAGEHLP_SYMBOL64*)malloc(sizeof(IMAGEHLP_SYMBOL64) + STACKWALK_MAX_NAMELEN); + if (!pSym) + goto cleanup; // not enough memory... + memset(pSym, 0, sizeof(IMAGEHLP_SYMBOL64) + STACKWALK_MAX_NAMELEN); + pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); + pSym->MaxNameLength = STACKWALK_MAX_NAMELEN; + + memset(&Line, 0, sizeof(Line)); + Line.SizeOfStruct = sizeof(Line); + + memset(&Module, 0, sizeof(Module)); + Module.SizeOfStruct = sizeof(Module); + + for (frameNum = 0;; ++frameNum) + { + // get next stack frame (StackWalk64(), SymFunctionTableAccess64(), SymGetModuleBase64()) + // if this returns ERROR_INVALID_ADDRESS (487) or ERROR_NOACCESS (998), you can + // assume that either you are done, or that the stack is so hosed that the next + // deeper frame could not be found. + // CONTEXT need not to be supplied if imageTyp is IMAGE_FILE_MACHINE_I386! + if (!this->m_sw->pSW(imageType, this->m_hProcess, hThread, &s, &c, myReadProcMem, + this->m_sw->pSFTA, this->m_sw->pSGMB, NULL)) + { + // INFO: "StackWalk64" does not set "GetLastError"... + this->OnDbgHelpErr("StackWalk64", 0, s.AddrPC.Offset); + break; + } + + csEntry.offset = s.AddrPC.Offset; + csEntry.name[0] = 0; + csEntry.undName[0] = 0; + csEntry.undFullName[0] = 0; + csEntry.offsetFromSmybol = 0; + csEntry.offsetFromLine = 0; + csEntry.lineFileName[0] = 0; + csEntry.lineNumber = 0; + csEntry.loadedImageName[0] = 0; + csEntry.moduleName[0] = 0; + if (s.AddrPC.Offset == s.AddrReturn.Offset) + { + if ((this->m_MaxRecursionCount > 0) && (curRecursionCount > m_MaxRecursionCount)) + { + this->OnDbgHelpErr("StackWalk64-Endless-Callstack!", 0, s.AddrPC.Offset); + break; + } + curRecursionCount++; + } + else + curRecursionCount = 0; + if (s.AddrPC.Offset != 0) + { + // we seem to have a valid PC + // show procedure info (SymGetSymFromAddr64()) + if (this->m_sw->pSGSFA(this->m_hProcess, s.AddrPC.Offset, &(csEntry.offsetFromSmybol), + pSym) != FALSE) + { + MyStrCpy(csEntry.name, STACKWALK_MAX_NAMELEN, pSym->Name); + // UnDecorateSymbolName() + this->m_sw->pUDSN(pSym->Name, csEntry.undName, STACKWALK_MAX_NAMELEN, UNDNAME_NAME_ONLY); + this->m_sw->pUDSN(pSym->Name, csEntry.undFullName, STACKWALK_MAX_NAMELEN, UNDNAME_COMPLETE); + } + else + { + this->OnDbgHelpErr("SymGetSymFromAddr64", GetLastError(), s.AddrPC.Offset); + } + + // show line number info, NT5.0-method (SymGetLineFromAddr64()) + if (this->m_sw->pSGLFA != NULL) + { // yes, we have SymGetLineFromAddr64() + if (this->m_sw->pSGLFA(this->m_hProcess, s.AddrPC.Offset, &(csEntry.offsetFromLine), + &Line) != FALSE) + { + csEntry.lineNumber = Line.LineNumber; + MyStrCpy(csEntry.lineFileName, STACKWALK_MAX_NAMELEN, Line.FileName); + } + else + { + this->OnDbgHelpErr("SymGetLineFromAddr64", GetLastError(), s.AddrPC.Offset); + } + } // yes, we have SymGetLineFromAddr64() + + // show module info (SymGetModuleInfo64()) + if (this->m_sw->GetModuleInfo(this->m_hProcess, s.AddrPC.Offset, &Module) != FALSE) + { // got module info OK + switch (Module.SymType) + { + case SymNone: + csEntry.symTypeString = "-nosymbols-"; + break; + case SymCoff: + csEntry.symTypeString = "COFF"; + break; + case SymCv: + csEntry.symTypeString = "CV"; + break; + case SymPdb: + csEntry.symTypeString = "PDB"; + break; + case SymExport: + csEntry.symTypeString = "-exported-"; + break; + case SymDeferred: + csEntry.symTypeString = "-deferred-"; + break; + case SymSym: + csEntry.symTypeString = "SYM"; + break; +#if API_VERSION_NUMBER >= 9 + case SymDia: + csEntry.symTypeString = "DIA"; + break; +#endif + case 8: //SymVirtual: + csEntry.symTypeString = "Virtual"; + break; + default: + //_snprintf( ty, sizeof(ty), "symtype=%ld", (long) Module.SymType ); + csEntry.symTypeString = NULL; + break; + } + + MyStrCpy(csEntry.moduleName, STACKWALK_MAX_NAMELEN, Module.ModuleName); + csEntry.baseOfImage = Module.BaseOfImage; + MyStrCpy(csEntry.loadedImageName, STACKWALK_MAX_NAMELEN, Module.LoadedImageName); + } // got module info OK + else + { + this->OnDbgHelpErr("SymGetModuleInfo64", GetLastError(), s.AddrPC.Offset); + } + } // we seem to have a valid PC + + CallstackEntryType et = nextEntry; + if (frameNum == 0) + et = firstEntry; + bLastEntryCalled = false; + this->OnCallstackEntry(et, csEntry); + + if (s.AddrReturn.Offset == 0) + { + bLastEntryCalled = true; + this->OnCallstackEntry(lastEntry, csEntry); + SetLastError(ERROR_SUCCESS); + break; + } + } // for ( frameNum ) + +cleanup: + if (pSym) + free(pSym); + + if (bLastEntryCalled == false) + this->OnCallstackEntry(lastEntry, csEntry); + + if (context == NULL) + ResumeThread(hThread); + + return TRUE; +} + +BOOL StackWalker::ShowObject(LPVOID pObject) +{ + // Load modules if not done yet + if (m_modulesLoaded == FALSE) + this->LoadModules(); // ignore the result... + + // Verify that the DebugHelp.dll was actually found + if (this->m_sw->m_hDbhHelp == NULL) + { + SetLastError(ERROR_DLL_INIT_FAILED); + return FALSE; + } + + // SymGetSymFromAddr64() is required + if (this->m_sw->pSGSFA == NULL) + return FALSE; + + // Show object info (SymGetSymFromAddr64()) + DWORD64 dwAddress = DWORD64(pObject); + DWORD64 dwDisplacement = 0; + const SIZE_T symSize = sizeof(IMAGEHLP_SYMBOL64) + STACKWALK_MAX_NAMELEN; + IMAGEHLP_SYMBOL64* pSym = (IMAGEHLP_SYMBOL64*) malloc(symSize); + if (!pSym) + return FALSE; + memset(pSym, 0, symSize); + pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); + pSym->MaxNameLength = STACKWALK_MAX_NAMELEN; + if (this->m_sw->pSGSFA(this->m_hProcess, dwAddress, &dwDisplacement, pSym) == FALSE) + { + this->OnDbgHelpErr("SymGetSymFromAddr64", GetLastError(), dwAddress); + return FALSE; + } + // Object name output + this->OnOutput(pSym->Name); + + free(pSym); + return TRUE; +}; + +BOOL __stdcall StackWalker::myReadProcMem(HANDLE hProcess, + DWORD64 qwBaseAddress, + PVOID lpBuffer, + DWORD nSize, + LPDWORD lpNumberOfBytesRead) +{ + if (s_readMemoryFunction == NULL) + { + SIZE_T st; + BOOL bRet = ReadProcessMemory(hProcess, (LPVOID)qwBaseAddress, lpBuffer, nSize, &st); + *lpNumberOfBytesRead = (DWORD)st; + //printf("ReadMemory: hProcess: %p, baseAddr: %p, buffer: %p, size: %d, read: %d, result: %d\n", hProcess, (LPVOID) qwBaseAddress, lpBuffer, nSize, (DWORD) st, (DWORD) bRet); + return bRet; + } + else + { + return s_readMemoryFunction(hProcess, qwBaseAddress, lpBuffer, nSize, lpNumberOfBytesRead, + s_readMemoryFunction_UserData); + } +} + +void StackWalker::OnLoadModule(LPCSTR img, + LPCSTR mod, + DWORD64 baseAddr, + DWORD size, + DWORD result, + LPCSTR symType, + LPCSTR pdbName, + ULONGLONG fileVersion) +{ + CHAR buffer[STACKWALK_MAX_NAMELEN]; + size_t maxLen = STACKWALK_MAX_NAMELEN; +#if _MSC_VER >= 1400 + maxLen = _TRUNCATE; +#endif + if (fileVersion == 0) + _snprintf_s(buffer, maxLen, "%s:%s (%p), size: %d (result: %d), SymType: '%s', PDB: '%s'\n", + img, mod, (LPVOID)baseAddr, size, result, symType, pdbName); + else + { + DWORD v4 = (DWORD)(fileVersion & 0xFFFF); + DWORD v3 = (DWORD)((fileVersion >> 16) & 0xFFFF); + DWORD v2 = (DWORD)((fileVersion >> 32) & 0xFFFF); + DWORD v1 = (DWORD)((fileVersion >> 48) & 0xFFFF); + _snprintf_s( + buffer, maxLen, + "%s:%s (%p), size: %d (result: %d), SymType: '%s', PDB: '%s', fileVersion: %d.%d.%d.%d\n", + img, mod, (LPVOID)baseAddr, size, result, symType, pdbName, v1, v2, v3, v4); + } + buffer[STACKWALK_MAX_NAMELEN - 1] = 0; // be sure it is NULL terminated + OnOutput(buffer); +} + +void StackWalker::OnCallstackEntry(CallstackEntryType eType, CallstackEntry& entry) +{ + CHAR buffer[STACKWALK_MAX_NAMELEN]; + size_t maxLen = STACKWALK_MAX_NAMELEN; +#if _MSC_VER >= 1400 + maxLen = _TRUNCATE; +#endif + if ((eType != lastEntry) && (entry.offset != 0)) + { + if (entry.name[0] == 0) + MyStrCpy(entry.name, STACKWALK_MAX_NAMELEN, "(function-name not available)"); + if (entry.undName[0] != 0) + MyStrCpy(entry.name, STACKWALK_MAX_NAMELEN, entry.undName); + if (entry.undFullName[0] != 0) + MyStrCpy(entry.name, STACKWALK_MAX_NAMELEN, entry.undFullName); + if (entry.lineFileName[0] == 0) + { + MyStrCpy(entry.lineFileName, STACKWALK_MAX_NAMELEN, "(filename not available)"); + if (entry.moduleName[0] == 0) + MyStrCpy(entry.moduleName, STACKWALK_MAX_NAMELEN, "(module-name not available)"); + _snprintf_s(buffer, maxLen, "%p (%s): %s: %s\n", (LPVOID)entry.offset, entry.moduleName, + entry.lineFileName, entry.name); + } + else + _snprintf_s(buffer, maxLen, "%s (%d): %s\n", entry.lineFileName, entry.lineNumber, + entry.name); + buffer[STACKWALK_MAX_NAMELEN - 1] = 0; + OnOutput(buffer); + } +} + +void StackWalker::OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr) +{ + CHAR buffer[STACKWALK_MAX_NAMELEN]; + size_t maxLen = STACKWALK_MAX_NAMELEN; +#if _MSC_VER >= 1400 + maxLen = _TRUNCATE; +#endif + _snprintf_s(buffer, maxLen, "ERROR: %s, GetLastError: %d (Address: %p)\n", szFuncName, gle, + (LPVOID)addr); + buffer[STACKWALK_MAX_NAMELEN - 1] = 0; + OnOutput(buffer); +} + +void StackWalker::OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName) +{ + CHAR buffer[STACKWALK_MAX_NAMELEN]; + size_t maxLen = STACKWALK_MAX_NAMELEN; +#if _MSC_VER >= 1400 + maxLen = _TRUNCATE; +#endif + _snprintf_s(buffer, maxLen, "SymInit: Symbol-SearchPath: '%s', symOptions: %d, UserName: '%s'\n", + szSearchPath, symOptions, szUserName); + buffer[STACKWALK_MAX_NAMELEN - 1] = 0; + OnOutput(buffer); + // Also display the OS-version +#if _MSC_VER <= 1200 + OSVERSIONINFOA ver; + ZeroMemory(&ver, sizeof(OSVERSIONINFOA)); + ver.dwOSVersionInfoSize = sizeof(ver); + if (GetVersionExA(&ver) != FALSE) + { + _snprintf_s(buffer, maxLen, "OS-Version: %d.%d.%d (%s)\n", ver.dwMajorVersion, + ver.dwMinorVersion, ver.dwBuildNumber, ver.szCSDVersion); + buffer[STACKWALK_MAX_NAMELEN - 1] = 0; + OnOutput(buffer); + } +#else + OSVERSIONINFOEXA ver; + ZeroMemory(&ver, sizeof(OSVERSIONINFOEXA)); + ver.dwOSVersionInfoSize = sizeof(ver); +#if _MSC_VER >= 1900 +#pragma warning(push) +#pragma warning(disable : 4996) +#endif + if (GetVersionExA((OSVERSIONINFOA*)&ver) != FALSE) + { + _snprintf_s(buffer, maxLen, "OS-Version: %d.%d.%d (%s) 0x%x-0x%x\n", ver.dwMajorVersion, + ver.dwMinorVersion, ver.dwBuildNumber, ver.szCSDVersion, ver.wSuiteMask, + ver.wProductType); + buffer[STACKWALK_MAX_NAMELEN - 1] = 0; + OnOutput(buffer); + } +#if _MSC_VER >= 1900 +#pragma warning(pop) +#endif +#endif +} + +void StackWalker::OnOutput(LPCSTR buffer) +{ + OutputDebugStringA(buffer); +} + +#endif diff --git a/core/shared/src/debug/StackWalker/StackWalker.h b/core/shared/src/debug/StackWalker/StackWalker.h new file mode 100644 index 000000000..5ab241ec1 --- /dev/null +++ b/core/shared/src/debug/StackWalker/StackWalker.h @@ -0,0 +1,273 @@ +#ifndef __STACKWALKER_H__ +#define __STACKWALKER_H__ + +#if defined(_MSC_VER) + +/********************************************************************** + * + * StackWalker.h + * + * + * + * LICENSE (http://www.opensource.org/licenses/bsd-license.php) + * + * Copyright (c) 2005-2009, Jochen Kalmbach + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of Jochen Kalmbach nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * **********************************************************************/ +// #pragma once is supported starting with _MSC_VER 1000, +// so we need not to check the version (because we only support _MSC_VER >= 1100)! +#pragma once + +#include + +// special defines for VC5/6 (if no actual PSDK is installed): +#if _MSC_VER < 1300 +typedef unsigned __int64 DWORD64, *PDWORD64; +#if defined(_WIN64) +typedef unsigned __int64 SIZE_T, *PSIZE_T; +#else +typedef unsigned long SIZE_T, *PSIZE_T; +#endif +#endif // _MSC_VER < 1300 + +class StackWalkerInternal; // forward +class StackWalker +{ +public: + typedef enum ExceptType + { + NonExcept = 0, // RtlCaptureContext + AfterExcept = 1, + AfterCatch = 2, // get_current_exception_context + } ExceptType; + + typedef enum StackWalkOptions + { + // No addition info will be retrieved + // (only the address is available) + RetrieveNone = 0, + + // Try to get the symbol-name + RetrieveSymbol = 1, + + // Try to get the line for this symbol + RetrieveLine = 2, + + // Try to retrieve the module-infos + RetrieveModuleInfo = 4, + + // Also retrieve the version for the DLL/EXE + RetrieveFileVersion = 8, + + // Contains all the above + RetrieveVerbose = 0xF, + + // Generate a "good" symbol-search-path + SymBuildPath = 0x10, + + // Also use the public Microsoft-Symbol-Server + SymUseSymSrv = 0x20, + + // Contains all the above "Sym"-options + SymAll = 0x30, + + // Contains all options (default) + OptionsAll = 0x3F + } StackWalkOptions; + + StackWalker(ExceptType extype, int options = OptionsAll, PEXCEPTION_POINTERS exp = NULL); + + StackWalker(int options = OptionsAll, // 'int' is by design, to combine the enum-flags + LPCSTR szSymPath = NULL, + DWORD dwProcessId = GetCurrentProcessId(), + HANDLE hProcess = GetCurrentProcess()); + + StackWalker(DWORD dwProcessId, HANDLE hProcess); + + virtual ~StackWalker(); + + bool SetSymPath(LPCSTR szSymPath); + + bool SetTargetProcess(DWORD dwProcessId, HANDLE hProcess); + + PCONTEXT GetCurrentExceptionContext(); + +private: + bool Init(ExceptType extype, int options, LPCSTR szSymPath, DWORD dwProcessId, + HANDLE hProcess, PEXCEPTION_POINTERS exp = NULL); + +public: + typedef BOOL(__stdcall* PReadProcessMemoryRoutine)( + HANDLE hProcess, + DWORD64 qwBaseAddress, + PVOID lpBuffer, + DWORD nSize, + LPDWORD lpNumberOfBytesRead, + LPVOID pUserData // optional data, which was passed in "ShowCallstack" + ); + + BOOL LoadModules(); + + BOOL ShowCallstack( + HANDLE hThread = GetCurrentThread(), + const CONTEXT* context = NULL, + PReadProcessMemoryRoutine readMemoryFunction = NULL, + LPVOID pUserData = NULL // optional to identify some data in the 'readMemoryFunction'-callback + ); + + BOOL ShowObject(LPVOID pObject); + +#if _MSC_VER >= 1300 + // due to some reasons, the "STACKWALK_MAX_NAMELEN" must be declared as "public" + // in older compilers in order to use it... starting with VC7 we can declare it as "protected" +protected: +#endif + enum + { + STACKWALK_MAX_NAMELEN = 1024 + }; // max name length for found symbols + +protected: + // Entry for each Callstack-Entry + typedef struct CallstackEntry + { + DWORD64 offset; // if 0, we have no valid entry + CHAR name[STACKWALK_MAX_NAMELEN]; + CHAR undName[STACKWALK_MAX_NAMELEN]; + CHAR undFullName[STACKWALK_MAX_NAMELEN]; + DWORD64 offsetFromSmybol; + DWORD offsetFromLine; + DWORD lineNumber; + CHAR lineFileName[STACKWALK_MAX_NAMELEN]; + DWORD symType; + LPCSTR symTypeString; + CHAR moduleName[STACKWALK_MAX_NAMELEN]; + DWORD64 baseOfImage; + CHAR loadedImageName[STACKWALK_MAX_NAMELEN]; + } CallstackEntry; + + typedef enum CallstackEntryType + { + firstEntry, + nextEntry, + lastEntry + } CallstackEntryType; + + virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName); + virtual void OnLoadModule(LPCSTR img, + LPCSTR mod, + DWORD64 baseAddr, + DWORD size, + DWORD result, + LPCSTR symType, + LPCSTR pdbName, + ULONGLONG fileVersion); + virtual void OnCallstackEntry(CallstackEntryType eType, CallstackEntry& entry); + virtual void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr); + virtual void OnOutput(LPCSTR szText); + + StackWalkerInternal* m_sw; + HANDLE m_hProcess; + DWORD m_dwProcessId; + BOOL m_modulesLoaded; + LPSTR m_szSymPath; + + int m_options; + int m_MaxRecursionCount; + + static BOOL __stdcall myReadProcMem(HANDLE hProcess, + DWORD64 qwBaseAddress, + PVOID lpBuffer, + DWORD nSize, + LPDWORD lpNumberOfBytesRead); + + friend StackWalkerInternal; +}; // class StackWalker + +// The "ugly" assembler-implementation is needed for systems before XP +// If you have a new PSDK and you only compile for XP and later, then you can use +// the "RtlCaptureContext" +// Currently there is no define which determines the PSDK-Version... +// So we just use the compiler-version (and assumes that the PSDK is +// the one which was installed by the VS-IDE) + +// INFO: If you want, you can use the RtlCaptureContext if you only target XP and later... +// But I currently use it in x64/IA64 environments... +//#if defined(_M_IX86) && (_WIN32_WINNT <= 0x0500) && (_MSC_VER < 1400) + +#if defined(_M_IX86) +#ifdef CURRENT_THREAD_VIA_EXCEPTION +// TODO: The following is not a "good" implementation, +// because the callstack is only valid in the "__except" block... +#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \ + do \ + { \ + memset(&c, 0, sizeof(CONTEXT)); \ + EXCEPTION_POINTERS* pExp = NULL; \ + __try \ + { \ + throw 0; \ + } \ + __except (((pExp = GetExceptionInformation()) ? EXCEPTION_EXECUTE_HANDLER \ + : EXCEPTION_EXECUTE_HANDLER)) \ + { \ + } \ + if (pExp != NULL) \ + memcpy(&c, pExp->ContextRecord, sizeof(CONTEXT)); \ + c.ContextFlags = contextFlags; \ + } while (0); +#else +// clang-format off +// The following should be enough for walking the callstack... +#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \ + do \ + { \ + memset(&c, 0, sizeof(CONTEXT)); \ + c.ContextFlags = contextFlags; \ + __asm call x \ + __asm x: pop eax \ + __asm mov c.Eip, eax \ + __asm mov c.Ebp, ebp \ + __asm mov c.Esp, esp \ + } while (0) +// clang-format on +#endif + +#else + +// The following is defined for x86 (XP and higher), x64 and IA64: +#define GET_CURRENT_CONTEXT_STACKWALKER_CODEPLEX(c, contextFlags) \ + do \ + { \ + memset(&c, 0, sizeof(CONTEXT)); \ + c.ContextFlags = contextFlags; \ + RtlCaptureContext(&c); \ + } while (0); +#endif + +#endif //defined(_MSC_VER) + +#endif // __STACKWALKER_H__ diff --git a/core/shared/src/debug/debug_lua_zerobrane.cpp b/core/shared/src/debug/debug_lua_zerobrane.cpp deleted file mode 100644 index 77d2614b8..000000000 --- a/core/shared/src/debug/debug_lua_zerobrane.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright (c) 2021 Silverlan - */ - -#include "stdafx_shared.h" -#include "pragma/debug/debug_lua_zerobrane.hpp" -#include -#include -// #include -// #include - -void debug::open_file_in_zerobrane(const std::string &fileName, uint32_t lineIdx) -{ - std::string zeroBranePath = "C:/Program Files (x86)/ZeroBraneStudio/zbstudio.exe"; // TODO: Find program path from registry? - util::start_process(zeroBranePath.c_str(), std::vector {fileName + ':' + std::to_string(lineIdx)}, true); - - /* - // Check if process is already running - PROCESSENTRY32 entry; - entry.dwSize = sizeof(PROCESSENTRY32); - - HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); - - if (Process32First(snapshot, &entry) == TRUE) - { - while (Process32Next(snapshot, &entry) == TRUE) - { - if (stricmp(entry.szExeFile, zeroBranePath.c_str()) == 0) - { - HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID); - - if(hProcess) - { - util::start_process(zeroBranePath.c_str(),std::vector{ - fileName +':' +std::to_string(lineIdx) - },true); - // ... - //GetExitCodeProcess(hProcess,exitCode); - - } - - CloseHandle(hProcess); - } - } - } - - CloseHandle(snapshot);*/ -} diff --git a/core/shared/src/debug/debug_utils.cpp b/core/shared/src/debug/debug_utils.cpp new file mode 100644 index 000000000..a56219e18 --- /dev/null +++ b/core/shared/src/debug/debug_utils.cpp @@ -0,0 +1,201 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2021 Silverlan + */ + +#include "stdafx_shared.h" +#include "pragma/debug/debug_utils.hpp" +#include +#include +// #include +// #include +#ifdef __linux__ +#include "pragma/localization.h" +#endif + +void pragma::debug::open_file_in_zerobrane(const std::string &fileName, uint32_t lineIdx) +{ + std::string zeroBranePath = "C:/Program Files (x86)/ZeroBraneStudio/zbstudio.exe"; // TODO: Find program path from registry? + util::start_process(zeroBranePath.c_str(), std::vector {fileName + ':' + std::to_string(lineIdx)}, true); + + /* + // Check if process is already running + PROCESSENTRY32 entry; + entry.dwSize = sizeof(PROCESSENTRY32); + + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); + + if (Process32First(snapshot, &entry) == TRUE) + { + while (Process32Next(snapshot, &entry) == TRUE) + { + if (stricmp(entry.szExeFile, zeroBranePath.c_str()) == 0) + { + HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID); + + if(hProcess) + { + util::start_process(zeroBranePath.c_str(),std::vector{ + fileName +':' +std::to_string(lineIdx) + },true); + // ... + //GetExitCodeProcess(hProcess,exitCode); + + } + + CloseHandle(hProcess); + } + } + } + + CloseHandle(snapshot);*/ +} + +std::optional pragma::debug::show_message_prompt(const std::string &msg, MessageBoxButtons bts, std::optional title) +{ + if(!title) + title = util::get_program_name(); +#ifdef _WIN32 + uint32_t winBt = 0; + switch(bts) { + case MessageBoxButtons::Ok: + winBt = MB_OK; + break; + case MessageBoxButtons::OkCancel: + winBt = MB_OKCANCEL; + break; + case MessageBoxButtons::AbortRetryIgnore: + winBt = MB_ABORTRETRYIGNORE; + break; + case MessageBoxButtons::YesNoCancel: + winBt = MB_YESNOCANCEL; + break; + case MessageBoxButtons::YesNo: + winBt = MB_YESNO; + break; + case MessageBoxButtons::RetryCancel: + winBt = MB_RETRYCANCEL; + break; + case MessageBoxButtons::CancelTryAgainContinue: + winBt = MB_CANCELTRYCONTINUE; + break; + } + + auto wmsg = ustring::string_to_wstring(msg); + auto wtitle = ustring::string_to_wstring(*title); + auto res = ::MessageBoxW(NULL, wmsg.c_str(), wtitle.c_str(), winBt); + switch(res) { + case IDOK: + return MessageBoxButton::Ok; + case IDCANCEL: + return MessageBoxButton::Cancel; + case IDABORT: + return MessageBoxButton::Abort; + case IDRETRY: + return MessageBoxButton::Retry; + case IDIGNORE: + return MessageBoxButton::Ignore; + case IDYES: + return MessageBoxButton::Yes; + case IDNO: + return MessageBoxButton::No; + case IDTRYAGAIN: + return MessageBoxButton::TryAgain; + case IDCONTINUE: + return MessageBoxButton::Continue; + default: + return {}; + } + return {}; +#else + std::vector buttons; + buttons.reserve(3); + switch(bts) { + case MessageBoxButtons::Ok: + buttons.push_back(MessageBoxButton::Ok); + break; + case MessageBoxButtons::OkCancel: + buttons.push_back(MessageBoxButton::Ok); + buttons.push_back(MessageBoxButton::Cancel); + break; + case MessageBoxButtons::AbortRetryIgnore: + buttons.push_back(MessageBoxButton::Abort); + buttons.push_back(MessageBoxButton::Retry); + buttons.push_back(MessageBoxButton::Ignore); + break; + case MessageBoxButtons::YesNoCancel: + buttons.push_back(MessageBoxButton::Yes); + buttons.push_back(MessageBoxButton::No); + buttons.push_back(MessageBoxButton::Cancel); + break; + case MessageBoxButtons::YesNo: + buttons.push_back(MessageBoxButton::Yes); + buttons.push_back(MessageBoxButton::No); + break; + case MessageBoxButtons::RetryCancel: + buttons.push_back(MessageBoxButton::Retry); + buttons.push_back(MessageBoxButton::Cancel); + break; + case MessageBoxButtons::CancelTryAgainContinue: + buttons.push_back(MessageBoxButton::Cancel); + buttons.push_back(MessageBoxButton::TryAgain); + buttons.push_back(MessageBoxButton::Continue); + break; + } + + if(buttons.empty()) + return {}; + std::stringstream cmd; + cmd<<"zenity "; + if(buttons.size() == 1) + cmd<<"--info "; + else + cmd<<"--question "; + cmd<<"--title='" +*title +"' "; + cmd<<"--text='" +msg +"' "; + + auto getButtonText = [](MessageBoxButton button) -> std::string { + auto identifier = ustring::to_snake_case(std::string{magic_enum::enum_name(button)}); + auto text = Locale::GetText("prompt_button_" +identifier); + return text; + }; + cmd<<"--ok-label='"< 1) { + cmd<<"--cancel-label='"< 2) { + cmd<<"--extra-button='"<= buttons.size()) + return {}; + return buttons[result]; +#endif +} + +#ifdef _WIN32 +#include "debug/StackWalker/StackWalker.h" +class StackWalkerModuleFinder : public StackWalker { + public: + static bool find_module_in_callstack(PEXCEPTION_POINTERS exp, const std::string &moduleName) + { + StackWalkerModuleFinder finder {moduleName, exp}; + finder.ShowCallstack(); + return finder.m_found; + } + protected: + StackWalkerModuleFinder(const std::string &moduleName, PEXCEPTION_POINTERS exp = NULL) : StackWalker {StackWalker::ExceptType::AfterCatch, OptionsAll, exp}, m_moduleName {moduleName} {} + virtual void OnOutput(LPCSTR szText) {} + virtual void OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry) override + { + if(ustring::find(std::string {entry.moduleName}, m_moduleName, false)) + m_found = true; + } + std::string m_moduleName; + bool m_found = false; +}; +bool pragma::debug::is_module_in_callstack(struct _EXCEPTION_POINTERS *exp, const std::string &moduleName) { return StackWalkerModuleFinder ::find_module_in_callstack(exp, moduleName); } +#endif diff --git a/core/shared/src/debug/mdump.cpp b/core/shared/src/debug/mdump.cpp index d883259aa..f9a522555 100644 --- a/core/shared/src/debug/mdump.cpp +++ b/core/shared/src/debug/mdump.cpp @@ -6,36 +6,52 @@ */ #include "stdafx_shared.h" -#ifdef _WIN32 #include "pragma/debug/mdump.h" -#include +#include "pragma/debug/debug_utils.hpp" #include #include #include #include #include +#include #include "pragma/engine_info.hpp" #include "pragma/logging.hpp" +#include "pragma/localization.h" +#ifdef _WIN32 +#include +#else +#include +#include +#endif import util_zip; extern DLLNETWORK Engine *engine; -LPCSTR MiniDumper::m_szAppName; +using namespace pragma::debug; std::string g_crashExceptionMessage = {}; -MiniDumper::MiniDumper(LPCSTR szAppName) +static CrashHandler *g_crashHandler = nullptr; +CrashHandler::CrashHandler(const std::string &appName) : m_appName {appName} { - // if this assert fires then you have two instances of MiniDumper - // which is not allowed - assert(m_szAppName == NULL); - - m_szAppName = szAppName ? strdup(szAppName) : "Application"; - + assert(!g_crashHandler); + g_crashHandler = this; +#ifdef _WIN32 ::SetUnhandledExceptionFilter(TopLevelFilter); +#else + signal(SIGSEGV, +[](int sig) { + if(!g_crashHandler) { + exit(1); + return; + } + g_crashHandler->m_sig = sig; + g_crashHandler->GenerateCrashDump(); + exit(1); + }); +#endif // Note: set_terminate handler is called before SetUnhandledExceptionFilter. // set_terminate allows us to retrieve the underlying message from the exception (if there was one) - set_terminate([]() { + std::set_terminate([]() { auto eptr = std::current_exception(); if(!eptr) { g_crashExceptionMessage = {}; @@ -55,9 +71,15 @@ MiniDumper::MiniDumper(LPCSTR szAppName) }); } -LONG MiniDumper::TopLevelFilter(struct _EXCEPTION_POINTERS *pExceptionInfo) +CrashHandler::~CrashHandler() { g_crashHandler = nullptr; } + +#ifdef _WIN32 +std::optional CrashHandler::GenerateMiniDump(std::string &outErr) const { - // MessageBox(0,s_exceptionMessage.c_str(),"Exception",MB_OK); + if(!m_pExceptionInfo) { + outErr = "No exception info!"; + return {}; + } LONG retval = EXCEPTION_CONTINUE_SEARCH; HWND hParent = NULL; // find a better value for your app @@ -80,102 +102,169 @@ LONG MiniDumper::TopLevelFilter(struct _EXCEPTION_POINTERS *pExceptionInfo) hDll = ::LoadLibrary("DBGHELP.DLL"); } + if(!hDll) { + outErr = "DBGHELP.DLL not found"; + return {}; + } + + auto programPath = util::get_program_path(); + auto szDumpPath = programPath + "/crashdumps/"; + szDumpPath += m_appName + std::string(".dmp"); + + MINIDUMPWRITEDUMP pDump = (MINIDUMPWRITEDUMP)::GetProcAddress(hDll, "MiniDumpWriteDump"); + if(!pDump) { + outErr = "DBGHELP.DLL too old"; + return {}; + } + + // create the file + HANDLE hFile = ::CreateFileA(szDumpPath.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + if(hFile == INVALID_HANDLE_VALUE) { + outErr = "Failed to open file '" + szDumpPath + "' for writing!"; + return {}; + } + _MINIDUMP_EXCEPTION_INFORMATION ExInfo; + + ExInfo.ThreadId = ::GetCurrentThreadId(); + ExInfo.ExceptionPointers = m_pExceptionInfo; + ExInfo.ClientPointers = NULL; + + // write the dump + //MiniDumpWithFullMemory + BOOL bOK = pDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &ExInfo, NULL, NULL); + ::CloseHandle(hFile); + if(bOK) + return szDumpPath; + outErr = "Failed to write minidump!"; + return {}; +} +#endif + +bool CrashHandler::GenerateCrashDump() const +{ spdlog::info("Generating crashdump..."); pragma::flush_loggers(); - LPCTSTR szResult = NULL; - - auto shouldShowMsBox = (util::get_subsystem() == util::SubSystem::GUI && !engine->IsNonInteractiveMode()); - if(hDll) { - MINIDUMPWRITEDUMP pDump = (MINIDUMPWRITEDUMP)::GetProcAddress(hDll, "MiniDumpWriteDump"); - if(pDump) { - FileManager::CreateDirectory("crashdumps"); - auto programPath = util::get_program_path(); - auto szDumpPath = programPath + "/crashdumps/"; - szDumpPath += std::string(m_szAppName) + std::string(".dmp"); - char szScratch[_MAX_PATH]; - - // ask the user if they want to save a dump file - if(!shouldShowMsBox - || (::MessageBox(NULL, - "A terminal error has occurred in the program. Would you like to save a diagnostic file? This file contains information about your system and the state of the game at the time of the crash and can be utilized by a developer to fix the underlying problem.", m_szAppName, - MB_YESNO) - == IDYES)) { - // create the file - HANDLE hFile = ::CreateFileA(szDumpPath.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - - if(hFile != INVALID_HANDLE_VALUE) { - _MINIDUMP_EXCEPTION_INFORMATION ExInfo; - - ExInfo.ThreadId = ::GetCurrentThreadId(); - ExInfo.ExceptionPointers = pExceptionInfo; - ExInfo.ClientPointers = NULL; - - // write the dump - //MiniDumpWithFullMemory - BOOL bOK = pDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &ExInfo, NULL, NULL); - ::CloseHandle(hFile); - if(bOK) { - std::string err; - std::string zipFileName; - pragma::detail::close_logger(); - auto zipFile = Engine::GenerateEngineDump("crashdumps/crashdump", zipFileName, err); - if(zipFile) { - // Write Minidump - VFilePtrReal f = nullptr; - auto t = util::Clock::now(); - while(f == nullptr) // Wait until dump has been written - { - auto tNow = util::Clock::now(); - auto tDelta = std::chrono::duration_cast(tNow - t).count(); - if(tDelta >= 4) // Don't wait more than 4 seconds - break; - std::this_thread::sleep_for(std::chrono::milliseconds(250)); - f = FileManager::OpenSystemFile(szDumpPath.c_str(), "rb"); - } - if(f != nullptr) { - auto size = f->GetSize(); - std::vector dumpData(size); - f->Read(dumpData.data(), size); - f = nullptr; - zipFile->AddFile(ufile::get_file_from_filename(szDumpPath), dumpData.data(), size); - std::remove(szDumpPath.c_str()); - } - zipFile = nullptr; - - sprintf(szScratch, "Saved dump file to '%s'. Please send it to a developer, along with a description of what you did to trigger the error.", zipFileName.c_str() /*,engine_info::get_author_mail_address().c_str()*/); - util::open_path_in_explorer(ufile::get_path_from_filename(zipFileName), ufile::get_file_from_filename(zipFileName)); - szResult = szScratch; - retval = EXCEPTION_EXECUTE_HANDLER; - } - else { - sprintf(szScratch, err.c_str()); - szResult = szScratch; - } - } - else { - sprintf(szScratch, "Failed to save dump file to '%s' (error %d)", szDumpPath.c_str(), GetLastError()); - szResult = szScratch; - } + std::string szResult; + Locale::Load("prompts.txt"); + + FileManager::CreateDirectory("crashdumps"); + + // ask the user if they want to save a dump file + auto saveDump = false; + auto shouldShowMsBox = !engine->IsNonInteractiveMode(); +#ifdef _WIN32 + shouldShowMsBox = (shouldShowMsBox && util::get_subsystem() == util::SubSystem::GUI); +#endif + if(!shouldShowMsBox) + saveDump = true; + else { + auto msg = Locale::GetText("prompt_crash"); + auto res = debug::show_message_prompt(msg, debug::MessageBoxButtons::YesNo, m_appName); + saveDump = (res == debug::MessageBoxButton::Yes); + } + +#ifdef __linux__ + void *array[10]; + size_t size; + char **symbols; + char buffer[1024]; + buffer[0] = '\0'; + + size = backtrace(array, 10); + symbols = backtrace_symbols(array, size); + + std::optional backtraceStr {}; + if (symbols != nullptr) { + snprintf(buffer, sizeof(buffer), "Error: signal %d:\n", m_sig); + + for (size_t i = 0; i < size; i++) { + strncat(buffer, symbols[i], sizeof(buffer) - strlen(buffer) - 1); + strncat(buffer, "\n", sizeof(buffer) - strlen(buffer) - 1); + } + + backtraceStr = buffer; + free(symbols); + } +#endif + + auto success = false; + if(saveDump) { + std::string err; + std::string zipFileName; + pragma::detail::close_logger(); + auto zipFile = Engine::GenerateEngineDump("crashdumps/crashdump", zipFileName, err); + if(zipFile) { +#ifdef _WIN32 + std::string dumpErr; + auto minidumpPath = GenerateMiniDump(dumpErr); + if(minidumpPath) { + // Write Minidump + VFilePtrReal f = nullptr; + auto t = util::Clock::now(); + while(f == nullptr) // Wait until dump has been written + { + auto tNow = util::Clock::now(); + auto tDelta = std::chrono::duration_cast(tNow - t).count(); + if(tDelta >= 4) // Don't wait more than 4 seconds + break; + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + f = FileManager::OpenSystemFile(minidumpPath->c_str(), "rb"); } - else { - sprintf(szScratch, "Failed to create dump file '%s' (error %d)", szDumpPath.c_str(), GetLastError()); - szResult = szScratch; + if(f != nullptr) { + auto size = f->GetSize(); + std::vector dumpData(size); + f->Read(dumpData.data(), size); + f = nullptr; + zipFile->AddFile(ufile::get_file_from_filename(*minidumpPath), dumpData.data(), size); + std::remove(minidumpPath->c_str()); } } + else + zipFile->AddFile("minidump_generation_error.txt", dumpErr); +#else + if (backtraceStr) + zipFile->AddFile("backtrace.txt", *backtraceStr); + else + zipFile->AddFile("backtrace_generation_error.txt", "Failed to generate backtrace symbols"); +#endif + zipFile = nullptr; + + szResult = Locale::GetText("prompt_crash_dump_saved", std::vector {zipFileName}); + auto absPath = util::Path::CreatePath(util::get_program_path()) + zipFileName; + util::open_path_in_explorer(std::string {absPath.GetPath()}, std::string {absPath.GetFileName()}); + + success = true; } - else { - szResult = "DBGHELP.DLL too old"; - } - } - else { - szResult = "DBGHELP.DLL not found"; + else + szResult = Locale::GetText("prompt_crash_dump_archive_failed", {err}); } - if(szResult && shouldShowMsBox) - ::MessageBox(NULL, szResult, m_szAppName, MB_OK); + if(!szResult.empty() && shouldShowMsBox) + debug::show_message_prompt(szResult, debug::MessageBoxButtons::Ok, m_appName); - return retval; + auto crashInProsperModule = false; +#ifdef _WIN32 + crashInProsperModule = pragma::debug::is_module_in_callstack(m_pExceptionInfo, "prosper"); +#else + crashInProsperModule = (backtraceStr && backtraceStr->find("libprosper") != std::string::npos); +#endif + if(crashInProsperModule) { + // Probably a rendering related crash. + engine->HandleOpenGLFallback(); + } + + return success; } +#ifdef _WIN32 +LONG CrashHandler::TopLevelFilter(struct _EXCEPTION_POINTERS *pExceptionInfo) +{ + if(!g_crashHandler) + return EXCEPTION_CONTINUE_SEARCH; + g_crashHandler->m_pExceptionInfo = pExceptionInfo; + g_crashHandler->GenerateCrashDump(); + return EXCEPTION_EXECUTE_HANDLER; +} #endif diff --git a/core/shared/src/engine.cpp b/core/shared/src/engine.cpp index 62498c867..74fa740d7 100644 --- a/core/shared/src/engine.cpp +++ b/core/shared/src/engine.cpp @@ -930,9 +930,7 @@ void Engine::Start() void Engine::UpdateTickCount() { m_ctTick.Update(); } -#ifdef _WIN32 extern std::string g_crashExceptionMessage; -#endif std::unique_ptr Engine::GenerateEngineDump(const std::string &baseName, std::string &outZipFileName, std::string &outErr) { auto programPath = util::Path::CreatePath(util::get_program_path()); @@ -944,11 +942,10 @@ std::unique_ptr Engine::GenerateEngineDump(const std::string &bas return nullptr; } -#ifdef _WIN32 // Write Exception if(g_crashExceptionMessage.empty() == false) zipFile->AddFile("exception.txt", g_crashExceptionMessage); - +#ifdef _WIN32 // Write Stack Backtrace zipFile->AddFile("stack_backtrace.txt", util::get_formatted_stack_backtrace_string()); #endif diff --git a/core/shared/src/lua/lua_error_handling.cpp b/core/shared/src/lua/lua_error_handling.cpp index c1e084429..c44ce59bc 100644 --- a/core/shared/src/lua/lua_error_handling.cpp +++ b/core/shared/src/lua/lua_error_handling.cpp @@ -14,7 +14,7 @@ #include "pragma/lua/lua_doc.hpp" #include "pragma/console/util_console_color.hpp" #include "pragma/console/conout.h" -#include +#include #include #include #include @@ -63,7 +63,7 @@ void Lua::OpenFileInZeroBrane(const std::string &fname, uint32_t lineId) tLastFileOpened = t; auto fullLocalPath = GetLuaFilePath(fname); if(fullLocalPath.has_value()) - debug::open_file_in_zerobrane(*fullLocalPath, lineId); + pragma::debug::open_file_in_zerobrane(*fullLocalPath, lineId); } } }