Line data Source code
1 : /* Copyright (C) 2021 Wildfire Games.
2 : *
3 : * Permission is hereby granted, free of charge, to any person obtaining
4 : * a copy of this software and associated documentation files (the
5 : * "Software"), to deal in the Software without restriction, including
6 : * without limitation the rights to use, copy, modify, merge, publish,
7 : * distribute, sublicense, and/or sell copies of the Software, and to
8 : * permit persons to whom the Software is furnished to do so, subject to
9 : * the following conditions:
10 : *
11 : * The above copyright notice and this permission notice shall be included
12 : * in all copies or substantial portions of the Software.
13 : *
14 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 : * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 : * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 : * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 : * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 : * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 : * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 : */
22 :
23 : // Note: This used to use BFD to get more useful debugging information.
24 : // (See SVN r8270 if you want that code.)
25 : // That requires binutils at runtime, which hits some bugs and library versioning
26 : // issues on some Linux distros.
27 : // The debugging info reported by this code with BFD wasn't especially useful,
28 : // and it's easy enough to get people to run in gdb if we want a proper backtrace.
29 : // So we now go with the simple approach of not using BFD.
30 :
31 : #include "precompiled.h"
32 :
33 : #include "lib/sysdep/sysdep.h"
34 : #include "lib/debug.h"
35 :
36 : #include <algorithm>
37 : #include <cstring>
38 : #include <sys/syscall.h>
39 :
40 : #if OS_ANDROID
41 :
42 : // Android NDK doesn't support backtrace()
43 : // TODO: use unwind.h or similar?
44 :
45 : void* debug_GetCaller(void* UNUSED(context), const wchar_t* UNUSED(lastFuncToSkip))
46 : {
47 : return NULL;
48 : }
49 :
50 : Status debug_DumpStack(wchar_t* UNUSED(buf), size_t UNUSED(max_chars), void* UNUSED(context), const wchar_t* UNUSED(lastFuncToSkip))
51 : {
52 : return ERR::NOT_SUPPORTED;
53 : }
54 :
55 : Status debug_ResolveSymbol(void* UNUSED(ptr_of_interest), wchar_t* UNUSED(sym_name), wchar_t* UNUSED(file), int* UNUSED(line))
56 : {
57 : return ERR::NOT_SUPPORTED;
58 : }
59 :
60 : #else
61 :
62 : #include <execinfo.h>
63 :
64 0 : void* debug_GetCaller(void* UNUSED(context), const wchar_t* UNUSED(lastFuncToSkip))
65 : {
66 : // bt[0] == this function
67 : // bt[1] == our caller
68 : // bt[2] == the first caller they are interested in
69 : // HACK: we currently don't support lastFuncToSkip (would require debug information),
70 : // instead just returning the caller of the function calling us
71 : void *bt[3];
72 0 : int bt_size = backtrace(bt, 3);
73 0 : if (bt_size < 3)
74 0 : return NULL;
75 0 : return bt[2];
76 : }
77 :
78 0 : Status debug_DumpStack(wchar_t* buf, size_t max_chars, void* UNUSED(context), const wchar_t* UNUSED(lastFuncToSkip))
79 : {
80 : static const size_t N_FRAMES = 16;
81 : void *bt[N_FRAMES];
82 0 : int bt_size = 0;
83 0 : wchar_t *bufpos = buf;
84 0 : wchar_t *bufend = buf + max_chars;
85 :
86 0 : bt_size = backtrace(bt, ARRAY_SIZE(bt));
87 :
88 : // Assumed max length of a single print-out
89 : static const size_t MAX_OUT_CHARS = 1024;
90 :
91 0 : for (size_t i = 0; (int)i < bt_size && bufpos+MAX_OUT_CHARS < bufend; i++)
92 : {
93 : wchar_t file[DEBUG_FILE_CHARS];
94 : wchar_t symbol[DEBUG_SYMBOL_CHARS];
95 : int line;
96 : int len;
97 :
98 0 : if (debug_ResolveSymbol(bt[i], symbol, file, &line) == 0)
99 : {
100 0 : if (file[0])
101 0 : len = swprintf(bufpos, MAX_OUT_CHARS, L"(%p) %ls:%d %ls\n", bt[i], file, line, symbol);
102 : else
103 0 : len = swprintf(bufpos, MAX_OUT_CHARS, L"(%p) %ls\n", bt[i], symbol);
104 : }
105 : else
106 : {
107 0 : len = swprintf(bufpos, MAX_OUT_CHARS, L"(%p)\n", bt[i]);
108 : }
109 :
110 0 : if (len < 0)
111 : {
112 : // MAX_OUT_CHARS exceeded, realistically this was caused by some
113 : // mindbogglingly long symbol name... replace the end with an
114 : // ellipsis and a newline
115 0 : memcpy(&bufpos[MAX_OUT_CHARS-6], L"...\n", 5*sizeof(wchar_t));
116 0 : len = MAX_OUT_CHARS;
117 : }
118 :
119 0 : bufpos += len;
120 : }
121 :
122 0 : return INFO::OK;
123 : }
124 :
125 0 : Status debug_ResolveSymbol(void* ptr_of_interest, wchar_t* sym_name, wchar_t* file, int* line)
126 : {
127 0 : if (sym_name)
128 0 : *sym_name = 0;
129 0 : if (file)
130 0 : *file = 0;
131 0 : if (line)
132 0 : *line = 0;
133 :
134 0 : char** symbols = backtrace_symbols(&ptr_of_interest, 1);
135 0 : if (symbols)
136 : {
137 0 : swprintf_s(sym_name, DEBUG_SYMBOL_CHARS, L"%hs", symbols[0]);
138 0 : free(symbols);
139 :
140 : // (Note that this will usually return a pretty useless string,
141 : // because we compile with -fvisibility=hidden and there won't be
142 : // any exposed symbols for backtrace_symbols to report.)
143 :
144 0 : return INFO::OK;
145 : }
146 : else
147 : {
148 0 : return ERR::FAIL;
149 : }
150 : }
151 :
152 : #endif
153 :
154 : /* This is basically a reimplementation of the pthread_setname_np() glibc
155 : * function, to make it work on every Linux distribution, no matter the libc
156 : * used.
157 : *
158 : * The thread name limit is 16 (including the terminating null byte) on Linux,
159 : * so we have to cut the provided name at 15 bytes.
160 : *
161 : * This API exists since Linux 2.6.33, on older kernels the user will just not
162 : * get thread names.
163 : */
164 14 : void debug_SetThreadName(const char* name)
165 : {
166 14 : constexpr size_t MAX_THREAD_NAME_LEN = 15;
167 14 : constexpr size_t MAX_FILE_PATH_LEN = 32;
168 :
169 : char pathname[MAX_FILE_PATH_LEN];
170 14 : pid_t tid = syscall(SYS_gettid);
171 14 : snprintf(pathname, MAX_FILE_PATH_LEN, "/proc/self/task/%d/comm", tid);
172 :
173 14 : FILE* comm = fopen(pathname, "w");
174 14 : if (!comm)
175 0 : return;
176 :
177 28 : std::string limited_name(name, std::min(strlen(name), MAX_THREAD_NAME_LEN));
178 14 : fputs(limited_name.c_str(), comm);
179 14 : fclose(comm);
180 3 : }
|