summaryrefslogtreecommitdiffstats
path: root/base
diff options
context:
space:
mode:
Diffstat (limited to 'base')
-rw-r--r--base/base.gyp1
-rw-r--r--base/debug_util.h10
-rw-r--r--base/debug_util_posix.cc38
-rw-r--r--base/debug_util_unittest.cc72
-rw-r--r--base/debug_util_win.cc190
-rw-r--r--base/logging.cc11
6 files changed, 307 insertions, 15 deletions
diff --git a/base/base.gyp b/base/base.gyp
index 977b9d8..58601ae 100644
--- a/base/base.gyp
+++ b/base/base.gyp
@@ -549,6 +549,7 @@
'command_line_unittest.cc',
'condition_variable_unittest.cc',
'data_pack_unittest.cc',
+ 'debug_util_unittest.cc',
'directory_watcher_unittest.cc',
'field_trial_unittest.cc',
'file_path_unittest.cc',
diff --git a/base/debug_util.h b/base/debug_util.h
index de49c43..3ca199c 100644
--- a/base/debug_util.h
+++ b/base/debug_util.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -9,10 +9,11 @@
#ifndef BASE_DEBUG_UTIL_H_
#define BASE_DEBUG_UTIL_H_
-#include "base/basictypes.h"
-
+#include <iostream>
#include <vector>
+#include "base/basictypes.h"
+
// A stacktrace can be helpful in debugging. For example, you can include a
// stacktrace member in a object (probably around #ifndef NDEBUG) so that you
// can later see where the given object was created from.
@@ -26,6 +27,9 @@ class StackTrace {
// Print a backtrace to stderr
void PrintBacktrace();
+ // Resolve backtrace to symbols and write to stream.
+ void OutputToStream(std::ostream* os);
+
private:
std::vector<void*> trace_;
diff --git a/base/debug_util_posix.cc b/base/debug_util_posix.cc
index 60e2ff5..ced59c8 100644
--- a/base/debug_util_posix.cc
+++ b/base/debug_util_posix.cc
@@ -1,10 +1,11 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "build/build_config.h"
#include "base/debug_util.h"
+#include <errno.h>
#include <execinfo.h>
#include <fcntl.h>
#include <stdio.h>
@@ -15,6 +16,7 @@
#include "base/basictypes.h"
#include "base/logging.h"
+#include "base/scoped_ptr.h"
#include "base/string_piece.h"
// static
@@ -109,15 +111,43 @@ void DebugUtil::BreakDebugger() {
}
StackTrace::StackTrace() {
- static const int kMaxCallers = 256;
+ const int kMaxCallers = 256;
void* callers[kMaxCallers];
int count = backtrace(callers, kMaxCallers);
- trace_.resize(count);
- memcpy(&trace_[0], callers, sizeof(void*) * count);
+
+ // Though the backtrace API man page does not list any possible negative
+ // return values, we still still exclude them because they would break the
+ // memcpy code below.
+ if (count > 0) {
+ trace_.resize(count);
+ memcpy(&trace_[0], callers, sizeof(callers[0]) * count);
+ } else {
+ trace_.resize(0);
+ }
}
void StackTrace::PrintBacktrace() {
fflush(stderr);
backtrace_symbols_fd(&trace_[0], trace_.size(), STDERR_FILENO);
}
+
+void StackTrace::OutputToStream(std::ostream* os) {
+ scoped_ptr_malloc<char*> trace_symbols(
+ backtrace_symbols(&trace_[0], trace_.size()));
+
+ // If we can't retrieve the symbols, print an error and just dump the raw
+ // addresses.
+ if (trace_symbols.get() == NULL) {
+ (*os) << "Unable get symbols for backtrace (" << strerror(errno)
+ << "). Dumping raw addresses in trace:\n";
+ for (size_t i = 0; i < trace_.size(); ++i) {
+ (*os) << "\t" << trace_[i] << "\n";
+ }
+ } else {
+ (*os) << "Backtrace:\n";
+ for (size_t i = 0; i < trace_.size(); ++i) {
+ (*os) << "\t" << trace_symbols.get()[i] << "\n";
+ }
+ }
+}
diff --git a/base/debug_util_unittest.cc b/base/debug_util_unittest.cc
new file mode 100644
index 0000000..2636e45
--- /dev/null
+++ b/base/debug_util_unittest.cc
@@ -0,0 +1,72 @@
+// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <sstream>
+#include <string>
+
+#include "base/debug_util.h"
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(StackTrace, OutputToStream) {
+ StackTrace trace;
+
+ // Dump the trace into a string.
+ std::ostringstream os;
+ trace.OutputToStream(&os);
+ std::string backtrace_message = os.str();
+
+ size_t frames_found = 0;
+ trace.Addresses(&frames_found);
+ if (frames_found == 0) {
+ LOG(ERROR) << "No stack frames found. Skipping rest of test.";
+ return;
+ }
+
+ // Check if the output has symbol initialization warning. If it does, fail.
+ if (backtrace_message.find("Dumping unresolved backtrace") !=
+ std::string::npos) {
+ LOG(ERROR) << "Unable to resolve symbols. Skipping rest of test.";
+ return;
+ }
+
+#if defined(OS_MACOSX)
+
+ // Symbol resolution via the backtrace_symbol funciton does not work well
+ // in OsX.
+ // See this thread:
+ //
+ // http://lists.apple.com/archives/darwin-dev/2009/Mar/msg00111.html
+ //
+ // Just check instead that we find our way back to the "start" symbol
+ // which should be the first symbol in the trace.
+ //
+ // TODO(port): Find a more reliable way to resolve symbols.
+
+ // Expect to at least find main.
+ EXPECT_TRUE(backtrace_message.find("start") != std::string::npos)
+ << "Expected to find start in backtrace:\n"
+ << backtrace_message;
+
+#else // defined(OS_MACOSX)
+
+ // Expect to at least find main.
+ EXPECT_TRUE(backtrace_message.find("main") != std::string::npos)
+ << "Expected to find main in backtrace:\n"
+ << backtrace_message;
+
+#if defined(OS_WIN)
+// MSVC doesn't allow the use of C99's __func__ within C++, so we fake it with
+// MSVC's __FUNCTION__ macro.
+#define __func__ __FUNCTION__
+#endif
+
+ // Expect to find this function as well.
+ // Note: This will fail if not linked with -rdynamic (aka -export_dynamic)
+ EXPECT_TRUE(backtrace_message.find(__func__) != std::string::npos)
+ << "Expected to find " << __func__ << " in backtrace:\n"
+ << backtrace_message;
+
+#endif // define(OS_MACOSX)
+}
diff --git a/base/debug_util_win.cc b/base/debug_util_win.cc
index a41f1b3..5201283 100644
--- a/base/debug_util_win.cc
+++ b/base/debug_util_win.cc
@@ -1,12 +1,16 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "base/debug_util.h"
+
#include <windows.h>
+#include <dbghelp.h>
#include "base/basictypes.h"
-#include "base/debug_util.h"
-#include "logging.h"
+#include "base/lock.h"
+#include "base/logging.h"
+#include "base/singleton.h"
namespace {
@@ -62,6 +66,151 @@ bool StringReplace(const wchar_t* input, int value, wchar_t* output,
return true;
}
+// SymbolContext is a threadsafe singleton that wraps the DbgHelp Sym* family
+// of functions. The Sym* family of functions may only be invoked by one
+// thread at a time. SymbolContext code may access a symbol server over the
+// network while holding the lock for this singleton. In the case of high
+// latency, this code will adversly affect performance.
+//
+// There is also a known issue where this backtrace code can interact
+// badly with breakpad if breakpad is invoked in a separate thread while
+// we are using the Sym* functions. This is because breakpad does now
+// share a lock with this function. See this related bug:
+//
+// http://code.google.com/p/google-breakpad/issues/detail?id=311
+//
+// This is a very unlikely edge case, and the current solution is to
+// just ignore it.
+class SymbolContext {
+ public:
+ static SymbolContext* Get() {
+ // We use a leaky singleton because code may call this during process
+ // termination.
+ return
+ Singleton<SymbolContext, LeakySingletonTraits<SymbolContext> >::get();
+ }
+
+ // Initializes the symbols for the process if it hasn't been done yet.
+ // Subsequent calls will not reinitialize the symbol, but instead return
+ // the error code from the first call.
+ bool Init() {
+ AutoLock lock(lock_);
+ if (!initialized_) {
+ process_ = GetCurrentProcess();
+
+ // Defer symbol load until they're needed, use undecorated names, and
+ // get line numbers.
+ SymSetOptions(SYMOPT_DEFERRED_LOADS |
+ SYMOPT_UNDNAME |
+ SYMOPT_LOAD_LINES);
+ if (SymInitialize(process_, NULL, TRUE)) {
+ init_error_ = ERROR_SUCCESS;
+ } else {
+ init_error_ = GetLastError();
+ }
+ }
+
+ initialized_ = true;
+ return init_error_ == ERROR_SUCCESS;
+ }
+
+ // Returns the error code of a failed initialization. This should only be
+ // called if Init() has been called. We do not LOG(FATAL) here because
+ // this code is called might be triggered by a LOG(FATAL) itself. Instead,
+ // we log an ERROR, and return ERROR_INVALID_DATA.
+ DWORD init_error() {
+ if (!initialized_) {
+ LOG(ERROR) << "Calling GetInitError() before Init() was called. "
+ << "Returning ERROR_INVALID_DATA.";
+ return ERROR_INVALID_DATA;
+ }
+
+ return init_error_;
+ }
+
+ // Returns the process this was initialized for. This should only be
+ // called if Init() has been called. We LOG(ERROR) in this situation.
+ // LOG(FATAL) is not used because this code is might be triggered
+ // by a LOG(FATAL) itself.
+ HANDLE process() {
+ if (!initialized_) {
+ LOG(ERROR) << "Calling process() before Init() was called. "
+ << "Returning NULL.";
+ return NULL;
+ }
+
+ return process_;
+ }
+
+ // For the given trace, attempts to resolve the symbols, and output a trace
+ // to the ostream os. The format for each line of the backtrace is:
+ //
+ // <tab>SymbolName[0xAddress+Offset] (FileName:LineNo)
+ //
+ // This function should only be called if Init() has been called. We do not
+ // LOG(FATAL) here because this code is called might be triggered by a
+ // LOG(FATAL) itself.
+ void OutputTraceToStream(const std::vector<void*>& trace, std::ostream* os) {
+ AutoLock lock(lock_);
+
+ for (size_t i = 0; (i < trace.size()) && os->good(); ++i) {
+ const int kMaxNameLength = 256;
+ DWORD_PTR frame = reinterpret_cast<DWORD_PTR>(trace[i]);
+
+ // Code adapted from MSDN example:
+ // http://msdn.microsoft.com/en-us/library/ms680578(VS.85).aspx
+ ULONG64 buffer[
+ (sizeof(SYMBOL_INFO) +
+ kMaxNameLength * sizeof(wchar_t) +
+ sizeof(ULONG64) - 1) /
+ sizeof(ULONG64)];
+
+ // Initialize symbol information retrieval structures.
+ DWORD64 sym_displacement = 0;
+ PSYMBOL_INFO symbol = reinterpret_cast<PSYMBOL_INFO>(&buffer[0]);
+ symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
+ symbol->MaxNameLen = kMaxNameLength;
+ BOOL has_symbol = SymFromAddr(process(), frame,
+ &sym_displacement, symbol);
+
+ // Attempt to retrieve line number information.
+ DWORD line_displacement = 0;
+ IMAGEHLP_LINE64 line = {};
+ line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
+ BOOL has_line = SymGetLineFromAddr64(process(), frame,
+ &line_displacement, &line);
+
+ // Output the backtrace line.
+ (*os) << "\t";
+ if (has_symbol) {
+ (*os) << symbol->Name << " [0x" << trace[i] << "+"
+ << sym_displacement << "]";
+ } else {
+ // If there is no symbol informtion, add a spacer.
+ (*os) << "(No symbol) [0x" << trace[i] << "]";
+ }
+ if (has_line) {
+ (*os) << " (" << line.FileName << ":" << line.LineNumber << ")";
+ }
+ (*os) << "\n";
+ }
+ }
+
+ SymbolContext()
+ : initialized_(false),
+ process_(NULL),
+ init_error_(ERROR_SUCCESS) {
+ }
+
+ private:
+ Lock lock_;
+ bool initialized_;
+ HANDLE process_;
+ DWORD init_error_;
+
+ DISALLOW_COPY_AND_ASSIGN(SymbolContext);
+};
+
} // namespace
// Note: Does not use the CRT.
@@ -102,10 +251,39 @@ void DebugUtil::BreakDebugger() {
__debugbreak();
}
-// TODO(port): not implemented on Windows
StackTrace::StackTrace() {
+ // From http://msdn.microsoft.com/en-us/library/bb204633(VS.85).aspx,
+ // the sum of FramesToSkip and FramesToCapture must be less than 63,
+ // so set it to 62.
+ const int kMaxCallers = 62;
+
+ void* callers[kMaxCallers];
+ // TODO(ajwong): Migrate this to StackWalk64.
+ int count = CaptureStackBackTrace(0, kMaxCallers, callers, NULL);
+ if (count > 0) {
+ trace_.resize(count);
+ memcpy(&trace_[0], callers, sizeof(callers[0]) * count);
+ } else {
+ trace_.resize(0);
+ }
}
-void PrintBacktrace() {
- NOTIMPLEMENTED();
+void StackTrace::PrintBacktrace() {
+ OutputToStream(&std::cerr);
+}
+
+void StackTrace::OutputToStream(std::ostream* os) {
+ SymbolContext* context = SymbolContext::Get();
+
+ if (context->Init() != ERROR_SUCCESS) {
+ DWORD error = context->init_error();
+ (*os) << "Error initializing symbols (" << error
+ << "). Dumping unresolved backtrace:\n";
+ for (size_t i = 0; (i < trace_.size()) && os->good(); ++i) {
+ (*os) << "\t" << trace_[i] << "\n";
+ }
+ } else {
+ (*os) << "Backtrace:\n";
+ context->OutputTraceToStream(trace_, os);
+ }
}
diff --git a/base/logging.cc b/base/logging.cc
index 961ea6c..7b89020 100644
--- a/base/logging.cc
+++ b/base/logging.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -520,6 +520,13 @@ LogMessage::~LogMessage() {
if (DebugUtil::BeingDebugged()) {
DebugUtil::BreakDebugger();
} else {
+#ifndef NDEBUG
+ // Dump a stack trace on a fatal.
+ StackTrace trace;
+ stream_ << "\n"; // Newline to separate from log message.
+ trace.OutputToStream(&stream_);
+#endif
+
if (log_assert_handler) {
// make a copy of the string for the handler out of paranoia
log_assert_handler(std::string(stream_.str()));
@@ -554,7 +561,7 @@ void CloseLogFile() {
log_file = NULL;
}
-} // namespace logging
+} // namespace logging
std::ostream& operator<<(std::ostream& out, const wchar_t* wstr) {
return out << base::SysWideToUTF8(std::wstring(wstr));