From 39a84c75eb0ebe889839ee8d511621a04da03089 Mon Sep 17 00:00:00 2001 From: ouned Date: Sat, 27 Jan 2018 12:53:06 +0100 Subject: [PATCH 01/14] add StackWalker class https://github.com/JochenKalmbach/StackWalker --- libs/StackWalker/LICENSE | 29 + libs/StackWalker/README.md | 332 ++++++ libs/StackWalker/StackWalker.cpp | 1365 ++++++++++++++++++++++++ libs/StackWalker/include/StackWalker.h | 218 ++++ src/CMakeLists.txt | 7 +- 5 files changed, 1950 insertions(+), 1 deletion(-) create mode 100644 libs/StackWalker/LICENSE create mode 100644 libs/StackWalker/README.md create mode 100644 libs/StackWalker/StackWalker.cpp create mode 100644 libs/StackWalker/include/StackWalker.h diff --git a/libs/StackWalker/LICENSE b/libs/StackWalker/LICENSE new file mode 100644 index 00000000..3496d677 --- /dev/null +++ b/libs/StackWalker/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2005 - 2017, 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 the copyright holder 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 HOLDER 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. diff --git a/libs/StackWalker/README.md b/libs/StackWalker/README.md new file mode 100644 index 00000000..56e5ef78 --- /dev/null +++ b/libs/StackWalker/README.md @@ -0,0 +1,332 @@ +# StackWalker - Walking the callstack + +This article describes the (documented) way to walk a callstack for any thread (own, other and remote). It has an abstraction layer, so the calling app does not need to know the internals. + +This project was initially published on Codeproject (http://www.codeproject.com/KB/threads/StackWalker.aspx). +But it is hard to maintain the article and the source on codeproject, +so I was pushed to publish the source code on an "easier to modify" platform. Therefor I have choosen "codeplex" ;( + +But time goes by, and codeplex went away ;) + +So I now migrated to GitHub ;) + +# Documentation + +## Introduction + +In some cases you need to display the callstack of the current thread or your are just interested in the callstack of other threads / processes. Therefore I wrote this project. + +The goal for this project was the following: + +* Simple interface to generate a callstack +* C++ based to allow overwrites of several methods +* Hiding the implementation details (API) from the class interface +* Support of x86, x64 and IA64 architecture +* Default output to debugger-output window (but can be customized) +* Support of user-provided read-memory-function +* Support of the widest range of development-IDEs (VC5-VC8) +* Most portable solution to walk the callstack + +## Background + +To walk the callstack there is a documented interface: [StackWalk64](http://msdn.microsoft.com/library/en-us/debug/base/stackwalk64.asp) +Starting with Win9x/W2K, this interface is in the *dbghelp.dll* library (on NT, it is in *imagehlp.dll*). +But the function name (`StackWalk64`) has changed starting with W2K (before it was called `StackWalk` (without the `64`))! +This project only supports the newer Xxx64-funtions. If you need to use it on older systems, you can download the [redistributable for NT/W9x](http://www.microsoft.com/downloads/release.asp?releaseid=30682). + +The latest *dbghelp.dll* can always be downloaded with the [Debugging Tools for Windows](http://www.microsoft.com/whdc/devtools/debugging/). +This also contains the *symsrv.dll* which enables the use of the public Microsoft symbols-server (can be used to retrieve debugging information for system-files; see below). + +## Using the code + +The usage of the class is very simple. For example if you want to display the callstack of the current thread, just instantiate a `StackWalk` object and call the `ShowCallstack` member: + +```c++ +#include +#include "StackWalker.h" + +void Func5() { StackWalker sw; sw.ShowCallstack(); } +void Func4() { Func5(); } +void Func3() { Func4(); } +void Func2() { Func3(); } +void Func1() { Func2(); } + +int main() +{ + Func1(); + return 0; +} +``` + +This produces the following output in the debugger-output window: + + [...] (output stripped) + d:\privat\Articles\stackwalker\stackwalker.cpp (736): StackWalker::ShowCallstack + d:\privat\Articles\stackwalker\main.cpp (4): Func5 + d:\privat\Articles\stackwalker\main.cpp (5): Func4 + d:\privat\Articles\stackwalker\main.cpp (6): Func3 + d:\privat\Articles\stackwalker\main.cpp (7): Func2 + d:\privat\Articles\stackwalker\main.cpp (8): Func1 + d:\privat\Articles\stackwalker\main.cpp (13): main + f:\vs70builds\3077\vc\crtbld\crt\src\crt0.c (259): mainCRTStartup + 77E614C7 (kernel32): (filename not available): _BaseProcessStart@4 + +You can now double-click on a line and the IDE will automatically jump to the desired file/line. + +### Providing own output-mechanism + +If you want to direct the output to a file or want to use some other output-mechanism, you simply need to derive from the `StackWalker` class. +You have two options to do this: only overwrite the `OnOutput` method or overwrite each `OnXxx`-function. +The first solution (`OnOutput`) is very easy and uses the default-implementation of the other `OnXxx`-functions (which should be enough for most of the cases). To output also to the console, you need to do the following: + +```c++ +class MyStackWalker : public StackWalker +{ +public: + MyStackWalker() : StackWalker() {} +protected: + virtual void OnOutput(LPCSTR szText) { + printf(szText); StackWalker::OnOutput(szText); + } +}; +``` + +### Retrieving detailed callstack info + +If you want detailed info about the callstack (like loaded-modules, addresses, errors, ...) you can overwrite the corresponding methods. The following methods are provided: + +```c++ +class StackWalker +{ +protected: + 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); +}; +``` + +These methods are called during the generation of the callstack. + +### Various kinds of callstacks + +In the constructor of the class, you need to specify if you want to generate callstacks for the current process or for another process. The following constructors are available: + +```c++ +{ +public: + StackWalker(int options = OptionsAll, + LPCSTR szSymPath = NULL, + DWORD dwProcessId = GetCurrentProcessId(), + HANDLE hProcess = GetCurrentProcess()); + // Just for other processes with + // default-values for options and symPath + StackWalker(DWORD dwProcessId, HANDLE hProcess); +}; +``` + +To do the actual stack-walking you need to call the following functions: + +```c++ +class StackWalker +{ +public: + BOOL ShowCallstack(HANDLE hThread = GetCurrentThread(), CONTEXT *context = NULL, + PReadProcessMemoryRoutine readMemoryFunction = NULL, LPVOID pUserData = NULL); +}; +``` + +### Displaying the callstack of an exception + +With this `StackWalker` you can also display the callstack inside an exception handler. You only need to write a filter-function which does the stack-walking: + +```c++ +// The exception filter function: +LONG WINAPI ExpFilter(EXCEPTION_POINTERS* pExp, DWORD dwExpCode) +{ + StackWalker sw; + sw.ShowCallstack(GetCurrentThread(), pExp->ContextRecord); + return EXCEPTION_EXECUTE_HANDLER; +} + +// This is how to catch an exception: +__try +{ + // do some ugly stuff... +} +__except (ExpFilter(GetExceptionInformation(), GetExceptionCode())) +{ +} +``` + +## Points of Interest + +### Context and callstack + +To walk the callstack of a given thread, you need at least two facts: + +#### The context of the thread + +The context is used to retrieve the current *Instruction Pointer* and the values for the *Stack Pointer (SP)* and sometimes the *Frame Pointer (FP)*. +The difference between SP and FP is in short: SP points to the latest address on the stack. FP is used to reference the arguments for a function. See also [Difference Between Stack Pointer and Frame Pointer](http://www.embeddedrelated.com/usenet/embedded/show/31646-1.php). +But only the SP is essential for the processor. The FP is only used by the compiler. You can also disable the usage of FP (see: (/Oy [Frame-Pointer Omission](http://msdn.microsoft.com/library/en-us/vccore/html/_core_.2f.oy.asp)). + +#### The callstack +The callstack is a memory-region which contains all the data/addresses of the callers. This data must be used to retrieve the callstack (as the name says). The most important part is: this data-region **must not change** until the stack-walking is finished! This is also the reason why the thread must be in the *Suspended* state to retrieve a valid callstack. If you want to do a stack-walking for the current thread, then you must not change the callstack memory after the addresses which are declared in the context record. + +### Initializing the [STACKFRAME64](http://msdn.microsoft.com/library/en-us/debug/base/stackframe64_str.asp]-structure) + +To successfully walk the callstack with [StackWalk64](http://msdn.microsoft.com/library/en-us/debug/base/stackwalk64.asp), you need to initialize the [STACKFRAME64](http://msdn.microsoft.com/library/en-us/debug/base/stackframe64_str.asp)-structure with *meaningful* values. In the documentation of `StackWalk64` there is only a small note about this structure: + +* *The first call to this function will fail if the `AddrPC` and `AddrFrame` members of the `STACKFRAME64` structure passed in the `StackFrame` parameter are not initialized.* + +According to this documentation, most programs only initialize `AddrPC` and `AddrFrame` and this had worked until the newest *dbhhelp.dll* (v5.6.3.7). Now, you also need to initialize `AddrStack`. After having some trouble with this (and other problems) I talked to the dbghelp-team and got the following answer (2005-08-02; my own comments are written in *italics*!): + +* `AddrStack` should always be set to the stack pointer value for all platforms. You can certainly publish that `AddrStack` should be set. You're also welcome to say that new releases of dbghelp are now requiring this. +* Given a current dbghelp, your code should: + 1. Always use [StackWalk64](http://msdn.microsoft.com/library/en-us/debug/base/stackwalk64.asp) + 2. Always set `AddrPC` to the current instruction pointer (*`Eip on x86, Rip on x64 and StIIP` on IA64*) + 3. Always set `AddrStack` to the current stack pointer (*`Esp` on x86, `Rsp` on x64 and `IntSp` on IA64*) + 4. Set `AddrFrame` to the current frame pointer when meaningful. On x86 this is `Ebp`, on x64 you can use `Rbp` (*but is not used by VC2005B2; instead it uses `Rdi`!*) and on IA64 you can use `RsBSP`. [StackWalk64](http://msdn.microsoft.com/library/en-us/debug/base/stackwalk64.asp) will ignore the value when it isn't needed for unwinding. + 5. Set `AddrBStore` to `RsBSP` for IA64. + +### Walking the callstack of the current thread + +On x86 systems (prior to XP), there is no direct supported function to retrieve the context of the current thread. +The recommended way is to throw an exception and catch it. Now you will have a valid context-record. +The default way of capturing the context of the current thread is by doing some inline-assembler to retrieve `EIP`, `ESP` and `EBP`. +If you want to use the *documented* way, then you need to define `CURRENT_THREAD_VIA_EXCEPTION` for the project. +But you should be aware of the fact, that `GET_CURRENT_CONTEXT` is a macro which internally uses [`__try __except`](http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccelng/htm/key_s-z_4.asp). +Your function must be able to contain these statements. + +Starting with XP and on x64 and IA64 systems, there is a documented function to retrieve the context of the current thread: [RtlCaptureContext](http://msdn.microsoft.com/library/en-us/debug/base/rtlcapturecontext.asp. + +To do a stack-walking of the current thread, you simply need to do: + +```c++ + StackWalker sw; + sw.ShowCallstack(); +``` + +### Walking the callstack of other threads in the same process + +To walk the callstack of another thread inside the same process, you need to suspend the target thread (so the callstack will not change during the stack-walking). +But you should be aware that suspending a thread in the same process might lead to dead-locks! (See: [Why you never should call Suspend/TerminateThread (Part I)](http://blog.kalmbachnet.de/?postid=6), [Part II](http://blog.kalmbachnet.de/?postid=16, [Part III](http://blog.kalmbachnet.de/?postid=17)) + +If you have the handle to the thread, you can do the following to retrieve the callstack: + +```c++ + MyStackWalker sw; + sw.ShowCallstack(hThread); +``` + +For a complete sample to retrieve the callstack of another thread, you can take a look at the demo-project. + +### Walking the callstack of other threads in other processes + +The approach is almost the same as for walking the callstack for the current process. +You only need to provide the `ProcessID` and a handle to the process (`hProcess`). Then you also need to suspend the thread to do the stack-walking. A complete sample to retrieve the callstack of another process is in the demo-project. + +### Reusing the `StackWalk` instance + +It is no problem to reuse the `StackWalk` instance, as long as you want to do the stack-walking for the same process. +If you want to do a lot of stack-walking it is recommended to reuse the instance. +The reason is simple: if you create a new instance, then the symbol-files must be re-loaded for each instance. +And this is really time-consuming. Also it is not allowed to access the `StackWalk` functions from different threads (the *dbghelp.dll* is **not** thread-safe!). Therefore it makes no sense to create more than one instance... + +### Symbol-Search-Path + +By default, a symbol-search path (`SymBuildPath` and `SymUseSymSrv`) is provided to the *dbghelp.dll*. This path contains the following directories: + +* The optional provided `szSymPath`. If this parameter is provided, the option SymBuildPath is automatically set. Each path must be separated with a ";" +* The current directory +* The directory of the EXE +* The environment variable `_NT_SYMBOL_PATH` +* The environment variable `_NT_ALTERNATE_SYMBOL_PATH` +* The environment variable `SYSTEMROOT` +* The environment variable `SYSTEMROOT` appended with "*\system32*" +* The public Microsoft symbol-server: *RV*%SYSTEMDRIVE%\websymbols*http://msdl.microsoft.com/download/symbols* + +### Symbol-Server + +If you want to use the public symbols for the OS-files from the [Microsoft-Symbol-Server](http://support.microsoft.com/?kbid=311503, you either need the [Debugging Tools for Windows](http://www.microsoft.com/whdc/devtools/debugging/ (then *symsrv.dll* and the latest *dbghelp.dll* will be found automatically) or you need to redistribute "*dbghelp.dll*" **and** "*smysrv.dll*" from this package! + +### Loading the modules and symbols + +To successfully walk the callstack of a thread, *dbghelp.dll* requires that the modules are known by the library. Therefore you need to "register" each module of the process via [`SymLoadModule64`](http://msdn.microsoft.com/library/en-us/debug/base/symloadmodule64.asp). To accomplish this you need to enumerate the modules of the given process. + +Starting with Win9x and W2K, it is possible to use the [ToolHelp32-API](http://msdn.microsoft.com/library/en-us/perfmon/base/tool_help_library.asp). You need to make a [snapshot (`CreateToolhelp32Snapshot`)](http://msdn.microsoft.com/library/en-us/perfmon/base/createtoolhelp32snapshot.asp) of the process and then you can enumerate the modules via [Module32First](http://msdn.microsoft.com/library/en-us/perfmon/base/module32first.asp) and [Module32Next](http://msdn.microsoft.com/library/en-us/perfmon/base/module32next.asp). Normally the ToolHelp functions are located in the *kernel32.dll* but on Win9x it is located in a separate DLL: *tlhelp32.dll*. Therefore we need to check the functions in both DLLs. + +If you have NT4, then the `ToolHelp32-API` is not available. But in NT4 you can use the PSAPI. To enumerate all modules you need to call [EnumProcessModules](), but you only get the handles to the modules. To feed [SymLoadModule64](http://msdn.microsoft.com/library/en-us/debug/base/symloadmodule64.asp) you also need to query the `ModuleBaseAddr`, `SizeOfImage` (via [GetModuleInformation](http://msdn.microsoft.com/library/en-us/perfmon/base/getmoduleinformation.asp)), `ModuleBaseName` (via [GetModuleBaseName](http://msdn.microsoft.com/library/en-us/perfmon/base/getmodulebasename.asp)) and `ModuleFileName(Path)` (via [GetModuleFileNameEx](http://msdn.microsoft.com/library/en-us/perfmon/base/getmodulefilenameex.asp)). + +### *dbghelp.dll + +There are a couple of issues with *dbghelp.dll*. + +* The first is, there are two "teams" at Microsoft which redistribute the dbghelp.dll. One team is the *OS-team*, the other is the *Debugging-Tools-Team* (I don't know the real names...). In general you can say: The *dbghelp.dll* provided with the [Debugging Tools for Windows](http://www.microsoft.com/whdc/devtools/debugging/) is the most recent version. +One problem of this two teams is the different versioning of the *dbghelp.dll*. For example, for XP-SP1 the version is *5.1.2600.1106* dated *2002-08-29*. The version *6.0.0017.0* which was redistributed from the *debug-team* is dated *2002-04-31*. So there is at least a conflict in the date (the newer version is older). And it is even harder to decide which version is "better" (or has more functionality). +* Starting with Me/W2K, the *dbghelp.dll*-file in the *system32* protected by the [System File Protection](http://support.microsoft.com/?kbid=222193). So if you want to use a newer *dbghelp.dll* you need to redistribute the version from the *Debugging Tools for Windows*(put it in the same directory as your EXE). +This leads to a problem on W2K if you want to walk the callstack for an app which was built using VC7 or later. The VC7 compiler generates a new PDB-format (called [DIA](http://msdn.microsoft.com/library/en-us/diasdk/html/vsoriDebugInterfaceAccessSDK.asp)). +This PDB-format cannot be read with the *dbghelp.dll* which is installed with the OS. Therefore you will not get very useful callstacks (or at least with no debugging info like filename, line, function name, ...). To overcome this problem, you need to redistribute a newer *dbghelp.dll*. +* The *dbghelp.dll* version *6.5.3.7* has a *bug* or at least a *documentation change* of the [StackWalk64](http://msdn.microsoft.com/library/en-us/debug/base/stackwalk64.asp) function. +In the documentation you can read: +*The first call to this function will fail if the `AddrPC` and `AddrFrame` members of the `STACKFRAME64` structure passed in the `StackFrame` parameter are not initialized.* + +and + +* *[The `ContextRecord`] parameter is required only when the `MachineType` parameter is not `IMAGE_FILE_MACHINE_I386`.* + +*But this is not true anymore.* +Now the callstack on x86-systems cannot be retrieved if you pass `NULL` as *`ContextRecord`*. +From my point of view this is a major documentation change. +Now you either need to initialize the `AddrStack` as well, or provide a valid *`ContextRecord`* which contains the `EIP`, `EBP` and `ESP` registers! +* See also comments in the *Initializing the STACKFRAME64-structure* chapter... + +### Options + +To do some kind of modification of the behaviour, you can optionally specify some options. Here is the list of the available options: + +```c++ +typedef enum StackWalkOptions +{ + // No addition info will be retrived + // (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; +``` + +## Known issues + +* NT/Win9x: This project only support the `StackWalk64` function. If you need to use it on NT4/Win9x, you need to [redistribute the *dbghelp.dll* for this platform.](http://www.microsoft.com/downloads/release.asp?releaseid=30682) +* Currently only supports ANSI-names in callbacks (of course, the project can be compiled with UNICODE...). +* To open a remote thread I used `OpenThread` which is not available on NT4/W9x. To have an example of doing this in NT4/Win9x please refer to [Remote Library](http://www.codeproject.com/win32/Remote.asp). +* Walking mixed-mode callstacks (managed/unmanaged) does only return the unmanaged functions. +* Doesn't work when debugging with the `/DEBUG:fastlink` [option](https://blogs.msdn.microsoft.com/vcblog/2014/11/12/speeding-up-the-incremental-developer-build-scenario/) diff --git a/libs/StackWalker/StackWalker.cpp b/libs/StackWalker/StackWalker.cpp new file mode 100644 index 00000000..e267f2ef --- /dev/null +++ b/libs/StackWalker/StackWalker.cpp @@ -0,0 +1,1365 @@ +/********************************************************************** + * + * StackWalker.cpp + * 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 paremeter) + * - 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 slihtly 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. + * + **********************************************************************/ +#include +#include +#include +#include +#pragma comment(lib, "version.lib") // for "VerQueryValue" +#pragma warning(disable:4826) + +#include "StackWalker.h" + + +// 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 + ); +#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; +#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); + szDest[nMaxDestSize-1] = 0; // INFO: _TRUNCATE will ensure that it is nul-terminated; but with older compilers (<1400) it uses "strncpy" and this does not!) +} // 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) + { + m_parent = parent; + m_hDbhHelp = NULL; + pSC = NULL; + m_hProcess = hProcess; + m_szSymPath = NULL; + pSFTA = NULL; + pSGLFA = NULL; + pSGMB = NULL; + pSGMI = NULL; + pSGO = NULL; + pSGSFA = NULL; + pSI = NULL; + pSLM = NULL; + pSSO = NULL; + pSW = NULL; + pUDSN = NULL; + pSGSP = NULL; + } + ~StackWalkerInternal() + { + if (pSC != NULL) + pSC(m_hProcess); // SymCleanup + if (m_hDbhHelp != NULL) + FreeLibrary(m_hDbhHelp); + m_hDbhHelp = NULL; + m_parent = NULL; + if(m_szSymPath != NULL) + free(m_szSymPath); + m_szSymPath = NULL; + } + BOOL Init(LPCSTR szSymPath) + { + if (m_parent == NULL) + return FALSE; + // Dynamically load the Entry-Points for dbghelp.dll: + // First try to load the newsest one from + TCHAR szTemp[4096]; + // But before wqe do this, we first check if the ".local" file exists + if (GetModuleFileName(NULL, szTemp, 4096) > 0) + { + _tcscat_s(szTemp, _T(".local")); + if (GetFileAttributes(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 archtitecture: +#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 (GetFileAttributes(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 (GetFileAttributes(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 (GetFileAttributes(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 (GetFileAttributes(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 (GetFileAttributes(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 (szSymPath != NULL) + m_szSymPath = _strdup(szSymPath); + if (this->pSI(m_hProcess, m_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; + + HMODULE m_hDbhHelp; + HANDLE m_hProcess; + LPSTR m_szSymPath; + +#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 +}; + +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 +}; +#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 PSTR UserSearchPath, IN BOOL fInvadeProcess ); + tSI pSI; + + // SymLoadModule64() + typedef DWORD64 (__stdcall *tSLM)( IN HANDLE hProcess, IN HANDLE hFile, + IN PSTR ImageName, IN PSTR 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 = 0; + 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 retrive 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); + } + } + } + + // Retrive 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 unneccessarry calls with the larger struct... + } + + // could not retrive 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; + } +}; + +// ############################################################# +StackWalker::StackWalker(DWORD dwProcessId, HANDLE hProcess) +{ + this->m_options = OptionsAll; + this->m_modulesLoaded = FALSE; + this->m_hProcess = hProcess; + this->m_sw = new StackWalkerInternal(this, this->m_hProcess); + this->m_dwProcessId = dwProcessId; + this->m_szSymPath = NULL; + this->m_MaxRecursionCount = 1000; +} +StackWalker::StackWalker(int options, LPCSTR szSymPath, DWORD dwProcessId, HANDLE hProcess) +{ + this->m_options = options; + this->m_modulesLoaded = FALSE; + this->m_hProcess = hProcess; + this->m_sw = new StackWalkerInternal(this, this->m_hProcess); + this->m_dwProcessId = dwProcessId; + if (szSymPath != NULL) + { + this->m_szSymPath = _strdup(szSymPath); + this->m_options |= SymBuildPath; + } + else + this->m_szSymPath = NULL; + this->m_MaxRecursionCount = 1000; +} + +StackWalker::~StackWalker() +{ + if (m_szSymPath != NULL) + free(m_szSymPath); + m_szSymPath = NULL; + if (this->m_sw != NULL) + delete this->m_sw; + this->m_sw = NULL; +} + +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, "*http://msdl.microsoft.com/download/symbols;"); + } + else + strcat_s(szSymPath, nSymPathLen, "SRV*c:\\websymbols*http://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 + { + 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 cannnot 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; +#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 suplied 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 __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]; + if (fileVersion == 0) + _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "%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, STACKWALK_MAX_NAMELEN, "%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); + } + OnOutput(buffer); +} + +void StackWalker::OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry) +{ + CHAR buffer[STACKWALK_MAX_NAMELEN]; + 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, STACKWALK_MAX_NAMELEN, "%p (%s): %s: %s\n", (LPVOID) entry.offset, entry.moduleName, entry.lineFileName, entry.name); + } + else + _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "%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]; + _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "ERROR: %s, GetLastError: %d (Address: %p)\n", szFuncName, gle, (LPVOID) addr); + OnOutput(buffer); +} + +void StackWalker::OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName) +{ + CHAR buffer[STACKWALK_MAX_NAMELEN]; + _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "SymInit: Symbol-SearchPath: '%s', symOptions: %d, UserName: '%s'\n", szSearchPath, symOptions, szUserName); + 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, STACKWALK_MAX_NAMELEN, "OS-Version: %d.%d.%d (%s)\n", + ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber, + ver.szCSDVersion); + 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, STACKWALK_MAX_NAMELEN, "OS-Version: %d.%d.%d (%s) 0x%x-0x%x\n", + ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber, + ver.szCSDVersion, ver.wSuiteMask, ver.wProductType); + OnOutput(buffer); + } +#if _MSC_VER >= 1900 +#pragma warning(pop) +#endif +#endif +} + +void StackWalker::OnOutput(LPCSTR buffer) +{ + OutputDebugStringA(buffer); +} diff --git a/libs/StackWalker/include/StackWalker.h b/libs/StackWalker/include/StackWalker.h new file mode 100644 index 00000000..a66dadc4 --- /dev/null +++ b/libs/StackWalker/include/StackWalker.h @@ -0,0 +1,218 @@ +/********************************************************************** + * + * 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 _MCS_VER 1000, +// so we need not to check the version (because we only support _MSC_VER >= 1100)! +#pragma once + +#include + +#if _MSC_VER >= 1900 +#pragma warning(disable : 4091) +#endif + +// 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 StackWalkOptions + { + // No addition info will be retrived + // (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 abouve + RetrieveVerbose = 0xF, + + // Generate a "good" symbol-search-path + SymBuildPath = 0x10, + + // Also use the public Microsoft-Symbol-Server + SymUseSymSrv = 0x20, + + // Contains all the abouve "Sym"-options + SymAll = 0x30, + + // Contains all options (default) + OptionsAll = 0x3F + } StackWalkOptions; + + 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(); + + 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 + ); + +#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}; + + 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 +// 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); +#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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 532a016f..2ad326a6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,4 @@ -# Make sure the user is not executing this script directly +# Make sure the user is not executing this script directly if(NOT InMV) message(FATAL_ERROR "Use the top-level cmake script!") endif(NOT InMV) @@ -214,9 +214,14 @@ if (BuildMVMP OR BuildMVDED) if(MSVC) set(MVMPDEDWIN32Files + "${LIBDir}/StackWalker/include/StackWalker.h" + + "${LIBDir}/StackWalker/StackWalker.cpp" + "win32/q3asm.asm" ) source_group("win32" FILES ${MVMPDEDWIN32Files}) + set(MVMPDEDIncludeDirs ${MVMPDEDIncludeDirs} "${LIBDir}/StackWalker/include") set(MVMPDEDFiles ${MVMPDEDFiles} ${MVMPDEDWIN32Files}) endif() From 36d9b1b96adcdd2581dd9db5bd6fe560d89368b1 Mon Sep 17 00:00:00 2001 From: ouned Date: Sat, 27 Jan 2018 12:55:17 +0100 Subject: [PATCH 02/14] basic crashlogging on win32 includes basic environment information and a stacktrace --- src/sys/sys_win32.cpp | 90 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/src/sys/sys_win32.cpp b/src/sys/sys_win32.cpp index 4c325244..eefa8a29 100644 --- a/src/sys/sys_win32.cpp +++ b/src/sys/sys_win32.cpp @@ -4,9 +4,14 @@ #include #include #include +#include +#include +#include #include "../qcommon/q_shared.h" #include "../qcommon/qcommon.h" +void Sys_CrashSignalHandler(int signal); + char *Sys_GetCurrentUser( void ) { static char s_userName[1024]; @@ -414,6 +419,13 @@ static UINT timerResolution = 0; ITaskbarList3 *win_taskbar; void Sys_PlatformInit(int argc, char *argv[]) { +#ifndef _DEBUG + signal(SIGABRT, Sys_CrashSignalHandler); + signal(SIGFPE, Sys_CrashSignalHandler); + signal(SIGILL, Sys_CrashSignalHandler); + signal(SIGSEGV, Sys_CrashSignalHandler); +#endif + TIMECAPS ptc; if (timeGetDevCaps(&ptc, sizeof(ptc)) == MMSYSERR_NOERROR) { @@ -540,3 +552,81 @@ void Sys_SnapVector(vec3_t vec) { } #endif + +/* +============================================================== + +Crash Handling + +============================================================== +*/ +#ifndef _DEBUG + +class MVStackWalker : public StackWalker +{ + FILE *f; + +public: + MVStackWalker(FILE *f) : StackWalker(RetrieveSymbol | RetrieveLine | SymBuildPath) { this->f = f; } +protected: + virtual void OnOutput(LPCSTR szText) { + fwrite(szText, 1, strlen(szText), f); + StackWalker::OnOutput(szText); + } +}; + +void Sys_CrashSignalHandler(int signal) { + time_t rawtime; + char timeStr[32]; + char crashlogName[MAX_OSPATH]; + char *path; + FILE *f = NULL; + + time(&rawtime); + strftime(timeStr, sizeof(timeStr), "%Y-%m-%d_%H-%M-%S", localtime(&rawtime)); // or gmtime + Com_sprintf(crashlogName, sizeof(crashlogName), "crashlog-%s.txt", timeStr); + path = FS_BuildOSPath(Sys_DefaultHomePath(), crashlogName); + + if (FS_CreatePath(Sys_DefaultHomePath())) { + f = fopen(path, "w"); + } + + if (f == NULL) { + f = fopen(crashlogName, "w"); + } + + if (f == NULL) { + f = stderr; + } + + fprintf(f, "---JK2MV Crashlog-----------------------\n"); + fprintf(f, "\n"); + fprintf(f, "Date: %s", ctime(&rawtime)); + fprintf(f, "Build Version: " JK2MV_VERSION "\n"); +#if defined(PORTABLE) + fprintf(f, "Build Type: Portable\n"); +#endif + fprintf(f, "Build Date: " __DATE__ " " __TIME__ "\n"); + fprintf(f, "Build Arch: " CPUSTRING "\n"); + + fprintf(f, "\n"); + fprintf(f, "\n"); + + fprintf(f, "---Callstack----------------------------\n"); + MVStackWalker sw(f); + sw.ShowCallstack(); + + fclose(f); + + char *err_msg = va("JK2MV just crashed. :( Sorry for that.\n\n" + "A crashlog has been written to the file %s.\n\n" + "If you think this needs to be fixed, send this file to the JK2MV developers.", path); +#ifndef DEDICATED + MessageBoxA(NULL, err_msg, "JK2MV Crashed", MB_OK | MB_ICONERROR | MB_TOPMOST); +#else + fprintf(stderr, err_msg); +#endif +} + +#endif + From 4bf06591a3446f5d1ee3d6661a0c0fd075594bc5 Mon Sep 17 00:00:00 2001 From: ouned Date: Sat, 27 Jan 2018 13:12:32 +0100 Subject: [PATCH 03/14] install pdb files for CMake RelWithDebInfo builds --- src/CMakeLists.txt | 2 ++ src/menu/CMakeLists.txt | 1 + 2 files changed, 3 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2ad326a6..bf26e9e7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -511,6 +511,7 @@ if (BuildMVMP) # installation if(MSVC) install(TARGETS ${MVMP} RUNTIME DESTINATION ".") + install(FILES $ DESTINATION "." OPTIONAL) install(FILES "${LIBDir}/OpenAL/softoal_${Architecture}.dll" DESTINATION ".") install(FILES "${LIBDir}/SDL2/bin/${Architecture}/SDL2.dll" DESTINATION ".") if(NOT BuildPortableVersion) @@ -585,6 +586,7 @@ if (BuildMVDED) # installation if(WIN32 OR APPLE) install(TARGETS ${MVDED} RUNTIME DESTINATION ".") + install(FILES $ DESTINATION "." OPTIONAL) else() install(TARGETS ${MVDED} RUNTIME DESTINATION "bin") endif() diff --git a/src/menu/CMakeLists.txt b/src/menu/CMakeLists.txt index 8a1df124..3a4cca2b 100644 --- a/src/menu/CMakeLists.txt +++ b/src/menu/CMakeLists.txt @@ -45,6 +45,7 @@ set_target_properties(${MVMENU} PROPERTIES PROJECT_LABEL "jk2mvmenu") if(WIN32) install(TARGETS ${MVMENU} RUNTIME DESTINATION ".") + install(FILES $ DESTINATION "." OPTIONAL) elseif(APPLE) install(TARGETS ${MVMENU} LIBRARY DESTINATION "${MVMP}.app/Contents/MacOS") else() From 17d73dce6c539c2a1bebc62865af8ac5f4e3c85d Mon Sep 17 00:00:00 2001 From: ouned Date: Sat, 27 Jan 2018 13:15:57 +0100 Subject: [PATCH 04/14] appveyor: build releases including debug information --- appveyor.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index d70b95de..3cfc6df8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -21,13 +21,13 @@ build_script: choco install -y nsis cmake -G "Visual Studio 14" -DBuildPortableVersion=OFF ../.. - msbuild /m /p:Configuration=Release PACKAGE.vcxproj + msbuild /m /p:Configuration=RelWithDebInfo PACKAGE.vcxproj } elseif ($env:TYPE -eq "win-x86-portable") { cmake -G "Visual Studio 14" -DBuildPortableVersion=ON ../.. - msbuild /m /p:Configuration=Release PACKAGE.vcxproj + msbuild /m /p:Configuration=RelWithDebInfo PACKAGE.vcxproj } elseif ($env:TYPE -eq "win-x64-portable") { cmake -G "Visual Studio 14 Win64" -DBuildPortableVersion=ON ../.. - msbuild /m /p:Configuration=Release PACKAGE.vcxproj + msbuild /m /p:Configuration=RelWithDebInfo PACKAGE.vcxproj } if (! $?) { From e0b4c218eeee7e7d717d0f35960be61013991b3e Mon Sep 17 00:00:00 2001 From: ouned Date: Sat, 27 Jan 2018 13:44:24 +0100 Subject: [PATCH 05/14] same build flags for release/relwithdebinfo builds --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index ccf8847c..2890f6ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -219,6 +219,10 @@ if(MSVC) set(CMAKE_ASM_MASM_FLAGS "${CMAKE_ASM_MASM_FLAGS} /safeseh") # safe exception handler endif() + + # Set the same options for the build including symbols + set(CMAKE_C_FLAGS_RELWITHDEBINFO ${CMAKE_C_FLAGS_RELEASE}) + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO ${CMAKE_CXX_FLAGS_RELEASE}) elseif ((${CMAKE_C_COMPILER_ID} STREQUAL GNU) OR (${CMAKE_C_COMPILER_ID} MATCHES Clang|AppleClang)) # Additional flags for debug configuration set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -ggdb3") From 94fee34c4899afc29e7452f7b8897e84d2ac5058 Mon Sep 17 00:00:00 2001 From: ouned Date: Sat, 27 Jan 2018 13:45:04 +0100 Subject: [PATCH 06/14] print installed in crashlog if build type is installed --- src/sys/sys_win32.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sys/sys_win32.cpp b/src/sys/sys_win32.cpp index eefa8a29..cf53ee9a 100644 --- a/src/sys/sys_win32.cpp +++ b/src/sys/sys_win32.cpp @@ -605,6 +605,8 @@ void Sys_CrashSignalHandler(int signal) { fprintf(f, "Build Version: " JK2MV_VERSION "\n"); #if defined(PORTABLE) fprintf(f, "Build Type: Portable\n"); +#else + fprintf(f, "Build Type: Installed\n"); #endif fprintf(f, "Build Date: " __DATE__ " " __TIME__ "\n"); fprintf(f, "Build Arch: " CPUSTRING "\n"); From 79fe21046ae7f45b97584da80bd16774ab70347a Mon Sep 17 00:00:00 2001 From: ouned Date: Sat, 27 Jan 2018 20:00:32 +0100 Subject: [PATCH 07/14] stackwalker: always use xp compatible way to get threadid --- libs/StackWalker/StackWalker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/StackWalker/StackWalker.cpp b/libs/StackWalker/StackWalker.cpp index e267f2ef..98524ed7 100644 --- a/libs/StackWalker/StackWalker.cpp +++ b/libs/StackWalker/StackWalker.cpp @@ -1033,7 +1033,7 @@ BOOL StackWalker::ShowCallstack(HANDLE hThread, const CONTEXT *context, PReadPro { // If no context is provided, capture the context // See: https://stackwalker.codeplex.com/discussions/446958 -#if _WIN32_WINNT <= 0x0501 +#if 1 // If we need to support XP, we need to use the "old way", because "GetThreadId" is not available! if (hThread == GetCurrentThread()) #else From a1351917fb34c8c85354e632f6ffde0a121b9966 Mon Sep 17 00:00:00 2001 From: ouned Date: Sat, 27 Jan 2018 20:01:48 +0100 Subject: [PATCH 08/14] set same Release defines for RelWithDebInfo --- src/CMakeLists.txt | 2 ++ src/menu/CMakeLists.txt | 1 + 2 files changed, 3 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bf26e9e7..d92d19c7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -501,6 +501,7 @@ if (BuildMVMP) PRIVATE ${GlobalDefines} PRIVATE $<$:${DebugDefines}> PRIVATE $<$:${ReleaseDefines}> + PRIVATE $<$:${ReleaseDefines}> ) set_target_properties(${MVMP} PROPERTIES CXX_STANDARD 11) @@ -572,6 +573,7 @@ if (BuildMVDED) PRIVATE ${GlobalDefines} ${MVDEDDefines} PRIVATE $<$:${DebugDefines}> PRIVATE $<$:${ReleaseDefines}> + PRIVATE $<$:${ReleaseDefines}> ) set_target_properties(${MVDED} PROPERTIES CXX_STANDARD 11) diff --git a/src/menu/CMakeLists.txt b/src/menu/CMakeLists.txt index 3a4cca2b..0d3fa3b5 100644 --- a/src/menu/CMakeLists.txt +++ b/src/menu/CMakeLists.txt @@ -37,6 +37,7 @@ target_compile_definitions(${MVMENU} PRIVATE ${GlobalDefines} ${MVMENUDefines} PRIVATE $<$:${DebugDefines}> PRIVATE $<$:${ReleaseDefines}> + PRIVATE $<$:${ReleaseDefines}> ) set_target_properties(${MVMENU} PROPERTIES C_STANDARD 90) From 9237a6552a0fc61655062e74f6cabf6f063bd29c Mon Sep 17 00:00:00 2001 From: ouned Date: Sat, 27 Jan 2018 20:03:18 +0100 Subject: [PATCH 09/14] use __try __exception block instead of signals for win32 crash handling --- src/sys/sys_main.cpp | 18 ++++++++++++++++- src/sys/sys_win32.cpp | 47 ++++++++++++++++++++++++++----------------- 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/sys/sys_main.cpp b/src/sys/sys_main.cpp index aa7905d5..b9bd94b9 100644 --- a/src/sys/sys_main.cpp +++ b/src/sys/sys_main.cpp @@ -9,11 +9,11 @@ #else #include #endif -#ifndef DEDICATED #ifdef WIN32 #define WIN32_LEAN_AND_MEAN #include #endif +#ifndef DEDICATED #include "SDL.h" #endif #include "../qcommon/qcommon.h" @@ -201,10 +201,19 @@ void Sys_SigHandler(int signal) { sys_signal = signal; } +#if defined(WIN32) && !defined(_DEBUG) +LONG WINAPI Sys_NoteException(EXCEPTION_POINTERS* pExp, DWORD dwExpCode); +void Sys_WriteCrashlog(); +#endif + int main(int argc, char* argv[]) { int i; char commandLine[MAX_STRING_CHARS] = { 0 }; +#if defined(WIN32) && !defined(_DEBUG) + __try { +#endif + Sys_PlatformInit(argc, argv); #if defined(_DEBUG) && !defined(DEDICATED) @@ -263,6 +272,13 @@ int main(int argc, char* argv[]) { Com_Quit(sys_signal); +#if defined(WIN32) && !defined(_DEBUG) + } __except(Sys_NoteException(GetExceptionInformation(), GetExceptionCode())) { + Sys_WriteCrashlog(); + return 1; + } +#endif + // never gets here return 0; } diff --git a/src/sys/sys_win32.cpp b/src/sys/sys_win32.cpp index cf53ee9a..9089cdfb 100644 --- a/src/sys/sys_win32.cpp +++ b/src/sys/sys_win32.cpp @@ -6,7 +6,9 @@ #include #include #include +#include #include +#include "con_local.h" #include "../qcommon/q_shared.h" #include "../qcommon/qcommon.h" @@ -419,13 +421,6 @@ static UINT timerResolution = 0; ITaskbarList3 *win_taskbar; void Sys_PlatformInit(int argc, char *argv[]) { -#ifndef _DEBUG - signal(SIGABRT, Sys_CrashSignalHandler); - signal(SIGFPE, Sys_CrashSignalHandler); - signal(SIGILL, Sys_CrashSignalHandler); - signal(SIGSEGV, Sys_CrashSignalHandler); -#endif - TIMECAPS ptc; if (timeGetDevCaps(&ptc, sizeof(ptc)) == MMSYSERR_NOERROR) { @@ -562,20 +557,28 @@ Crash Handling */ #ifndef _DEBUG +std::string callstack_str; + class MVStackWalker : public StackWalker { - FILE *f; - public: - MVStackWalker(FILE *f) : StackWalker(RetrieveSymbol | RetrieveLine | SymBuildPath) { this->f = f; } + MVStackWalker() : StackWalker(RetrieveSymbol | RetrieveLine | SymBuildPath) {} protected: virtual void OnOutput(LPCSTR szText) { - fwrite(szText, 1, strlen(szText), f); - StackWalker::OnOutput(szText); + callstack_str.append(szText); } }; -void Sys_CrashSignalHandler(int signal) { +LONG WINAPI Sys_NoteException(EXCEPTION_POINTERS* pExp, DWORD dwExpCode) { + callstack_str.clear(); + + MVStackWalker sw; + sw.ShowCallstack(GetCurrentThread(), pExp->ContextRecord); + + return EXCEPTION_EXECUTE_HANDLER; +} + +void Sys_WriteCrashlog() { time_t rawtime; char timeStr[32]; char crashlogName[MAX_OSPATH]; @@ -587,16 +590,17 @@ void Sys_CrashSignalHandler(int signal) { Com_sprintf(crashlogName, sizeof(crashlogName), "crashlog-%s.txt", timeStr); path = FS_BuildOSPath(Sys_DefaultHomePath(), crashlogName); - if (FS_CreatePath(Sys_DefaultHomePath())) { - f = fopen(path, "w"); - } + FS_CreatePath(Sys_DefaultHomePath()); + f = fopen(path, "wb"); if (f == NULL) { - f = fopen(crashlogName, "w"); + f = fopen(crashlogName, "wb"); + path = crashlogName; } if (f == NULL) { f = stderr; + path = "stderr"; } fprintf(f, "---JK2MV Crashlog-----------------------\n"); @@ -615,8 +619,13 @@ void Sys_CrashSignalHandler(int signal) { fprintf(f, "\n"); fprintf(f, "---Callstack----------------------------\n"); - MVStackWalker sw(f); - sw.ShowCallstack(); + fprintf(f, callstack_str.c_str()); + + fprintf(f, "\n"); + fprintf(f, "\n"); + + fprintf(f, "---Console Log--------------------------\n"); + ConsoleLogWriteOut(f); fclose(f); From b2690ef798d010a6cf724102834a1776563e222d Mon Sep 17 00:00:00 2001 From: ouned Date: Sun, 28 Jan 2018 12:21:05 +0100 Subject: [PATCH 10/14] log modules / callstack in separate sections --- libs/StackWalker/StackWalker.cpp | 41 +++++++------------------- libs/StackWalker/include/StackWalker.h | 6 ++++ src/sys/sys_win32.cpp | 19 ++++++++++-- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/libs/StackWalker/StackWalker.cpp b/libs/StackWalker/StackWalker.cpp index 98524ed7..f250045c 100644 --- a/libs/StackWalker/StackWalker.cpp +++ b/libs/StackWalker/StackWalker.cpp @@ -1017,6 +1017,8 @@ BOOL StackWalker::ShowCallstack(HANDLE hThread, const CONTEXT *context, PReadPro bool bLastEntryCalled = true; int curRecursionCount = 0; + SwitchOutputType(OutputType::OTYPE_MODULE); + if (m_modulesLoaded == FALSE) this->LoadModules(); // ignore the result... @@ -1109,6 +1111,8 @@ BOOL StackWalker::ShowCallstack(HANDLE hThread, const CONTEXT *context, PReadPro memset(&Module, 0, sizeof(Module)); Module.SizeOfStruct = sizeof(Module); + SwitchOutputType(OutputType::OTYPE_CALLSTACK); + for (frameNum = 0; ; ++frameNum ) { // get next stack frame (StackWalk64(), SymFunctionTableAccess64(), SymGetModuleBase64()) @@ -1123,6 +1127,8 @@ BOOL StackWalker::ShowCallstack(HANDLE hThread, const CONTEXT *context, PReadPro break; } + const char *VM_SymbolForCompiledPointer(void *code); + csEntry.offset = s.AddrPC.Offset; csEntry.name[0] = 0; csEntry.undName[0] = 0; @@ -1326,37 +1332,10 @@ void StackWalker::OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUser CHAR buffer[STACKWALK_MAX_NAMELEN]; _snprintf_s(buffer, STACKWALK_MAX_NAMELEN, "SymInit: Symbol-SearchPath: '%s', symOptions: %d, UserName: '%s'\n", szSearchPath, symOptions, szUserName); 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, STACKWALK_MAX_NAMELEN, "OS-Version: %d.%d.%d (%s)\n", - ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber, - ver.szCSDVersion); - 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, STACKWALK_MAX_NAMELEN, "OS-Version: %d.%d.%d (%s) 0x%x-0x%x\n", - ver.dwMajorVersion, ver.dwMinorVersion, ver.dwBuildNumber, - ver.szCSDVersion, ver.wSuiteMask, ver.wProductType); - OnOutput(buffer); - } -#if _MSC_VER >= 1900 -#pragma warning(pop) -#endif -#endif +} + +void StackWalker::SwitchOutputType(OutputType type) { + otype = type; } void StackWalker::OnOutput(LPCSTR buffer) diff --git a/libs/StackWalker/include/StackWalker.h b/libs/StackWalker/include/StackWalker.h index a66dadc4..afedc9f6 100644 --- a/libs/StackWalker/include/StackWalker.h +++ b/libs/StackWalker/include/StackWalker.h @@ -143,12 +143,18 @@ class StackWalker CHAR loadedImageName[STACKWALK_MAX_NAMELEN]; } CallstackEntry; + enum OutputType { + OTYPE_MODULE, + OTYPE_CALLSTACK + } otype; + typedef enum CallstackEntryType {firstEntry, nextEntry, lastEntry}; 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 SwitchOutputType(OutputType type); virtual void OnOutput(LPCSTR szText); StackWalkerInternal *m_sw; diff --git a/src/sys/sys_win32.cpp b/src/sys/sys_win32.cpp index 9089cdfb..fed6f280 100644 --- a/src/sys/sys_win32.cpp +++ b/src/sys/sys_win32.cpp @@ -557,7 +557,7 @@ Crash Handling */ #ifndef _DEBUG -std::string callstack_str; +std::string callstack_str, modules_str; class MVStackWalker : public StackWalker { @@ -565,8 +565,17 @@ class MVStackWalker : public StackWalker MVStackWalker() : StackWalker(RetrieveSymbol | RetrieveLine | SymBuildPath) {} protected: virtual void OnOutput(LPCSTR szText) { - callstack_str.append(szText); + switch (otype) { + case OutputType::OTYPE_MODULE: + modules_str.append(szText); + break; + case OutputType::OTYPE_CALLSTACK: + callstack_str.append(szText); + break; + } } + + virtual void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr) {} }; LONG WINAPI Sys_NoteException(EXCEPTION_POINTERS* pExp, DWORD dwExpCode) { @@ -618,6 +627,12 @@ void Sys_WriteCrashlog() { fprintf(f, "\n"); fprintf(f, "\n"); + fprintf(f, "---Modules------------------------------\n"); + fprintf(f, modules_str.c_str()); + + fprintf(f, "\n"); + fprintf(f, "\n"); + fprintf(f, "---Callstack----------------------------\n"); fprintf(f, callstack_str.c_str()); From 21afe8659e2ea72aedddb6e99d16638e6073dcf9 Mon Sep 17 00:00:00 2001 From: ouned Date: Sun, 28 Jan 2018 13:17:24 +0100 Subject: [PATCH 11/14] log exception type --- src/sys/sys_win32.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/sys/sys_win32.cpp b/src/sys/sys_win32.cpp index fed6f280..2d103401 100644 --- a/src/sys/sys_win32.cpp +++ b/src/sys/sys_win32.cpp @@ -557,6 +557,7 @@ Crash Handling */ #ifndef _DEBUG +DWORD exception_type; std::string callstack_str, modules_str; class MVStackWalker : public StackWalker @@ -580,6 +581,8 @@ class MVStackWalker : public StackWalker LONG WINAPI Sys_NoteException(EXCEPTION_POINTERS* pExp, DWORD dwExpCode) { callstack_str.clear(); + modules_str.clear(); + exception_type = dwExpCode; MVStackWalker sw; sw.ShowCallstack(GetCurrentThread(), pExp->ContextRecord); @@ -613,7 +616,6 @@ void Sys_WriteCrashlog() { } fprintf(f, "---JK2MV Crashlog-----------------------\n"); - fprintf(f, "\n"); fprintf(f, "Date: %s", ctime(&rawtime)); fprintf(f, "Build Version: " JK2MV_VERSION "\n"); #if defined(PORTABLE) @@ -624,6 +626,23 @@ void Sys_WriteCrashlog() { fprintf(f, "Build Date: " __DATE__ " " __TIME__ "\n"); fprintf(f, "Build Arch: " CPUSTRING "\n"); + fprintf(f, "\n"); + + fprintf(f, "Exception Type: "); + switch (exception_type) { + case EXCEPTION_ACCESS_VIOLATION: fprintf(f, "Access Violation"); break; + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: fprintf(f, "Range Check"); break; + case EXCEPTION_BREAKPOINT: fprintf(f, "Breakpoint"); break; + case EXCEPTION_DATATYPE_MISALIGNMENT: fprintf(f, "Datatype misaligment"); break; + case EXCEPTION_ILLEGAL_INSTRUCTION: fprintf(f, "Illegal instruction"); break; + case EXCEPTION_INT_DIVIDE_BY_ZERO: fprintf(f, "Divide by zero"); break; + case EXCEPTION_INT_OVERFLOW: fprintf(f, "Integer overflow"); break; + case EXCEPTION_PRIV_INSTRUCTION: fprintf(f, "Privileged instruction"); break; + case EXCEPTION_STACK_OVERFLOW: fprintf(f, "Stack overflow"); break; + default: fprintf(f, "Unknown (%d)", exception_type); + } + fprintf(f, "\n"); + fprintf(f, "\n"); fprintf(f, "\n"); From 114db32b504c2c68275005e0fbe02e21fd521084 Mon Sep 17 00:00:00 2001 From: ouned Date: Sun, 28 Jan 2018 13:37:50 +0100 Subject: [PATCH 12/14] add qvm support to stackwalker lib --- libs/StackWalker/StackWalker.cpp | 15 +++++++++++++-- libs/StackWalker/include/StackWalker.h | 4 ++++ src/sys/sys_win32.cpp | 3 ++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/libs/StackWalker/StackWalker.cpp b/libs/StackWalker/StackWalker.cpp index f250045c..ad7ca079 100644 --- a/libs/StackWalker/StackWalker.cpp +++ b/libs/StackWalker/StackWalker.cpp @@ -857,13 +857,14 @@ StackWalker::StackWalker(DWORD dwProcessId, HANDLE hProcess) this->m_szSymPath = NULL; this->m_MaxRecursionCount = 1000; } -StackWalker::StackWalker(int options, LPCSTR szSymPath, DWORD dwProcessId, HANDLE hProcess) +StackWalker::StackWalker(int options, vm_getsymbol_ptr vm_get_sym, LPCSTR szSymPath, DWORD dwProcessId, HANDLE hProcess) { this->m_options = options; this->m_modulesLoaded = FALSE; this->m_hProcess = hProcess; this->m_sw = new StackWalkerInternal(this, this->m_hProcess); this->m_dwProcessId = dwProcessId; + this->m_vm_get_sym = vm_get_sym; if (szSymPath != NULL) { this->m_szSymPath = _strdup(szSymPath); @@ -1163,7 +1164,17 @@ BOOL StackWalker::ShowCallstack(HANDLE hThread, const CONTEXT *context, PReadPro } else { - this->OnDbgHelpErr("SymGetSymFromAddr64", GetLastError(), s.AddrPC.Offset); + const char *qvm_name = m_vm_get_sym((void *)s.AddrPC.Offset); + if (qvm_name) + { + MyStrCpy(csEntry.name, STACKWALK_MAX_NAMELEN, qvm_name); + this->m_sw->pUDSN(qvm_name, csEntry.undName, STACKWALK_MAX_NAMELEN, UNDNAME_NAME_ONLY); + this->m_sw->pUDSN(qvm_name, csEntry.undFullName, STACKWALK_MAX_NAMELEN, UNDNAME_COMPLETE); + } + else + { + this->OnDbgHelpErr("SymGetSymFromAddr64", GetLastError(), s.AddrPC.Offset); + } } // show line number info, NT5.0-method (SymGetLineFromAddr64()) diff --git a/libs/StackWalker/include/StackWalker.h b/libs/StackWalker/include/StackWalker.h index afedc9f6..cf2b8ff9 100644 --- a/libs/StackWalker/include/StackWalker.h +++ b/libs/StackWalker/include/StackWalker.h @@ -90,8 +90,11 @@ class StackWalker OptionsAll = 0x3F } StackWalkOptions; + typedef const char *(*vm_getsymbol_ptr)(void *); + StackWalker( int options = OptionsAll, // 'int' is by design, to combine the enum-flags + vm_getsymbol_ptr vm_get_sym = NULL, LPCSTR szSymPath = NULL, DWORD dwProcessId = GetCurrentProcessId(), HANDLE hProcess = GetCurrentProcess() @@ -162,6 +165,7 @@ class StackWalker DWORD m_dwProcessId; BOOL m_modulesLoaded; LPSTR m_szSymPath; + vm_getsymbol_ptr m_vm_get_sym; int m_options; int m_MaxRecursionCount; diff --git a/src/sys/sys_win32.cpp b/src/sys/sys_win32.cpp index 2d103401..fe760dbf 100644 --- a/src/sys/sys_win32.cpp +++ b/src/sys/sys_win32.cpp @@ -9,6 +9,7 @@ #include #include #include "con_local.h" +#include "../qcommon/vm_local.h" #include "../qcommon/q_shared.h" #include "../qcommon/qcommon.h" @@ -563,7 +564,7 @@ std::string callstack_str, modules_str; class MVStackWalker : public StackWalker { public: - MVStackWalker() : StackWalker(RetrieveSymbol | RetrieveLine | SymBuildPath) {} + MVStackWalker() : StackWalker(RetrieveSymbol | RetrieveLine | SymBuildPath, VM_SymbolForCompiledPointer) {} protected: virtual void OnOutput(LPCSTR szText) { switch (otype) { From 9271dad700a21b604514d17bd7e41374001f633f Mon Sep 17 00:00:00 2001 From: ouned Date: Sun, 28 Jan 2018 13:57:27 +0100 Subject: [PATCH 13/14] use win32 crash-handling only with msvc builds --- src/sys/sys_main.cpp | 6 +++--- src/sys/sys_win32.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sys/sys_main.cpp b/src/sys/sys_main.cpp index b9bd94b9..f1103787 100644 --- a/src/sys/sys_main.cpp +++ b/src/sys/sys_main.cpp @@ -201,7 +201,7 @@ void Sys_SigHandler(int signal) { sys_signal = signal; } -#if defined(WIN32) && !defined(_DEBUG) +#if defined(_MSC_VER) && !defined(_DEBUG) LONG WINAPI Sys_NoteException(EXCEPTION_POINTERS* pExp, DWORD dwExpCode); void Sys_WriteCrashlog(); #endif @@ -210,7 +210,7 @@ int main(int argc, char* argv[]) { int i; char commandLine[MAX_STRING_CHARS] = { 0 }; -#if defined(WIN32) && !defined(_DEBUG) +#if defined(_MSC_VER) && !defined(_DEBUG) __try { #endif @@ -272,7 +272,7 @@ int main(int argc, char* argv[]) { Com_Quit(sys_signal); -#if defined(WIN32) && !defined(_DEBUG) +#if defined(_MSC_VER) && !defined(_DEBUG) } __except(Sys_NoteException(GetExceptionInformation(), GetExceptionCode())) { Sys_WriteCrashlog(); return 1; diff --git a/src/sys/sys_win32.cpp b/src/sys/sys_win32.cpp index fe760dbf..5c030780 100644 --- a/src/sys/sys_win32.cpp +++ b/src/sys/sys_win32.cpp @@ -556,7 +556,7 @@ Crash Handling ============================================================== */ -#ifndef _DEBUG +#if defined(_MSC_VER) && !defined(_DEBUG) DWORD exception_type; std::string callstack_str, modules_str; From 5564844bdb555a27c1e6511a32a7f5fa23bf7729 Mon Sep 17 00:00:00 2001 From: ouned Date: Sun, 28 Jan 2018 14:03:20 +0100 Subject: [PATCH 14/14] add dedicated/client build type to crashlog --- src/sys/sys_win32.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/sys/sys_win32.cpp b/src/sys/sys_win32.cpp index 5c030780..9a35dfc0 100644 --- a/src/sys/sys_win32.cpp +++ b/src/sys/sys_win32.cpp @@ -620,9 +620,14 @@ void Sys_WriteCrashlog() { fprintf(f, "Date: %s", ctime(&rawtime)); fprintf(f, "Build Version: " JK2MV_VERSION "\n"); #if defined(PORTABLE) - fprintf(f, "Build Type: Portable\n"); + fprintf(f, "Build Type: Portable"); #else - fprintf(f, "Build Type: Installed\n"); + fprintf(f, "Build Type: Installed"); +#endif +#if defined(DEDICATED) + fprintf(f, ", Dedicated\n"); +#else + fprintf(f, ", Client\n"); #endif fprintf(f, "Build Date: " __DATE__ " " __TIME__ "\n"); fprintf(f, "Build Arch: " CPUSTRING "\n");