michael@0: // Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: #include "base/debug_util.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "base/basictypes.h" michael@0: #include "base/lock.h" michael@0: #include "base/logging.h" michael@0: #include "base/singleton.h" michael@0: michael@0: namespace { michael@0: michael@0: // Minimalist key reader. michael@0: // Note: Does not use the CRT. michael@0: bool RegReadString(HKEY root, const wchar_t* subkey, michael@0: const wchar_t* value_name, wchar_t* buffer, int* len) { michael@0: HKEY key = NULL; michael@0: DWORD res = RegOpenKeyEx(root, subkey, 0, KEY_READ, &key); michael@0: if (ERROR_SUCCESS != res || key == NULL) michael@0: return false; michael@0: michael@0: DWORD type = 0; michael@0: DWORD buffer_size = *len * sizeof(wchar_t); michael@0: // We don't support REG_EXPAND_SZ. michael@0: res = RegQueryValueEx(key, value_name, NULL, &type, michael@0: reinterpret_cast(buffer), &buffer_size); michael@0: if (ERROR_SUCCESS == res && buffer_size != 0 && type == REG_SZ) { michael@0: // Make sure the buffer is NULL terminated. michael@0: buffer[*len - 1] = 0; michael@0: *len = lstrlen(buffer); michael@0: RegCloseKey(key); michael@0: return true; michael@0: } michael@0: RegCloseKey(key); michael@0: return false; michael@0: } michael@0: michael@0: // Replaces each "%ld" in input per a value. Not efficient but it works. michael@0: // Note: Does not use the CRT. michael@0: bool StringReplace(const wchar_t* input, int value, wchar_t* output, michael@0: int output_len) { michael@0: memset(output, 0, output_len*sizeof(wchar_t)); michael@0: int input_len = lstrlen(input); michael@0: michael@0: for (int i = 0; i < input_len; ++i) { michael@0: int current_output_len = lstrlen(output); michael@0: michael@0: if (input[i] == L'%' && input[i + 1] == L'l' && input[i + 2] == L'd') { michael@0: // Make sure we have enough place left. michael@0: if ((current_output_len + 12) >= output_len) michael@0: return false; michael@0: michael@0: // Cheap _itow(). michael@0: wsprintf(output+current_output_len, L"%d", value); michael@0: i += 2; michael@0: } else { michael@0: if (current_output_len >= output_len) michael@0: return false; michael@0: output[current_output_len] = input[i]; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: // SymbolContext is a threadsafe singleton that wraps the DbgHelp Sym* family michael@0: // of functions. The Sym* family of functions may only be invoked by one michael@0: // thread at a time. SymbolContext code may access a symbol server over the michael@0: // network while holding the lock for this singleton. In the case of high michael@0: // latency, this code will adversly affect performance. michael@0: // michael@0: // There is also a known issue where this backtrace code can interact michael@0: // badly with breakpad if breakpad is invoked in a separate thread while michael@0: // we are using the Sym* functions. This is because breakpad does now michael@0: // share a lock with this function. See this related bug: michael@0: // michael@0: // http://code.google.com/p/google-breakpad/issues/detail?id=311 michael@0: // michael@0: // This is a very unlikely edge case, and the current solution is to michael@0: // just ignore it. michael@0: class SymbolContext { michael@0: public: michael@0: static SymbolContext* Get() { michael@0: // We use a leaky singleton because code may call this during process michael@0: // termination. michael@0: return michael@0: Singleton >::get(); michael@0: } michael@0: michael@0: // Initializes the symbols for the process if it hasn't been done yet. michael@0: // Subsequent calls will not reinitialize the symbol, but instead return michael@0: // the error code from the first call. michael@0: bool Init() { michael@0: AutoLock lock(lock_); michael@0: if (!initialized_) { michael@0: process_ = GetCurrentProcess(); michael@0: michael@0: // Defer symbol load until they're needed, use undecorated names, and michael@0: // get line numbers. michael@0: SymSetOptions(SYMOPT_DEFERRED_LOADS | michael@0: SYMOPT_UNDNAME | michael@0: SYMOPT_LOAD_LINES); michael@0: if (SymInitialize(process_, NULL, TRUE)) { michael@0: init_error_ = ERROR_SUCCESS; michael@0: } else { michael@0: init_error_ = GetLastError(); michael@0: } michael@0: } michael@0: michael@0: initialized_ = true; michael@0: return init_error_ == ERROR_SUCCESS; michael@0: } michael@0: michael@0: // Returns the error code of a failed initialization. This should only be michael@0: // called if Init() has been called. We do not CHROMIUM_LOG(FATAL) here because michael@0: // this code is called might be triggered by a CHROMIUM_LOG(FATAL) itself. Instead, michael@0: // we log an ERROR, and return ERROR_INVALID_DATA. michael@0: DWORD init_error() { michael@0: if (!initialized_) { michael@0: CHROMIUM_LOG(ERROR) << "Calling GetInitError() before Init() was called. " michael@0: << "Returning ERROR_INVALID_DATA."; michael@0: return ERROR_INVALID_DATA; michael@0: } michael@0: michael@0: return init_error_; michael@0: } michael@0: michael@0: // Returns the process this was initialized for. This should only be michael@0: // called if Init() has been called. We CHROMIUM_LOG(ERROR) in this situation. michael@0: // CHROMIUM_LOG(FATAL) is not used because this code is might be triggered michael@0: // by a CHROMIUM_LOG(FATAL) itself. michael@0: HANDLE process() { michael@0: if (!initialized_) { michael@0: CHROMIUM_LOG(ERROR) << "Calling process() before Init() was called. " michael@0: << "Returning NULL."; michael@0: return NULL; michael@0: } michael@0: michael@0: return process_; michael@0: } michael@0: michael@0: // For the given trace, attempts to resolve the symbols, and output a trace michael@0: // to the ostream os. The format for each line of the backtrace is: michael@0: // michael@0: // SymbolName[0xAddress+Offset] (FileName:LineNo) michael@0: // michael@0: // This function should only be called if Init() has been called. We do not michael@0: // CHROMIUM_LOG(FATAL) here because this code is called might be triggered by a michael@0: // CHROMIUM_LOG(FATAL) itself. michael@0: void OutputTraceToStream(const std::vector& trace, std::ostream* os) { michael@0: AutoLock lock(lock_); michael@0: michael@0: for (size_t i = 0; (i < trace.size()) && os->good(); ++i) { michael@0: const int kMaxNameLength = 256; michael@0: DWORD_PTR frame = reinterpret_cast(trace[i]); michael@0: michael@0: // Code adapted from MSDN example: michael@0: // http://msdn.microsoft.com/en-us/library/ms680578(VS.85).aspx michael@0: ULONG64 buffer[ michael@0: (sizeof(SYMBOL_INFO) + michael@0: kMaxNameLength * sizeof(wchar_t) + michael@0: sizeof(ULONG64) - 1) / michael@0: sizeof(ULONG64)]; michael@0: michael@0: // Initialize symbol information retrieval structures. michael@0: DWORD64 sym_displacement = 0; michael@0: PSYMBOL_INFO symbol = reinterpret_cast(&buffer[0]); michael@0: symbol->SizeOfStruct = sizeof(SYMBOL_INFO); michael@0: symbol->MaxNameLen = kMaxNameLength; michael@0: BOOL has_symbol = SymFromAddr(process(), frame, michael@0: &sym_displacement, symbol); michael@0: michael@0: // Attempt to retrieve line number information. michael@0: DWORD line_displacement = 0; michael@0: IMAGEHLP_LINE64 line = {}; michael@0: line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); michael@0: BOOL has_line = SymGetLineFromAddr64(process(), frame, michael@0: &line_displacement, &line); michael@0: michael@0: // Output the backtrace line. michael@0: (*os) << "\t"; michael@0: if (has_symbol) { michael@0: (*os) << symbol->Name << " [0x" << trace[i] << "+" michael@0: << sym_displacement << "]"; michael@0: } else { michael@0: // If there is no symbol informtion, add a spacer. michael@0: (*os) << "(No symbol) [0x" << trace[i] << "]"; michael@0: } michael@0: if (has_line) { michael@0: (*os) << " (" << line.FileName << ":" << line.LineNumber << ")"; michael@0: } michael@0: (*os) << "\n"; michael@0: } michael@0: } michael@0: michael@0: SymbolContext() michael@0: : initialized_(false), michael@0: process_(NULL), michael@0: init_error_(ERROR_SUCCESS) { michael@0: } michael@0: michael@0: private: michael@0: Lock lock_; michael@0: bool initialized_; michael@0: HANDLE process_; michael@0: DWORD init_error_; michael@0: michael@0: DISALLOW_COPY_AND_ASSIGN(SymbolContext); michael@0: }; michael@0: michael@0: } // namespace michael@0: michael@0: // Note: Does not use the CRT. michael@0: bool DebugUtil::SpawnDebuggerOnProcess(unsigned process_id) { michael@0: wchar_t reg_value[1026]; michael@0: int len = arraysize(reg_value); michael@0: if (RegReadString(HKEY_LOCAL_MACHINE, michael@0: L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug", michael@0: L"Debugger", reg_value, &len)) { michael@0: wchar_t command_line[1026]; michael@0: if (StringReplace(reg_value, process_id, command_line, michael@0: arraysize(command_line))) { michael@0: // We don't mind if the debugger is present because it will simply fail michael@0: // to attach to this process. michael@0: STARTUPINFO startup_info = {0}; michael@0: startup_info.cb = sizeof(startup_info); michael@0: PROCESS_INFORMATION process_info = {0}; michael@0: michael@0: if (CreateProcess(NULL, command_line, NULL, NULL, FALSE, 0, NULL, NULL, michael@0: &startup_info, &process_info)) { michael@0: CloseHandle(process_info.hThread); michael@0: WaitForInputIdle(process_info.hProcess, 10000); michael@0: CloseHandle(process_info.hProcess); michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: // static michael@0: bool DebugUtil::BeingDebugged() { michael@0: return ::IsDebuggerPresent() != 0; michael@0: } michael@0: michael@0: // static michael@0: void DebugUtil::BreakDebugger() { michael@0: __debugbreak(); michael@0: } michael@0: michael@0: StackTrace::StackTrace() { michael@0: // From http://msdn.microsoft.com/en-us/library/bb204633(VS.85).aspx, michael@0: // the sum of FramesToSkip and FramesToCapture must be less than 63, michael@0: // so set it to 62. michael@0: const int kMaxCallers = 62; michael@0: michael@0: void* callers[kMaxCallers]; michael@0: // TODO(ajwong): Migrate this to StackWalk64. michael@0: int count = CaptureStackBackTrace(0, kMaxCallers, callers, NULL); michael@0: if (count > 0) { michael@0: trace_.resize(count); michael@0: memcpy(&trace_[0], callers, sizeof(callers[0]) * count); michael@0: } else { michael@0: trace_.resize(0); michael@0: } michael@0: } michael@0: michael@0: void StackTrace::PrintBacktrace() { michael@0: OutputToStream(&std::cerr); michael@0: } michael@0: michael@0: void StackTrace::OutputToStream(std::ostream* os) { michael@0: SymbolContext* context = SymbolContext::Get(); michael@0: michael@0: if (context->Init() != ERROR_SUCCESS) { michael@0: DWORD error = context->init_error(); michael@0: (*os) << "Error initializing symbols (" << error michael@0: << "). Dumping unresolved backtrace:\n"; michael@0: for (size_t i = 0; (i < trace_.size()) && os->good(); ++i) { michael@0: (*os) << "\t" << trace_[i] << "\n"; michael@0: } michael@0: } else { michael@0: (*os) << "Backtrace:\n"; michael@0: context->OutputTraceToStream(trace_, os); michael@0: } michael@0: }