| Avi Drissman | e4622aa | 2022-09-08 20:36:06 | [diff] [blame] | 1 | // Copyright 2012 The Chromium Authors |
| license.bot | bf09a50 | 2008-08-24 00:55:55 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| [email protected] | 1ffe08c1 | 2008-08-13 11:15:11 | [diff] [blame] | 4 | |
| [email protected] | 5858035 | 2010-10-26 04:07:50 | [diff] [blame] | 5 | #include "base/debug/stack_trace.h" |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 6 | |
| [email protected] | 1ffe08c1 | 2008-08-13 11:15:11 | [diff] [blame] | 7 | #include <windows.h> |
| Peter Birk Pakkenberg | 9e3302d | 2022-06-30 09:40:15 | [diff] [blame] | 8 | |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 9 | #include <dbghelp.h> |
| avi | ebe805c | 2015-12-24 08:20:28 | [diff] [blame] | 10 | #include <stddef.h> |
| [email protected] | 1ffe08c1 | 2008-08-13 11:15:11 | [diff] [blame] | 11 | |
| wez | 73f8d7e | 2017-01-29 06:18:13 | [diff] [blame] | 12 | #include <algorithm> |
| [email protected] | f539333 | 2009-06-03 15:01:29 | [diff] [blame] | 13 | #include <iostream> |
| Daniel Cheng | 84cf518 | 2022-03-05 01:00:05 | [diff] [blame] | 14 | #include <iterator> |
| ananta | f265187 | 2016-06-16 22:21:02 | [diff] [blame] | 15 | #include <memory> |
| [email protected] | f539333 | 2009-06-03 15:01:29 | [diff] [blame] | 16 | |
| Tom Sepez | 978847c | 2025-03-22 03:43:59 | [diff] [blame] | 17 | #include "base/compiler_specific.h" |
| ananta | f265187 | 2016-06-16 22:21:02 | [diff] [blame] | 18 | #include "base/files/file_path.h" |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 19 | #include "base/logging.h" |
| [email protected] | 3b63f8f4 | 2011-03-28 01:54:15 | [diff] [blame] | 20 | #include "base/memory/singleton.h" |
| Peter Kasting | bfb5e7d | 2023-09-21 23:59:35 | [diff] [blame] | 21 | #include "base/strings/strcat_win.h" |
| jdoerrie | 5c4dc4e | 2019-02-01 18:02:33 | [diff] [blame] | 22 | #include "base/strings/string_util.h" |
| Alex Gough | 02b452c | 2025-11-26 18:44:47 | [diff] [blame] | 23 | #include "base/strings/stringprintf.h" |
| [email protected] | 20305ec | 2011-01-21 04:55:52 | [diff] [blame] | 24 | #include "base/synchronization/lock.h" |
| Tom Tan | 15d3ed3 | 2018-11-14 18:20:50 | [diff] [blame] | 25 | #include "build/build_config.h" |
| [email protected] | 1ffe08c1 | 2008-08-13 11:15:11 | [diff] [blame] | 26 | |
| [email protected] | 5858035 | 2010-10-26 04:07:50 | [diff] [blame] | 27 | namespace base { |
| 28 | namespace debug { |
| 29 | |
| [email protected] | 1ffe08c1 | 2008-08-13 11:15:11 | [diff] [blame] | 30 | namespace { |
| 31 | |
| [email protected] | 11b93faa | 2012-11-01 21:58:30 | [diff] [blame] | 32 | // Previous unhandled filter. Will be called if not NULL when we intercept an |
| 33 | // exception. Only used in unit tests. |
| 34 | LPTOP_LEVEL_EXCEPTION_FILTER g_previous_filter = NULL; |
| 35 | |
| jam | 79dc59a | 2015-08-17 03:38:16 | [diff] [blame] | 36 | bool g_initialized_symbols = false; |
| 37 | DWORD g_init_error = ERROR_SUCCESS; |
| Peter Birk Pakkenberg | 9e3302d | 2022-06-30 09:40:15 | [diff] [blame] | 38 | // STATUS_INFO_LENGTH_MISMATCH is declared in <ntstatus.h>, but including that |
| 39 | // header creates a conflict with base/win/windows_types.h, so re-declaring it |
| 40 | // here. |
| 41 | DWORD g_status_info_length_mismatch = 0xC0000004; |
| jam | 79dc59a | 2015-08-17 03:38:16 | [diff] [blame] | 42 | |
| Alex Gough | d007d62 | 2025-09-11 17:55:49 | [diff] [blame] | 43 | // Are symbolized in-process stack dumps enabled? |
| 44 | bool g_in_process_stack_dumps_enabled = false; |
| 45 | |
| [email protected] | 11b93faa | 2012-11-01 21:58:30 | [diff] [blame] | 46 | // Prints the exception call stack. |
| 47 | // This is the unit tests exception filter. |
| 48 | long WINAPI StackDumpExceptionFilter(EXCEPTION_POINTERS* info) { |
| Peter Collingbourne | 76a6655c | 2017-10-12 18:01:03 | [diff] [blame] | 49 | DWORD exc_code = info->ExceptionRecord->ExceptionCode; |
| 50 | std::cerr << "Received fatal exception "; |
| 51 | switch (exc_code) { |
| 52 | case EXCEPTION_ACCESS_VIOLATION: |
| 53 | std::cerr << "EXCEPTION_ACCESS_VIOLATION"; |
| 54 | break; |
| 55 | case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: |
| 56 | std::cerr << "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; |
| 57 | break; |
| 58 | case EXCEPTION_BREAKPOINT: |
| 59 | std::cerr << "EXCEPTION_BREAKPOINT"; |
| 60 | break; |
| 61 | case EXCEPTION_DATATYPE_MISALIGNMENT: |
| 62 | std::cerr << "EXCEPTION_DATATYPE_MISALIGNMENT"; |
| 63 | break; |
| 64 | case EXCEPTION_FLT_DENORMAL_OPERAND: |
| 65 | std::cerr << "EXCEPTION_FLT_DENORMAL_OPERAND"; |
| 66 | break; |
| 67 | case EXCEPTION_FLT_DIVIDE_BY_ZERO: |
| 68 | std::cerr << "EXCEPTION_FLT_DIVIDE_BY_ZERO"; |
| 69 | break; |
| 70 | case EXCEPTION_FLT_INEXACT_RESULT: |
| 71 | std::cerr << "EXCEPTION_FLT_INEXACT_RESULT"; |
| 72 | break; |
| 73 | case EXCEPTION_FLT_INVALID_OPERATION: |
| 74 | std::cerr << "EXCEPTION_FLT_INVALID_OPERATION"; |
| 75 | break; |
| 76 | case EXCEPTION_FLT_OVERFLOW: |
| 77 | std::cerr << "EXCEPTION_FLT_OVERFLOW"; |
| 78 | break; |
| 79 | case EXCEPTION_FLT_STACK_CHECK: |
| 80 | std::cerr << "EXCEPTION_FLT_STACK_CHECK"; |
| 81 | break; |
| 82 | case EXCEPTION_FLT_UNDERFLOW: |
| 83 | std::cerr << "EXCEPTION_FLT_UNDERFLOW"; |
| 84 | break; |
| 85 | case EXCEPTION_ILLEGAL_INSTRUCTION: |
| 86 | std::cerr << "EXCEPTION_ILLEGAL_INSTRUCTION"; |
| 87 | break; |
| 88 | case EXCEPTION_IN_PAGE_ERROR: |
| 89 | std::cerr << "EXCEPTION_IN_PAGE_ERROR"; |
| 90 | break; |
| 91 | case EXCEPTION_INT_DIVIDE_BY_ZERO: |
| 92 | std::cerr << "EXCEPTION_INT_DIVIDE_BY_ZERO"; |
| 93 | break; |
| 94 | case EXCEPTION_INT_OVERFLOW: |
| 95 | std::cerr << "EXCEPTION_INT_OVERFLOW"; |
| 96 | break; |
| 97 | case EXCEPTION_INVALID_DISPOSITION: |
| 98 | std::cerr << "EXCEPTION_INVALID_DISPOSITION"; |
| 99 | break; |
| 100 | case EXCEPTION_NONCONTINUABLE_EXCEPTION: |
| 101 | std::cerr << "EXCEPTION_NONCONTINUABLE_EXCEPTION"; |
| 102 | break; |
| 103 | case EXCEPTION_PRIV_INSTRUCTION: |
| 104 | std::cerr << "EXCEPTION_PRIV_INSTRUCTION"; |
| 105 | break; |
| 106 | case EXCEPTION_SINGLE_STEP: |
| 107 | std::cerr << "EXCEPTION_SINGLE_STEP"; |
| 108 | break; |
| 109 | case EXCEPTION_STACK_OVERFLOW: |
| 110 | std::cerr << "EXCEPTION_STACK_OVERFLOW"; |
| 111 | break; |
| 112 | default: |
| Alex Gough | 02b452c | 2025-11-26 18:44:47 | [diff] [blame] | 113 | std::cerr << base::StringPrintf("0x%08x", exc_code); |
| Peter Collingbourne | 76a6655c | 2017-10-12 18:01:03 | [diff] [blame] | 114 | break; |
| 115 | } |
| 116 | std::cerr << "\n"; |
| 117 | |
| [email protected] | 5ddbf1c | 2013-08-29 01:59:38 | [diff] [blame] | 118 | debug::StackTrace(info).Print(); |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 119 | if (g_previous_filter) { |
| [email protected] | 11b93faa | 2012-11-01 21:58:30 | [diff] [blame] | 120 | return g_previous_filter(info); |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 121 | } |
| [email protected] | 11b93faa | 2012-11-01 21:58:30 | [diff] [blame] | 122 | return EXCEPTION_CONTINUE_SEARCH; |
| 123 | } |
| 124 | |
| [email protected] | d285aba | 2014-07-03 21:09:20 | [diff] [blame] | 125 | FilePath GetExePath() { |
| danakj | d53babfe | 2024-10-09 14:04:16 | [diff] [blame] | 126 | std::array<wchar_t, MAX_PATH> system_buffer; |
| 127 | GetModuleFileName(NULL, system_buffer.data(), system_buffer.size()); |
| 128 | system_buffer.back() = L'\0'; |
| 129 | return FilePath(system_buffer.data()); |
| [email protected] | d285aba | 2014-07-03 21:09:20 | [diff] [blame] | 130 | } |
| 131 | |
| Peter Birk Pakkenberg | 9e3302d | 2022-06-30 09:40:15 | [diff] [blame] | 132 | constexpr size_t kSymInitializeRetryCount = 3; |
| 133 | |
| 134 | // A wrapper for SymInitialize. SymInitialize seems to occasionally fail |
| 135 | // because of an internal race condition. So wrap it and retry a finite |
| 136 | // number of times. |
| 137 | // See crbug.com/1339753 |
| 138 | bool SymInitializeWrapper(HANDLE handle, BOOL invade_process) { |
| 139 | for (size_t i = 0; i < kSymInitializeRetryCount; ++i) { |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 140 | if (SymInitialize(handle, nullptr, invade_process)) { |
| Peter Birk Pakkenberg | 9e3302d | 2022-06-30 09:40:15 | [diff] [blame] | 141 | return true; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 142 | } |
| Peter Birk Pakkenberg | 9e3302d | 2022-06-30 09:40:15 | [diff] [blame] | 143 | |
| 144 | g_init_error = GetLastError(); |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 145 | if (g_init_error != g_status_info_length_mismatch) { |
| Peter Birk Pakkenberg | 9e3302d | 2022-06-30 09:40:15 | [diff] [blame] | 146 | return false; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 147 | } |
| Peter Birk Pakkenberg | 9e3302d | 2022-06-30 09:40:15 | [diff] [blame] | 148 | } |
| 149 | DLOG(ERROR) << "SymInitialize failed repeatedly."; |
| 150 | return false; |
| 151 | } |
| 152 | |
| Peter Kasting | b5edda77 | 2020-07-31 22:35:47 | [diff] [blame] | 153 | bool SymInitializeCurrentProc() { |
| 154 | const HANDLE current_process = GetCurrentProcess(); |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 155 | if (SymInitializeWrapper(current_process, TRUE)) { |
| Peter Kasting | b5edda77 | 2020-07-31 22:35:47 | [diff] [blame] | 156 | return true; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 157 | } |
| Peter Kasting | b5edda77 | 2020-07-31 22:35:47 | [diff] [blame] | 158 | |
| Peter Birk Pakkenberg | 9e3302d | 2022-06-30 09:40:15 | [diff] [blame] | 159 | // g_init_error is updated by SymInitializeWrapper. |
| 160 | // No need to do "g_init_error = GetLastError()" here. |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 161 | if (g_init_error != ERROR_INVALID_PARAMETER) { |
| Peter Kasting | b5edda77 | 2020-07-31 22:35:47 | [diff] [blame] | 162 | return false; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 163 | } |
| Peter Kasting | b5edda77 | 2020-07-31 22:35:47 | [diff] [blame] | 164 | |
| 165 | // SymInitialize() can fail with ERROR_INVALID_PARAMETER when something has |
| 166 | // already called SymInitialize() in this process. For example, when absl |
| 167 | // support for gtest is enabled, it results in absl calling SymInitialize() |
| 168 | // almost immediately after startup. In such a case, try to reinit to see if |
| 169 | // that succeeds. |
| 170 | SymCleanup(current_process); |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 171 | if (SymInitializeWrapper(current_process, TRUE)) { |
| Peter Kasting | b5edda77 | 2020-07-31 22:35:47 | [diff] [blame] | 172 | return true; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 173 | } |
| Peter Kasting | b5edda77 | 2020-07-31 22:35:47 | [diff] [blame] | 174 | |
| Peter Kasting | b5edda77 | 2020-07-31 22:35:47 | [diff] [blame] | 175 | return false; |
| 176 | } |
| 177 | |
| jam | 79dc59a | 2015-08-17 03:38:16 | [diff] [blame] | 178 | bool InitializeSymbols() { |
| Jamie Madill | a1b600e5 | 2019-04-12 16:32:35 | [diff] [blame] | 179 | if (g_initialized_symbols) { |
| 180 | // Force a reinitialization. Will ensure any modules loaded after process |
| 181 | // startup also get symbolized. |
| 182 | SymCleanup(GetCurrentProcess()); |
| 183 | g_initialized_symbols = false; |
| 184 | } |
| jam | 79dc59a | 2015-08-17 03:38:16 | [diff] [blame] | 185 | g_initialized_symbols = true; |
| 186 | // Defer symbol load until they're needed, use undecorated names, and get line |
| 187 | // numbers. |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 188 | SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_UNDNAME | SYMOPT_LOAD_LINES); |
| Peter Kasting | b5edda77 | 2020-07-31 22:35:47 | [diff] [blame] | 189 | if (!SymInitializeCurrentProc()) { |
| jam | 79dc59a | 2015-08-17 03:38:16 | [diff] [blame] | 190 | // When it fails, we should not call debugbreak since it kills the current |
| 191 | // process (prevents future tests from running or kills the browser |
| 192 | // process). |
| 193 | DLOG(ERROR) << "SymInitialize failed: " << g_init_error; |
| 194 | return false; |
| 195 | } |
| 196 | |
| 197 | // When transferring the binaries e.g. between bots, path put |
| 198 | // into the executable will get off. To still retrieve symbols correctly, |
| 199 | // add the directory of the executable to symbol search path. |
| 200 | // All following errors are non-fatal. |
| jdoerrie | 5c4dc4e | 2019-02-01 18:02:33 | [diff] [blame] | 201 | static constexpr size_t kSymbolsArraySize = 1024; |
| Jan Wilken Dörrie | b630aca7 | 2019-12-04 10:59:11 | [diff] [blame] | 202 | wchar_t symbols_path[kSymbolsArraySize]; |
| jam | 79dc59a | 2015-08-17 03:38:16 | [diff] [blame] | 203 | |
| 204 | // Note: The below function takes buffer size as number of characters, |
| 205 | // not number of bytes! |
| Jan Wilken Dörrie | b630aca7 | 2019-12-04 10:59:11 | [diff] [blame] | 206 | if (!SymGetSearchPathW(GetCurrentProcess(), symbols_path, |
| jam | 79dc59a | 2015-08-17 03:38:16 | [diff] [blame] | 207 | kSymbolsArraySize)) { |
| jam | 79dc59a | 2015-08-17 03:38:16 | [diff] [blame] | 208 | g_init_error = GetLastError(); |
| brucedawson | 4af09d1d | 2015-09-17 20:21:44 | [diff] [blame] | 209 | DLOG(WARNING) << "SymGetSearchPath failed: " << g_init_error; |
| jam | 79dc59a | 2015-08-17 03:38:16 | [diff] [blame] | 210 | return false; |
| 211 | } |
| 212 | |
| Peter Kasting | bfb5e7d | 2023-09-21 23:59:35 | [diff] [blame] | 213 | std::wstring new_path = |
| 214 | StrCat({symbols_path, L";", GetExePath().DirName().value()}); |
| Jan Wilken Dörrie | b630aca7 | 2019-12-04 10:59:11 | [diff] [blame] | 215 | if (!SymSetSearchPathW(GetCurrentProcess(), new_path.c_str())) { |
| jam | 79dc59a | 2015-08-17 03:38:16 | [diff] [blame] | 216 | g_init_error = GetLastError(); |
| 217 | DLOG(WARNING) << "SymSetSearchPath failed." << g_init_error; |
| 218 | return false; |
| 219 | } |
| 220 | |
| 221 | g_init_error = ERROR_SUCCESS; |
| 222 | return true; |
| 223 | } |
| 224 | |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 225 | // SymbolContext is a threadsafe singleton that wraps the DbgHelp Sym* family |
| 226 | // of functions. The Sym* family of functions may only be invoked by one |
| 227 | // thread at a time. SymbolContext code may access a symbol server over the |
| 228 | // network while holding the lock for this singleton. In the case of high |
| [email protected] | e645c7a | 2012-07-25 21:43:44 | [diff] [blame] | 229 | // latency, this code will adversely affect performance. |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 230 | // |
| 231 | // There is also a known issue where this backtrace code can interact |
| 232 | // badly with breakpad if breakpad is invoked in a separate thread while |
| 233 | // we are using the Sym* functions. This is because breakpad does now |
| 234 | // share a lock with this function. See this related bug: |
| 235 | // |
| Mark Mentovai | ebb9ddd6 | 2017-09-25 17:24:41 | [diff] [blame] | 236 | // https://crbug.com/google-breakpad/311 |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 237 | // |
| 238 | // This is a very unlikely edge case, and the current solution is to |
| 239 | // just ignore it. |
| 240 | class SymbolContext { |
| 241 | public: |
| [email protected] | 8e8bb6d | 2010-12-13 08:18:55 | [diff] [blame] | 242 | static SymbolContext* GetInstance() { |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 243 | // We use a leaky singleton because code may call this during process |
| 244 | // termination. |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 245 | return Singleton<SymbolContext, LeakySingletonTraits<SymbolContext>>::get(); |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 246 | } |
| 247 | |
| Peter Boström | 75cd3c0 | 2021-09-28 15:23:18 | [diff] [blame] | 248 | SymbolContext(const SymbolContext&) = delete; |
| 249 | SymbolContext& operator=(const SymbolContext&) = delete; |
| 250 | |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 251 | // For the given trace, attempts to resolve the symbols, and output a trace |
| 252 | // to the ostream os. The format for each line of the backtrace is: |
| 253 | // |
| 254 | // <tab>SymbolName[0xAddress+Offset] (FileName:LineNo) |
| [email protected] | f539333 | 2009-06-03 15:01:29 | [diff] [blame] | 255 | // |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 256 | // This function should only be called if Init() has been called. We do not |
| 257 | // LOG(FATAL) here because this code is called might be triggered by a |
| [email protected] | d285aba | 2014-07-03 21:09:20 | [diff] [blame] | 258 | // LOG(FATAL) itself. Also, it should not be calling complex code that is |
| 259 | // extensible like PathService since that can in turn fire CHECKs. |
| Jan Keitel | 4161bcb | 2024-08-01 12:44:24 | [diff] [blame] | 260 | void OutputTraceToStream(base::span<const void* const> traces, |
| Mason Freed | b9ef2b6 | 2018-09-10 17:17:27 | [diff] [blame] | 261 | std::ostream* os, |
| Greg Thompson | 8b1b295b | 2024-04-10 07:44:14 | [diff] [blame] | 262 | cstring_view prefix_string) { |
| jdoerrie | 5c4dc4e | 2019-02-01 18:02:33 | [diff] [blame] | 263 | AutoLock lock(lock_); |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 264 | |
| Jan Keitel | 4161bcb | 2024-08-01 12:44:24 | [diff] [blame] | 265 | for (size_t i = 0; (i < traces.size()) && os->good(); ++i) { |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 266 | const int kMaxNameLength = 256; |
| Jan Keitel | 4161bcb | 2024-08-01 12:44:24 | [diff] [blame] | 267 | DWORD_PTR frame = reinterpret_cast<DWORD_PTR>(traces[i]); |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 268 | |
| 269 | // Code adapted from MSDN example: |
| 270 | // http://msdn.microsoft.com/en-us/library/ms680578(VS.85).aspx |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 271 | ULONG64 buffer[(sizeof(SYMBOL_INFO) + kMaxNameLength * sizeof(wchar_t) + |
| 272 | sizeof(ULONG64) - 1) / |
| 273 | sizeof(ULONG64)]; |
| Tom Sepez | 978847c | 2025-03-22 03:43:59 | [diff] [blame] | 274 | UNSAFE_TODO(memset(buffer, 0, sizeof(buffer))); |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 275 | |
| 276 | // Initialize symbol information retrieval structures. |
| 277 | DWORD64 sym_displacement = 0; |
| 278 | PSYMBOL_INFO symbol = reinterpret_cast<PSYMBOL_INFO>(&buffer[0]); |
| 279 | symbol->SizeOfStruct = sizeof(SYMBOL_INFO); |
| [email protected] | b3f97bc7 | 2009-10-27 15:54:05 | [diff] [blame] | 280 | symbol->MaxNameLen = kMaxNameLength - 1; |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 281 | BOOL has_symbol = |
| 282 | SymFromAddr(GetCurrentProcess(), frame, &sym_displacement, symbol); |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 283 | |
| 284 | // Attempt to retrieve line number information. |
| 285 | DWORD line_displacement = 0; |
| 286 | IMAGEHLP_LINE64 line = {}; |
| 287 | line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); |
| [email protected] | de1b764f | 2009-08-24 15:36:41 | [diff] [blame] | 288 | BOOL has_line = SymGetLineFromAddr64(GetCurrentProcess(), frame, |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 289 | &line_displacement, &line); |
| 290 | |
| 291 | // Output the backtrace line. |
| Greg Thompson | 8b1b295b | 2024-04-10 07:44:14 | [diff] [blame] | 292 | (*os) << prefix_string << "\t"; |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 293 | if (has_symbol) { |
| Alex Gough | 02b452c | 2025-11-26 18:44:47 | [diff] [blame] | 294 | (*os) << symbol->Name << " " |
| 295 | << base::StringPrintf("[%p+%x]", traces[i], sym_displacement); |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 296 | } else { |
| [email protected] | e645c7a | 2012-07-25 21:43:44 | [diff] [blame] | 297 | // If there is no symbol information, add a spacer. |
| Alex Gough | 02b452c | 2025-11-26 18:44:47 | [diff] [blame] | 298 | (*os) << "(No symbol) " << base::StringPrintf("[%p]", traces[i]); |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 299 | } |
| 300 | if (has_line) { |
| Alex Gough | 02b452c | 2025-11-26 18:44:47 | [diff] [blame] | 301 | (*os) << " (" << line.FileName << ":" |
| 302 | << base::StringPrintf("%lu", line.LineNumber) << ")"; |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 303 | } |
| 304 | (*os) << "\n"; |
| 305 | } |
| 306 | } |
| 307 | |
| [email protected] | de1b764f | 2009-08-24 15:36:41 | [diff] [blame] | 308 | private: |
| 309 | friend struct DefaultSingletonTraits<SymbolContext>; |
| 310 | |
| Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 311 | SymbolContext() { InitializeSymbols(); } |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 312 | |
| jdoerrie | 5c4dc4e | 2019-02-01 18:02:33 | [diff] [blame] | 313 | Lock lock_; |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 314 | }; |
| 315 | |
| Alex Gough | 0bbe87d | 2025-09-12 21:12:11 | [diff] [blame] | 316 | // Raw output when symbols are not available. |
| 317 | void OutputAddressesWithPrefix(std::ostream* os, |
| 318 | cstring_view prefix_string, |
| 319 | span<const void* const> addresses) { |
| 320 | for (const void* const addr : addresses) { |
| Alex Gough | 02b452c | 2025-11-26 18:44:47 | [diff] [blame] | 321 | (*os) << prefix_string << "\t" << base::StringPrintf("%p", addr) << "\n"; |
| Alex Gough | 0bbe87d | 2025-09-12 21:12:11 | [diff] [blame] | 322 | if (!os->good()) { |
| 323 | break; |
| 324 | } |
| 325 | } |
| 326 | } |
| 327 | |
| [email protected] | 1ffe08c1 | 2008-08-13 11:15:11 | [diff] [blame] | 328 | } // namespace |
| 329 | |
| Anna Tsvirchkova | e4a1037 | 2023-06-28 08:52:41 | [diff] [blame] | 330 | bool EnableInProcessStackDumping() { |
| [email protected] | 11b93faa | 2012-11-01 21:58:30 | [diff] [blame] | 331 | // Add stack dumping support on exception on windows. Similar to OS_POSIX |
| 332 | // signal() handling in process_util_posix.cc. |
| 333 | g_previous_filter = SetUnhandledExceptionFilter(&StackDumpExceptionFilter); |
| jam | 79dc59a | 2015-08-17 03:38:16 | [diff] [blame] | 334 | |
| 335 | // Need to initialize symbols early in the process or else this fails on |
| 336 | // swarming (since symbols are in different directory than in the exes) and |
| 337 | // also release x64. |
| Alex Gough | d007d62 | 2025-09-11 17:55:49 | [diff] [blame] | 338 | g_in_process_stack_dumps_enabled = InitializeSymbols(); |
| 339 | return g_in_process_stack_dumps_enabled; |
| 340 | } |
| 341 | |
| 342 | bool InProcessStackDumpingEnabled() { |
| 343 | return g_in_process_stack_dumps_enabled; |
| [email protected] | 11b93faa | 2012-11-01 21:58:30 | [diff] [blame] | 344 | } |
| 345 | |
| Alex Gough | 0bbe87d | 2025-09-12 21:12:11 | [diff] [blame] | 346 | bool DisableInProcessStackDumpingForTesting() { |
| 347 | g_previous_filter = SetUnhandledExceptionFilter(g_previous_filter); |
| 348 | g_in_process_stack_dumps_enabled = false; |
| 349 | return true; |
| 350 | } |
| 351 | |
| danakj | e75c6c81 | 2024-07-26 20:37:47 | [diff] [blame] | 352 | NOINLINE size_t CollectStackTrace(span<const void*> trace) { |
| [email protected] | 0fe31f2 | 2009-09-08 23:53:53 | [diff] [blame] | 353 | // When walking our own stack, use CaptureStackBackTrace(). |
| danakj | e75c6c81 | 2024-07-26 20:37:47 | [diff] [blame] | 354 | return CaptureStackBackTrace(0, trace.size(), |
| 355 | const_cast<void**>(trace.data()), NULL); |
| [email protected] | 0fe31f2 | 2009-09-08 23:53:53 | [diff] [blame] | 356 | } |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 357 | |
| jam | 79dc59a | 2015-08-17 03:38:16 | [diff] [blame] | 358 | StackTrace::StackTrace(EXCEPTION_POINTERS* exception_pointers) { |
| 359 | InitTrace(exception_pointers->ContextRecord); |
| wittman | 7808da7 | 2015-02-03 17:46:51 | [diff] [blame] | 360 | } |
| 361 | |
| rnk | 0565cdd6 | 2015-10-06 16:48:30 | [diff] [blame] | 362 | StackTrace::StackTrace(const CONTEXT* context) { |
| jam | 79dc59a | 2015-08-17 03:38:16 | [diff] [blame] | 363 | InitTrace(context); |
| wittman | 7808da7 | 2015-02-03 17:46:51 | [diff] [blame] | 364 | } |
| 365 | |
| rnk | 0565cdd6 | 2015-10-06 16:48:30 | [diff] [blame] | 366 | void StackTrace::InitTrace(const CONTEXT* context_record) { |
| Greg Thompson | 75724f9 | 2024-03-21 08:31:27 | [diff] [blame] | 367 | if (ShouldSuppressOutput()) { |
| 368 | CHECK_EQ(count_, 0U); |
| Peter Kasting | 025a9425 | 2025-01-29 21:28:37 | [diff] [blame] | 369 | std::ranges::fill(trace_, nullptr); |
| Greg Thompson | 75724f9 | 2024-03-21 08:31:27 | [diff] [blame] | 370 | return; |
| 371 | } |
| 372 | |
| rnk | 0565cdd6 | 2015-10-06 16:48:30 | [diff] [blame] | 373 | // StackWalk64 modifies the register context in place, so we have to copy it |
| 374 | // so that downstream exception handlers get the right context. The incoming |
| 375 | // context may have had more register state (YMM, etc) than we need to unwind |
| 376 | // the stack. Typically StackWalk64 only needs integer and control registers. |
| 377 | CONTEXT context_copy; |
| Tom Sepez | 978847c | 2025-03-22 03:43:59 | [diff] [blame] | 378 | UNSAFE_TODO(memcpy(&context_copy, context_record, sizeof(context_copy))); |
| rnk | 0565cdd6 | 2015-10-06 16:48:30 | [diff] [blame] | 379 | context_copy.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL; |
| 380 | |
| wittman | 7808da7 | 2015-02-03 17:46:51 | [diff] [blame] | 381 | // When walking an exception stack, we need to use StackWalk64(). |
| 382 | count_ = 0; |
| [email protected] | 0fe31f2 | 2009-09-08 23:53:53 | [diff] [blame] | 383 | // Initialize stack walking. |
| Arthur Sonzogni | d9cbbfb0 | 2025-08-29 16:24:15 | [diff] [blame] | 384 | STACKFRAME64 stack_frame = {}; |
| Tom Tan | 15d3ed3 | 2018-11-14 18:20:50 | [diff] [blame] | 385 | #if defined(ARCH_CPU_X86_64) |
| Peter Kasting | d892d34 | 2022-05-27 21:56:36 | [diff] [blame] | 386 | DWORD machine_type = IMAGE_FILE_MACHINE_AMD64; |
| wittman | 7808da7 | 2015-02-03 17:46:51 | [diff] [blame] | 387 | stack_frame.AddrPC.Offset = context_record->Rip; |
| 388 | stack_frame.AddrFrame.Offset = context_record->Rbp; |
| 389 | stack_frame.AddrStack.Offset = context_record->Rsp; |
| Tom Tan | 15d3ed3 | 2018-11-14 18:20:50 | [diff] [blame] | 390 | #elif defined(ARCH_CPU_ARM64) |
| Peter Kasting | d892d34 | 2022-05-27 21:56:36 | [diff] [blame] | 391 | DWORD machine_type = IMAGE_FILE_MACHINE_ARM64; |
| Tom Tan | 15d3ed3 | 2018-11-14 18:20:50 | [diff] [blame] | 392 | stack_frame.AddrPC.Offset = context_record->Pc; |
| 393 | stack_frame.AddrFrame.Offset = context_record->Fp; |
| 394 | stack_frame.AddrStack.Offset = context_record->Sp; |
| 395 | #elif defined(ARCH_CPU_X86) |
| Peter Kasting | d892d34 | 2022-05-27 21:56:36 | [diff] [blame] | 396 | DWORD machine_type = IMAGE_FILE_MACHINE_I386; |
| wittman | 7808da7 | 2015-02-03 17:46:51 | [diff] [blame] | 397 | stack_frame.AddrPC.Offset = context_record->Eip; |
| 398 | stack_frame.AddrFrame.Offset = context_record->Ebp; |
| 399 | stack_frame.AddrStack.Offset = context_record->Esp; |
| Tom Tan | 15d3ed3 | 2018-11-14 18:20:50 | [diff] [blame] | 400 | #else |
| 401 | #error Unsupported Windows Arch |
| [email protected] | 0fe31f2 | 2009-09-08 23:53:53 | [diff] [blame] | 402 | #endif |
| 403 | stack_frame.AddrPC.Mode = AddrModeFlat; |
| 404 | stack_frame.AddrFrame.Mode = AddrModeFlat; |
| 405 | stack_frame.AddrStack.Mode = AddrModeFlat; |
| Avi Drissman | 72b4cba | 2019-01-10 21:54:23 | [diff] [blame] | 406 | while (StackWalk64(machine_type, GetCurrentProcess(), GetCurrentThread(), |
| 407 | &stack_frame, &context_copy, NULL, |
| 408 | &SymFunctionTableAccess64, &SymGetModuleBase64, NULL) && |
| Daniel Cheng | 84cf518 | 2022-03-05 01:00:05 | [diff] [blame] | 409 | count_ < std::size(trace_)) { |
| [email protected] | 0fe31f2 | 2009-09-08 23:53:53 | [diff] [blame] | 410 | trace_[count_++] = reinterpret_cast<void*>(stack_frame.AddrPC.Offset); |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 411 | } |
| [email protected] | e645c7a | 2012-07-25 21:43:44 | [diff] [blame] | 412 | |
| Peter Kasting | 025a9425 | 2025-01-29 21:28:37 | [diff] [blame] | 413 | std::ranges::fill(span(trace_).last(trace_.size() - count_), nullptr); |
| [email protected] | 5485cdc | 2009-01-16 21:17:30 | [diff] [blame] | 414 | } |
| 415 | |
| Greg Thompson | c82cbc9 | 2024-04-10 07:36:06 | [diff] [blame] | 416 | // static |
| Greg Thompson | 8b1b295b | 2024-04-10 07:44:14 | [diff] [blame] | 417 | void StackTrace::PrintMessageWithPrefix(cstring_view prefix_string, |
| Greg Thompson | c82cbc9 | 2024-04-10 07:36:06 | [diff] [blame] | 418 | cstring_view message) { |
| Greg Thompson | 8b1b295b | 2024-04-10 07:44:14 | [diff] [blame] | 419 | std::cerr << prefix_string << message; |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 420 | } |
| 421 | |
| Greg Thompson | 8b1b295b | 2024-04-10 07:44:14 | [diff] [blame] | 422 | void StackTrace::PrintWithPrefixImpl(cstring_view prefix_string) const { |
| Greg Thompson | c82cbc9 | 2024-04-10 07:36:06 | [diff] [blame] | 423 | OutputToStreamWithPrefixImpl(&std::cerr, prefix_string); |
| 424 | } |
| 425 | |
| Greg Thompson | 8b1b295b | 2024-04-10 07:44:14 | [diff] [blame] | 426 | void StackTrace::OutputToStreamWithPrefixImpl( |
| 427 | std::ostream* os, |
| 428 | cstring_view prefix_string) const { |
| Alex Gough | 0bbe87d | 2025-09-12 21:12:11 | [diff] [blame] | 429 | if (!InProcessStackDumpingEnabled()) { |
| 430 | (*os) << "Symbols not available. Dumping unresolved backtrace:\n"; |
| 431 | OutputAddressesWithPrefix(os, prefix_string, addresses()); |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 432 | } else { |
| Alex Gough | 0bbe87d | 2025-09-12 21:12:11 | [diff] [blame] | 433 | SymbolContext* context = SymbolContext::GetInstance(); |
| 434 | if (g_init_error != ERROR_SUCCESS) { |
| 435 | (*os) << "Error initializing symbols (" << g_init_error |
| 436 | << "). Dumping unresolved backtrace:\n"; |
| 437 | OutputAddressesWithPrefix(os, prefix_string, addresses()); |
| 438 | } else { |
| 439 | context->OutputTraceToStream(addresses(), os, prefix_string); |
| 440 | } |
| [email protected] | 96fd003 | 2009-04-24 00:13:08 | [diff] [blame] | 441 | } |
| [email protected] | 5485cdc | 2009-01-16 21:17:30 | [diff] [blame] | 442 | } |
| [email protected] | 5858035 | 2010-10-26 04:07:50 | [diff] [blame] | 443 | |
| 444 | } // namespace debug |
| 445 | } // namespace base |