OLD | NEW |
| (Empty) |
1 // Copyright (c) 2010 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 "tools/memory_watcher/call_stack.h" | |
6 | |
7 #include <shlwapi.h> | |
8 #include <tlhelp32.h> | |
9 | |
10 #include "base/strings/string_number_conversions.h" | |
11 #include "tools/memory_watcher/memory_hook.h" | |
12 | |
13 // Typedefs for explicit dynamic linking with functions exported from | |
14 // dbghelp.dll. | |
15 typedef BOOL (__stdcall *t_StackWalk64)(DWORD, HANDLE, HANDLE, | |
16 LPSTACKFRAME64, PVOID, | |
17 PREAD_PROCESS_MEMORY_ROUTINE64, | |
18 PFUNCTION_TABLE_ACCESS_ROUTINE64, | |
19 PGET_MODULE_BASE_ROUTINE64, | |
20 PTRANSLATE_ADDRESS_ROUTINE64); | |
21 typedef PVOID (__stdcall *t_SymFunctionTableAccess64)(HANDLE, DWORD64); | |
22 typedef DWORD64 (__stdcall *t_SymGetModuleBase64)(HANDLE, DWORD64); | |
23 typedef BOOL (__stdcall *t_SymCleanup)(HANDLE); | |
24 typedef BOOL (__stdcall *t_SymGetSymFromAddr64)(HANDLE, DWORD64, | |
25 PDWORD64, PIMAGEHLP_SYMBOL64); | |
26 typedef BOOL (__stdcall *t_SymGetLineFromAddr64)(HANDLE, DWORD64, PDWORD, | |
27 PIMAGEHLP_LINE64); | |
28 typedef BOOL (__stdcall *t_SymInitialize)(HANDLE, PCTSTR, BOOL); | |
29 typedef DWORD (__stdcall *t_SymGetOptions)(void); | |
30 typedef DWORD (__stdcall *t_SymSetOptions)(DWORD); | |
31 typedef BOOL (__stdcall *t_SymGetSearchPath)(HANDLE, PTSTR, DWORD); | |
32 typedef DWORD64 (__stdcall *t_SymLoadModule64)(HANDLE, HANDLE, PCSTR, | |
33 PCSTR, DWORD64, DWORD); | |
34 typedef BOOL (__stdcall *t_SymGetModuleInfo64)(HANDLE, DWORD64, | |
35 PIMAGEHLP_MODULE64); | |
36 | |
37 // static | |
38 base::Lock CallStack::dbghelp_lock_; | |
39 // static | |
40 bool CallStack::dbghelp_loaded_ = false; | |
41 // static | |
42 DWORD CallStack::active_thread_id_ = 0; | |
43 | |
44 | |
45 static t_StackWalk64 pStackWalk64 = NULL; | |
46 static t_SymCleanup pSymCleanup = NULL; | |
47 static t_SymGetSymFromAddr64 pSymGetSymFromAddr64 = NULL; | |
48 static t_SymFunctionTableAccess64 pSymFunctionTableAccess64 = NULL; | |
49 static t_SymGetModuleBase64 pSymGetModuleBase64 = NULL; | |
50 static t_SymGetLineFromAddr64 pSymGetLineFromAddr64 = NULL; | |
51 static t_SymInitialize pSymInitialize = NULL; | |
52 static t_SymGetOptions pSymGetOptions = NULL; | |
53 static t_SymSetOptions pSymSetOptions = NULL; | |
54 static t_SymGetModuleInfo64 pSymGetModuleInfo64 = NULL; | |
55 static t_SymGetSearchPath pSymGetSearchPath = NULL; | |
56 static t_SymLoadModule64 pSymLoadModule64 = NULL; | |
57 | |
58 #define LOADPROC(module, name) do { \ | |
59 p##name = reinterpret_cast<t_##name>(GetProcAddress(module, #name)); \ | |
60 if (p##name == NULL) return false; \ | |
61 } while (0) | |
62 | |
63 // This code has to be VERY careful to not induce any allocations, as memory | |
64 // watching code may cause recursion, which may obscure the stack for the truly | |
65 // offensive issue. We use this function to break into a debugger, and it | |
66 // is guaranteed to not do any allocations (in fact, not do anything). | |
67 static void UltraSafeDebugBreak() { | |
68 _asm int(3); | |
69 } | |
70 | |
71 // static | |
72 bool CallStack::LoadDbgHelp() { | |
73 if (!dbghelp_loaded_) { | |
74 base::AutoLock Lock(dbghelp_lock_); | |
75 | |
76 // Re-check if we've loaded successfully now that we have the lock. | |
77 if (dbghelp_loaded_) | |
78 return true; | |
79 | |
80 // Load dbghelp.dll, and obtain pointers to the exported functions that we | |
81 // will be using. | |
82 HMODULE dbghelp_module = LoadLibrary(L"dbghelp.dll"); | |
83 if (dbghelp_module) { | |
84 LOADPROC(dbghelp_module, StackWalk64); | |
85 LOADPROC(dbghelp_module, SymFunctionTableAccess64); | |
86 LOADPROC(dbghelp_module, SymGetModuleBase64); | |
87 LOADPROC(dbghelp_module, SymCleanup); | |
88 LOADPROC(dbghelp_module, SymGetSymFromAddr64); | |
89 LOADPROC(dbghelp_module, SymGetLineFromAddr64); | |
90 LOADPROC(dbghelp_module, SymInitialize); | |
91 LOADPROC(dbghelp_module, SymGetOptions); | |
92 LOADPROC(dbghelp_module, SymSetOptions); | |
93 LOADPROC(dbghelp_module, SymGetModuleInfo64); | |
94 LOADPROC(dbghelp_module, SymGetSearchPath); | |
95 LOADPROC(dbghelp_module, SymLoadModule64); | |
96 dbghelp_loaded_ = true; | |
97 } else { | |
98 UltraSafeDebugBreak(); | |
99 return false; | |
100 } | |
101 } | |
102 return dbghelp_loaded_; | |
103 } | |
104 | |
105 // Load the symbols for generating stack traces. | |
106 static bool LoadSymbols(HANDLE process_handle) { | |
107 static bool symbols_loaded = false; | |
108 if (symbols_loaded) return true; | |
109 | |
110 BOOL ok; | |
111 | |
112 // Initialize the symbol engine. | |
113 ok = pSymInitialize(process_handle, /* hProcess */ | |
114 NULL, /* UserSearchPath */ | |
115 FALSE); /* fInvadeProcess */ | |
116 if (!ok) return false; | |
117 | |
118 DWORD options = pSymGetOptions(); | |
119 options |= SYMOPT_LOAD_LINES; | |
120 options |= SYMOPT_FAIL_CRITICAL_ERRORS; | |
121 options |= SYMOPT_UNDNAME; | |
122 options = pSymSetOptions(options); | |
123 | |
124 const DWORD kMaxSearchPath = 1024; | |
125 TCHAR buf[kMaxSearchPath] = {0}; | |
126 ok = pSymGetSearchPath(process_handle, buf, kMaxSearchPath); | |
127 if (!ok) | |
128 return false; | |
129 | |
130 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, | |
131 GetCurrentProcessId()); | |
132 if (snapshot == INVALID_HANDLE_VALUE) | |
133 return false; | |
134 | |
135 MODULEENTRY32W module; | |
136 module.dwSize = sizeof(module); // Set the size of the structure. | |
137 BOOL cont = Module32FirstW(snapshot, &module); | |
138 while (cont) { | |
139 DWORD64 base; | |
140 // NOTE the SymLoadModule64 function has the peculiarity of accepting a | |
141 // both unicode and ASCII strings even though the parameter is PSTR. | |
142 base = pSymLoadModule64(process_handle, | |
143 0, | |
144 reinterpret_cast<PSTR>(module.szExePath), | |
145 reinterpret_cast<PSTR>(module.szModule), | |
146 reinterpret_cast<DWORD64>(module.modBaseAddr), | |
147 module.modBaseSize); | |
148 if (base == 0) { | |
149 int err = GetLastError(); | |
150 if (err != ERROR_MOD_NOT_FOUND && err != ERROR_INVALID_HANDLE) | |
151 return false; | |
152 } | |
153 cont = Module32NextW(snapshot, &module); | |
154 } | |
155 CloseHandle(snapshot); | |
156 | |
157 symbols_loaded = true; | |
158 return true; | |
159 } | |
160 | |
161 | |
162 CallStack::SymbolCache* CallStack::symbol_cache_; | |
163 | |
164 bool CallStack::Initialize() { | |
165 // We need to delay load the symbol cache until after | |
166 // the MemoryHook heap is alive. | |
167 symbol_cache_ = new SymbolCache(); | |
168 return LoadDbgHelp(); | |
169 } | |
170 | |
171 CallStack::CallStack() { | |
172 static LONG callstack_id = 0; | |
173 frame_count_ = 0; | |
174 hash_ = 0; | |
175 id_ = InterlockedIncrement(&callstack_id); | |
176 valid_ = false; | |
177 | |
178 if (!dbghelp_loaded_) { | |
179 UltraSafeDebugBreak(); // Initialize should have been called. | |
180 return; | |
181 } | |
182 | |
183 GetStackTrace(); | |
184 } | |
185 | |
186 bool CallStack::IsEqual(const CallStack &target) { | |
187 if (frame_count_ != target.frame_count_) | |
188 return false; // They can't be equal if the sizes are different. | |
189 | |
190 // Walk the frames array until we | |
191 // either find a mismatch, or until we reach the end of the call stacks. | |
192 for (int index = 0; index < frame_count_; index++) { | |
193 if (frames_[index] != target.frames_[index]) | |
194 return false; // Found a mismatch. They are not equal. | |
195 } | |
196 | |
197 // Reached the end of the call stacks. They are equal. | |
198 return true; | |
199 } | |
200 | |
201 void CallStack::AddFrame(DWORD_PTR pc) { | |
202 DCHECK(frame_count_ < kMaxTraceFrames); | |
203 frames_[frame_count_++] = pc; | |
204 | |
205 // Create a unique id for this CallStack. | |
206 pc = pc + (frame_count_ * 13); // Alter the PC based on position in stack. | |
207 hash_ = ~hash_ + (pc << 15); | |
208 hash_ = hash_ ^ (pc >> 12); | |
209 hash_ = hash_ + (pc << 2); | |
210 hash_ = hash_ ^ (pc >> 4); | |
211 hash_ = hash_ * 2057; | |
212 hash_ = hash_ ^ (pc >> 16); | |
213 } | |
214 | |
215 bool CallStack::LockedRecursionDetected() const { | |
216 if (!active_thread_id_) return false; | |
217 DWORD thread_id = GetCurrentThreadId(); | |
218 // TODO(jar): Perchance we should use atomic access to member. | |
219 return thread_id == active_thread_id_; | |
220 } | |
221 | |
222 bool CallStack::GetStackTrace() { | |
223 if (LockedRecursionDetected()) | |
224 return false; | |
225 | |
226 // Initialize the context record. | |
227 CONTEXT context; | |
228 memset(&context, 0, sizeof(context)); | |
229 context.ContextFlags = CONTEXT_FULL; | |
230 __asm call x | |
231 __asm x: pop eax | |
232 __asm mov context.Eip, eax | |
233 __asm mov context.Ebp, ebp | |
234 __asm mov context.Esp, esp | |
235 | |
236 STACKFRAME64 frame; | |
237 memset(&frame, 0, sizeof(frame)); | |
238 | |
239 #ifdef _M_IX86 | |
240 DWORD image_type = IMAGE_FILE_MACHINE_I386; | |
241 frame.AddrPC.Offset = context.Eip; | |
242 frame.AddrPC.Mode = AddrModeFlat; | |
243 frame.AddrFrame.Offset = context.Ebp; | |
244 frame.AddrFrame.Mode = AddrModeFlat; | |
245 frame.AddrStack.Offset = context.Esp; | |
246 frame.AddrStack.Mode = AddrModeFlat; | |
247 #elif | |
248 NOT IMPLEMENTED! | |
249 #endif | |
250 | |
251 HANDLE current_process = GetCurrentProcess(); | |
252 HANDLE current_thread = GetCurrentThread(); | |
253 | |
254 // Walk the stack. | |
255 unsigned int count = 0; | |
256 { | |
257 AutoDbgHelpLock thread_monitoring_lock; | |
258 | |
259 while (count < kMaxTraceFrames) { | |
260 count++; | |
261 if (!pStackWalk64(image_type, | |
262 current_process, | |
263 current_thread, | |
264 &frame, | |
265 &context, | |
266 0, | |
267 pSymFunctionTableAccess64, | |
268 pSymGetModuleBase64, | |
269 NULL)) | |
270 break; // Couldn't trace back through any more frames. | |
271 | |
272 if (frame.AddrFrame.Offset == 0) | |
273 continue; // End of stack. | |
274 | |
275 // Push this frame's program counter onto the provided CallStack. | |
276 AddFrame((DWORD_PTR)frame.AddrPC.Offset); | |
277 } | |
278 valid_ = true; | |
279 } | |
280 return true; | |
281 } | |
282 | |
283 void CallStack::ToString(PrivateAllocatorString* output) { | |
284 static const int kStackWalkMaxNameLen = MAX_SYM_NAME; | |
285 HANDLE current_process = GetCurrentProcess(); | |
286 | |
287 if (!LoadSymbols(current_process)) { | |
288 *output = "Error"; | |
289 return; | |
290 } | |
291 | |
292 base::AutoLock lock(dbghelp_lock_); | |
293 | |
294 // Iterate through each frame in the call stack. | |
295 for (int32 index = 0; index < frame_count_; index++) { | |
296 PrivateAllocatorString line; | |
297 | |
298 DWORD_PTR intruction_pointer = frame(index); | |
299 | |
300 SymbolCache::iterator it; | |
301 it = symbol_cache_->find(intruction_pointer); | |
302 if (it != symbol_cache_->end()) { | |
303 line = it->second; | |
304 } else { | |
305 // Try to locate a symbol for this frame. | |
306 DWORD64 symbol_displacement = 0; | |
307 ULONG64 buffer[(sizeof(IMAGEHLP_SYMBOL64) + | |
308 sizeof(TCHAR)*kStackWalkMaxNameLen + | |
309 sizeof(ULONG64) - 1) / sizeof(ULONG64)]; | |
310 IMAGEHLP_SYMBOL64* symbol = reinterpret_cast<IMAGEHLP_SYMBOL64*>(buffer); | |
311 memset(buffer, 0, sizeof(buffer)); | |
312 symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); | |
313 symbol->MaxNameLength = kStackWalkMaxNameLen; | |
314 BOOL ok = pSymGetSymFromAddr64(current_process, // hProcess | |
315 intruction_pointer, // Address | |
316 &symbol_displacement, // Displacement | |
317 symbol); // Symbol | |
318 if (ok) { | |
319 // Try to locate more source information for the symbol. | |
320 IMAGEHLP_LINE64 Line; | |
321 memset(&Line, 0, sizeof(Line)); | |
322 Line.SizeOfStruct = sizeof(Line); | |
323 DWORD line_displacement; | |
324 ok = pSymGetLineFromAddr64(current_process, | |
325 intruction_pointer, | |
326 &line_displacement, | |
327 &Line); | |
328 if (ok) { | |
329 // Skip junk symbols from our internal stuff. | |
330 if (strstr(symbol->Name, "CallStack::") || | |
331 strstr(symbol->Name, "MemoryWatcher::") || | |
332 strstr(symbol->Name, "Perftools_") || | |
333 strstr(symbol->Name, "MemoryHook::") ) { | |
334 // Just record a blank string. | |
335 (*symbol_cache_)[intruction_pointer] = ""; | |
336 continue; | |
337 } | |
338 | |
339 line += " "; | |
340 line += static_cast<char*>(Line.FileName); | |
341 line += " ("; | |
342 // TODO(jar): get something like this template to work :-/ | |
343 // line += IntToCustomString<PrivateAllocatorString>(Line.LineNumber); | |
344 // ...and then delete this line, which uses std::string. | |
345 line += base::IntToString(Line.LineNumber).c_str(); | |
346 line += "): "; | |
347 line += symbol->Name; | |
348 line += "\n"; | |
349 } else { | |
350 line += " unknown (0):"; | |
351 line += symbol->Name; | |
352 line += "\n"; | |
353 } | |
354 } else { | |
355 // OK - couldn't get any info. Try for the module. | |
356 IMAGEHLP_MODULE64 module_info; | |
357 module_info.SizeOfStruct = sizeof(module_info); | |
358 if (pSymGetModuleInfo64(current_process, intruction_pointer, | |
359 &module_info)) { | |
360 line += " ("; | |
361 line += static_cast<char*>(module_info.ModuleName); | |
362 line += ")\n"; | |
363 } else { | |
364 line += " ???\n"; | |
365 } | |
366 } | |
367 } | |
368 | |
369 (*symbol_cache_)[intruction_pointer] = line; | |
370 *output += line; | |
371 } | |
372 *output += "==================\n"; | |
373 } | |
374 | |
375 | |
376 base::Lock AllocationStack::freelist_lock_; | |
377 AllocationStack* AllocationStack::freelist_ = NULL; | |
378 | |
379 void* AllocationStack::operator new(size_t size) { | |
380 DCHECK(size == sizeof(AllocationStack)); | |
381 { | |
382 base::AutoLock lock(freelist_lock_); | |
383 if (freelist_ != NULL) { | |
384 AllocationStack* stack = freelist_; | |
385 freelist_ = freelist_->next_; | |
386 stack->next_ = NULL; | |
387 return stack; | |
388 } | |
389 } | |
390 return MemoryHook::Alloc(size); | |
391 } | |
392 | |
393 void AllocationStack::operator delete(void* ptr) { | |
394 AllocationStack *stack = reinterpret_cast<AllocationStack*>(ptr); | |
395 base::AutoLock lock(freelist_lock_); | |
396 DCHECK(stack->next_ == NULL); | |
397 stack->next_ = freelist_; | |
398 freelist_ = stack; | |
399 } | |
OLD | NEW |