|
1 // Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. |
|
2 // Use of this source code is governed by a BSD-style license that can be |
|
3 // found in the LICENSE file. |
|
4 |
|
5 #include "base/debug_util.h" |
|
6 |
|
7 #include <windows.h> |
|
8 #include <dbghelp.h> |
|
9 #include <iostream> |
|
10 |
|
11 #include "base/basictypes.h" |
|
12 #include "base/lock.h" |
|
13 #include "base/logging.h" |
|
14 #include "base/singleton.h" |
|
15 |
|
16 namespace { |
|
17 |
|
18 // Minimalist key reader. |
|
19 // Note: Does not use the CRT. |
|
20 bool RegReadString(HKEY root, const wchar_t* subkey, |
|
21 const wchar_t* value_name, wchar_t* buffer, int* len) { |
|
22 HKEY key = NULL; |
|
23 DWORD res = RegOpenKeyEx(root, subkey, 0, KEY_READ, &key); |
|
24 if (ERROR_SUCCESS != res || key == NULL) |
|
25 return false; |
|
26 |
|
27 DWORD type = 0; |
|
28 DWORD buffer_size = *len * sizeof(wchar_t); |
|
29 // We don't support REG_EXPAND_SZ. |
|
30 res = RegQueryValueEx(key, value_name, NULL, &type, |
|
31 reinterpret_cast<BYTE*>(buffer), &buffer_size); |
|
32 if (ERROR_SUCCESS == res && buffer_size != 0 && type == REG_SZ) { |
|
33 // Make sure the buffer is NULL terminated. |
|
34 buffer[*len - 1] = 0; |
|
35 *len = lstrlen(buffer); |
|
36 RegCloseKey(key); |
|
37 return true; |
|
38 } |
|
39 RegCloseKey(key); |
|
40 return false; |
|
41 } |
|
42 |
|
43 // Replaces each "%ld" in input per a value. Not efficient but it works. |
|
44 // Note: Does not use the CRT. |
|
45 bool StringReplace(const wchar_t* input, int value, wchar_t* output, |
|
46 int output_len) { |
|
47 memset(output, 0, output_len*sizeof(wchar_t)); |
|
48 int input_len = lstrlen(input); |
|
49 |
|
50 for (int i = 0; i < input_len; ++i) { |
|
51 int current_output_len = lstrlen(output); |
|
52 |
|
53 if (input[i] == L'%' && input[i + 1] == L'l' && input[i + 2] == L'd') { |
|
54 // Make sure we have enough place left. |
|
55 if ((current_output_len + 12) >= output_len) |
|
56 return false; |
|
57 |
|
58 // Cheap _itow(). |
|
59 wsprintf(output+current_output_len, L"%d", value); |
|
60 i += 2; |
|
61 } else { |
|
62 if (current_output_len >= output_len) |
|
63 return false; |
|
64 output[current_output_len] = input[i]; |
|
65 } |
|
66 } |
|
67 return true; |
|
68 } |
|
69 |
|
70 // SymbolContext is a threadsafe singleton that wraps the DbgHelp Sym* family |
|
71 // of functions. The Sym* family of functions may only be invoked by one |
|
72 // thread at a time. SymbolContext code may access a symbol server over the |
|
73 // network while holding the lock for this singleton. In the case of high |
|
74 // latency, this code will adversly affect performance. |
|
75 // |
|
76 // There is also a known issue where this backtrace code can interact |
|
77 // badly with breakpad if breakpad is invoked in a separate thread while |
|
78 // we are using the Sym* functions. This is because breakpad does now |
|
79 // share a lock with this function. See this related bug: |
|
80 // |
|
81 // http://code.google.com/p/google-breakpad/issues/detail?id=311 |
|
82 // |
|
83 // This is a very unlikely edge case, and the current solution is to |
|
84 // just ignore it. |
|
85 class SymbolContext { |
|
86 public: |
|
87 static SymbolContext* Get() { |
|
88 // We use a leaky singleton because code may call this during process |
|
89 // termination. |
|
90 return |
|
91 Singleton<SymbolContext, LeakySingletonTraits<SymbolContext> >::get(); |
|
92 } |
|
93 |
|
94 // Initializes the symbols for the process if it hasn't been done yet. |
|
95 // Subsequent calls will not reinitialize the symbol, but instead return |
|
96 // the error code from the first call. |
|
97 bool Init() { |
|
98 AutoLock lock(lock_); |
|
99 if (!initialized_) { |
|
100 process_ = GetCurrentProcess(); |
|
101 |
|
102 // Defer symbol load until they're needed, use undecorated names, and |
|
103 // get line numbers. |
|
104 SymSetOptions(SYMOPT_DEFERRED_LOADS | |
|
105 SYMOPT_UNDNAME | |
|
106 SYMOPT_LOAD_LINES); |
|
107 if (SymInitialize(process_, NULL, TRUE)) { |
|
108 init_error_ = ERROR_SUCCESS; |
|
109 } else { |
|
110 init_error_ = GetLastError(); |
|
111 } |
|
112 } |
|
113 |
|
114 initialized_ = true; |
|
115 return init_error_ == ERROR_SUCCESS; |
|
116 } |
|
117 |
|
118 // Returns the error code of a failed initialization. This should only be |
|
119 // called if Init() has been called. We do not CHROMIUM_LOG(FATAL) here because |
|
120 // this code is called might be triggered by a CHROMIUM_LOG(FATAL) itself. Instead, |
|
121 // we log an ERROR, and return ERROR_INVALID_DATA. |
|
122 DWORD init_error() { |
|
123 if (!initialized_) { |
|
124 CHROMIUM_LOG(ERROR) << "Calling GetInitError() before Init() was called. " |
|
125 << "Returning ERROR_INVALID_DATA."; |
|
126 return ERROR_INVALID_DATA; |
|
127 } |
|
128 |
|
129 return init_error_; |
|
130 } |
|
131 |
|
132 // Returns the process this was initialized for. This should only be |
|
133 // called if Init() has been called. We CHROMIUM_LOG(ERROR) in this situation. |
|
134 // CHROMIUM_LOG(FATAL) is not used because this code is might be triggered |
|
135 // by a CHROMIUM_LOG(FATAL) itself. |
|
136 HANDLE process() { |
|
137 if (!initialized_) { |
|
138 CHROMIUM_LOG(ERROR) << "Calling process() before Init() was called. " |
|
139 << "Returning NULL."; |
|
140 return NULL; |
|
141 } |
|
142 |
|
143 return process_; |
|
144 } |
|
145 |
|
146 // For the given trace, attempts to resolve the symbols, and output a trace |
|
147 // to the ostream os. The format for each line of the backtrace is: |
|
148 // |
|
149 // <tab>SymbolName[0xAddress+Offset] (FileName:LineNo) |
|
150 // |
|
151 // This function should only be called if Init() has been called. We do not |
|
152 // CHROMIUM_LOG(FATAL) here because this code is called might be triggered by a |
|
153 // CHROMIUM_LOG(FATAL) itself. |
|
154 void OutputTraceToStream(const std::vector<void*>& trace, std::ostream* os) { |
|
155 AutoLock lock(lock_); |
|
156 |
|
157 for (size_t i = 0; (i < trace.size()) && os->good(); ++i) { |
|
158 const int kMaxNameLength = 256; |
|
159 DWORD_PTR frame = reinterpret_cast<DWORD_PTR>(trace[i]); |
|
160 |
|
161 // Code adapted from MSDN example: |
|
162 // http://msdn.microsoft.com/en-us/library/ms680578(VS.85).aspx |
|
163 ULONG64 buffer[ |
|
164 (sizeof(SYMBOL_INFO) + |
|
165 kMaxNameLength * sizeof(wchar_t) + |
|
166 sizeof(ULONG64) - 1) / |
|
167 sizeof(ULONG64)]; |
|
168 |
|
169 // Initialize symbol information retrieval structures. |
|
170 DWORD64 sym_displacement = 0; |
|
171 PSYMBOL_INFO symbol = reinterpret_cast<PSYMBOL_INFO>(&buffer[0]); |
|
172 symbol->SizeOfStruct = sizeof(SYMBOL_INFO); |
|
173 symbol->MaxNameLen = kMaxNameLength; |
|
174 BOOL has_symbol = SymFromAddr(process(), frame, |
|
175 &sym_displacement, symbol); |
|
176 |
|
177 // Attempt to retrieve line number information. |
|
178 DWORD line_displacement = 0; |
|
179 IMAGEHLP_LINE64 line = {}; |
|
180 line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); |
|
181 BOOL has_line = SymGetLineFromAddr64(process(), frame, |
|
182 &line_displacement, &line); |
|
183 |
|
184 // Output the backtrace line. |
|
185 (*os) << "\t"; |
|
186 if (has_symbol) { |
|
187 (*os) << symbol->Name << " [0x" << trace[i] << "+" |
|
188 << sym_displacement << "]"; |
|
189 } else { |
|
190 // If there is no symbol informtion, add a spacer. |
|
191 (*os) << "(No symbol) [0x" << trace[i] << "]"; |
|
192 } |
|
193 if (has_line) { |
|
194 (*os) << " (" << line.FileName << ":" << line.LineNumber << ")"; |
|
195 } |
|
196 (*os) << "\n"; |
|
197 } |
|
198 } |
|
199 |
|
200 SymbolContext() |
|
201 : initialized_(false), |
|
202 process_(NULL), |
|
203 init_error_(ERROR_SUCCESS) { |
|
204 } |
|
205 |
|
206 private: |
|
207 Lock lock_; |
|
208 bool initialized_; |
|
209 HANDLE process_; |
|
210 DWORD init_error_; |
|
211 |
|
212 DISALLOW_COPY_AND_ASSIGN(SymbolContext); |
|
213 }; |
|
214 |
|
215 } // namespace |
|
216 |
|
217 // Note: Does not use the CRT. |
|
218 bool DebugUtil::SpawnDebuggerOnProcess(unsigned process_id) { |
|
219 wchar_t reg_value[1026]; |
|
220 int len = arraysize(reg_value); |
|
221 if (RegReadString(HKEY_LOCAL_MACHINE, |
|
222 L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug", |
|
223 L"Debugger", reg_value, &len)) { |
|
224 wchar_t command_line[1026]; |
|
225 if (StringReplace(reg_value, process_id, command_line, |
|
226 arraysize(command_line))) { |
|
227 // We don't mind if the debugger is present because it will simply fail |
|
228 // to attach to this process. |
|
229 STARTUPINFO startup_info = {0}; |
|
230 startup_info.cb = sizeof(startup_info); |
|
231 PROCESS_INFORMATION process_info = {0}; |
|
232 |
|
233 if (CreateProcess(NULL, command_line, NULL, NULL, FALSE, 0, NULL, NULL, |
|
234 &startup_info, &process_info)) { |
|
235 CloseHandle(process_info.hThread); |
|
236 WaitForInputIdle(process_info.hProcess, 10000); |
|
237 CloseHandle(process_info.hProcess); |
|
238 return true; |
|
239 } |
|
240 } |
|
241 } |
|
242 return false; |
|
243 } |
|
244 |
|
245 // static |
|
246 bool DebugUtil::BeingDebugged() { |
|
247 return ::IsDebuggerPresent() != 0; |
|
248 } |
|
249 |
|
250 // static |
|
251 void DebugUtil::BreakDebugger() { |
|
252 __debugbreak(); |
|
253 } |
|
254 |
|
255 StackTrace::StackTrace() { |
|
256 // From http://msdn.microsoft.com/en-us/library/bb204633(VS.85).aspx, |
|
257 // the sum of FramesToSkip and FramesToCapture must be less than 63, |
|
258 // so set it to 62. |
|
259 const int kMaxCallers = 62; |
|
260 |
|
261 void* callers[kMaxCallers]; |
|
262 // TODO(ajwong): Migrate this to StackWalk64. |
|
263 int count = CaptureStackBackTrace(0, kMaxCallers, callers, NULL); |
|
264 if (count > 0) { |
|
265 trace_.resize(count); |
|
266 memcpy(&trace_[0], callers, sizeof(callers[0]) * count); |
|
267 } else { |
|
268 trace_.resize(0); |
|
269 } |
|
270 } |
|
271 |
|
272 void StackTrace::PrintBacktrace() { |
|
273 OutputToStream(&std::cerr); |
|
274 } |
|
275 |
|
276 void StackTrace::OutputToStream(std::ostream* os) { |
|
277 SymbolContext* context = SymbolContext::Get(); |
|
278 |
|
279 if (context->Init() != ERROR_SUCCESS) { |
|
280 DWORD error = context->init_error(); |
|
281 (*os) << "Error initializing symbols (" << error |
|
282 << "). Dumping unresolved backtrace:\n"; |
|
283 for (size_t i = 0; (i < trace_.size()) && os->good(); ++i) { |
|
284 (*os) << "\t" << trace_[i] << "\n"; |
|
285 } |
|
286 } else { |
|
287 (*os) << "Backtrace:\n"; |
|
288 context->OutputTraceToStream(trace_, os); |
|
289 } |
|
290 } |