| // Copyright (c) 2008, 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. |
| |
| #include "client/windows/crash_generation/client_info.h" |
| #include "client/windows/common/ipc_protocol.h" |
| |
| static const wchar_t kCustomInfoProcessUptimeName[] = L"ptime"; |
| static const size_t kMaxCustomInfoEntries = 4096; |
| |
| namespace google_breakpad { |
| |
| ClientInfo::ClientInfo(CrashGenerationServer* crash_server, |
| DWORD pid, |
| MINIDUMP_TYPE dump_type, |
| DWORD* thread_id, |
| EXCEPTION_POINTERS** ex_info, |
| MDRawAssertionInfo* assert_info, |
| const CustomClientInfo& custom_client_info) |
| : crash_server_(crash_server), |
| pid_(pid), |
| dump_type_(dump_type), |
| ex_info_(ex_info), |
| assert_info_(assert_info), |
| custom_client_info_(custom_client_info), |
| thread_id_(thread_id), |
| process_handle_(NULL), |
| dump_requested_handle_(NULL), |
| dump_generated_handle_(NULL), |
| dump_request_wait_handle_(NULL), |
| process_exit_wait_handle_(NULL), |
| crash_id_(NULL) { |
| GetSystemTimeAsFileTime(&start_time_); |
| } |
| |
| bool ClientInfo::Initialize() { |
| process_handle_ = OpenProcess(GENERIC_ALL, FALSE, pid_); |
| if (!process_handle_) { |
| return false; |
| } |
| |
| // The crash_id will be the low order word of the process creation time. |
| FILETIME creation_time, exit_time, kernel_time, user_time; |
| if (GetProcessTimes(process_handle_, &creation_time, &exit_time, |
| &kernel_time, &user_time)) { |
| start_time_ = creation_time; |
| } |
| crash_id_ = start_time_.dwLowDateTime; |
| |
| dump_requested_handle_ = CreateEvent(NULL, // Security attributes. |
| TRUE, // Manual reset. |
| FALSE, // Initial state. |
| NULL); // Name. |
| if (!dump_requested_handle_) { |
| return false; |
| } |
| |
| dump_generated_handle_ = CreateEvent(NULL, // Security attributes. |
| TRUE, // Manual reset. |
| FALSE, // Initial state. |
| NULL); // Name. |
| return dump_generated_handle_ != NULL; |
| } |
| |
| void ClientInfo::UnregisterDumpRequestWaitAndBlockUntilNoPending() { |
| if (dump_request_wait_handle_) { |
| // Wait for callbacks that might already be running to finish. |
| UnregisterWaitEx(dump_request_wait_handle_, INVALID_HANDLE_VALUE); |
| dump_request_wait_handle_ = NULL; |
| } |
| } |
| |
| void ClientInfo::UnregisterProcessExitWait(bool block_until_no_pending) { |
| if (process_exit_wait_handle_) { |
| if (block_until_no_pending) { |
| // Wait for the callback that might already be running to finish. |
| UnregisterWaitEx(process_exit_wait_handle_, INVALID_HANDLE_VALUE); |
| } else { |
| UnregisterWait(process_exit_wait_handle_); |
| } |
| process_exit_wait_handle_ = NULL; |
| } |
| } |
| |
| ClientInfo::~ClientInfo() { |
| // Waiting for the callback to finish here is safe because ClientInfo's are |
| // never destroyed from the dump request handling callback. |
| UnregisterDumpRequestWaitAndBlockUntilNoPending(); |
| |
| // This is a little tricky because ClientInfo's may be destroyed by the same |
| // callback (OnClientEnd) and waiting for it to finish will cause a deadlock. |
| // Regardless of this complication, wait for any running callbacks to finish |
| // so that the common case is properly handled. In order to avoid deadlocks, |
| // the OnClientEnd callback must call UnregisterProcessExitWait(false) |
| // before deleting the ClientInfo. |
| UnregisterProcessExitWait(true); |
| |
| if (process_handle_) { |
| CloseHandle(process_handle_); |
| } |
| |
| if (dump_requested_handle_) { |
| CloseHandle(dump_requested_handle_); |
| } |
| |
| if (dump_generated_handle_) { |
| CloseHandle(dump_generated_handle_); |
| } |
| } |
| |
| bool ClientInfo::GetClientExceptionInfo(EXCEPTION_POINTERS** ex_info) const { |
| SIZE_T bytes_count = 0; |
| if (!ReadProcessMemory(process_handle_, |
| ex_info_, |
| ex_info, |
| sizeof(*ex_info), |
| &bytes_count)) { |
| return false; |
| } |
| |
| return bytes_count == sizeof(*ex_info); |
| } |
| |
| bool ClientInfo::GetClientThreadId(DWORD* thread_id) const { |
| SIZE_T bytes_count = 0; |
| if (!ReadProcessMemory(process_handle_, |
| thread_id_, |
| thread_id, |
| sizeof(*thread_id), |
| &bytes_count)) { |
| return false; |
| } |
| |
| return bytes_count == sizeof(*thread_id); |
| } |
| |
| void ClientInfo::SetProcessUptime() { |
| FILETIME now = {0}; |
| GetSystemTimeAsFileTime(&now); |
| |
| ULARGE_INTEGER time_start; |
| time_start.HighPart = start_time_.dwHighDateTime; |
| time_start.LowPart = start_time_.dwLowDateTime; |
| |
| ULARGE_INTEGER time_now; |
| time_now.HighPart = now.dwHighDateTime; |
| time_now.LowPart = now.dwLowDateTime; |
| |
| // Calculate the delay and convert it from 100-nanoseconds to milliseconds. |
| __int64 delay = (time_now.QuadPart - time_start.QuadPart) / 10 / 1000; |
| |
| // Convert it to a string. |
| wchar_t* value = custom_info_entries_.get()[custom_client_info_.count].value; |
| _i64tow_s(delay, value, CustomInfoEntry::kValueMaxLength, 10); |
| } |
| |
| bool ClientInfo::PopulateCustomInfo() { |
| if (custom_client_info_.count > kMaxCustomInfoEntries) |
| return false; |
| |
| SIZE_T bytes_count = 0; |
| SIZE_T read_count = sizeof(CustomInfoEntry) * custom_client_info_.count; |
| |
| // If the scoped array for custom info already has an array, it will be |
| // the same size as what we need. This is because the number of custom info |
| // entries is always the same. So allocate memory only if scoped array has |
| // a NULL pointer. |
| if (!custom_info_entries_.get()) { |
| // Allocate an extra entry for reporting uptime for the client process. |
| custom_info_entries_.reset( |
| new CustomInfoEntry[custom_client_info_.count + 1]); |
| // Use the last element in the array for uptime. |
| custom_info_entries_.get()[custom_client_info_.count].set_name( |
| kCustomInfoProcessUptimeName); |
| } |
| |
| if (!ReadProcessMemory(process_handle_, |
| custom_client_info_.entries, |
| custom_info_entries_.get(), |
| read_count, |
| &bytes_count)) { |
| return false; |
| } |
| |
| SetProcessUptime(); |
| return (bytes_count == read_count); |
| } |
| |
| CustomClientInfo ClientInfo::GetCustomInfo() const { |
| CustomClientInfo custom_info; |
| custom_info.entries = custom_info_entries_.get(); |
| // Add 1 to the count from the client process to account for extra entry for |
| // process uptime. |
| custom_info.count = custom_client_info_.count + 1; |
| return custom_info; |
| } |
| |
| } // namespace google_breakpad |