diff options
author | brettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-10-26 04:07:50 +0000 |
---|---|---|
committer | brettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-10-26 04:07:50 +0000 |
commit | 58580359a452cb7c3b9580edc0843c3ab3d158df (patch) | |
tree | 964dbcc1505f4b9c2bbb5e7a64720861d604c8f3 /base/debug | |
parent | 23872906817de5d402b0c2da6d5f7ee6026378e6 (diff) | |
download | chromium_src-58580359a452cb7c3b9580edc0843c3ab3d158df.zip chromium_src-58580359a452cb7c3b9580edc0843c3ab3d158df.tar.gz chromium_src-58580359a452cb7c3b9580edc0843c3ab3d158df.tar.bz2 |
Move debug-related stuff from base to the base/debug directory and use the
base::debug namespace.
This splits apart debug_util into base/debugger and base/stack_trace
There are still two functions in debug_util that I'm not sure what to do with.
Since this uses the base::debug namespace, I removed the functions in
debugger.h from the static class and just made them free functions in the
namespace.
TEST=it compiles
BUG=none
Review URL: http://codereview.chromium.org/3945002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@63859 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base/debug')
-rw-r--r-- | base/debug/debugger.cc | 25 | ||||
-rw-r--r-- | base/debug/debugger.h | 39 | ||||
-rw-r--r-- | base/debug/debugger_posix.cc | 165 | ||||
-rw-r--r-- | base/debug/debugger_win.cc | 112 | ||||
-rw-r--r-- | base/debug/leak_annotations.h | 28 | ||||
-rw-r--r-- | base/debug/leak_tracker.h | 134 | ||||
-rw-r--r-- | base/debug/leak_tracker_unittest.cc | 113 | ||||
-rw-r--r-- | base/debug/stack_trace.cc | 21 | ||||
-rw-r--r-- | base/debug/stack_trace.h | 63 | ||||
-rw-r--r-- | base/debug/stack_trace_posix.cc | 199 | ||||
-rw-r--r-- | base/debug/stack_trace_unittest.cc | 112 | ||||
-rw-r--r-- | base/debug/stack_trace_win.cc | 197 | ||||
-rw-r--r-- | base/debug/trace_event.cc | 166 | ||||
-rw-r--r-- | base/debug/trace_event.h | 147 | ||||
-rw-r--r-- | base/debug/trace_event_win.cc | 116 | ||||
-rw-r--r-- | base/debug/trace_event_win.h | 151 | ||||
-rw-r--r-- | base/debug/trace_event_win_unittest.cc | 308 |
17 files changed, 2096 insertions, 0 deletions
diff --git a/base/debug/debugger.cc b/base/debug/debugger.cc new file mode 100644 index 0000000..9ca7e8d --- /dev/null +++ b/base/debug/debugger.cc @@ -0,0 +1,25 @@ +// Copyright (c) 2010 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/debugger.h" + +#include "base/platform_thread.h" + +namespace base { +namespace debug { + +bool WaitForDebugger(int wait_seconds, bool silent) { + for (int i = 0; i < wait_seconds * 10; ++i) { + if (BeingDebugged()) { + if (!silent) + BreakDebugger(); + return true; + } + PlatformThread::Sleep(100); + } + return false; +} + +} // namespace debug +} // namespace base diff --git a/base/debug/debugger.h b/base/debug/debugger.h new file mode 100644 index 0000000..008d77d --- /dev/null +++ b/base/debug/debugger.h @@ -0,0 +1,39 @@ +// Copyright (c) 2010 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. + +// This is a cross platform interface for helper functions related to +// debuggers. You should use this to test if you're running under a debugger, +// and if you would like to yield (breakpoint) into the debugger. + +#ifndef BASE_DEBUG_DEBUGGER_H +#define BASE_DEBUG_DEBUGGER_H +#pragma once + +namespace base { +namespace debug { + +// Starts the registered system-wide JIT debugger to attach it to specified +// process. +bool SpawnDebuggerOnProcess(unsigned process_id); + +// Waits wait_seconds seconds for a debugger to attach to the current process. +// When silent is false, an exception is thrown when a debugger is detected. +bool WaitForDebugger(int wait_seconds, bool silent); + +// Returns true if the given process is being run under a debugger. +// +// On OS X, the underlying mechanism doesn't work when the sandbox is enabled. +// To get around this, this function caches its value. +// +// WARNING: Because of this, on OS X, a call MUST be made to this function +// BEFORE the sandbox is enabled. +bool BeingDebugged(); + +// Break into the debugger, assumes a debugger is present. +void BreakDebugger(); + +} // namespace debug +} // namespace base + +#endif // BASE_DEBUG_DEBUGGER_H diff --git a/base/debug/debugger_posix.cc b/base/debug/debugger_posix.cc new file mode 100644 index 0000000..a5ab066 --- /dev/null +++ b/base/debug/debugger_posix.cc @@ -0,0 +1,165 @@ +// Copyright (c) 2010 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/debugger.h" + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/sysctl.h> +#include <sys/types.h> +#include <unistd.h> + +#include <string> +#include <vector> + +#if defined(__GLIBCXX__) +#include <cxxabi.h> +#endif + +#if defined(OS_MACOSX) +#include <AvailabilityMacros.h> +#endif + +#include <iostream> + +#include "base/basictypes.h" +#include "base/compat_execinfo.h" +#include "base/eintr_wrapper.h" +#include "base/logging.h" +#include "base/safe_strerror_posix.h" +#include "base/scoped_ptr.h" +#include "base/string_piece.h" +#include "base/stringprintf.h" + +#if defined(USE_SYMBOLIZE) +#include "base/third_party/symbolize/symbolize.h" +#endif + +namespace base { +namespace debug { + +bool SpawnDebuggerOnProcess(unsigned /* process_id */) { + NOTIMPLEMENTED(); + return false; +} + +#if defined(OS_MACOSX) + +// Based on Apple's recommended method as described in +// http://developer.apple.com/qa/qa2004/qa1361.html +bool BeingDebugged() { + // If the process is sandboxed then we can't use the sysctl, so cache the + // value. + static bool is_set = false; + static bool being_debugged = false; + + if (is_set) { + return being_debugged; + } + + // Initialize mib, which tells sysctl what info we want. In this case, + // we're looking for information about a specific process ID. + int mib[] = { + CTL_KERN, + KERN_PROC, + KERN_PROC_PID, + getpid() + }; + + // Caution: struct kinfo_proc is marked __APPLE_API_UNSTABLE. The source and + // binary interfaces may change. + struct kinfo_proc info; + size_t info_size = sizeof(info); + + int sysctl_result = sysctl(mib, arraysize(mib), &info, &info_size, NULL, 0); + DCHECK_EQ(sysctl_result, 0); + if (sysctl_result != 0) { + is_set = true; + being_debugged = false; + return being_debugged; + } + + // This process is being debugged if the P_TRACED flag is set. + is_set = true; + being_debugged = (info.kp_proc.p_flag & P_TRACED) != 0; + return being_debugged; +} + +#elif defined(OS_LINUX) + +// We can look in /proc/self/status for TracerPid. We are likely used in crash +// handling, so we are careful not to use the heap or have side effects. +// Another option that is common is to try to ptrace yourself, but then we +// can't detach without forking(), and that's not so great. +// static +bool BeingDebugged() { + int status_fd = open("/proc/self/status", O_RDONLY); + if (status_fd == -1) + return false; + + // We assume our line will be in the first 1024 characters and that we can + // read this much all at once. In practice this will generally be true. + // This simplifies and speeds up things considerably. + char buf[1024]; + + ssize_t num_read = HANDLE_EINTR(read(status_fd, buf, sizeof(buf))); + if (HANDLE_EINTR(close(status_fd)) < 0) + return false; + + if (num_read <= 0) + return false; + + StringPiece status(buf, num_read); + StringPiece tracer("TracerPid:\t"); + + StringPiece::size_type pid_index = status.find(tracer); + if (pid_index == StringPiece::npos) + return false; + + // Our pid is 0 without a debugger, assume this for any pid starting with 0. + pid_index += tracer.size(); + return pid_index < status.size() && status[pid_index] != '0'; +} + +#elif defined(OS_FREEBSD) + +bool DebugUtil::BeingDebugged() { + // TODO(benl): can we determine this under FreeBSD? + NOTIMPLEMENTED(); + return false; +} + +#endif // defined(OS_FREEBSD) + +// We want to break into the debugger in Debug mode, and cause a crash dump in +// Release mode. Breakpad behaves as follows: +// +// +-------+-----------------+-----------------+ +// | OS | Dump on SIGTRAP | Dump on SIGABRT | +// +-------+-----------------+-----------------+ +// | Linux | N | Y | +// | Mac | Y | N | +// +-------+-----------------+-----------------+ +// +// Thus we do the following: +// Linux: Debug mode, send SIGTRAP; Release mode, send SIGABRT. +// Mac: Always send SIGTRAP. + +#if defined(NDEBUG) && !defined(OS_MACOSX) +#define DEBUG_BREAK() abort() +#elif defined(ARCH_CPU_ARM_FAMILY) +#define DEBUG_BREAK() asm("bkpt 0") +#else +#define DEBUG_BREAK() asm("int3") +#endif + +void BreakDebugger() { + DEBUG_BREAK(); +} + +} // namespace debug +} // namespace base diff --git a/base/debug/debugger_win.cc b/base/debug/debugger_win.cc new file mode 100644 index 0000000..d1d47cd --- /dev/null +++ b/base/debug/debugger_win.cc @@ -0,0 +1,112 @@ +// Copyright (c) 2010 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/debugger.h" + +#include <windows.h> +#include <dbghelp.h> + +#include "base/basictypes.h" +#include "base/debug_util.h" +#include "base/logging.h" + +namespace base { +namespace debug { + +namespace { + +// Minimalist key reader. +// Note: Does not use the CRT. +bool RegReadString(HKEY root, const wchar_t* subkey, + const wchar_t* value_name, wchar_t* buffer, int* len) { + HKEY key = NULL; + DWORD res = RegOpenKeyEx(root, subkey, 0, KEY_READ, &key); + if (ERROR_SUCCESS != res || key == NULL) + return false; + + DWORD type = 0; + DWORD buffer_size = *len * sizeof(wchar_t); + // We don't support REG_EXPAND_SZ. + res = RegQueryValueEx(key, value_name, NULL, &type, + reinterpret_cast<BYTE*>(buffer), &buffer_size); + if (ERROR_SUCCESS == res && buffer_size != 0 && type == REG_SZ) { + // Make sure the buffer is NULL terminated. + buffer[*len - 1] = 0; + *len = lstrlen(buffer); + RegCloseKey(key); + return true; + } + RegCloseKey(key); + return false; +} + +// Replaces each "%ld" in input per a value. Not efficient but it works. +// Note: Does not use the CRT. +bool StringReplace(const wchar_t* input, int value, wchar_t* output, + int output_len) { + memset(output, 0, output_len*sizeof(wchar_t)); + int input_len = lstrlen(input); + + for (int i = 0; i < input_len; ++i) { + int current_output_len = lstrlen(output); + + if (input[i] == L'%' && input[i + 1] == L'l' && input[i + 2] == L'd') { + // Make sure we have enough place left. + if ((current_output_len + 12) >= output_len) + return false; + + // Cheap _itow(). + wsprintf(output+current_output_len, L"%d", value); + i += 2; + } else { + if (current_output_len >= output_len) + return false; + output[current_output_len] = input[i]; + } + } + return true; +} + +} // namespace + +// Note: Does not use the CRT. +bool SpawnDebuggerOnProcess(unsigned process_id) { + wchar_t reg_value[1026]; + int len = arraysize(reg_value); + if (RegReadString(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug", + L"Debugger", reg_value, &len)) { + wchar_t command_line[1026]; + if (StringReplace(reg_value, process_id, command_line, + arraysize(command_line))) { + // We don't mind if the debugger is present because it will simply fail + // to attach to this process. + STARTUPINFO startup_info = {0}; + startup_info.cb = sizeof(startup_info); + PROCESS_INFORMATION process_info = {0}; + + if (CreateProcess(NULL, command_line, NULL, NULL, FALSE, 0, NULL, NULL, + &startup_info, &process_info)) { + CloseHandle(process_info.hThread); + WaitForInputIdle(process_info.hProcess, 10000); + CloseHandle(process_info.hProcess); + return true; + } + } + } + return false; +} + +bool BeingDebugged() { + return ::IsDebuggerPresent() != 0; +} + +void BreakDebugger() { + if (DebugUtil::AreDialogsSuppressed()) + _exit(1); + __debugbreak(); +} + +} // namespace debug +} // namespace base diff --git a/base/debug/leak_annotations.h b/base/debug/leak_annotations.h new file mode 100644 index 0000000..e1086fe --- /dev/null +++ b/base/debug/leak_annotations.h @@ -0,0 +1,28 @@ +// Copyright (c) 2010 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. + +#ifndef BASE_DEBUG_LEAK_ANNOTATIONS_H_ +#define BASE_DEBUG_LEAK_ANNOTATIONS_H_ +#pragma once + +#include "build/build_config.h" + +#if defined(OS_LINUX) && defined(USE_HEAPCHECKER) + +#include "third_party/tcmalloc/chromium/src/google/heap-checker.h" + +// Annotate a program scope as having memory leaks. Tcmalloc's heap leak +// checker will ignore them. Note that these annotations may mask real bugs +// and should not be used in the production code. +#define ANNOTATE_SCOPED_MEMORY_LEAK \ + HeapLeakChecker::Disabler heap_leak_checker_disabler + +#else + +// If tcmalloc is not used, the annotations should be no-ops. +#define ANNOTATE_SCOPED_MEMORY_LEAK + +#endif + +#endif // BASE_DEBUG_LEAK_ANNOTATIONS_H_ diff --git a/base/debug/leak_tracker.h b/base/debug/leak_tracker.h new file mode 100644 index 0000000..8af82a9 --- /dev/null +++ b/base/debug/leak_tracker.h @@ -0,0 +1,134 @@ +// Copyright (c) 2010 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. + +#ifndef BASE_DEBUG_LEAK_TRACKER_H_ +#define BASE_DEBUG_LEAK_TRACKER_H_ +#pragma once + +// Only enable leak tracking in debug builds. +#ifndef NDEBUG +#define ENABLE_LEAK_TRACKER +#endif + +#ifdef ENABLE_LEAK_TRACKER +#include "base/debug/stack_trace.h" +#include "base/linked_list.h" +#include "base/logging.h" +#endif // ENABLE_LEAK_TRACKER + +// LeakTracker is a helper to verify that all instances of a class +// have been destroyed. +// +// It is particularly useful for classes that are bound to a single thread -- +// before destroying that thread, one can check that there are no remaining +// instances of that class. +// +// For example, to enable leak tracking for class URLRequest, start by +// adding a member variable of type LeakTracker<URLRequest>. +// +// class URLRequest { +// ... +// private: +// base::LeakTracker<URLRequest> leak_tracker_; +// }; +// +// +// Next, when we believe all instances of URLRequest have been deleted: +// +// LeakTracker<URLRequest>::CheckForLeaks(); +// +// Should the check fail (because there are live instances of URLRequest), +// then the allocation callstack for each leaked instances is dumped to +// the error log. +// +// If ENABLE_LEAK_TRACKER is not defined, then the check has no effect. + +namespace base { +namespace debug { + +#ifndef ENABLE_LEAK_TRACKER + +// If leak tracking is disabled, do nothing. +template<typename T> +class LeakTracker { + public: + static void CheckForLeaks() {} + static int NumLiveInstances() { return -1; } +}; + +#else + +// If leak tracking is enabled we track where the object was allocated from. + +template<typename T> +class LeakTracker : public LinkNode<LeakTracker<T> > { + public: + LeakTracker() { + instances()->Append(this); + } + + ~LeakTracker() { + this->RemoveFromList(); + } + + static void CheckForLeaks() { + // Walk the allocation list and print each entry it contains. + size_t count = 0; + + // Copy the first 3 leak allocation callstacks onto the stack. + // This way if we hit the CHECK() in a release build, the leak + // information will be available in mini-dump. + const size_t kMaxStackTracesToCopyOntoStack = 3; + StackTrace stacktraces[kMaxStackTracesToCopyOntoStack]; + + for (LinkNode<LeakTracker<T> >* node = instances()->head(); + node != instances()->end(); + node = node->next()) { + StackTrace& allocation_stack = node->value()->allocation_stack_; + + if (count < kMaxStackTracesToCopyOntoStack) + stacktraces[count] = allocation_stack; + + ++count; + LOG(ERROR) << "Leaked " << node << " which was allocated by:"; + allocation_stack.OutputToStream(&LOG_STREAM(ERROR)); + } + + CHECK_EQ(0u, count); + + // Hack to keep |stacktraces| and |count| alive (so compiler + // doesn't optimize it out, and it will appear in mini-dumps). + if (count == 0x1234) { + for (size_t i = 0; i < kMaxStackTracesToCopyOntoStack; ++i) + stacktraces[i].PrintBacktrace(); + } + } + + static int NumLiveInstances() { + // Walk the allocation list and count how many entries it has. + int count = 0; + for (LinkNode<LeakTracker<T> >* node = instances()->head(); + node != instances()->end(); + node = node->next()) { + ++count; + } + return count; + } + + private: + // Each specialization of LeakTracker gets its own static storage. + static LinkedList<LeakTracker<T> >* instances() { + static LinkedList<LeakTracker<T> > list; + return &list; + } + + StackTrace allocation_stack_; +}; + +#endif // ENABLE_LEAK_TRACKER + +} // namespace debug +} // namespace base + +#endif // BASE_DEBUG_LEAK_TRACKER_H_ diff --git a/base/debug/leak_tracker_unittest.cc b/base/debug/leak_tracker_unittest.cc new file mode 100644 index 0000000..2e6a9a5 --- /dev/null +++ b/base/debug/leak_tracker_unittest.cc @@ -0,0 +1,113 @@ +// Copyright (c) 2010 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/leak_tracker.h" +#include "base/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace debug { + +namespace { + +class ClassA { + private: + LeakTracker<ClassA> leak_tracker_; +}; + +class ClassB { + private: + LeakTracker<ClassB> leak_tracker_; +}; + +#ifndef ENABLE_LEAK_TRACKER + +// If leak tracking is disabled, we should do nothing. +TEST(LeakTrackerTest, NotEnabled) { + EXPECT_EQ(-1, LeakTracker<ClassA>::NumLiveInstances()); + EXPECT_EQ(-1, LeakTracker<ClassB>::NumLiveInstances()); + + // Use scoped_ptr so compiler doesn't complain about unused variables. + scoped_ptr<ClassA> a1(new ClassA); + scoped_ptr<ClassB> b1(new ClassB); + scoped_ptr<ClassB> b2(new ClassB); + + EXPECT_EQ(-1, LeakTracker<ClassA>::NumLiveInstances()); + EXPECT_EQ(-1, LeakTracker<ClassB>::NumLiveInstances()); +} + +#else + +TEST(LeakTrackerTest, Basic) { + { + ClassA a1; + + EXPECT_EQ(1, LeakTracker<ClassA>::NumLiveInstances()); + EXPECT_EQ(0, LeakTracker<ClassB>::NumLiveInstances()); + + ClassB b1; + ClassB b2; + + EXPECT_EQ(1, LeakTracker<ClassA>::NumLiveInstances()); + EXPECT_EQ(2, LeakTracker<ClassB>::NumLiveInstances()); + + scoped_ptr<ClassA> a2(new ClassA); + + EXPECT_EQ(2, LeakTracker<ClassA>::NumLiveInstances()); + EXPECT_EQ(2, LeakTracker<ClassB>::NumLiveInstances()); + + a2.reset(); + + EXPECT_EQ(1, LeakTracker<ClassA>::NumLiveInstances()); + EXPECT_EQ(2, LeakTracker<ClassB>::NumLiveInstances()); + } + + EXPECT_EQ(0, LeakTracker<ClassA>::NumLiveInstances()); + EXPECT_EQ(0, LeakTracker<ClassB>::NumLiveInstances()); +} + +// Try some orderings of create/remove to hit different cases in the linked-list +// assembly. +TEST(LeakTrackerTest, LinkedList) { + EXPECT_EQ(0, LeakTracker<ClassB>::NumLiveInstances()); + + scoped_ptr<ClassA> a1(new ClassA); + scoped_ptr<ClassA> a2(new ClassA); + scoped_ptr<ClassA> a3(new ClassA); + scoped_ptr<ClassA> a4(new ClassA); + + EXPECT_EQ(4, LeakTracker<ClassA>::NumLiveInstances()); + + // Remove the head of the list (a1). + a1.reset(); + EXPECT_EQ(3, LeakTracker<ClassA>::NumLiveInstances()); + + // Remove the tail of the list (a4). + a4.reset(); + EXPECT_EQ(2, LeakTracker<ClassA>::NumLiveInstances()); + + // Append to the new tail of the list (a3). + scoped_ptr<ClassA> a5(new ClassA); + EXPECT_EQ(3, LeakTracker<ClassA>::NumLiveInstances()); + + a2.reset(); + a3.reset(); + + EXPECT_EQ(1, LeakTracker<ClassA>::NumLiveInstances()); + + a5.reset(); + EXPECT_EQ(0, LeakTracker<ClassA>::NumLiveInstances()); +} + +TEST(LeakTrackerTest, NoOpCheckForLeaks) { + // There are no live instances of ClassA, so this should do nothing. + LeakTracker<ClassA>::CheckForLeaks(); +} + +#endif // ENABLE_LEAK_TRACKER + +} // namespace + +} // namespace debug +} // namespace base diff --git a/base/debug/stack_trace.cc b/base/debug/stack_trace.cc new file mode 100644 index 0000000..5be4c5c --- /dev/null +++ b/base/debug/stack_trace.cc @@ -0,0 +1,21 @@ +// Copyright (c) 2010 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/stack_trace.h" + +namespace base { +namespace debug { + +StackTrace::~StackTrace() { +} + +const void *const *StackTrace::Addresses(size_t* count) { + *count = count_; + if (count_) + return trace_; + return NULL; +} + +} // namespace debug +} // namespace base diff --git a/base/debug/stack_trace.h b/base/debug/stack_trace.h new file mode 100644 index 0000000..8afc32c --- /dev/null +++ b/base/debug/stack_trace.h @@ -0,0 +1,63 @@ +// Copyright (c) 2010 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. + +#ifndef BASE_DEBUG_STACK_TRACE_H_ +#define BASE_DEBUG_STACK_TRACE_H_ +#pragma once + +#include <iosfwd> + +#include "build/build_config.h" + +#if defined(OS_WIN) +struct _EXCEPTION_POINTERS; +#endif + +namespace base { +namespace debug { + +// 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. +class StackTrace { + public: + // Creates a stacktrace from the current location. + StackTrace(); + +#if defined(OS_WIN) + // Creates a stacktrace for an exception. + // Note: this function will throw an import not found (StackWalk64) exception + // on system without dbghelp 5.1. + StackTrace(_EXCEPTION_POINTERS* exception_pointers); +#endif + + // Copying and assignment are allowed with the default functions. + + ~StackTrace(); + + // Gets an array of instruction pointer values. |*count| will be set to the + // number of elements in the returned array. + const void* const* Addresses(size_t* count); + + // Prints a backtrace to stderr + void PrintBacktrace(); + + // Resolves backtrace to symbols and write to stream. + void OutputToStream(std::ostream* os); + + private: + // From http://msdn.microsoft.com/en-us/library/bb204633.aspx, + // the sum of FramesToSkip and FramesToCapture must be less than 63, + // so set it to 62. Even if on POSIX it could be a larger value, it usually + // doesn't give much more information. + static const int kMaxTraces = 62; + + void* trace_[kMaxTraces]; + int count_; +}; + +} // namespace debug +} // namespace base + +#endif // BASE_DEBUG_STACK_TRACE_H_ diff --git a/base/debug/stack_trace_posix.cc b/base/debug/stack_trace_posix.cc new file mode 100644 index 0000000..e4b0ef2 --- /dev/null +++ b/base/debug/stack_trace_posix.cc @@ -0,0 +1,199 @@ +// Copyright (c) 2010 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/stack_trace.h" + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/sysctl.h> +#include <sys/types.h> +#include <unistd.h> + +#include <string> +#include <vector> + +#if defined(__GLIBCXX__) +#include <cxxabi.h> +#endif + +#if defined(OS_MACOSX) +#include <AvailabilityMacros.h> +#endif + +#include <iostream> + +#include "base/basictypes.h" +#include "base/compat_execinfo.h" +#include "base/eintr_wrapper.h" +#include "base/logging.h" +#include "base/safe_strerror_posix.h" +#include "base/scoped_ptr.h" +#include "base/string_piece.h" +#include "base/stringprintf.h" + +#if defined(USE_SYMBOLIZE) +#include "base/third_party/symbolize/symbolize.h" +#endif + +namespace base { +namespace debug { + +namespace { + +// The prefix used for mangled symbols, per the Itanium C++ ABI: +// http://www.codesourcery.com/cxx-abi/abi.html#mangling +const char kMangledSymbolPrefix[] = "_Z"; + +// Characters that can be used for symbols, generated by Ruby: +// (('a'..'z').to_a+('A'..'Z').to_a+('0'..'9').to_a + ['_']).join +const char kSymbolCharacters[] = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; + +#if !defined(USE_SYMBOLIZE) +// Demangles C++ symbols in the given text. Example: +// +// "sconsbuild/Debug/base_unittests(_ZN10StackTraceC1Ev+0x20) [0x817778c]" +// => +// "sconsbuild/Debug/base_unittests(StackTrace::StackTrace()+0x20) [0x817778c]" +void DemangleSymbols(std::string* text) { +#if defined(__GLIBCXX__) + + std::string::size_type search_from = 0; + while (search_from < text->size()) { + // Look for the start of a mangled symbol, from search_from. + std::string::size_type mangled_start = + text->find(kMangledSymbolPrefix, search_from); + if (mangled_start == std::string::npos) { + break; // Mangled symbol not found. + } + + // Look for the end of the mangled symbol. + std::string::size_type mangled_end = + text->find_first_not_of(kSymbolCharacters, mangled_start); + if (mangled_end == std::string::npos) { + mangled_end = text->size(); + } + std::string mangled_symbol = + text->substr(mangled_start, mangled_end - mangled_start); + + // Try to demangle the mangled symbol candidate. + int status = 0; + scoped_ptr_malloc<char> demangled_symbol( + abi::__cxa_demangle(mangled_symbol.c_str(), NULL, 0, &status)); + if (status == 0) { // Demangling is successful. + // Remove the mangled symbol. + text->erase(mangled_start, mangled_end - mangled_start); + // Insert the demangled symbol. + text->insert(mangled_start, demangled_symbol.get()); + // Next time, we'll start right after the demangled symbol we inserted. + search_from = mangled_start + strlen(demangled_symbol.get()); + } else { + // Failed to demangle. Retry after the "_Z" we just found. + search_from = mangled_start + 2; + } + } + +#endif // defined(__GLIBCXX__) +} +#endif // !defined(USE_SYMBOLIZE) + +// Gets the backtrace as a vector of strings. If possible, resolve symbol +// names and attach these. Otherwise just use raw addresses. Returns true +// if any symbol name is resolved. Returns false on error and *may* fill +// in |error_message| if an error message is available. +bool GetBacktraceStrings(void **trace, int size, + std::vector<std::string>* trace_strings, + std::string* error_message) { + bool symbolized = false; + +#if defined(USE_SYMBOLIZE) + for (int i = 0; i < size; ++i) { + char symbol[1024]; + // Subtract by one as return address of function may be in the next + // function when a function is annotated as noreturn. + if (google::Symbolize(static_cast<char *>(trace[i]) - 1, + symbol, sizeof(symbol))) { + // Don't call DemangleSymbols() here as the symbol is demangled by + // google::Symbolize(). + trace_strings->push_back( + base::StringPrintf("%s [%p]", symbol, trace[i])); + symbolized = true; + } else { + trace_strings->push_back(base::StringPrintf("%p", trace[i])); + } + } +#else + scoped_ptr_malloc<char*> trace_symbols(backtrace_symbols(trace, size)); + if (trace_symbols.get()) { + for (int i = 0; i < size; ++i) { + std::string trace_symbol = trace_symbols.get()[i]; + DemangleSymbols(&trace_symbol); + trace_strings->push_back(trace_symbol); + } + symbolized = true; + } else { + if (error_message) + *error_message = safe_strerror(errno); + for (int i = 0; i < size; ++i) { + trace_strings->push_back(base::StringPrintf("%p", trace[i])); + } + } +#endif // defined(USE_SYMBOLIZE) + + return symbolized; +} + +} // namespace + +StackTrace::StackTrace() { +#if defined(OS_MACOSX) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 + if (backtrace == NULL) { + count_ = 0; + return; + } +#endif + // Though the backtrace API man page does not list any possible negative + // return values, we take no chance. + count_ = std::max(backtrace(trace_, arraysize(trace_)), 0); +} + +void StackTrace::PrintBacktrace() { +#if defined(OS_MACOSX) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 + if (backtrace_symbols_fd == NULL) + return; +#endif + fflush(stderr); + std::vector<std::string> trace_strings; + GetBacktraceStrings(trace_, count_, &trace_strings, NULL); + for (size_t i = 0; i < trace_strings.size(); ++i) { + std::cerr << "\t" << trace_strings[i] << "\n"; + } +} + +void StackTrace::OutputToStream(std::ostream* os) { +#if defined(OS_MACOSX) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 + if (backtrace_symbols == NULL) + return; +#endif + std::vector<std::string> trace_strings; + std::string error_message; + if (GetBacktraceStrings(trace_, count_, &trace_strings, &error_message)) { + (*os) << "Backtrace:\n"; + } else { + if (!error_message.empty()) + error_message = " (" + error_message + ")"; + (*os) << "Unable to get symbols for backtrace" << error_message << ". " + << "Dumping raw addresses in trace:\n"; + } + + for (size_t i = 0; i < trace_strings.size(); ++i) { + (*os) << "\t" << trace_strings[i] << "\n"; + } +} + +} // namespace debug +} // namespace base diff --git a/base/debug/stack_trace_unittest.cc b/base/debug/stack_trace_unittest.cc new file mode 100644 index 0000000..e8e1e19 --- /dev/null +++ b/base/debug/stack_trace_unittest.cc @@ -0,0 +1,112 @@ +// Copyright (c) 2010 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/stack_trace.h" +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace debug { + +// Note: On Linux, this test currently only fully works on Debug builds. +// See comments in the #ifdef soup if you intend to change this. +// Flaky, crbug.com/32070. +TEST(StackTrace, FLAKY_OutputToStream) { + StackTrace trace; + + // Dump the trace into a string. + std::ostringstream os; + trace.OutputToStream(&os); + std::string backtrace_message = os.str(); + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && NDEBUG + // Stack traces require an extra data table that bloats our binaries, + // so they're turned off for release builds. We stop the test here, + // at least letting us verify that the calls don't crash. + return; +#endif // defined(OS_POSIX) && !defined(OS_MACOSX) && NDEBUG + + size_t frames_found = 0; + trace.Addresses(&frames_found); + ASSERT_GE(frames_found, 5u) << + "No stack frames found. Skipping rest of test."; + + // Check if the output has symbol initialization warning. If it does, fail. + ASSERT_EQ(backtrace_message.find("Dumping unresolved backtrace"), + std::string::npos) << + "Unable to resolve symbols. Skipping rest of test."; + +#if defined(OS_MACOSX) +#if 0 + // Disabled due to -fvisibility=hidden in build config. + + // Symbol resolution via the backtrace_symbol function does not work well + // in OS X. + // 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; + +#endif +#elif defined(__GLIBCXX__) + // This branch is for gcc-compiled code, but not Mac due to the + // above #if. + // Expect a demangled symbol. + EXPECT_TRUE(backtrace_message.find("testing::Test::Run()") != + std::string::npos) + << "Expected a demangled symbol in backtrace:\n" + << backtrace_message; + +#elif 0 + // This is the fall-through case; it used to cover Windows. + // But it's disabled because of varying buildbot configs; + // some lack symbols. + + // 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) +} + +// The test is used for manual testing, e.g., to see the raw output. +TEST(StackTrace, DebugOutputToStream) { + StackTrace trace; + std::ostringstream os; + trace.OutputToStream(&os); + VLOG(1) << os.str(); +} + +// The test is used for manual testing, e.g., to see the raw output. +TEST(StackTrace, DebugPrintBacktrace) { + StackTrace().PrintBacktrace(); +} + +} // namespace debug +} // namespace base diff --git a/base/debug/stack_trace_win.cc b/base/debug/stack_trace_win.cc new file mode 100644 index 0000000..653d234 --- /dev/null +++ b/base/debug/stack_trace_win.cc @@ -0,0 +1,197 @@ +// Copyright (c) 2010 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/stack_trace.h" + +#include <windows.h> +#include <dbghelp.h> + +#include <iostream> + +#include "base/basictypes.h" +#include "base/lock.h" +#include "base/logging.h" +#include "base/singleton.h" + +namespace base { +namespace debug { + +namespace { + +// 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(); + } + + // Returns the error code of a failed initialization. + DWORD init_error() const { + return init_error_; + } + + // 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 void* const* trace, + int count, + std::ostream* os) { + AutoLock lock(lock_); + + for (size_t i = 0; (i < count) && 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)]; + memset(buffer, 0, sizeof(buffer)); + + // 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 - 1; + BOOL has_symbol = SymFromAddr(GetCurrentProcess(), 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(GetCurrentProcess(), 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"; + } + } + + private: + friend struct DefaultSingletonTraits<SymbolContext>; + + SymbolContext() : init_error_(ERROR_SUCCESS) { + // Initializes the symbols for the process. + // 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(GetCurrentProcess(), NULL, TRUE)) { + init_error_ = ERROR_SUCCESS; + } else { + init_error_ = GetLastError(); + // TODO(awong): Handle error: SymInitialize can fail with + // ERROR_INVALID_PARAMETER. + // When it fails, we should not call debugbreak since it kills the current + // process (prevents future tests from running or kills the browser + // process). + DLOG(ERROR) << "SymInitialize failed: " << init_error_; + } + } + + DWORD init_error_; + Lock lock_; + DISALLOW_COPY_AND_ASSIGN(SymbolContext); +}; + +} // namespace + +StackTrace::StackTrace() { + // When walking our own stack, use CaptureStackBackTrace(). + count_ = CaptureStackBackTrace(0, arraysize(trace_), trace_, NULL); +} + +StackTrace::StackTrace(EXCEPTION_POINTERS* exception_pointers) { + // When walking an exception stack, we need to use StackWalk64(). + count_ = 0; + // Initialize stack walking. + STACKFRAME64 stack_frame; + memset(&stack_frame, 0, sizeof(stack_frame)); +#if defined(_WIN64) + int machine_type = IMAGE_FILE_MACHINE_AMD64; + stack_frame.AddrPC.Offset = exception_pointers->ContextRecord->Rip; + stack_frame.AddrFrame.Offset = exception_pointers->ContextRecord->Rbp; + stack_frame.AddrStack.Offset = exception_pointers->ContextRecord->Rsp; +#else + int machine_type = IMAGE_FILE_MACHINE_I386; + stack_frame.AddrPC.Offset = exception_pointers->ContextRecord->Eip; + stack_frame.AddrFrame.Offset = exception_pointers->ContextRecord->Ebp; + stack_frame.AddrStack.Offset = exception_pointers->ContextRecord->Esp; +#endif + stack_frame.AddrPC.Mode = AddrModeFlat; + stack_frame.AddrFrame.Mode = AddrModeFlat; + stack_frame.AddrStack.Mode = AddrModeFlat; + while (StackWalk64(machine_type, + GetCurrentProcess(), + GetCurrentThread(), + &stack_frame, + exception_pointers->ContextRecord, + NULL, + &SymFunctionTableAccess64, + &SymGetModuleBase64, + NULL) && + count_ < arraysize(trace_)) { + trace_[count_++] = reinterpret_cast<void*>(stack_frame.AddrPC.Offset); + } +} + +void StackTrace::PrintBacktrace() { + OutputToStream(&std::cerr); +} + +void StackTrace::OutputToStream(std::ostream* os) { + SymbolContext* context = SymbolContext::Get(); + DWORD error = context->init_error(); + if (error != ERROR_SUCCESS) { + (*os) << "Error initializing symbols (" << error + << "). Dumping unresolved backtrace:\n"; + for (int i = 0; (i < count_) && os->good(); ++i) { + (*os) << "\t" << trace_[i] << "\n"; + } + } else { + (*os) << "Backtrace:\n"; + context->OutputTraceToStream(trace_, count_, os); + } +} + +} // namespace debug +} // namespace base diff --git a/base/debug/trace_event.cc b/base/debug/trace_event.cc new file mode 100644 index 0000000..4d1d315 --- /dev/null +++ b/base/debug/trace_event.cc @@ -0,0 +1,166 @@ +// Copyright (c) 2010 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/trace_event.h" + +#include "base/format_macros.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/platform_thread.h" +#include "base/process_util.h" +#include "base/stringprintf.h" +#include "base/utf_string_conversions.h" +#include "base/time.h" + +#define USE_UNRELIABLE_NOW + +namespace base { +namespace debug { + +static const char* kEventTypeNames[] = { + "BEGIN", + "END", + "INSTANT" +}; + +static const FilePath::CharType* kLogFileName = + FILE_PATH_LITERAL("trace_%d.log"); + +TraceLog::TraceLog() : enabled_(false), log_file_(NULL) { + base::ProcessHandle proc = base::GetCurrentProcessHandle(); +#if !defined(OS_MACOSX) + process_metrics_.reset(base::ProcessMetrics::CreateProcessMetrics(proc)); +#else + // The default port provider is sufficient to get data for the current + // process. + process_metrics_.reset(base::ProcessMetrics::CreateProcessMetrics(proc, + NULL)); +#endif +} + +TraceLog::~TraceLog() { + Stop(); +} + +// static +bool TraceLog::IsTracing() { + TraceLog* trace = Singleton<TraceLog>::get(); + return trace->enabled_; +} + +// static +bool TraceLog::StartTracing() { + TraceLog* trace = Singleton<TraceLog>::get(); + return trace->Start(); +} + +bool TraceLog::Start() { + if (enabled_) + return true; + enabled_ = OpenLogFile(); + if (enabled_) { + Log("var raw_trace_events = [\n"); + trace_start_time_ = TimeTicks::Now(); + timer_.Start(TimeDelta::FromMilliseconds(250), this, &TraceLog::Heartbeat); + } + return enabled_; +} + +// static +void TraceLog::StopTracing() { + TraceLog* trace = Singleton<TraceLog>::get(); + return trace->Stop(); +} + +void TraceLog::Stop() { + if (enabled_) { + enabled_ = false; + Log("];\n"); + CloseLogFile(); + timer_.Stop(); + } +} + +void TraceLog::Heartbeat() { + std::string cpu = StringPrintf("%.0f", process_metrics_->GetCPUUsage()); + TRACE_EVENT_INSTANT("heartbeat.cpu", 0, cpu); +} + +void TraceLog::CloseLogFile() { + if (log_file_) { + file_util::CloseFile(log_file_); + } +} + +bool TraceLog::OpenLogFile() { + FilePath::StringType pid_filename = + StringPrintf(kLogFileName, base::GetCurrentProcId()); + FilePath log_file_path; + if (!PathService::Get(base::DIR_EXE, &log_file_path)) + return false; + log_file_path = log_file_path.Append(pid_filename); + log_file_ = file_util::OpenFile(log_file_path, "a"); + if (!log_file_) { + // try the current directory + log_file_ = file_util::OpenFile(FilePath(pid_filename), "a"); + if (!log_file_) { + return false; + } + } + return true; +} + +void TraceLog::Trace(const std::string& name, + EventType type, + const void* id, + const std::wstring& extra, + const char* file, + int line) { + if (!enabled_) + return; + Trace(name, type, id, WideToUTF8(extra), file, line); +} + +void TraceLog::Trace(const std::string& name, + EventType type, + const void* id, + const std::string& extra, + const char* file, + int line) { + if (!enabled_) + return; + +#ifdef USE_UNRELIABLE_NOW + TimeTicks tick = TimeTicks::HighResNow(); +#else + TimeTicks tick = TimeTicks::Now(); +#endif + TimeDelta delta = tick - trace_start_time_; + int64 usec = delta.InMicroseconds(); + std::string msg = + StringPrintf("{'pid':'0x%lx', 'tid':'0x%lx', 'type':'%s', " + "'name':'%s', 'id':'%p', 'extra':'%s', 'file':'%s', " + "'line_number':'%d', 'usec_begin': %" PRId64 "},\n", + static_cast<unsigned long>(base::GetCurrentProcId()), + static_cast<unsigned long>(PlatformThread::CurrentId()), + kEventTypeNames[type], + name.c_str(), + id, + extra.c_str(), + file, + line, + usec); + + Log(msg); +} + +void TraceLog::Log(const std::string& msg) { + AutoLock lock(file_lock_); + + fprintf(log_file_, "%s", msg.c_str()); +} + +} // namespace debug +} // namespace base diff --git a/base/debug/trace_event.h b/base/debug/trace_event.h new file mode 100644 index 0000000..49ba4bd --- /dev/null +++ b/base/debug/trace_event.h @@ -0,0 +1,147 @@ +// Copyright (c) 2010 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. + +// Trace events to track application performance. Events consist of a name +// a type (BEGIN, END or INSTANT), a tracking id and extra string data. +// In addition, the current process id, thread id, a timestamp down to the +// microsecond and a file and line number of the calling location. +// +// The current implementation logs these events into a log file of the form +// trace_<pid>.log where it's designed to be post-processed to generate a +// trace report. In the future, it may use another mechansim to facilitate +// real-time analysis. + +#ifndef BASE_DEBUG_TRACE_EVENT_H_ +#define BASE_DEBUG_TRACE_EVENT_H_ +#pragma once + +#include "build/build_config.h" + +#if defined(OS_WIN) +// On Windows we always pull in an alternative implementation +// which logs to Event Tracing for Windows. +// +// Note that the Windows implementation is always enabled, irrespective the +// value of the CHROMIUM_ENABLE_TRACE_EVENT define. The Windows implementation +// is controlled by Event Tracing for Windows, which will turn tracing on only +// if there is someone listening for the events it generates. +#include "base/debug/trace_event_win.h" +#else // defined(OS_WIN) + +#include <string> + +#include "base/lock.h" +#include "base/scoped_ptr.h" +#include "base/singleton.h" +#include "base/time.h" +#include "base/timer.h" + +#ifndef CHROMIUM_ENABLE_TRACE_EVENT +#define TRACE_EVENT_BEGIN(name, id, extra) ((void) 0) +#define TRACE_EVENT_END(name, id, extra) ((void) 0) +#define TRACE_EVENT_INSTANT(name, id, extra) ((void) 0) + +#else // CHROMIUM_ENABLE_TRACE_EVENT +// Use the following macros rather than using the TraceLog class directly as the +// underlying implementation may change in the future. Here's a sample usage: +// TRACE_EVENT_BEGIN("v8.run", documentId, scriptLocation); +// RunScript(script); +// TRACE_EVENT_END("v8.run", documentId, scriptLocation); + +// Record that an event (of name, id) has begun. All BEGIN events should have +// corresponding END events with a matching (name, id). +#define TRACE_EVENT_BEGIN(name, id, extra) \ + Singleton<base::debug::TraceLog>::get()->Trace( \ + name, \ + base::debug::TraceLog::EVENT_BEGIN, \ + reinterpret_cast<const void*>(id), \ + extra, \ + __FILE__, \ + __LINE__) + +// Record that an event (of name, id) has ended. All END events should have +// corresponding BEGIN events with a matching (name, id). +#define TRACE_EVENT_END(name, id, extra) \ + Singleton<base::debug::TraceLog>::get()->Trace( \ + name, \ + base::debug::TraceLog::EVENT_END, \ + reinterpret_cast<const void*>(id), \ + extra, \ + __FILE__, \ + __LINE__) + +// Record that an event (of name, id) with no duration has happened. +#define TRACE_EVENT_INSTANT(name, id, extra) \ + Singleton<base::debug::TraceLog>::get()->Trace( \ + name, \ + base::debug::TraceLog::EVENT_INSTANT, \ + reinterpret_cast<const void*>(id), \ + extra, \ + __FILE__, \ + __LINE__) +#endif // CHROMIUM_ENABLE_TRACE_EVENT + +namespace base { + +class ProcessMetrics; + +namespace debug { + +class TraceLog { + public: + enum EventType { + EVENT_BEGIN, + EVENT_END, + EVENT_INSTANT + }; + + // Is tracing currently enabled. + static bool IsTracing(); + // Start logging trace events. + static bool StartTracing(); + // Stop logging trace events. + static void StopTracing(); + + // Log a trace event of (name, type, id) with the optional extra string. + void Trace(const std::string& name, + EventType type, + const void* id, + const std::wstring& extra, + const char* file, + int line); + void Trace(const std::string& name, + EventType type, + const void* id, + const std::string& extra, + const char* file, + int line); + + private: + // This allows constructor and destructor to be private and usable only + // by the Singleton class. + friend struct DefaultSingletonTraits<TraceLog>; + + TraceLog(); + ~TraceLog(); + bool OpenLogFile(); + void CloseLogFile(); + bool Start(); + void Stop(); + void Heartbeat(); + void Log(const std::string& msg); + + bool enabled_; + FILE* log_file_; + Lock file_lock_; + TimeTicks trace_start_time_; + scoped_ptr<base::ProcessMetrics> process_metrics_; + RepeatingTimer<TraceLog> timer_; +}; + +} // namespace debug +} // namespace base + +#endif // defined(OS_WIN) + +#endif // BASE_DEBUG_TRACE_EVENT_H_ diff --git a/base/debug/trace_event_win.cc b/base/debug/trace_event_win.cc new file mode 100644 index 0000000..129a992 --- /dev/null +++ b/base/debug/trace_event_win.cc @@ -0,0 +1,116 @@ +// Copyright (c) 2010 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/trace_event_win.h" + +#include "base/logging.h" +#include "base/singleton.h" +#include <initguid.h> // NOLINT + +namespace base { +namespace debug { + +// {3DADA31D-19EF-4dc1-B345-037927193422} +const GUID kChromeTraceProviderName = { + 0x3dada31d, 0x19ef, 0x4dc1, 0xb3, 0x45, 0x3, 0x79, 0x27, 0x19, 0x34, 0x22 }; + +// {B967AE67-BB22-49d7-9406-55D91EE1D560} +const GUID kTraceEventClass32 = { + 0xb967ae67, 0xbb22, 0x49d7, 0x94, 0x6, 0x55, 0xd9, 0x1e, 0xe1, 0xd5, 0x60 }; + +// {97BE602D-2930-4ac3-8046-B6763B631DFE} +const GUID kTraceEventClass64 = { + 0x97be602d, 0x2930, 0x4ac3, 0x80, 0x46, 0xb6, 0x76, 0x3b, 0x63, 0x1d, 0xfe}; + + +TraceLog::TraceLog() : EtwTraceProvider(kChromeTraceProviderName) { + Register(); +} + +TraceLog* TraceLog::Get() { + return Singleton<TraceLog, StaticMemorySingletonTraits<TraceLog>>::get(); +} + +bool TraceLog::StartTracing() { + return true; +} + +void TraceLog::TraceEvent(const char* name, + size_t name_len, + EventType type, + const void* id, + const char* extra, + size_t extra_len) { + // Make sure we don't touch NULL. + if (name == NULL) + name = ""; + if (extra == NULL) + extra = ""; + + EtwEventType etw_type = 0; + switch (type) { + case TraceLog::EVENT_BEGIN: + etw_type = kTraceEventTypeBegin; + break; + case TraceLog::EVENT_END: + etw_type = kTraceEventTypeEnd; + break; + + case TraceLog::EVENT_INSTANT: + etw_type = kTraceEventTypeInstant; + break; + + default: + NOTREACHED() << "Unknown event type"; + etw_type = kTraceEventTypeInstant; + break; + } + + EtwMofEvent<5> event(kTraceEventClass32, + etw_type, + TRACE_LEVEL_INFORMATION); + event.SetField(0, name_len + 1, name); + event.SetField(1, sizeof(id), &id); + event.SetField(2, extra_len + 1, extra); + + // See whether we're to capture a backtrace. + void* backtrace[32]; + if (enable_flags() & CAPTURE_STACK_TRACE) { + DWORD hash = 0; + DWORD depth = CaptureStackBackTrace(0, + arraysize(backtrace), + backtrace, + &hash); + event.SetField(3, sizeof(depth), &depth); + event.SetField(4, sizeof(backtrace[0]) * depth, backtrace); + } + + // Trace the event. + Log(event.get()); +} + +void TraceLog::Trace(const char* name, + size_t name_len, + EventType type, + const void* id, + const char* extra, + size_t extra_len) { + TraceLog* provider = TraceLog::Get(); + if (provider && provider->IsTracing()) { + // Compute the name & extra lengths if not supplied already. + if (name_len == -1) + name_len = (name == NULL) ? 0 : strlen(name); + if (extra_len == -1) + extra_len = (extra == NULL) ? 0 : strlen(extra); + + provider->TraceEvent(name, name_len, type, id, extra, extra_len); + } +} + +void TraceLog::Resurrect() { + StaticMemorySingletonTraits<TraceLog>::Resurrect(); +} + +} // namespace debug +} // namespace base diff --git a/base/debug/trace_event_win.h b/base/debug/trace_event_win.h new file mode 100644 index 0000000..669667b --- /dev/null +++ b/base/debug/trace_event_win.h @@ -0,0 +1,151 @@ +// Copyright (c) 2010 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. + +// This file contains the Windows-specific declarations for trace_event.h. +#ifndef BASE_DEBUG_TRACE_EVENT_WIN_H_ +#define BASE_DEBUG_TRACE_EVENT_WIN_H_ +#pragma once + +#include <string> +#include "base/event_trace_provider_win.h" + +#define TRACE_EVENT_BEGIN(name, id, extra) \ + base::debug::TraceLog::Trace( \ + name, \ + base::debug::TraceLog::EVENT_BEGIN, \ + reinterpret_cast<const void*>(id), \ + extra); + +#define TRACE_EVENT_END(name, id, extra) \ + base::debug::TraceLog::Trace( \ + name, \ + base::debug::TraceLog::EVENT_END, \ + reinterpret_cast<const void*>(id), \ + extra); + +#define TRACE_EVENT_INSTANT(name, id, extra) \ + base::debug::TraceLog::Trace( \ + name, \ + base::debug::TraceLog::EVENT_INSTANT, \ + reinterpret_cast<const void*>(id), \ + extra); + +// Fwd. +template <typename Type> +struct StaticMemorySingletonTraits; + +namespace base { +namespace debug { + +// This EtwTraceProvider subclass implements ETW logging +// for the macros above on Windows. +class TraceLog : public EtwTraceProvider { + public: + enum EventType { + EVENT_BEGIN, + EVENT_END, + EVENT_INSTANT + }; + + // Start logging trace events. + // This is a noop in this implementation. + static bool StartTracing(); + + // Trace begin/end/instant events, this is the bottleneck implementation + // all the others defer to. + // Allowing the use of std::string for name or extra is a convenience, + // whereas passing name or extra as a const char* avoids the construction + // of temporary std::string instances. + // If -1 is passed for name_len or extra_len, the strlen of the string will + // be used for length. + static void Trace(const char* name, + size_t name_len, + EventType type, + const void* id, + const char* extra, + size_t extra_len); + + // Allows passing extra as a std::string for convenience. + static void Trace(const char* name, + EventType type, + const void* id, + const std::string& extra) { + return Trace(name, -1, type, id, extra.c_str(), extra.length()); + } + + // Allows passing extra as a const char* to avoid constructing temporary + // std::string instances where not needed. + static void Trace(const char* name, + EventType type, + const void* id, + const char* extra) { + return Trace(name, -1, type, id, extra, -1); + } + + // Retrieves the singleton. + // Note that this may return NULL post-AtExit processing. + static TraceLog* Get(); + + // Returns true iff tracing is turned on. + bool IsTracing() { + return enable_level() >= TRACE_LEVEL_INFORMATION; + } + + // Emit a trace of type |type| containing |name|, |id|, and |extra|. + // Note: |name| and |extra| must be NULL, or a zero-terminated string of + // length |name_len| or |extra_len| respectively. + // Note: if name_len or extra_len are -1, the length of the corresponding + // string will be used. + void TraceEvent(const char* name, + size_t name_len, + EventType type, + const void* id, + const char* extra, + size_t extra_len); + + // Exposed for unittesting only, allows resurrecting our + // singleton instance post-AtExit processing. + static void Resurrect(); + + private: + // Ensure only the provider can construct us. + friend struct StaticMemorySingletonTraits<TraceLog>; + TraceLog(); + + DISALLOW_COPY_AND_ASSIGN(TraceLog); +}; + +// The ETW trace provider GUID. +extern const GUID kChromeTraceProviderName; + +// The ETW event class GUID for 32 bit events. +extern const GUID kTraceEventClass32; + +// The ETW event class GUID for 64 bit events. +extern const GUID kTraceEventClass64; + +// The ETW event types, IDs 0x00-0x09 are reserved, so start at 0x10. +const EtwEventType kTraceEventTypeBegin = 0x10; +const EtwEventType kTraceEventTypeEnd = 0x11; +const EtwEventType kTraceEventTypeInstant = 0x12; + +// If this flag is set in enable flags +enum TraceEventFlags { + CAPTURE_STACK_TRACE = 0x0001, +}; + +// The event format consists of: +// The "name" string as a zero-terminated ASCII string. +// The id pointer in the machine bitness. +// The "extra" string as a zero-terminated ASCII string. +// Optionally the stack trace, consisting of a DWORD "depth", followed +// by an array of void* (machine bitness) of length "depth". + +// Forward decl. +struct TraceLogSingletonTraits; + +} // nemspace debug +} // namespace base + +#endif // BASE_DEBUG_TRACE_EVENT_WIN_H_ diff --git a/base/debug/trace_event_win_unittest.cc b/base/debug/trace_event_win_unittest.cc new file mode 100644 index 0000000..a818b9b --- /dev/null +++ b/base/debug/trace_event_win_unittest.cc @@ -0,0 +1,308 @@ +// Copyright (c) 2010 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/trace_event.h" + +#include <strstream> + +#include "base/at_exit.h" +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/event_trace_consumer_win.h" +#include "base/event_trace_controller_win.h" +#include "base/win/windows_version.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include <initguid.h> // NOLINT - must be last include. + +namespace base { +namespace debug { + +namespace { + +using testing::_; +using testing::AnyNumber; +using testing::InSequence; +using testing::Ge; +using testing::Le; +using testing::NotNull; + +// Data for unittests traces. +const char kEmpty[] = ""; +const char kName[] = "unittest.trace_name"; +const char kExtra[] = "UnittestDummyExtraString"; +const void* kId = kName; + +const wchar_t kTestSessionName[] = L"TraceEvent unittest session"; + +MATCHER_P(BufferStartsWith, str, "Buffer starts with") { + return memcmp(arg, str.c_str(), str.length()) == 0; +} + +// Duplicated from <evntrace.h> to fix link problems. +DEFINE_GUID( /* 68fdd900-4a3e-11d1-84f4-0000f80464e3 */ + kEventTraceGuid, + 0x68fdd900, + 0x4a3e, + 0x11d1, + 0x84, 0xf4, 0x00, 0x00, 0xf8, 0x04, 0x64, 0xe3); + +class TestEventConsumer: public EtwTraceConsumerBase<TestEventConsumer> { + public: + TestEventConsumer() { + EXPECT_TRUE(current_ == NULL); + current_ = this; + } + + ~TestEventConsumer() { + EXPECT_TRUE(current_ == this); + current_ = NULL; + } + + MOCK_METHOD4(Event, void(REFGUID event_class, + EtwEventType event_type, + size_t buf_len, + const void* buf)); + + static void ProcessEvent(EVENT_TRACE* event) { + ASSERT_TRUE(current_ != NULL); + current_->Event(event->Header.Guid, + event->Header.Class.Type, + event->MofLength, + event->MofData); + } + + private: + static TestEventConsumer* current_; +}; + +TestEventConsumer* TestEventConsumer::current_ = NULL; + +class TraceEventTest: public testing::Test { + public: + TraceEventTest() { + } + + void SetUp() { + bool is_xp = win::GetVersion() < base::win::VERSION_VISTA; + + if (is_xp) { + // Tear down any dangling session from an earlier failing test. + EtwTraceProperties ignore; + EtwTraceController::Stop(kTestSessionName, &ignore); + } + + // Resurrect and initialize the TraceLog singleton instance. + // On Vista and better, we need the provider registered before we + // start the private, in-proc session, but on XP we need the global + // session created and the provider enabled before we register our + // provider. + TraceLog* tracelog = NULL; + if (!is_xp) { + TraceLog::Resurrect(); + tracelog = TraceLog::Get(); + ASSERT_TRUE(tracelog != NULL); + ASSERT_FALSE(tracelog->IsTracing()); + } + + // Create the log file. + ASSERT_TRUE(file_util::CreateTemporaryFile(&log_file_)); + + // Create a private log session on the file. + EtwTraceProperties prop; + ASSERT_HRESULT_SUCCEEDED(prop.SetLoggerFileName(log_file_.value().c_str())); + EVENT_TRACE_PROPERTIES& p = *prop.get(); + p.Wnode.ClientContext = 1; // QPC timer accuracy. + p.LogFileMode = EVENT_TRACE_FILE_MODE_SEQUENTIAL; // Sequential log. + + // On Vista and later, we create a private in-process log session, because + // otherwise we'd need administrator privileges. Unfortunately we can't + // do the same on XP and better, because the semantics of a private + // logger session are different, and the IN_PROC flag is not supported. + if (!is_xp) { + p.LogFileMode |= EVENT_TRACE_PRIVATE_IN_PROC | // In-proc for non-admin. + EVENT_TRACE_PRIVATE_LOGGER_MODE; // Process-private log. + } + + p.MaximumFileSize = 100; // 100M file size. + p.FlushTimer = 1; // 1 second flush lag. + ASSERT_HRESULT_SUCCEEDED(controller_.Start(kTestSessionName, &prop)); + + // Enable the TraceLog provider GUID. + ASSERT_HRESULT_SUCCEEDED( + controller_.EnableProvider(kChromeTraceProviderName, + TRACE_LEVEL_INFORMATION, + 0)); + + if (is_xp) { + TraceLog::Resurrect(); + tracelog = TraceLog::Get(); + } + ASSERT_TRUE(tracelog != NULL); + EXPECT_TRUE(tracelog->IsTracing()); + } + + void TearDown() { + EtwTraceProperties prop; + if (controller_.session() != 0) + EXPECT_HRESULT_SUCCEEDED(controller_.Stop(&prop)); + + if (!log_file_.value().empty()) + file_util::Delete(log_file_, false); + } + + void ExpectEvent(REFGUID guid, + EtwEventType type, + const char* name, + size_t name_len, + const void* id, + const char* extra, + size_t extra_len) { + // Build the trace event buffer we expect will result from this. + std::stringbuf str; + str.sputn(name, name_len + 1); + str.sputn(reinterpret_cast<const char*>(&id), sizeof(id)); + str.sputn(extra, extra_len + 1); + + // And set up the expectation for the event callback. + EXPECT_CALL(consumer_, Event(guid, + type, + testing::Ge(str.str().length()), + BufferStartsWith(str.str()))); + } + + void ExpectPlayLog() { + // Ignore EventTraceGuid events. + EXPECT_CALL(consumer_, Event(kEventTraceGuid, _, _, _)) + .Times(AnyNumber()); + } + + void PlayLog() { + EtwTraceProperties prop; + EXPECT_HRESULT_SUCCEEDED(controller_.Flush(&prop)); + EXPECT_HRESULT_SUCCEEDED(controller_.Stop(&prop)); + ASSERT_HRESULT_SUCCEEDED( + consumer_.OpenFileSession(log_file_.value().c_str())); + + ASSERT_HRESULT_SUCCEEDED(consumer_.Consume()); + } + + private: + // We want our singleton torn down after each test. + ShadowingAtExitManager at_exit_manager_; + EtwTraceController controller_; + FilePath log_file_; + TestEventConsumer consumer_; +}; + +} // namespace + + +TEST_F(TraceEventTest, TraceLog) { + ExpectPlayLog(); + + // The events should arrive in the same sequence as the expects. + InSequence in_sequence; + + // Full argument version, passing lengths explicitly. + TraceLog::Trace(kName, + strlen(kName), + TraceLog::EVENT_BEGIN, + kId, + kExtra, + strlen(kExtra)); + + ExpectEvent(kTraceEventClass32, + kTraceEventTypeBegin, + kName, strlen(kName), + kId, + kExtra, strlen(kExtra)); + + // Const char* version. + TraceLog::Trace(static_cast<const char*>(kName), + TraceLog::EVENT_END, + kId, + static_cast<const char*>(kExtra)); + + ExpectEvent(kTraceEventClass32, + kTraceEventTypeEnd, + kName, strlen(kName), + kId, + kExtra, strlen(kExtra)); + + // std::string extra version. + TraceLog::Trace(static_cast<const char*>(kName), + TraceLog::EVENT_INSTANT, + kId, + std::string(kExtra)); + + ExpectEvent(kTraceEventClass32, + kTraceEventTypeInstant, + kName, strlen(kName), + kId, + kExtra, strlen(kExtra)); + + + // Test for sanity on NULL inputs. + TraceLog::Trace(NULL, + 0, + TraceLog::EVENT_BEGIN, + kId, + NULL, + 0); + + ExpectEvent(kTraceEventClass32, + kTraceEventTypeBegin, + kEmpty, 0, + kId, + kEmpty, 0); + + TraceLog::Trace(NULL, + -1, + TraceLog::EVENT_END, + kId, + NULL, + -1); + + ExpectEvent(kTraceEventClass32, + kTraceEventTypeEnd, + kEmpty, 0, + kId, + kEmpty, 0); + + PlayLog(); +} + +TEST_F(TraceEventTest, Macros) { + ExpectPlayLog(); + + // The events should arrive in the same sequence as the expects. + InSequence in_sequence; + + TRACE_EVENT_BEGIN(kName, kId, kExtra); + ExpectEvent(kTraceEventClass32, + kTraceEventTypeBegin, + kName, strlen(kName), + kId, + kExtra, strlen(kExtra)); + + TRACE_EVENT_END(kName, kId, kExtra); + ExpectEvent(kTraceEventClass32, + kTraceEventTypeEnd, + kName, strlen(kName), + kId, + kExtra, strlen(kExtra)); + + TRACE_EVENT_INSTANT(kName, kId, kExtra); + ExpectEvent(kTraceEventClass32, + kTraceEventTypeInstant, + kName, strlen(kName), + kId, + kExtra, strlen(kExtra)); + + PlayLog(); +} + +} // namespace debug +} // namespace base |