diff options
Diffstat (limited to 'o3d/breakpad')
-rw-r--r-- | o3d/breakpad/build.scons | 86 | ||||
-rw-r--r-- | o3d/breakpad/win/bluescreen_detector.cc | 331 | ||||
-rw-r--r-- | o3d/breakpad/win/bluescreen_detector.h | 219 | ||||
-rw-r--r-- | o3d/breakpad/win/bluescreen_detector_test.cc | 322 | ||||
-rw-r--r-- | o3d/breakpad/win/breakpad_config.cc | 74 | ||||
-rw-r--r-- | o3d/breakpad/win/breakpad_config.h | 57 | ||||
-rw-r--r-- | o3d/breakpad/win/crash_sender_win32.cc | 153 | ||||
-rw-r--r-- | o3d/breakpad/win/exception_handler_win32.cc | 210 | ||||
-rw-r--r-- | o3d/breakpad/win/exception_handler_win32.h | 76 |
9 files changed, 1528 insertions, 0 deletions
diff --git a/o3d/breakpad/build.scons b/o3d/breakpad/build.scons new file mode 100644 index 0000000..235bfdc --- /dev/null +++ b/o3d/breakpad/build.scons @@ -0,0 +1,86 @@ +# Copyright 2009, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +# Import the build environment that was decided in the project SConstruct. +# This may be the Debug or Optimized environment +Import('env') + +env.Append( + CPPPATH = [ + '$BREAKPAD_DIR', + ], +) +# Create a list of the files used to build the target library. +library_inputs = [ + 'win/exception_handler_win32.cc', + 'win/breakpad_config.cc', + 'win/bluescreen_detector.cc', +] + +library_breakpad_inputs = [ + 'client/windows/handler/exception_handler', + 'client/windows/crash_generation/crash_generation_client', + 'common/windows/guid_string', + 'common/windows/http_upload', + 'common/windows/string_utils', +] +library_breakpad_objects = env.MakeObjects(library_breakpad_inputs, + '$BREAKPAD_DIR', + 'cc') + +# Create a target library from the sources called 'o3dBreakpad' +env.ComponentLibrary('o3dBreakpad', library_inputs + library_breakpad_objects) + +# Create a list of the files used to build 'reporter.exe'. +reporter_inputs = [ + 'win/crash_sender_win32.cc', +] +reporter_breakpad_inputs = [ + 'client/windows/sender/crash_report_sender', +] +reporter_breakpad_objects = env.MakeObjects(reporter_breakpad_inputs, + '$BREAKPAD_DIR', + 'cc') + +env.Append( + LIBS = [ + 'AdvAPI32', + 'WinInet', + 'shlwapi', + 'o3dBreakpad', + ], +) + +# Create the breakpad 'reporter.exe' tool for uploading crash reports +reporter = env.ComponentProgram('reporter', + reporter_inputs + reporter_breakpad_objects) + +# Copy the resulting executable to the Artifacts directory. +env.Replicate('$ARTIFACTS_DIR', reporter) diff --git a/o3d/breakpad/win/bluescreen_detector.cc b/o3d/breakpad/win/bluescreen_detector.cc new file mode 100644 index 0000000..9cdd51f --- /dev/null +++ b/o3d/breakpad/win/bluescreen_detector.cc @@ -0,0 +1,331 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +// BluescreenDetector attempts to identify cases where the machine bluescreened +// and was caused by o3d. + +#include "breakpad/win/bluescreen_detector.h" +#include "plugin/cross/plugin_logging.h" +#include "plugin/cross/plugin_metrics.h" + +#ifdef OS_WIN +#include <windows.h> +#include <rpc.h> +#endif + +extern o3d::PluginLogging* g_logger; + +namespace o3d { + +using std::vector; + +#ifdef OS_WIN + +const wchar_t *kMarkerFileSuffix = L"_bs"; + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +BluescreenDetector::BluescreenDetector() + : started_(false) { + time_manager_ = new TimeManager(); + marker_file_manager_ = new MarkerFileManager(time_manager_); + bluescreen_logger_ = new BluescreenLogger(); +} +#endif // OS_WIN + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +BluescreenDetector::~BluescreenDetector() { + if (started_) { + Stop(); + } +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void BluescreenDetector::Start() { + // Here we check if any marker files (from a previous session) were not + // properly cleaned up. If so, then a blue-screen may have been caused + // by us and we'll log it. + int num_bluescreens = marker_file_manager_->DetectStrayMarkerFiles(); + + if (num_bluescreens > 0) { + bluescreen_logger_->LogBluescreen(num_bluescreens); + } + + // Create a marker file for this session - it will be removed when the plugin + // unloads (or in the breakpad exception handler). If a blue-screen happens, + // then this file will not be deleted and we'll hopefully detect it the next + // time around (in a call to DetectStrayMarkerFiles()) + marker_file_manager_->CreateMarkerFile(); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void BluescreenDetector::Stop() { + marker_file_manager_->RemoveMarkerFile(); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// MarkerFileManager + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Base implementation shared by mock and actual sub-classes +int MarkerFileManagerInterface::DetectStrayMarkerFiles() { + vector<MarkerFileInfo> marker_files; + GetMarkerFileList(&marker_files); + + // Go through all marker files and look for ones which were created before + // the machine was last booted + int stray_file_count = 0; + + for (int i = 0; i < marker_files.size(); ++i) { + const MarkerFileInfo &file_info = marker_files[i]; + + if (time_manager_->IsMarkerFileOld(file_info)) { + // We've found a marker file which was created before we last re-booted + // This could signal a blue-screen which we caused + + // Clean it up, so we don't continue detecting it again and logging a + // blue-screen more than once + DeleteMarkerFile(file_info); + ++stray_file_count; + // don't break here, since we need to detect and delete all old ones + } + } + + return stray_file_count; +} + +#ifdef OS_WIN + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void MarkerFileManager::CreateMarkerFile() { + if (marker_file_) { + // error + return; + } + + std::wstring marker_dir = GetMarkerDirectory(); + std::wstring uuid_string = GetUUIDString(); + + // format a complete file path for the marker file + wchar_t fullpath[MAX_PATH]; + _snwprintf(fullpath, MAX_PATH, L"%s%s%s", + marker_dir.c_str(), + uuid_string.c_str(), + kMarkerFileSuffix); + + marker_file_name_ = fullpath; + + // Note that the file is created with the attribute FILE_FLAG_DELETE_ON_CLOSE + // so even if the process crashes, this file will get cleaned up. + // It's only if a bluescreen occurs (or plug is pulled out of wall) that the + // file will not be deleted (in theory) + marker_file_ = CreateFileW(fullpath, + GENERIC_WRITE, + 0, + NULL, + CREATE_NEW, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, + NULL); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void MarkerFileManager::RemoveMarkerFile() { + if (marker_file_) { + // Strictly speaking, we don't really need to delete the file here since + // the system will do it for us since FILE_FLAG_DELETE_ON_CLOSE was used, + // but let's do it just to be sure... + CloseHandle(marker_file_); + marker_file_ = NULL; + DeleteFile(marker_file_name_.c_str()); + } +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +static MarkerFileInfo GetMarkerFileInfo(const WIN32_FIND_DATA &find_data, + const std::wstring &marker_dir) { + std::wstring file_name = find_data.cFileName; + std::wstring full_pathname_w = marker_dir + file_name; + String full_pathname = WideToUTF8(full_pathname_w); + uint64 creation_time = + TimeManager::FileTimeToUInt64(find_data.ftCreationTime); + + MarkerFileInfo file_info(full_pathname, creation_time); + return file_info; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +bool MarkerFileManager::GetMarkerFileList(vector<MarkerFileInfo> *file_list) { + // Search the marker directory for all files ending in kMarkerFileSuffix + std::wstring marker_dir = GetMarkerDirectory(); + std::wstring search_string = marker_dir + L"*" + kMarkerFileSuffix; + + WIN32_FIND_DATA find_data; + HANDLE h = ::FindFirstFile(search_string.c_str(), &find_data); + + if (h != INVALID_HANDLE_VALUE) { + MarkerFileInfo file_info = GetMarkerFileInfo(find_data, marker_dir); + file_list->push_back(file_info); + + BOOL file_valid = true; + + while (file_valid) { + file_valid = ::FindNextFile(h, &find_data); + if (file_valid) { + MarkerFileInfo file_info = GetMarkerFileInfo(find_data, marker_dir); + file_list->push_back(file_info); + } + } + + ::FindClose(h); + } else { + // This can happen if there are no marker files found + return true; + } + + return true; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void MarkerFileManager::DeleteMarkerFile(const MarkerFileInfo &file_info) { + std::wstring filename_w = UTF8ToWide(file_info.GetName()); + ::DeleteFileW(filename_w.c_str()); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +const std::wstring MarkerFileManager::GetUUIDString() { + // now generate a GUID + UUID guid = {0}; + ::UuidCreate(&guid); + + // and format into a wide-string + wchar_t guid_string[37]; + _snwprintf( + guid_string, sizeof(guid_string) / sizeof(guid_string[0]), + L"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + guid.Data1, guid.Data2, guid.Data3, + guid.Data4[0], guid.Data4[1], guid.Data4[2], + guid.Data4[3], guid.Data4[4], guid.Data4[5], + guid.Data4[6], guid.Data4[7]); + + return guid_string; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +const std::wstring MarkerFileManager::GetMarkerDirectory() { + wchar_t temp_path[MAX_PATH]; + if (!::GetTempPathW(MAX_PATH, temp_path)) { + return L"c:\\windows\\temp\\"; + } + return temp_path; +} + +#endif // OS_WIN + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// TimeManager + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +bool TimeManagerInterface::IsMarkerFileOld(const MarkerFileInfo &file_info) { + uint64 creation_time = file_info.GetCreationTime(); + uint64 current_time = GetCurrentTime(); + + if (current_time < creation_time) { + // should never happen, but log error + return false; // something wrong has happened here + } + + uint64 file_time_since_now = current_time - creation_time; + uint64 up_time = GetUpTime(); + + bool is_old = (file_time_since_now > up_time); + return is_old; +} + +#ifdef OS_WIN + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +uint64 TimeManager::FileTimeToUInt64(FILETIME time) { + // FILETIME units are 100-nanosecond intervals + ULARGE_INTEGER li; + li.LowPart = time.dwLowDateTime; + li.HighPart = time.dwHighDateTime; + return li.QuadPart; +} + + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +uint64 TimeManager::GetCurrentTime() { + SYSTEMTIME current_time; + ::GetSystemTime(¤t_time); + + FILETIME file_time; + ::SystemTimeToFileTime(¤t_time, &file_time); + + // Now convert to uint64... + return FileTimeToUInt64(file_time); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// in units of 100-nanosecond intervals (FILETIME units) +uint64 TimeManager::GetUpTime() { + // NOTE : It would have been easier to simply use GetTickCount(), + // but it wraps around to zero after 49.7 days! There is a GetTickCount64() + // function but it's only available on Vista. + // Using QueryPerformanceCounter() and QueryPerformanceFrequency() appears + // to be the best alternative. + + uint64 tick_count64 = 0; + LARGE_INTEGER count; + LARGE_INTEGER frequency; + if (QueryPerformanceCounter(&count) && + QueryPerformanceFrequency(&frequency)) { + LONGLONG total_seconds = count.QuadPart / frequency.QuadPart; + + // convert to 100-nanosecond intervals + const LONGLONG k100NanosecondIntervalsPerSecond = 1000*1000*10; + tick_count64 = total_seconds * k100NanosecondIntervalsPerSecond; + } + + return tick_count64; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// BluescreenLogger +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void BluescreenLogger::LogBluescreen(int num_bluescreens) { + metric_bluescreens_total += num_bluescreens; + // Make sure we write this out to the registry immediately in case we're + // about to bluescreen again before the metrics timer fires! + if (g_logger) g_logger->ProcessMetrics(false, true); +} +#endif // OS_WIN + +} // namespace o3d diff --git a/o3d/breakpad/win/bluescreen_detector.h b/o3d/breakpad/win/bluescreen_detector.h new file mode 100644 index 0000000..16f47a6 --- /dev/null +++ b/o3d/breakpad/win/bluescreen_detector.h @@ -0,0 +1,219 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +// BluescreenDetector attempts to identify cases where the machine bluescreened +// and was caused by o3d. + +#ifndef CLIENT3D_BREAKPAD_WIN_BLUESCREENDETECTOR_H_ +#define CLIENT3D_BREAKPAD_WIN_BLUESCREENDETECTOR_H_ + +#include <string.h> +#include <stdlib.h> +#include <vector> + +#ifdef OS_WIN +#include <windows.h> +#endif + +#include "base/basictypes.h" +#include "core/cross/types.h" + +namespace o3d { + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class MarkerFileInfo { + public: + MarkerFileInfo(const String &name, uint64 creation_time) + : name_(name), creation_time_(creation_time) { }; + + MarkerFileInfo() : name_(""), creation_time_(0) { }; + + // explicit copy constructor + MarkerFileInfo(const MarkerFileInfo& info) + : name_(info.name_), + creation_time_(info.creation_time_) { } + + const String &GetName() const { return name_; } + uint64 GetCreationTime() const { return creation_time_; } + + private: + String name_; + uint64 creation_time_; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// We create interfaces so that we can mock these elements in the unit tests + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// TimeManagerInterface deals with times: +// - time since boot +// - current time +// - file creation time +// +// with this information, it helps determine if marker file is "new" or "old" +// "old" means the file was created before the last time the machine was booted +// + +class TimeManagerInterface { + public: + // times are in units of 100-nanosecond intervals (FILETIME units) + virtual uint64 GetCurrentTime() = 0; + virtual uint64 GetUpTime() = 0; + + // Returns |true| if the marker file was created before the machine + // was last re-booted + bool IsMarkerFileOld(const MarkerFileInfo &file_info); +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Manages a directory where "marker" files will be written. The presence or +// absence of a marker file, along with its creation date, current time, and +// time since boot can be used to help determine if a blue-screen has occurred. +class MarkerFileManagerInterface { + public: + MarkerFileManagerInterface(TimeManagerInterface *time_manager) + : time_manager_(time_manager) {} + + virtual void CreateMarkerFile() = 0; + virtual void RemoveMarkerFile() = 0; + + + // By looking at the creation date, along with the current time, + // and the "up time" (time since boot) we can tell if any "marker" files were + // created before boot time. If found, they will be considered as evidence for + // a blue-screen event in the past. Returns the number of such files found. + int DetectStrayMarkerFiles(); + + protected: + virtual bool GetMarkerFileList(std::vector<MarkerFileInfo> *file_list) = 0; + virtual void DeleteMarkerFile(const MarkerFileInfo &file_info) = 0; + + TimeManagerInterface *time_manager_; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class BluescreenLoggerInterface { + public: + virtual void LogBluescreen(int num_bluescreens) = 0; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Actual implementation + +// We only actually implement the interfaces for Windows, but we can unit test +// (with mocks) on all platforms +#ifdef OS_WIN + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class MarkerFileManager : public MarkerFileManagerInterface { + public: + MarkerFileManager(TimeManagerInterface *time_manager) + : MarkerFileManagerInterface(time_manager), marker_file_(NULL) { }; + + // "marker" file management. The marker file is used to check for future + // blue-screens. + virtual void CreateMarkerFile(); + virtual void RemoveMarkerFile(); + + private: + virtual bool GetMarkerFileList(std::vector<MarkerFileInfo> *file_list); + virtual void DeleteMarkerFile(const MarkerFileInfo &file_info); + + const std::wstring GetMarkerDirectory(); + const std::wstring GetUUIDString(); + + HANDLE marker_file_; + std::wstring marker_file_name_; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class TimeManager : public TimeManagerInterface { + public: + virtual uint64 GetCurrentTime(); + virtual uint64 GetUpTime(); + + static uint64 FileTimeToUInt64(FILETIME time); +}; + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class BluescreenLogger : public BluescreenLoggerInterface { + public: + // Sends blue-screen info to Google + virtual void LogBluescreen(int num_bluescreens); +}; + +#endif // OS_WIN + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class BluescreenDetector { + public: + // Default constructor for real-world use + BluescreenDetector(); + + // For mocking/testing + BluescreenDetector(TimeManagerInterface *time_manager, + MarkerFileManagerInterface *marker_file_manager, + BluescreenLoggerInterface *bluescreen_logger) + : started_(false), + time_manager_(time_manager), + marker_file_manager_(marker_file_manager), + bluescreen_logger_(bluescreen_logger) { } + + virtual ~BluescreenDetector(); + + // Call Start() to check for blue-screens which may have occured and log them + // if so. Also, writes out a "marker" to be used to check for future + // blue-screens. + // + // Should be called when the plugin first loads + void Start(); + + // Call when the plugin unloads - the marker file is deleted here + // on Windows it's unnecessary to call Stop() since the file will be + // automatically deleted by the system when the process exits (or crashes) + void Stop(); + + private: + bool started_; + + TimeManagerInterface *time_manager_; + MarkerFileManagerInterface *marker_file_manager_; + BluescreenLoggerInterface *bluescreen_logger_; + + DISALLOW_COPY_AND_ASSIGN(BluescreenDetector); +}; + +} // namespace o3d + +#endif // CLIENT3D_BREAKPAD_WIN_BLUESCREENDETECTOR_H_ diff --git a/o3d/breakpad/win/bluescreen_detector_test.cc b/o3d/breakpad/win/bluescreen_detector_test.cc new file mode 100644 index 0000000..ac279f2 --- /dev/null +++ b/o3d/breakpad/win/bluescreen_detector_test.cc @@ -0,0 +1,322 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +// Tests functionality of the BluescreenDetector class + +#include "core/cross/client.h" +#include "tests/common/win/testing_common.h" +#include "core/cross/error.h" +#include "breakpad/win/bluescreen_detector.h" +#include "plugin/cross/plugin_logging.h" + +// This is defined in "main_win.cc" but not in unit tests +o3d::PluginLogging *g_logger = NULL; + +namespace o3d { + +// Test fixture for BluescreenDetector testing. +class BluescreenDetectorTest : public testing::Test { +}; + +using std::vector; +using o3d::MarkerFileInfo; + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Mock implementation + +const uint64 kInitialCurrentTime = 20000; +const uint64 kInitialUpTime = 10000; + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class TimeManagerMock : public TimeManagerInterface { + public: + TimeManagerMock() + : current_time_(kInitialCurrentTime), + up_time_(kInitialUpTime) { } + + virtual uint64 GetCurrentTime() { return current_time_; } + virtual uint64 GetUpTime() { return up_time_; } + + void AdvanceTime(uint64 n) { + current_time_ += n; + up_time_ += n; + } + + void SetCurrentTime(uint64 t) { current_time_ = t; } + void SetUpTime(uint64 t) { up_time_ = t; } + + private: + uint64 current_time_; + uint64 up_time_; +}; + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class MarkerFileManagerMock : public MarkerFileManagerInterface { + public: + MarkerFileManagerMock(TimeManagerMock *time_manager) + : MarkerFileManagerInterface(time_manager), + mock_time_manager_(time_manager), + created_count_(0), + removed_count_(0) { + }; + + // "marker" file management. The marker file is used to check for future + // blue-screens. + virtual void CreateMarkerFile(); + virtual void RemoveMarkerFile(); + + void AddFileEntry(const String &name, uint64 creation_time) { + file_list_.push_back(MarkerFileInfo(name, creation_time)); + } + + // Each should be 1 after the test is run + int GetCreatedCount() const { + return created_count_; + } + + int GetRemovedCount() const { + return removed_count_; + } + + int GetMarkerFileCount() const { + return file_list_.size(); + } + + private: + virtual bool GetMarkerFileList(vector<MarkerFileInfo> *file_list); + virtual void DeleteMarkerFile(const MarkerFileInfo &file_info); + + TimeManagerMock *mock_time_manager_; + vector<MarkerFileInfo> file_list_; + int created_count_; + int removed_count_; +}; + + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class BluescreenLoggerMock : public BluescreenLoggerInterface { + public: + BluescreenLoggerMock() : bluescreen_count_(0), log_bluescreen_count_(0) { } + + // Pretends to send blue-screen info to Google + virtual void LogBluescreen(int num_bluescreens) { + ++log_bluescreen_count_; // counts number of times this method is called + bluescreen_count_ += num_bluescreens; + } + + // Returns number of bluescreens logged + int GetBluescreenCount() const { + return bluescreen_count_; + } + + // Returns total number of times LogBluescreen() was called + // (it should only be called once) + int GetLogBluescreenCount() const { + return log_bluescreen_count_; + } + + private: + int bluescreen_count_; + int log_bluescreen_count_; +}; + + + +const String kOurMarkerFileName("OurMarkerFile"); + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void MarkerFileManagerMock::CreateMarkerFile() { + AddFileEntry(kOurMarkerFileName, time_manager_->GetCurrentTime()); + mock_time_manager_->AdvanceTime(1); + created_count_++; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void MarkerFileManagerMock::RemoveMarkerFile() { + for (int i = 0; i < file_list_.size(); ++i) { + if (file_list_[i].GetName() == kOurMarkerFileName) { + file_list_.erase(file_list_.begin() + i); + removed_count_++; + break; + } + } +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +bool MarkerFileManagerMock::GetMarkerFileList( + vector<MarkerFileInfo> *file_list) { + // Just duplicate our interval file list + for (int i = 0; i < file_list_.size(); ++i) { + file_list->push_back(file_list_[i]); + } + return true; +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void MarkerFileManagerMock::DeleteMarkerFile(const MarkerFileInfo &file_info) { + for (int i = 0; i < file_list_.size(); ++i) { + if (file_list_[i].GetName() == file_info.GetName()) { + file_list_.erase(file_list_.begin() + i); + break; + } + } +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +// Make sure marker file is written, then removed... +// This is also testing the case where there was a regular crash +// (such as "bus error" or "divide-by-zero") in which case the +// exception handler will call Stop() on the detector (instead of the plugin +// shutdown code calling it) +TEST_F(BluescreenDetectorTest, Basic) { + TimeManagerMock time_manager; + MarkerFileManagerMock marker_file_manager(&time_manager); + BluescreenLoggerMock bluescreen_logger; + + BluescreenDetector detector(&time_manager, + &marker_file_manager, + &bluescreen_logger); + + detector.Start(); + + // Make sure marker file was created (but not yet removed) + EXPECT_EQ(1, marker_file_manager.GetCreatedCount()); + EXPECT_EQ(0, marker_file_manager.GetRemovedCount()); + + detector.Stop(); + + // Make sure marker file was created then removed + EXPECT_EQ(1, marker_file_manager.GetCreatedCount()); + EXPECT_EQ(1, marker_file_manager.GetRemovedCount()); + + // we didn't add any old marker files, so there should be no bluescreens + EXPECT_EQ(0, bluescreen_logger.GetBluescreenCount()); + EXPECT_EQ(0, bluescreen_logger.GetLogBluescreenCount()); +} + +// Let's try simulating a simple blue-screen +TEST_F(BluescreenDetectorTest, SimulateBluescreen) { + TimeManagerMock time_manager; + MarkerFileManagerMock marker_file_manager(&time_manager); + BluescreenLoggerMock bluescreen_logger; + + BluescreenDetector detector(&time_manager, + &marker_file_manager, + &bluescreen_logger); + + // Let's create a couple of stray marker files :) + // and say they were created 100 time units before the machine was + // booted + uint64 kStrayCreationTime1 = kInitialCurrentTime - kInitialUpTime - 100; + marker_file_manager.AddFileEntry("Stray1", kStrayCreationTime1); + marker_file_manager.AddFileEntry("Stray2", kStrayCreationTime1); + + // Verify the two we just added + EXPECT_EQ(2, marker_file_manager.GetMarkerFileCount()); + + detector.Start(); + + // Check that two bluescreens were detected (and reported) + EXPECT_EQ(2, bluescreen_logger.GetBluescreenCount()); + + // Check that LogBluescreen() was only called once (with two detections) + EXPECT_EQ(1, bluescreen_logger.GetLogBluescreenCount()); + + // Make sure the two "stray" marker files were removed + // (so we won't report bluescreens multiple times) + // the marker file added by |detector| should still be there + EXPECT_EQ(1, marker_file_manager.GetMarkerFileCount()); + + detector.Stop(); + + // Now make sure the marker file added by |detector| + // was also removed + EXPECT_EQ(0, marker_file_manager.GetMarkerFileCount()); + + // Make sure marker file was created then removed + EXPECT_EQ(1, marker_file_manager.GetCreatedCount()); + EXPECT_EQ(1, marker_file_manager.GetRemovedCount()); +} + +// Let's make sure we don't detect a blue-screen from marker +// files written since boot time (these marker files may be +// written by o3d running in other browsers alongside ours) +TEST_F(BluescreenDetectorTest, OtherBrowsersRunning) { + TimeManagerMock time_manager; + MarkerFileManagerMock marker_file_manager(&time_manager); + BluescreenLoggerMock bluescreen_logger; + + BluescreenDetector detector(&time_manager, + &marker_file_manager, + &bluescreen_logger); + + // Let's create a couple of other marker files :) + // but this time 100 time units AFTER the machine was + // booted, simulating other browsers which are still running + // o3d + uint64 kStrayCreationTime1 = kInitialCurrentTime - kInitialUpTime + 100; + marker_file_manager.AddFileEntry("OtherBrowserMarker1", kStrayCreationTime1); + marker_file_manager.AddFileEntry("OtherBrowserMarker2", kStrayCreationTime1); + + // Verify the two we just added + EXPECT_EQ(2, marker_file_manager.GetMarkerFileCount()); + + detector.Start(); + + // Check that NO bluescreens were detected + EXPECT_EQ(0, bluescreen_logger.GetBluescreenCount()); + + // Check that NO bluescreen reports were logged/uploaded + EXPECT_EQ(0, bluescreen_logger.GetLogBluescreenCount()); + + // Make sure the two other marker files were NOT removed + // because they were not created before boot time and + // are owned by a different browser... + // There should be the two we added, plus the one added + // by |detector| + EXPECT_EQ(3, marker_file_manager.GetMarkerFileCount()); + + detector.Stop(); + + // Now make sure the marker file added by |detector| + // was removed, so we're left with the original two + EXPECT_EQ(2, marker_file_manager.GetMarkerFileCount()); + + // Make sure marker file was created then removed + EXPECT_EQ(1, marker_file_manager.GetCreatedCount()); + EXPECT_EQ(1, marker_file_manager.GetRemovedCount()); +} + +} // namespace o3d diff --git a/o3d/breakpad/win/breakpad_config.cc b/o3d/breakpad/win/breakpad_config.cc new file mode 100644 index 0000000..6ceb73d --- /dev/null +++ b/o3d/breakpad/win/breakpad_config.cc @@ -0,0 +1,74 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// +// Breakpad configuration constants + +// Product-specific constants. MODIFY THESE TO SUIT YOUR PROJECT. + +#define _QUOTEME(x) #x +#define QUOTEME(x) _QUOTEME(x) + +#define _MAKE_LONG_STRING(string) L ## string +#define MAKE_LONG_STRING(string) _MAKE_LONG_STRING(string) + +#define PRODUCT_VERSION_STRING MAKE_LONG_STRING(QUOTEME(O3D_VERSION_NUMBER)) + +// !!@ CURRENTLY WE'RE HARDCODING (win32 firefox) HERE!! +wchar_t *kCrashReportProductName = L"O3D"; // [naming] + +wchar_t *kCrashReportProductVersion = + PRODUCT_VERSION_STRING L" (win32 firefox)"; + +// Crash report uploading configuration (used by reporter.exe) + +// production server +wchar_t *kCrashReportUrl = L"http://www.google.com/cr/report"; + +wchar_t *kCrashReportProductParam = L"prod"; +wchar_t *kCrashReportVersionParam = L"ver"; + +// Throttling-related constants +// (we don't want to upload too many crash reports in a row) + +bool kCrashReportAlwaysUpload = false; // disables throttling if set to |true| + +wchar_t *kCrashReportThrottlingRegKey = + L"Software\\Google\\Breakpad\\Throttling"; + +int kCrashReportAttempts = 3; +int kCrashReportResendPeriodMs = (1 * 60 * 60 * 1000); + +// kCrashReportsMaxPerInterval is defined (using #define) +// in the header file, since the compiler requires it to be a constant +// (can't be global variable as defined in this file) + +int kCrashReportsIntervalSeconds = (24 * 60 * 60); diff --git a/o3d/breakpad/win/breakpad_config.h b/o3d/breakpad/win/breakpad_config.h new file mode 100644 index 0000000..5b28801 --- /dev/null +++ b/o3d/breakpad/win/breakpad_config.h @@ -0,0 +1,57 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// +// Breakpad configuration constants + +#ifndef O3D_BREAKPAD_WIN_BREAKPAD_CONFIG_H_ +#define O3D_BREAKPAD_WIN_BREAKPAD_CONFIG_H_ + +extern wchar_t *kCrashReportProductName; +extern wchar_t *kCrashReportProductVersion; + +extern wchar_t *kReporterPath; + +// Crash report uploading configuration (used by reporter.exe) +extern wchar_t *kCrashReportUrl; +extern wchar_t *kCrashReportStagingUrl; +extern wchar_t *kCrashReportProductParam; +extern wchar_t *kCrashReportVersionParam; + +// Throttling-related constants +extern bool kCrashReportAlwaysUpload; +extern wchar_t *kCrashReportThrottlingRegKey; +extern int kCrashReportAttempts; +extern int kCrashReportResendPeriodMs; +#define kCrashReportsMaxPerInterval 5 +extern int kCrashReportsIntervalSeconds; + +#endif // O3D_BREAKPAD_WIN_BREAKPAD_CONFIG_H_ diff --git a/o3d/breakpad/win/crash_sender_win32.cc b/o3d/breakpad/win/crash_sender_win32.cc new file mode 100644 index 0000000..4d62e5f --- /dev/null +++ b/o3d/breakpad/win/crash_sender_win32.cc @@ -0,0 +1,153 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// +// Breakpad crash report uploader +// (adapted from code in Google Gears) +// + + +#include <map> +#include <windows.h> +#include <assert.h> +#include <shellapi.h> +#include <tchar.h> +#include <time.h> + +#include "breakpad_config.h" +#include "client/windows/sender/crash_report_sender.h" + +using std::map; +using std::wstring; +using google_breakpad::CrashReportSender; +using google_breakpad::ReportResult; + +bool CanSendMinidump(const wchar_t *product_name); +void SendMinidump(const wchar_t *minidump_filename, + const wchar_t *product_name, + const wchar_t *product_version); + + +int _tmain(int argc, _TCHAR* argv[]) { + SendMinidump(argv[1], argv[2], argv[3]); + return 0; +} + +bool CanSendMinidump(const wchar_t *product_name) { + // useful for testing purposes + if (kCrashReportAlwaysUpload) { + return true; + } + + bool can_send = false; + + time_t current_time; + time(¤t_time); + + // For throttling, we remember when the last N minidumps were sent. + + time_t past_send_times[kCrashReportsMaxPerInterval]; + DWORD bytes = sizeof(past_send_times); + memset(&past_send_times, 0, bytes); + + HKEY reg_key; + DWORD create_type; + if (ERROR_SUCCESS != RegCreateKeyExW(HKEY_CURRENT_USER, + kCrashReportThrottlingRegKey, + 0, + NULL, + 0, + KEY_READ | KEY_WRITE, NULL, + ®_key, &create_type)) { + return false; // this should never happen, but just in case + } + + if (ERROR_SUCCESS != RegQueryValueEx(reg_key, product_name, NULL, + NULL, + reinterpret_cast<BYTE*>(past_send_times), + &bytes)) { + // this product hasn't sent any crash reports yet + can_send = true; + } else { + // find crash reports within the last interval + int crashes_in_last_interval = 0; + for (int i = 0; i < kCrashReportsMaxPerInterval; ++i) { + if (current_time - past_send_times[i] < kCrashReportsIntervalSeconds) { + ++crashes_in_last_interval; + } + } + + can_send = crashes_in_last_interval < kCrashReportsMaxPerInterval; + } + + if (can_send) { + memmove(&past_send_times[1], + &past_send_times[0], + sizeof(time_t) * (kCrashReportsMaxPerInterval - 1)); + past_send_times[0] = current_time; + } + + RegSetValueEx(reg_key, product_name, 0, REG_BINARY, + reinterpret_cast<BYTE*>(past_send_times), + sizeof(past_send_times)); + + return can_send; +} + +void SendMinidump(const wchar_t *minidump_filename, + const wchar_t *product_name, + const wchar_t *product_version) { + if (CanSendMinidump(product_name)) { + map<std::wstring, std::wstring> parameters; + parameters[kCrashReportProductParam] = product_name; + parameters[kCrashReportVersionParam] = std::wstring(product_version); + + std::wstring minidump_wstr(minidump_filename); + + CrashReportSender sender(L""); + + for (int i = 0; i < kCrashReportAttempts; ++i) { + wstring upload_result; + ReportResult result = sender.SendCrashReport(kCrashReportUrl, + parameters, + minidump_wstr, + &upload_result); + if (result == google_breakpad::RESULT_FAILED) { + Sleep(kCrashReportResendPeriodMs); + } else { + // RESULT_SUCCEEDED or RESULT_REJECTED + break; + } + } + } + + DeleteFileW(minidump_filename); +} diff --git a/o3d/breakpad/win/exception_handler_win32.cc b/o3d/breakpad/win/exception_handler_win32.cc new file mode 100644 index 0000000..96851f0 --- /dev/null +++ b/o3d/breakpad/win/exception_handler_win32.cc @@ -0,0 +1,210 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// +// Wrapper class for using the Breakpad crash reporting system. +// (adapted from code in Google Gears) +// + +#include <windows.h> +#include <assert.h> +#include <shellapi.h> +#include <shlobj.h> +#include <shlwapi.h> +#include <tchar.h> +#include <time.h> +#include <string> + +#include "breakpad/win/breakpad_config.h" +#include "breakpad/win/exception_handler_win32.h" +#include "client/windows/handler/exception_handler.h" + +// We use g_logger to determine if we opt-in/out of crash reporting +namespace o3d { + class PluginLogging; +} +extern o3d::PluginLogging* g_logger; + +// Some simple string typedefs +#define STRING16(x) reinterpret_cast<const char16*>(x) + +typedef wchar_t char16; + +namespace std { +typedef wstring string16; +} + + +ExceptionManager* ExceptionManager::instance_ = NULL; + + +ExceptionManager::ExceptionManager(bool catch_entire_process) + : catch_entire_process_(catch_entire_process), + exception_handler_(NULL) { + assert(!instance_); + instance_ = this; +} + + +ExceptionManager::~ExceptionManager() { + if (exception_handler_) + delete exception_handler_; + assert(instance_ == this); + instance_ = NULL; +} + + +static HMODULE GetModuleHandleFromAddress(void *address) { + MEMORY_BASIC_INFORMATION mbi; + SIZE_T result = VirtualQuery(address, &mbi, sizeof(mbi)); + return static_cast<HMODULE>(mbi.AllocationBase); +} + + +// Gets the handle to the currently executing module. +static HMODULE GetCurrentModuleHandle() { + // pass a pointer to the current function + return GetModuleHandleFromAddress(GetCurrentModuleHandle); +} + + +static bool IsAddressInCurrentModule(void *address) { + return GetCurrentModuleHandle() == GetModuleHandleFromAddress(address); +} + + +// Called back when an exception occurs - we can decide here if we +// want to handle this crash... +// +static bool FilterCallback(void *context, + EXCEPTION_POINTERS *exinfo, + MDRawAssertionInfo *assertion) { + // g_logger will be NULL if user opts out of metrics/crash reporting + if (!g_logger) return false; + + ExceptionManager* this_ptr = reinterpret_cast<ExceptionManager*>(context); + + if (this_ptr->catch_entire_process()) + return true; + + if (!exinfo) + return true; + + return IsAddressInCurrentModule(exinfo->ExceptionRecord->ExceptionAddress); +} + + +// Is called by Breakpad when an exception occurs and a minidump has been +// written to disk. +static bool MinidumpCallback(const wchar_t *minidump_folder, + const wchar_t *minidump_id, + void *context, + EXCEPTION_POINTERS *exinfo, + MDRawAssertionInfo *assertion, + bool succeeded) { + // If this is set to |false| then the exception will be forwarded to + // Windows and a crash dialog will appear + bool handled_exception = true; + + // get the full path to the minidump + wchar_t minidump_path[MAX_PATH]; + _snwprintf(minidump_path, sizeof(minidump_path), L"%s\\%s.dmp", + minidump_folder, minidump_id); + + + + // determine the full path to "reporter.exe" + // it will look something like: + // "c:\Documents and Settings\user\Application Data\Google\O3D\reporter.exe" + TCHAR reporterPath[MAX_PATH]; + + HRESULT result = SHGetFolderPath( + NULL, + CSIDL_APPDATA, + NULL, + 0, + reporterPath); + + if (result == 0) { + PathAppend(reporterPath, _T("Google\\O3D\\reporter.exe")); + } + + if (PathFileExists(reporterPath)) { + std::string16 command_line; + command_line += L"\""; + command_line += reporterPath; + command_line += L"\" \""; + command_line += minidump_path; + command_line += L"\" \""; + command_line += kCrashReportProductName; + command_line += L"\" \""; + command_line += kCrashReportProductVersion; + command_line += L"\""; + + // execute the process + STARTUPINFO startup_info = {0}; + startup_info.cb = sizeof(startup_info); + PROCESS_INFORMATION process_info = {0}; + CreateProcessW(NULL, // application name (NULL to get from command line) + const_cast<char16 *>(command_line.c_str()), + NULL, // process attributes (NULL means process handle not + // inheritable) + NULL, // thread attributes (NULL means thread handle not + // inheritable) + FALSE, // inherit handles + 0, // creation flags + NULL, // environment block (NULL to use parent's) + NULL, // starting block (NULL to use parent's) + &startup_info, + &process_info); + CloseHandle(process_info.hProcess); + CloseHandle(process_info.hThread); + } else { + handled_exception = false; + } + + return handled_exception; +} + + +void ExceptionManager::StartMonitoring() { +#ifdef O3D_ENABLE_BREAKPAD + if (exception_handler_) { return; } // don't init more than once + + wchar_t temp_path[MAX_PATH]; + if (!GetTempPathW(MAX_PATH, temp_path)) { return; } + + exception_handler_ = new google_breakpad::ExceptionHandler(temp_path, + FilterCallback, + MinidumpCallback, + this, true); +#endif +} diff --git a/o3d/breakpad/win/exception_handler_win32.h b/o3d/breakpad/win/exception_handler_win32.h new file mode 100644 index 0000000..9986023 --- /dev/null +++ b/o3d/breakpad/win/exception_handler_win32.h @@ -0,0 +1,76 @@ +/* + * Copyright 2009, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// +// Wrapper class for using the Breakpad crash reporting system. +// (adapted from code in Google Gears) +// + +#ifndef O3D_BREAKPAD_WIN_EXCEPTION_HANDLER_WIN32_H_ +#define O3D_BREAKPAD_WIN_EXCEPTION_HANDLER_WIN32_H_ + +namespace google_breakpad { +class ExceptionHandler; +}; + +// Sample usage: +// int main(void) { +// static ExceptionManager exception_manager(false); +// exception_manager.StartMonitoring(); +// ... +// } +class ExceptionManager { + public: + // If catch_entire_process is true, then all minidumps are captured. + // Otherwise, only crashes in this module are captured. + // Use the latter when running inside IE or Firefox. + // StartMonitoring needs to be called before any minidumps are captured. + explicit ExceptionManager(bool catch_entire_process); + ~ExceptionManager(); + + // Starts monitoring for crashes. When a crash occurs a minidump will + // automatically be captured and sent. + void StartMonitoring(); + + // TODO: Cleanup. The following should not be called + // directly, ideally these should be private methods. + bool catch_entire_process() { return catch_entire_process_; } + static void SendMinidump(const char *minidump_filename); + static bool CanSendMinidump(); // considers throttling + + private: + static ExceptionManager *instance_; + + bool catch_entire_process_; + google_breakpad::ExceptionHandler *exception_handler_; +}; + +#endif // O3D_BREAKPAD_WIN_EXCEPTION_HANDLER_WIN32_H_ |