source: XIOS3/trunk/extern/cpptrace/src/symbols_with_dbghelp.cpp

Last change on this file was 2573, checked in by ymipsl, 9 months ago

create new external source lib : cpptrace, for statck trace output
YM

File size: 20.5 KB
Line 
1#ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
2
3#include "cpptrace.hpp"
4#include "symbols.hpp"
5#include "program_name.hpp"
6
7#include <memory>
8#include <regex>
9#include <stdexcept>
10#include <system_error>
11#include <vector>
12
13#include <windows.h>
14#include <dbghelp.h>
15
16namespace cpptrace {
17    namespace detail {
18        namespace dbghelp {
19
20            // SymFromAddr only returns the function's name. In order to get information about parameters,
21            // important for C++ stack traces where functions may be overloaded, we have to manually use
22            // Windows DIA to walk debug info structures. Resources:
23            // https://web.archive.org/web/20201027025750/http://www.debuginfo.com/articles/dbghelptypeinfo.html
24            // https://web.archive.org/web/20201203160805/http://www.debuginfo.com/articles/dbghelptypeinfofigures.html
25            // https://github.com/DynamoRIO/dynamorio/blob/master/ext/drsyms/drsyms_windows.c#L1370-L1439
26            // TODO: Currently unable to detect rvalue references
27            // TODO: Currently unable to detect const
28            enum class SymTagEnum {
29                SymTagNull, SymTagExe, SymTagCompiland, SymTagCompilandDetails, SymTagCompilandEnv,
30                SymTagFunction, SymTagBlock, SymTagData, SymTagAnnotation, SymTagLabel, SymTagPublicSymbol,
31                SymTagUDT, SymTagEnum, SymTagFunctionType, SymTagPointerType, SymTagArrayType,
32                SymTagBaseType, SymTagTypedef, SymTagBaseClass, SymTagFriend, SymTagFunctionArgType,
33                SymTagFuncDebugStart, SymTagFuncDebugEnd, SymTagUsingNamespace, SymTagVTableShape,
34                SymTagVTable, SymTagCustom, SymTagThunk, SymTagCustomType, SymTagManagedType,
35                SymTagDimension, SymTagCallSite, SymTagInlineSite, SymTagBaseInterface, SymTagVectorType,
36                SymTagMatrixType, SymTagHLSLType, SymTagCaller, SymTagCallee, SymTagExport,
37                SymTagHeapAllocationSite, SymTagCoffGroup, SymTagMax
38            };
39
40            enum class IMAGEHLP_SYMBOL_TYPE_INFO {
41                TI_GET_SYMTAG, TI_GET_SYMNAME, TI_GET_LENGTH, TI_GET_TYPE, TI_GET_TYPEID, TI_GET_BASETYPE,
42                TI_GET_ARRAYINDEXTYPEID, TI_FINDCHILDREN, TI_GET_DATAKIND, TI_GET_ADDRESSOFFSET,
43                TI_GET_OFFSET, TI_GET_VALUE, TI_GET_COUNT, TI_GET_CHILDRENCOUNT, TI_GET_BITPOSITION,
44                TI_GET_VIRTUALBASECLASS, TI_GET_VIRTUALTABLESHAPEID, TI_GET_VIRTUALBASEPOINTEROFFSET,
45                TI_GET_CLASSPARENTID, TI_GET_NESTED, TI_GET_SYMINDEX, TI_GET_LEXICALPARENT, TI_GET_ADDRESS,
46                TI_GET_THISADJUST, TI_GET_UDTKIND, TI_IS_EQUIV_TO, TI_GET_CALLING_CONVENTION,
47                TI_IS_CLOSE_EQUIV_TO, TI_GTIEX_REQS_VALID, TI_GET_VIRTUALBASEOFFSET,
48                TI_GET_VIRTUALBASEDISPINDEX, TI_GET_IS_REFERENCE, TI_GET_INDIRECTVIRTUALBASECLASS,
49                TI_GET_VIRTUALBASETABLETYPE, TI_GET_OBJECTPOINTERTYPE, IMAGEHLP_SYMBOL_TYPE_INFO_MAX
50            };
51
52            enum class BasicType {
53                btNoType = 0, btVoid = 1, btChar = 2, btWChar = 3, btInt = 6, btUInt = 7, btFloat = 8,
54                btBCD = 9, btBool = 10, btLong = 13, btULong = 14, btCurrency = 25, btDate = 26,
55                btVariant = 27, btComplex = 28, btBit = 29, btBSTR = 30, btHresult = 31
56            };
57
58            // SymGetTypeInfo utility
59            template<typename T, IMAGEHLP_SYMBOL_TYPE_INFO SymType, bool FAILABLE = false>
60            T get_info(ULONG type_index, HANDLE proc, ULONG64 modbase) {
61                T info;
62                if(
63                    !SymGetTypeInfo(
64                        proc,
65                        modbase,
66                        type_index,
67                        static_cast<::IMAGEHLP_SYMBOL_TYPE_INFO>(SymType),
68                        &info
69                    )
70                ) {
71                    if(FAILABLE) {
72                        return (T)-1;
73                    } else {
74                        throw std::logic_error(
75                            std::string("SymGetTypeInfo failed: ")
76                            + std::system_error(GetLastError(), std::system_category()).what()
77                        );
78                    }
79                }
80                return info;
81            }
82
83            template<IMAGEHLP_SYMBOL_TYPE_INFO SymType, bool FAILABLE = false>
84            std::string get_info_wchar(ULONG type_index, HANDLE proc, ULONG64 modbase) {
85                WCHAR* info;
86                if(
87                    !SymGetTypeInfo(proc, modbase, type_index, static_cast<::IMAGEHLP_SYMBOL_TYPE_INFO>(SymType), &info)
88                ) {
89                    throw std::logic_error(
90                        std::string("SymGetTypeInfo failed: ")
91                        + std::system_error(GetLastError(), std::system_category()).what()
92                    );
93                }
94                // special case to properly free a buffer and convert string to narrow chars, only used for
95                // TI_GET_SYMNAME
96                static_assert(
97                    SymType == IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_SYMNAME,
98                    "get_info_wchar called with unexpected IMAGEHLP_SYMBOL_TYPE_INFO"
99                );
100                std::wstring wstr(info);
101                std::string str;
102                str.reserve(wstr.size());
103                for(const auto c : wstr) {
104                    str.push_back(static_cast<char>(c));
105                }
106                LocalFree(info);
107                return str;
108            }
109
110            // Translate basic types to string
111            static std::string get_basic_type(ULONG type_index, HANDLE proc, ULONG64 modbase) {
112                auto basic_type = get_info<BasicType, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_BASETYPE>(
113                    type_index,
114                    proc,
115                    modbase
116                );
117                //auto length = get_info<ULONG64, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_LENGTH>(type_index, proc, modbase);
118                switch(basic_type) {
119                    case BasicType::btNoType:
120                        return "<no basic type>";
121                    case BasicType::btVoid:
122                        return "void";
123                    case BasicType::btChar:
124                        return "char";
125                    case BasicType::btWChar:
126                        return "wchar_t";
127                    case BasicType::btInt:
128                        return "int";
129                    case BasicType::btUInt:
130                        return "unsigned int";
131                    case BasicType::btFloat:
132                        return "float";
133                    case BasicType::btBool:
134                        return "bool";
135                    case BasicType::btLong:
136                        return "long";
137                    case BasicType::btULong:
138                        return "unsigned long";
139                    default:
140                        return "<unknown basic type>";
141                }
142            }
143
144            static std::string resolve_type(ULONG type_index, HANDLE proc, ULONG64 modbase);
145
146            struct class_name_result {
147                bool has_class_name;
148                std::string name;
149            };
150            // Helper for member pointers
151            static class_name_result lookup_class_name(ULONG type_index, HANDLE proc, ULONG64 modbase) {
152                DWORD class_parent_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_CLASSPARENTID, true>(
153                    type_index,
154                    proc,
155                    modbase
156                );
157                if(class_parent_id == (DWORD)-1) {
158                    return {false, ""};
159                } else {
160                    return {true, resolve_type(class_parent_id, proc, modbase)};
161                }
162            }
163
164            struct type_result {
165                std::string base;
166                std::string extent;
167            };
168            // Resolve more complex types
169            // returns [base, extent]
170            static type_result lookup_type(ULONG type_index, HANDLE proc, ULONG64 modbase) {
171                auto tag = get_info<SymTagEnum, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_SYMTAG>(type_index, proc, modbase);
172                switch(tag) {
173                    case SymTagEnum::SymTagBaseType:
174                        return {get_basic_type(type_index, proc, modbase), ""};
175                    case SymTagEnum::SymTagPointerType: {
176                        DWORD underlying_type_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_TYPEID>(
177                            type_index,
178                            proc,
179                            modbase
180                        );
181                        bool is_ref = get_info<BOOL, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_IS_REFERENCE>(
182                            type_index,
183                            proc,
184                            modbase
185                        );
186                        std::string pp = is_ref ? "&" : "*"; // pointer punctuator
187                        auto class_name_res = lookup_class_name(type_index, proc, modbase);
188                        if(class_name_res.has_class_name) {
189                            pp = class_name_res.name + "::" + pp;
190                        }
191                        const auto type = lookup_type(underlying_type_id, proc, modbase);
192                        if(type.extent.empty()) {
193                            return {type.base + (pp.size() > 1 ? " " : "") + pp, ""};
194                        } else {
195                            return {type.base + "(" + pp, ")" + type.extent};
196                        }
197                    }
198                    case SymTagEnum::SymTagArrayType: {
199                        DWORD underlying_type_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_TYPEID>(
200                            type_index,
201                            proc,
202                            modbase
203                        );
204                        DWORD length = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_COUNT>(
205                            type_index,
206                            proc,
207                            modbase
208                        );
209                        const auto type = lookup_type(underlying_type_id, proc, modbase);
210                        return {type.base, "[" + std::to_string(length) + "]" + type.extent};
211                    }
212                    case SymTagEnum::SymTagFunctionType: {
213                        DWORD return_type_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_TYPEID>(
214                            type_index,
215                            proc,
216                            modbase
217                        );
218                        DWORD n_children = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_COUNT, true>(
219                            type_index,
220                            proc,
221                            modbase
222                        );
223                        DWORD class_parent_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_CLASSPARENTID, true>(
224                            type_index,
225                            proc,
226                            modbase
227                        );
228                        int n_ignore = class_parent_id != (DWORD)-1; // ignore this param
229                        // this must be ignored before TI_FINDCHILDREN_PARAMS::Count is set, else error
230                        n_children -= n_ignore;
231                        // return type
232                        const auto return_type = lookup_type(return_type_id, proc, modbase);
233                        if(n_children == 0) {
234                            return {return_type.base, "()" + return_type.extent};
235                        } else {
236                            // alignment should be fine
237                            size_t sz = sizeof(TI_FINDCHILDREN_PARAMS) +
238                                            (n_children) * sizeof(TI_FINDCHILDREN_PARAMS::ChildId[0]);
239                            TI_FINDCHILDREN_PARAMS* children = (TI_FINDCHILDREN_PARAMS*) new char[sz];
240                            children->Start = 0;
241                            children->Count = n_children;
242                            if(
243                                !SymGetTypeInfo(
244                                    proc, modbase, type_index,
245                                    static_cast<::IMAGEHLP_SYMBOL_TYPE_INFO>(
246                                        IMAGEHLP_SYMBOL_TYPE_INFO::TI_FINDCHILDREN
247                                    ),
248                                    children
249                                )
250                            ) {
251                                throw std::logic_error(
252                                    std::string("SymGetTypeInfo failed: ")
253                                    + std::system_error(GetLastError(), std::system_category()).what()
254                                );
255                            }
256                            // get children type
257                            std::string extent = "(";
258                            if(children->Start != 0) {
259                                throw std::logic_error("Error: children->Start == 0");
260                            }
261                            for(std::size_t i = 0; i < n_children; i++) {
262                                extent += (i == 0 ? "" : ", ") + resolve_type(children->ChildId[i], proc, modbase);
263                            }
264                            extent += ")";
265                            delete[] (char*) children;
266                            return {return_type.base, extent + return_type.extent};
267                        }
268                    }
269                    case SymTagEnum::SymTagFunctionArgType: {
270                        DWORD underlying_type_id =
271                            get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_TYPEID>(type_index, proc, modbase);
272                        return {resolve_type(underlying_type_id, proc, modbase), ""};
273                    }
274                    case SymTagEnum::SymTagTypedef:
275                    case SymTagEnum::SymTagEnum:
276                    case SymTagEnum::SymTagUDT:
277                    case SymTagEnum::SymTagBaseClass:
278                        return {
279                            get_info_wchar<IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_SYMNAME>(type_index, proc, modbase), ""
280                        };
281                    default:
282                        return {
283                            "<unknown type " +
284                                std::to_string(static_cast<std::underlying_type<SymTagEnum>::type>(tag)) +
285                                ">",
286                            ""
287                        };
288                };
289            }
290
291            static std::string resolve_type(ULONG type_index, HANDLE proc, ULONG64 modbase) {
292                const auto type = lookup_type(type_index, proc, modbase);
293                return type.base + type.extent;
294            }
295
296            struct function_info {
297                HANDLE proc;
298                ULONG64 modbase;
299                int counter;
300                int n_children;
301                int n_ignore;
302                std::string str;
303            };
304
305            // Enumerates function parameters
306            static BOOL __stdcall enumerator_callback(
307                PSYMBOL_INFO symbol_info,
308                ULONG,
309                PVOID data
310            ) {
311                function_info* ctx = (function_info*)data;
312                if(ctx->counter++ >= ctx->n_children) {
313                    return false;
314                }
315                if(ctx->n_ignore-- > 0) {
316                    return true; // just skip
317                }
318                ctx->str += resolve_type(symbol_info->TypeIndex, ctx->proc, ctx->modbase);
319                if(ctx->counter < ctx->n_children) {
320                    ctx->str += ", ";
321                }
322                return true;
323            }
324
325            std::mutex dbghelp_lock;
326
327            // TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions
328            stacktrace_frame resolve_frame(HANDLE proc, void* addr) {
329                const std::lock_guard<std::mutex> lock(dbghelp_lock); // all dbghelp functions are not thread safe
330                alignas(SYMBOL_INFO) char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
331                SYMBOL_INFO* symbol = (SYMBOL_INFO*)buffer;
332                symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
333                symbol->MaxNameLen = MAX_SYM_NAME;
334                union { DWORD64 a; DWORD b; } displacement;
335                IMAGEHLP_LINE64 line;
336                bool got_line = SymGetLineFromAddr64(proc, (DWORD64)addr, &displacement.b, &line);
337                if(SymFromAddr(proc, (DWORD64)addr, &displacement.a, symbol)) {
338                    if(got_line) {
339                        IMAGEHLP_STACK_FRAME frame;
340                        frame.InstructionOffset = symbol->Address;
341                        // https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symsetcontext
342                        // "If you call SymSetContext to set the context to its current value, the
343                        // function fails but GetLastError returns ERROR_SUCCESS."
344                        // This is the stupidest fucking api I've ever worked with.
345                        if(SymSetContext(proc, &frame, nullptr) == FALSE && GetLastError() != ERROR_SUCCESS) {
346                            fprintf(stderr, "Stack trace: Internal error while calling SymSetContext\n");
347                            return {
348                                reinterpret_cast<uintptr_t>(addr),
349                                static_cast<std::uint_least32_t>(line.LineNumber),
350                                0,
351                                line.FileName,
352                                symbol->Name
353                            };
354                        }
355                        DWORD n_children = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_COUNT, true>(
356                            symbol->TypeIndex,
357                            proc,
358                            symbol->ModBase
359                        );
360                        DWORD class_parent_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_CLASSPARENTID, true>(
361                            symbol->TypeIndex,
362                            proc,
363                            symbol->ModBase
364                        );
365                        function_info fi {
366                            proc,
367                            symbol->ModBase,
368                            0,
369                            int(n_children),
370                            class_parent_id != (DWORD)-1,
371                            ""
372                        };
373                        SymEnumSymbols(proc, 0, nullptr, enumerator_callback, &fi);
374                        std::string signature = symbol->Name + std::string("(") + fi.str + ")";
375                        // There's a phenomina with DIA not inserting commas after template parameters. Fix them here.
376                        static std::regex comma_re(R"(,(?=\S))");
377                        signature = std::regex_replace(signature, comma_re, ", ");
378                        return {
379                            reinterpret_cast<uintptr_t>(addr),
380                            static_cast<std::uint_least32_t>(line.LineNumber),
381                            0,
382                            line.FileName,
383                            signature
384                        };
385                    } else {
386                        return {
387                            reinterpret_cast<uintptr_t>(addr),
388                            0,
389                            0,
390                            "",
391                            symbol->Name
392                        };
393                    }
394                } else {
395                    return {
396                        reinterpret_cast<uintptr_t>(addr),
397                        0,
398                        0,
399                        "",
400                        ""
401                    };
402                }
403            }
404
405            std::vector<stacktrace_frame> resolve_frames(const std::vector<void*>& frames) {
406                std::vector<stacktrace_frame> trace;
407                trace.reserve(frames.size());
408
409                // TODO: When does this need to be called? Can it be moved to the symbolizer?
410                SymSetOptions(SYMOPT_ALLOW_ABSOLUTE_SYMBOLS);
411                HANDLE proc = GetCurrentProcess();
412                if(!SymInitialize(proc, NULL, TRUE)) {
413                    //TODO?
414                    throw std::logic_error("SymInitialize failed");
415                }
416                for(const auto frame : frames) {
417                    trace.push_back(resolve_frame(proc, frame));
418                }
419                if(!SymCleanup(proc)) {
420                    //throw std::logic_error("SymCleanup failed");
421                }
422
423                return trace;
424            }
425        }
426    }
427}
428
429#endif
Note: See TracBrowser for help on using the repository browser.