summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/breakpad-client/windows/unittests
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/crashreporter/breakpad-client/windows/unittests')
-rw-r--r--toolkit/crashreporter/breakpad-client/windows/unittests/crash_generation_server_test.cc303
-rw-r--r--toolkit/crashreporter/breakpad-client/windows/unittests/dump_analysis.cc184
-rw-r--r--toolkit/crashreporter/breakpad-client/windows/unittests/dump_analysis.h102
-rw-r--r--toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_death_test.cc587
-rw-r--r--toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_nesting_test.cc327
-rw-r--r--toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_test.cc503
-rw-r--r--toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_test.h61
-rw-r--r--toolkit/crashreporter/breakpad-client/windows/unittests/minidump_test.cc332
8 files changed, 2399 insertions, 0 deletions
diff --git a/toolkit/crashreporter/breakpad-client/windows/unittests/crash_generation_server_test.cc b/toolkit/crashreporter/breakpad-client/windows/unittests/crash_generation_server_test.cc
new file mode 100644
index 0000000000..09f2dd200d
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/windows/unittests/crash_generation_server_test.cc
@@ -0,0 +1,303 @@
+// Copyright 2010, 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 "breakpad_googletest_includes.h"
+#include "client/windows/crash_generation/crash_generation_server.h"
+#include "client/windows/common/ipc_protocol.h"
+
+using testing::_;
+
+namespace {
+
+const wchar_t kPipeName[] =
+ L"\\\\.\\pipe\\CrashGenerationServerTest\\TestCaseServer";
+
+const DWORD kPipeDesiredAccess = FILE_READ_DATA |
+ FILE_WRITE_DATA |
+ FILE_WRITE_ATTRIBUTES;
+
+const DWORD kPipeFlagsAndAttributes = SECURITY_IDENTIFICATION |
+ SECURITY_SQOS_PRESENT;
+
+const DWORD kPipeMode = PIPE_READMODE_MESSAGE;
+
+#define arraysize(f) (sizeof(f) / sizeof(*f))
+const google_breakpad::CustomInfoEntry kCustomInfoEntries[] = {
+ google_breakpad::CustomInfoEntry(L"prod", L"CrashGenerationServerTest"),
+ google_breakpad::CustomInfoEntry(L"ver", L"1.0"),
+};
+
+class CrashGenerationServerTest : public ::testing::Test {
+ public:
+ CrashGenerationServerTest()
+ : crash_generation_server_(kPipeName,
+ NULL,
+ CallOnClientConnected, &mock_callbacks_,
+ CallOnClientDumpRequested, &mock_callbacks_,
+ CallOnClientExited, &mock_callbacks_,
+ CallOnClientUploadRequested, &mock_callbacks_,
+ false,
+ NULL),
+ thread_id_(0),
+ exception_pointers_(NULL) {
+ memset(&assert_info_, 0, sizeof(assert_info_));
+ }
+
+ protected:
+ class MockCrashGenerationServerCallbacks {
+ public:
+ MOCK_METHOD1(OnClientConnected,
+ void(const google_breakpad::ClientInfo* client_info));
+ MOCK_METHOD2(OnClientDumpRequested,
+ void(const google_breakpad::ClientInfo* client_info,
+ const std::wstring* file_path));
+ MOCK_METHOD1(OnClientExited,
+ void(const google_breakpad::ClientInfo* client_info));
+ MOCK_METHOD1(OnClientUploadRequested,
+ void(const DWORD crash_id));
+ };
+
+ enum ClientFault {
+ NO_FAULT,
+ CLOSE_AFTER_CONNECT,
+ SEND_INVALID_REGISTRATION,
+ TRUNCATE_REGISTRATION,
+ CLOSE_AFTER_REGISTRATION,
+ RESPONSE_BUFFER_TOO_SMALL,
+ CLOSE_AFTER_RESPONSE,
+ SEND_INVALID_ACK
+ };
+
+ void SetUp() {
+ ASSERT_TRUE(crash_generation_server_.Start());
+ }
+
+ void FaultyClient(ClientFault fault_type) {
+ HANDLE pipe = CreateFile(kPipeName,
+ kPipeDesiredAccess,
+ 0,
+ NULL,
+ OPEN_EXISTING,
+ kPipeFlagsAndAttributes,
+ NULL);
+
+ if (pipe == INVALID_HANDLE_VALUE) {
+ ASSERT_EQ(ERROR_PIPE_BUSY, GetLastError());
+
+ // Cannot continue retrying if wait on pipe fails.
+ ASSERT_TRUE(WaitNamedPipe(kPipeName, 500));
+
+ pipe = CreateFile(kPipeName,
+ kPipeDesiredAccess,
+ 0,
+ NULL,
+ OPEN_EXISTING,
+ kPipeFlagsAndAttributes,
+ NULL);
+ }
+
+ ASSERT_NE(pipe, INVALID_HANDLE_VALUE);
+
+ DWORD mode = kPipeMode;
+ ASSERT_TRUE(SetNamedPipeHandleState(pipe, &mode, NULL, NULL));
+
+ DoFaultyClient(fault_type, pipe);
+
+ CloseHandle(pipe);
+ }
+
+ void DoTestFault(ClientFault fault) {
+ EXPECT_CALL(mock_callbacks_, OnClientConnected(_)).Times(0);
+ ASSERT_NO_FATAL_FAILURE(FaultyClient(fault));
+ ASSERT_NO_FATAL_FAILURE(FaultyClient(fault));
+ ASSERT_NO_FATAL_FAILURE(FaultyClient(fault));
+
+ EXPECT_CALL(mock_callbacks_, OnClientConnected(_));
+
+ ASSERT_NO_FATAL_FAILURE(FaultyClient(NO_FAULT));
+
+ // Slight hack. The OnClientConnected is only invoked after the ack is
+ // received by the server. At that point, the FaultyClient call has already
+ // returned. The best way to wait until the server is done handling that is
+ // to send one more ping, whose processing will be blocked by delivery of
+ // the OnClientConnected message.
+ ASSERT_NO_FATAL_FAILURE(FaultyClient(CLOSE_AFTER_CONNECT));
+ }
+
+ MockCrashGenerationServerCallbacks mock_callbacks_;
+
+ private:
+ // Depends on the caller to successfully open the pipe before invocation and
+ // to close it immediately afterwards.
+ void DoFaultyClient(ClientFault fault_type, HANDLE pipe) {
+ if (fault_type == CLOSE_AFTER_CONNECT) {
+ return;
+ }
+
+ google_breakpad::CustomClientInfo custom_info = {kCustomInfoEntries,
+ arraysize(kCustomInfoEntries)};
+
+ google_breakpad::ProtocolMessage msg(
+ fault_type == SEND_INVALID_REGISTRATION ?
+ google_breakpad::MESSAGE_TAG_NONE :
+ google_breakpad::MESSAGE_TAG_REGISTRATION_REQUEST,
+ GetCurrentProcessId(),
+ MiniDumpNormal,
+ &thread_id_,
+ &exception_pointers_,
+ &assert_info_,
+ custom_info,
+ NULL,
+ NULL,
+ NULL);
+
+ DWORD bytes_count = 0;
+
+ ASSERT_TRUE(WriteFile(pipe,
+ &msg,
+ fault_type == TRUNCATE_REGISTRATION ?
+ sizeof(msg) / 2 : sizeof(msg),
+ &bytes_count,
+ NULL));
+
+ if (fault_type == CLOSE_AFTER_REGISTRATION) {
+ return;
+ }
+
+ google_breakpad::ProtocolMessage reply;
+
+ if (!ReadFile(pipe,
+ &reply,
+ fault_type == RESPONSE_BUFFER_TOO_SMALL ?
+ sizeof(google_breakpad::ProtocolMessage) / 2 :
+ sizeof(google_breakpad::ProtocolMessage),
+ &bytes_count,
+ NULL)) {
+ switch (fault_type) {
+ case TRUNCATE_REGISTRATION:
+ case RESPONSE_BUFFER_TOO_SMALL:
+ case SEND_INVALID_REGISTRATION:
+ return;
+
+ default:
+ FAIL() << "Unexpectedly failed to register.";
+ }
+ }
+
+ if (fault_type == CLOSE_AFTER_RESPONSE) {
+ return;
+ }
+
+ google_breakpad::ProtocolMessage ack_msg;
+ ack_msg.tag = google_breakpad::MESSAGE_TAG_REGISTRATION_ACK;
+
+ ASSERT_TRUE(WriteFile(pipe,
+ &ack_msg,
+ SEND_INVALID_ACK ?
+ sizeof(ack_msg) : sizeof(ack_msg) / 2,
+ &bytes_count,
+ NULL));
+
+ return;
+ }
+
+ static void CallOnClientConnected(
+ void* context, const google_breakpad::ClientInfo* client_info) {
+ static_cast<MockCrashGenerationServerCallbacks*>(context)->
+ OnClientConnected(client_info);
+ }
+
+ static void CallOnClientDumpRequested(
+ void* context,
+ const google_breakpad::ClientInfo* client_info,
+ const std::wstring* file_path) {
+ static_cast<MockCrashGenerationServerCallbacks*>(context)->
+ OnClientDumpRequested(client_info, file_path);
+ }
+
+ static void CallOnClientExited(
+ void* context, const google_breakpad::ClientInfo* client_info) {
+ static_cast<MockCrashGenerationServerCallbacks*>(context)->
+ OnClientExited(client_info);
+ }
+
+ static void CallOnClientUploadRequested(void* context, const DWORD crash_id) {
+ static_cast<MockCrashGenerationServerCallbacks*>(context)->
+ OnClientUploadRequested(crash_id);
+ }
+
+ DWORD thread_id_;
+ EXCEPTION_POINTERS* exception_pointers_;
+ MDRawAssertionInfo assert_info_;
+
+ google_breakpad::CrashGenerationServer crash_generation_server_;
+};
+
+TEST_F(CrashGenerationServerTest, PingServerTest) {
+ DoTestFault(CLOSE_AFTER_CONNECT);
+}
+
+TEST_F(CrashGenerationServerTest, InvalidRegistration) {
+ DoTestFault(SEND_INVALID_REGISTRATION);
+}
+
+TEST_F(CrashGenerationServerTest, TruncateRegistration) {
+ DoTestFault(TRUNCATE_REGISTRATION);
+}
+
+TEST_F(CrashGenerationServerTest, CloseAfterRegistration) {
+ DoTestFault(CLOSE_AFTER_REGISTRATION);
+}
+
+TEST_F(CrashGenerationServerTest, ResponseBufferTooSmall) {
+ DoTestFault(RESPONSE_BUFFER_TOO_SMALL);
+}
+
+TEST_F(CrashGenerationServerTest, CloseAfterResponse) {
+ DoTestFault(CLOSE_AFTER_RESPONSE);
+}
+
+// It turns out that, as long as you send one byte, the ACK is accepted and
+// registration succeeds.
+TEST_F(CrashGenerationServerTest, SendInvalidAck) {
+ EXPECT_CALL(mock_callbacks_, OnClientConnected(_));
+ ASSERT_NO_FATAL_FAILURE(FaultyClient(SEND_INVALID_ACK));
+
+ // See DoTestFault for an explanation of this line
+ ASSERT_NO_FATAL_FAILURE(FaultyClient(CLOSE_AFTER_CONNECT));
+
+ EXPECT_CALL(mock_callbacks_, OnClientConnected(_));
+ ASSERT_NO_FATAL_FAILURE(FaultyClient(NO_FAULT));
+
+ // See DoTestFault for an explanation of this line
+ ASSERT_NO_FATAL_FAILURE(FaultyClient(CLOSE_AFTER_CONNECT));
+}
+
+} // anonymous namespace
diff --git a/toolkit/crashreporter/breakpad-client/windows/unittests/dump_analysis.cc b/toolkit/crashreporter/breakpad-client/windows/unittests/dump_analysis.cc
new file mode 100644
index 0000000000..53d4ddbde9
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/windows/unittests/dump_analysis.cc
@@ -0,0 +1,184 @@
+// Copyright (c) 2010, 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 <windows.h>
+#include <objbase.h>
+#include <dbghelp.h>
+
+#include "breakpad_googletest_includes.h"
+#include "client/windows/unittests/dump_analysis.h" // NOLINT
+
+DumpAnalysis::~DumpAnalysis() {
+ if (dump_file_view_ != NULL) {
+ EXPECT_TRUE(::UnmapViewOfFile(dump_file_view_));
+ ::CloseHandle(dump_file_mapping_);
+ dump_file_mapping_ = NULL;
+ }
+
+ if (dump_file_handle_ != NULL) {
+ ::CloseHandle(dump_file_handle_);
+ dump_file_handle_ = NULL;
+ }
+}
+
+void DumpAnalysis::EnsureDumpMapped() {
+ if (dump_file_view_ == NULL) {
+ dump_file_handle_ = ::CreateFile(dump_file_.c_str(),
+ GENERIC_READ,
+ 0,
+ NULL,
+ OPEN_EXISTING,
+ 0,
+ NULL);
+ ASSERT_TRUE(dump_file_handle_ != NULL);
+ ASSERT_TRUE(dump_file_mapping_ == NULL);
+
+ dump_file_mapping_ = ::CreateFileMapping(dump_file_handle_,
+ NULL,
+ PAGE_READONLY,
+ 0,
+ 0,
+ NULL);
+ ASSERT_TRUE(dump_file_mapping_ != NULL);
+
+ dump_file_view_ = ::MapViewOfFile(dump_file_mapping_,
+ FILE_MAP_READ,
+ 0,
+ 0,
+ 0);
+ ASSERT_TRUE(dump_file_view_ != NULL);
+ }
+}
+
+bool DumpAnalysis::HasTebs() const {
+ MINIDUMP_THREAD_LIST* thread_list = NULL;
+ size_t thread_list_size = GetStream(ThreadListStream, &thread_list);
+
+ if (thread_list_size > 0 && thread_list != NULL) {
+ for (ULONG i = 0; i < thread_list->NumberOfThreads; ++i) {
+ if (!HasMemory(thread_list->Threads[i].Teb))
+ return false;
+ }
+
+ return true;
+ }
+
+ // No thread list, no TEB info.
+ return false;
+}
+
+bool DumpAnalysis::HasPeb() const {
+ MINIDUMP_THREAD_LIST* thread_list = NULL;
+ size_t thread_list_size = GetStream(ThreadListStream, &thread_list);
+
+ if (thread_list_size > 0 && thread_list != NULL &&
+ thread_list->NumberOfThreads > 0) {
+ FakeTEB* teb = NULL;
+ if (!HasMemory(thread_list->Threads[0].Teb, &teb))
+ return false;
+
+ return HasMemory(teb->peb);
+ }
+
+ return false;
+}
+
+bool DumpAnalysis::HasStream(ULONG stream_number) const {
+ void* stream = NULL;
+ size_t stream_size = GetStreamImpl(stream_number, &stream);
+ return stream_size > 0 && stream != NULL;
+}
+
+size_t DumpAnalysis::GetStreamImpl(ULONG stream_number, void** stream) const {
+ MINIDUMP_DIRECTORY* directory = NULL;
+ ULONG memory_list_size = 0;
+ BOOL ret = ::MiniDumpReadDumpStream(dump_file_view_,
+ stream_number,
+ &directory,
+ stream,
+ &memory_list_size);
+
+ return ret ? memory_list_size : 0;
+}
+
+bool DumpAnalysis::HasMemoryImpl(const void *addr_in, size_t structuresize,
+ void **structure) const {
+ uintptr_t address = reinterpret_cast<uintptr_t>(addr_in);
+ MINIDUMP_MEMORY_LIST* memory_list = NULL;
+ size_t memory_list_size = GetStream(MemoryListStream, &memory_list);
+ if (memory_list_size > 0 && memory_list != NULL) {
+ for (ULONG i = 0; i < memory_list->NumberOfMemoryRanges; ++i) {
+ MINIDUMP_MEMORY_DESCRIPTOR& descr = memory_list->MemoryRanges[i];
+ const uintptr_t range_start =
+ static_cast<uintptr_t>(descr.StartOfMemoryRange);
+ uintptr_t range_end = range_start + descr.Memory.DataSize;
+
+ if (address >= range_start &&
+ address + structuresize < range_end) {
+ // The start address falls in the range, and the end address is
+ // in bounds, return a pointer to the structure if requested.
+ if (structure != NULL)
+ *structure = RVA_TO_ADDR(dump_file_view_, descr.Memory.Rva);
+
+ return true;
+ }
+ }
+ }
+
+ // We didn't find the range in a MINIDUMP_MEMORY_LIST, so maybe this
+ // is a full dump using MINIDUMP_MEMORY64_LIST with all the memory at the
+ // end of the dump file.
+ MINIDUMP_MEMORY64_LIST* memory64_list = NULL;
+ memory_list_size = GetStream(Memory64ListStream, &memory64_list);
+ if (memory_list_size > 0 && memory64_list != NULL) {
+ // Keep track of where the current descriptor maps to.
+ RVA64 curr_rva = memory64_list->BaseRva;
+ for (ULONG i = 0; i < memory64_list->NumberOfMemoryRanges; ++i) {
+ MINIDUMP_MEMORY_DESCRIPTOR64& descr = memory64_list->MemoryRanges[i];
+ uintptr_t range_start =
+ static_cast<uintptr_t>(descr.StartOfMemoryRange);
+ uintptr_t range_end = range_start + static_cast<size_t>(descr.DataSize);
+
+ if (address >= range_start &&
+ address + structuresize < range_end) {
+ // The start address falls in the range, and the end address is
+ // in bounds, return a pointer to the structure if requested.
+ if (structure != NULL)
+ *structure = RVA_TO_ADDR(dump_file_view_, curr_rva);
+
+ return true;
+ }
+
+ // Advance the current RVA.
+ curr_rva += descr.DataSize;
+ }
+ }
+
+ return false;
+}
diff --git a/toolkit/crashreporter/breakpad-client/windows/unittests/dump_analysis.h b/toolkit/crashreporter/breakpad-client/windows/unittests/dump_analysis.h
new file mode 100644
index 0000000000..6cef48d814
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/windows/unittests/dump_analysis.h
@@ -0,0 +1,102 @@
+// Copyright (c) 2010, 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.
+
+#ifndef CLIENT_WINDOWS_UNITTESTS_DUMP_ANALYSIS_H_
+#define CLIENT_WINDOWS_UNITTESTS_DUMP_ANALYSIS_H_
+
+#include "client/windows/crash_generation/minidump_generator.h"
+
+// Convenience to get to the PEB pointer in a TEB.
+struct FakeTEB {
+ char dummy[0x30];
+ void* peb;
+};
+
+class DumpAnalysis {
+ public:
+ explicit DumpAnalysis(const std::wstring& file_path)
+ : dump_file_(file_path), dump_file_view_(NULL), dump_file_mapping_(NULL),
+ dump_file_handle_(NULL) {
+ EnsureDumpMapped();
+ }
+ ~DumpAnalysis();
+
+ bool HasStream(ULONG stream_number) const;
+
+ // This is template to keep type safety in the front, but we end up casting
+ // to void** inside the implementation to pass the pointer to Win32. So
+ // casting here is considered safe.
+ template <class StreamType>
+ size_t GetStream(ULONG stream_number, StreamType** stream) const {
+ return GetStreamImpl(stream_number, reinterpret_cast<void**>(stream));
+ }
+
+ bool HasTebs() const;
+ bool HasPeb() const;
+ bool HasMemory(ULONG64 address) const {
+ return HasMemory<BYTE>(address, NULL);
+ }
+
+ bool HasMemory(const void* address) const {
+ return HasMemory<BYTE>(address, NULL);
+ }
+
+ template <class StructureType>
+ bool HasMemory(ULONG64 address, StructureType** structure = NULL) const {
+ // We can't cope with 64 bit addresses for now.
+ if (address > 0xFFFFFFFFUL)
+ return false;
+
+ return HasMemory(reinterpret_cast<void*>(address), structure);
+ }
+
+ template <class StructureType>
+ bool HasMemory(const void* addr_in, StructureType** structure = NULL) const {
+ return HasMemoryImpl(addr_in, sizeof(StructureType),
+ reinterpret_cast<void**>(structure));
+ }
+
+ protected:
+ void EnsureDumpMapped();
+
+ HANDLE dump_file_mapping_;
+ HANDLE dump_file_handle_;
+ void* dump_file_view_;
+ std::wstring dump_file_;
+
+ private:
+ // This is the implementation of GetStream<>.
+ size_t GetStreamImpl(ULONG stream_number, void** stream) const;
+
+ // This is the implementation of HasMemory<>.
+ bool HasMemoryImpl(const void* addr_in, size_t pointersize,
+ void** structure) const;
+};
+
+#endif // CLIENT_WINDOWS_UNITTESTS_DUMP_ANALYSIS_H_
diff --git a/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_death_test.cc b/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_death_test.cc
new file mode 100644
index 0000000000..5ef9e64d1d
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_death_test.cc
@@ -0,0 +1,587 @@
+// 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.
+
+#include <windows.h>
+#include <dbghelp.h>
+#include <strsafe.h>
+#include <objbase.h>
+#include <shellapi.h>
+
+#include <string>
+
+#include "breakpad_googletest_includes.h"
+#include "client/windows/crash_generation/crash_generation_server.h"
+#include "client/windows/handler/exception_handler.h"
+#include "client/windows/unittests/exception_handler_test.h"
+#include "common/windows/string_utils-inl.h"
+#include "google_breakpad/processor/minidump.h"
+
+namespace {
+
+using std::wstring;
+using namespace google_breakpad;
+
+const wchar_t kPipeName[] = L"\\\\.\\pipe\\BreakpadCrashTest\\TestCaseServer";
+const char kSuccessIndicator[] = "success";
+const char kFailureIndicator[] = "failure";
+
+// Utility function to test for a path's existence.
+BOOL DoesPathExist(const TCHAR *path_name);
+
+enum OutOfProcGuarantee {
+ OUT_OF_PROC_GUARANTEED,
+ OUT_OF_PROC_BEST_EFFORT,
+};
+
+class ExceptionHandlerDeathTest : public ::testing::Test {
+ protected:
+ // Member variable for each test that they can use
+ // for temporary storage.
+ TCHAR temp_path_[MAX_PATH];
+ // Actually constructs a temp path name.
+ virtual void SetUp();
+ // A helper method that tests can use to crash.
+ void DoCrashAccessViolation(const OutOfProcGuarantee out_of_proc_guarantee);
+ void DoCrashPureVirtualCall();
+};
+
+void ExceptionHandlerDeathTest::SetUp() {
+ const ::testing::TestInfo* const test_info =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+ TCHAR temp_path[MAX_PATH] = { '\0' };
+ TCHAR test_name_wide[MAX_PATH] = { '\0' };
+ // We want the temporary directory to be what the OS returns
+ // to us, + the test case name.
+ GetTempPath(MAX_PATH, temp_path);
+ // The test case name is exposed as a c-style string,
+ // convert it to a wchar_t string.
+ int dwRet = MultiByteToWideChar(CP_ACP, 0, test_info->name(),
+ static_cast<int>(strlen(test_info->name())),
+ test_name_wide,
+ MAX_PATH);
+ if (!dwRet) {
+ assert(false);
+ }
+ StringCchPrintfW(temp_path_, MAX_PATH, L"%s%s", temp_path, test_name_wide);
+ CreateDirectory(temp_path_, NULL);
+}
+
+BOOL DoesPathExist(const TCHAR *path_name) {
+ DWORD flags = GetFileAttributes(path_name);
+ if (flags == INVALID_FILE_ATTRIBUTES) {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+bool MinidumpWrittenCallback(const wchar_t* dump_path,
+ const wchar_t* minidump_id,
+ void* context,
+ EXCEPTION_POINTERS* exinfo,
+ MDRawAssertionInfo* assertion,
+ bool succeeded) {
+ if (succeeded && DoesPathExist(dump_path)) {
+ fprintf(stderr, kSuccessIndicator);
+ } else {
+ fprintf(stderr, kFailureIndicator);
+ }
+ // If we don't flush, the output doesn't get sent before
+ // this process dies.
+ fflush(stderr);
+ return succeeded;
+}
+
+TEST_F(ExceptionHandlerDeathTest, InProcTest) {
+ // For the in-proc test, we just need to instantiate an exception
+ // handler in in-proc mode, and crash. Since the entire test is
+ // reexecuted in the child process, we don't have to worry about
+ // the semantics of the exception handler being inherited/not
+ // inherited across CreateProcess().
+ ASSERT_TRUE(DoesPathExist(temp_path_));
+ scoped_ptr<google_breakpad::ExceptionHandler> exc(
+ new google_breakpad::ExceptionHandler(
+ temp_path_,
+ NULL,
+ &MinidumpWrittenCallback,
+ NULL,
+ google_breakpad::ExceptionHandler::HANDLER_ALL));
+
+ // Disable GTest SEH handler
+ testing::DisableExceptionHandlerInScope disable_exception_handler;
+
+ int *i = NULL;
+ ASSERT_DEATH((*i)++, kSuccessIndicator);
+}
+
+static bool gDumpCallbackCalled = false;
+
+void clientDumpCallback(void *dump_context,
+ const google_breakpad::ClientInfo *client_info,
+ const std::wstring *dump_path) {
+ gDumpCallbackCalled = true;
+}
+
+void ExceptionHandlerDeathTest::DoCrashAccessViolation(
+ const OutOfProcGuarantee out_of_proc_guarantee) {
+ scoped_ptr<google_breakpad::ExceptionHandler> exc;
+
+ if (out_of_proc_guarantee == OUT_OF_PROC_GUARANTEED) {
+ google_breakpad::CrashGenerationClient *client =
+ new google_breakpad::CrashGenerationClient(kPipeName,
+ MiniDumpNormal,
+ NULL); // custom_info
+ ASSERT_TRUE(client->Register());
+ exc.reset(new google_breakpad::ExceptionHandler(
+ temp_path_,
+ NULL, // filter
+ NULL, // callback
+ NULL, // callback_context
+ google_breakpad::ExceptionHandler::HANDLER_ALL,
+ client));
+ } else {
+ ASSERT_TRUE(out_of_proc_guarantee == OUT_OF_PROC_BEST_EFFORT);
+ exc.reset(new google_breakpad::ExceptionHandler(
+ temp_path_,
+ NULL, // filter
+ NULL, // callback
+ NULL, // callback_context
+ google_breakpad::ExceptionHandler::HANDLER_ALL,
+ MiniDumpNormal,
+ kPipeName,
+ NULL)); // custom_info
+ }
+
+ // Disable GTest SEH handler
+ testing::DisableExceptionHandlerInScope disable_exception_handler;
+
+ // Although this is executing in the child process of the death test,
+ // if it's not true we'll still get an error rather than the crash
+ // being expected.
+ ASSERT_TRUE(exc->IsOutOfProcess());
+ int *i = NULL;
+ printf("%d\n", (*i)++);
+}
+
+TEST_F(ExceptionHandlerDeathTest, OutOfProcTest) {
+ // We can take advantage of a detail of google test here to save some
+ // complexity in testing: when you do a death test, it actually forks.
+ // So we can make the main test harness the crash generation server,
+ // and call ASSERT_DEATH on a NULL dereference, it to expecting test
+ // the out of process scenario, since it's happening in a different
+ // process! This is different from the above because, above, we pass
+ // a NULL pipe name, and we also don't start a crash generation server.
+
+ ASSERT_TRUE(DoesPathExist(temp_path_));
+ std::wstring dump_path(temp_path_);
+ google_breakpad::CrashGenerationServer server(
+ kPipeName, NULL, NULL, NULL, &clientDumpCallback, NULL, NULL, NULL, NULL,
+ NULL, true, &dump_path);
+
+ // This HAS to be EXPECT_, because when this test case is executed in the
+ // child process, the server registration will fail due to the named pipe
+ // being the same.
+ EXPECT_TRUE(server.Start());
+ gDumpCallbackCalled = false;
+ ASSERT_DEATH(this->DoCrashAccessViolation(OUT_OF_PROC_BEST_EFFORT), "");
+ EXPECT_TRUE(gDumpCallbackCalled);
+}
+
+TEST_F(ExceptionHandlerDeathTest, OutOfProcGuaranteedTest) {
+ // This is similar to the previous test (OutOfProcTest). The only difference
+ // is that in this test, the crash generation client is created and registered
+ // with the crash generation server outside of the ExceptionHandler
+ // constructor which allows breakpad users to opt out of the default
+ // in-process dump generation when the registration with the crash generation
+ // server fails.
+
+ ASSERT_TRUE(DoesPathExist(temp_path_));
+ std::wstring dump_path(temp_path_);
+ google_breakpad::CrashGenerationServer server(
+ kPipeName, NULL, NULL, NULL, &clientDumpCallback, NULL, NULL, NULL, NULL,
+ NULL, true, &dump_path);
+
+ // This HAS to be EXPECT_, because when this test case is executed in the
+ // child process, the server registration will fail due to the named pipe
+ // being the same.
+ EXPECT_TRUE(server.Start());
+ gDumpCallbackCalled = false;
+ ASSERT_DEATH(this->DoCrashAccessViolation(OUT_OF_PROC_GUARANTEED), "");
+ EXPECT_TRUE(gDumpCallbackCalled);
+}
+
+TEST_F(ExceptionHandlerDeathTest, InvalidParameterTest) {
+ using google_breakpad::ExceptionHandler;
+
+ ASSERT_TRUE(DoesPathExist(temp_path_));
+ ExceptionHandler handler(temp_path_, NULL, NULL, NULL,
+ ExceptionHandler::HANDLER_INVALID_PARAMETER);
+
+ // Disable the message box for assertions
+ _CrtSetReportMode(_CRT_ASSERT, 0);
+
+ // Call with a bad argument. The invalid parameter will be swallowed
+ // and a dump will be generated, the process will exit(0).
+ ASSERT_EXIT(printf(NULL), ::testing::ExitedWithCode(0), "");
+}
+
+
+struct PureVirtualCallBase {
+ PureVirtualCallBase() {
+ // We have to reinterpret so the linker doesn't get confused because the
+ // method isn't defined.
+ reinterpret_cast<PureVirtualCallBase*>(this)->PureFunction();
+ }
+ virtual ~PureVirtualCallBase() {}
+ virtual void PureFunction() const = 0;
+};
+struct PureVirtualCall : public PureVirtualCallBase {
+ PureVirtualCall() { PureFunction(); }
+ virtual void PureFunction() const {}
+};
+
+void ExceptionHandlerDeathTest::DoCrashPureVirtualCall() {
+ PureVirtualCall instance;
+}
+
+TEST_F(ExceptionHandlerDeathTest, PureVirtualCallTest) {
+ using google_breakpad::ExceptionHandler;
+
+ ASSERT_TRUE(DoesPathExist(temp_path_));
+ ExceptionHandler handler(temp_path_, NULL, NULL, NULL,
+ ExceptionHandler::HANDLER_PURECALL);
+
+ // Disable the message box for assertions
+ _CrtSetReportMode(_CRT_ASSERT, 0);
+
+ // Calls a pure virtual function.
+ EXPECT_EXIT(DoCrashPureVirtualCall(), ::testing::ExitedWithCode(0), "");
+}
+
+wstring find_minidump_in_directory(const wstring &directory) {
+ wstring search_path = directory + L"\\*";
+ WIN32_FIND_DATA find_data;
+ HANDLE find_handle = FindFirstFileW(search_path.c_str(), &find_data);
+ if (find_handle == INVALID_HANDLE_VALUE)
+ return wstring();
+
+ wstring filename;
+ do {
+ const wchar_t extension[] = L".dmp";
+ const size_t extension_length = sizeof(extension) / sizeof(extension[0]) - 1;
+ const size_t filename_length = wcslen(find_data.cFileName);
+ if (filename_length > extension_length &&
+ wcsncmp(extension,
+ find_data.cFileName + filename_length - extension_length,
+ extension_length) == 0) {
+ filename = directory + L"\\" + find_data.cFileName;
+ break;
+ }
+ } while (FindNextFile(find_handle, &find_data));
+ FindClose(find_handle);
+ return filename;
+}
+
+#ifndef ADDRESS_SANITIZER
+
+TEST_F(ExceptionHandlerDeathTest, InstructionPointerMemory) {
+ ASSERT_TRUE(DoesPathExist(temp_path_));
+ scoped_ptr<google_breakpad::ExceptionHandler> exc(
+ new google_breakpad::ExceptionHandler(
+ temp_path_,
+ NULL,
+ NULL,
+ NULL,
+ google_breakpad::ExceptionHandler::HANDLER_ALL));
+
+ // Disable GTest SEH handler
+ testing::DisableExceptionHandlerInScope disable_exception_handler;
+
+ // Get some executable memory.
+ const uint32_t kMemorySize = 256; // bytes
+ const int kOffset = kMemorySize / 2;
+ // This crashes with SIGILL on x86/x86-64/arm.
+ const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
+ char* memory = reinterpret_cast<char*>(VirtualAlloc(NULL,
+ kMemorySize,
+ MEM_COMMIT | MEM_RESERVE,
+ PAGE_EXECUTE_READWRITE));
+ ASSERT_TRUE(memory);
+
+ // Write some instructions that will crash. Put them
+ // in the middle of the block of memory, because the
+ // minidump should contain 128 bytes on either side of the
+ // instruction pointer.
+ memcpy(memory + kOffset, instructions, sizeof(instructions));
+
+ // Now execute the instructions, which should crash.
+ typedef void (*void_function)(void);
+ void_function memory_function =
+ reinterpret_cast<void_function>(memory + kOffset);
+ ASSERT_DEATH(memory_function(), "");
+
+ // free the memory.
+ VirtualFree(memory, 0, MEM_RELEASE);
+
+ // Verify that the resulting minidump contains the memory around the IP
+ wstring minidump_filename_wide = find_minidump_in_directory(temp_path_);
+ ASSERT_FALSE(minidump_filename_wide.empty());
+ string minidump_filename;
+ ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(minidump_filename_wide,
+ &minidump_filename));
+
+ // Read the minidump. Locate the exception record and the
+ // memory list, and then ensure that there is a memory region
+ // in the memory list that covers at least 128 bytes on either
+ // side of the instruction pointer from the exception record.
+ {
+ Minidump minidump(minidump_filename);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpException* exception = minidump.GetException();
+ MinidumpMemoryList* memory_list = minidump.GetMemoryList();
+ ASSERT_TRUE(exception);
+ ASSERT_TRUE(memory_list);
+ ASSERT_LT((unsigned)0, memory_list->region_count());
+
+ MinidumpContext* context = exception->GetContext();
+ ASSERT_TRUE(context);
+
+ uint64_t instruction_pointer;
+ ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer));
+
+ MinidumpMemoryRegion* region =
+ memory_list->GetMemoryRegionForAddress(instruction_pointer);
+ ASSERT_TRUE(region);
+
+ EXPECT_LE(kMemorySize, region->GetSize());
+ const uint8_t* bytes = region->GetMemory();
+ ASSERT_TRUE(bytes);
+
+ uint64_t ip_offset = instruction_pointer - region->GetBase();
+ EXPECT_GE(region->GetSize() - kOffset, ip_offset);
+ EXPECT_LE(kOffset, ip_offset);
+
+ uint8_t prefix_bytes[kOffset];
+ uint8_t suffix_bytes[kMemorySize - kOffset - sizeof(instructions)];
+ memset(prefix_bytes, 0, sizeof(prefix_bytes));
+ memset(suffix_bytes, 0, sizeof(suffix_bytes));
+ EXPECT_EQ(0, memcmp(bytes + ip_offset - kOffset, prefix_bytes,
+ sizeof(prefix_bytes)));
+ EXPECT_EQ(0, memcmp(bytes + ip_offset, instructions, sizeof(instructions)));
+ EXPECT_EQ(0, memcmp(bytes + ip_offset + sizeof(instructions), suffix_bytes,
+ sizeof(suffix_bytes)));
+ }
+
+ DeleteFileW(minidump_filename_wide.c_str());
+}
+
+TEST_F(ExceptionHandlerDeathTest, InstructionPointerMemoryMinBound) {
+ ASSERT_TRUE(DoesPathExist(temp_path_));
+ scoped_ptr<google_breakpad::ExceptionHandler> exc(
+ new google_breakpad::ExceptionHandler(
+ temp_path_,
+ NULL,
+ NULL,
+ NULL,
+ google_breakpad::ExceptionHandler::HANDLER_ALL));
+
+ // Disable GTest SEH handler
+ testing::DisableExceptionHandlerInScope disable_exception_handler;
+
+ SYSTEM_INFO sSysInfo; // Useful information about the system
+ GetSystemInfo(&sSysInfo); // Initialize the structure.
+
+ const uint32_t kMemorySize = 256; // bytes
+ const DWORD kPageSize = sSysInfo.dwPageSize;
+ const int kOffset = 0;
+ // This crashes with SIGILL on x86/x86-64/arm.
+ const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
+ // Get some executable memory. Specifically, reserve two pages,
+ // but only commit the second.
+ char* all_memory = reinterpret_cast<char*>(VirtualAlloc(NULL,
+ kPageSize * 2,
+ MEM_RESERVE,
+ PAGE_NOACCESS));
+ ASSERT_TRUE(all_memory);
+ char* memory = all_memory + kPageSize;
+ ASSERT_TRUE(VirtualAlloc(memory, kPageSize,
+ MEM_COMMIT, PAGE_EXECUTE_READWRITE));
+
+ // Write some instructions that will crash. Put them
+ // in the middle of the block of memory, because the
+ // minidump should contain 128 bytes on either side of the
+ // instruction pointer.
+ memcpy(memory + kOffset, instructions, sizeof(instructions));
+
+ // Now execute the instructions, which should crash.
+ typedef void (*void_function)(void);
+ void_function memory_function =
+ reinterpret_cast<void_function>(memory + kOffset);
+ ASSERT_DEATH(memory_function(), "");
+
+ // free the memory.
+ VirtualFree(memory, 0, MEM_RELEASE);
+
+ // Verify that the resulting minidump contains the memory around the IP
+ wstring minidump_filename_wide = find_minidump_in_directory(temp_path_);
+ ASSERT_FALSE(minidump_filename_wide.empty());
+ string minidump_filename;
+ ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(minidump_filename_wide,
+ &minidump_filename));
+
+ // Read the minidump. Locate the exception record and the
+ // memory list, and then ensure that there is a memory region
+ // in the memory list that covers the instruction pointer from
+ // the exception record.
+ {
+ Minidump minidump(minidump_filename);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpException* exception = minidump.GetException();
+ MinidumpMemoryList* memory_list = minidump.GetMemoryList();
+ ASSERT_TRUE(exception);
+ ASSERT_TRUE(memory_list);
+ ASSERT_LT((unsigned)0, memory_list->region_count());
+
+ MinidumpContext* context = exception->GetContext();
+ ASSERT_TRUE(context);
+
+ uint64_t instruction_pointer;
+ ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer));
+
+ MinidumpMemoryRegion* region =
+ memory_list->GetMemoryRegionForAddress(instruction_pointer);
+ ASSERT_TRUE(region);
+
+ EXPECT_EQ(kMemorySize / 2, region->GetSize());
+ const uint8_t* bytes = region->GetMemory();
+ ASSERT_TRUE(bytes);
+
+ uint8_t suffix_bytes[kMemorySize / 2 - sizeof(instructions)];
+ memset(suffix_bytes, 0, sizeof(suffix_bytes));
+ EXPECT_TRUE(memcmp(bytes + kOffset,
+ instructions, sizeof(instructions)) == 0);
+ EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(instructions),
+ suffix_bytes, sizeof(suffix_bytes)) == 0);
+ }
+
+ DeleteFileW(minidump_filename_wide.c_str());
+}
+
+TEST_F(ExceptionHandlerDeathTest, InstructionPointerMemoryMaxBound) {
+ ASSERT_TRUE(DoesPathExist(temp_path_));
+ scoped_ptr<google_breakpad::ExceptionHandler> exc(
+ new google_breakpad::ExceptionHandler(
+ temp_path_,
+ NULL,
+ NULL,
+ NULL,
+ google_breakpad::ExceptionHandler::HANDLER_ALL));
+
+ // Disable GTest SEH handler
+ testing::DisableExceptionHandlerInScope disable_exception_handler;
+
+ SYSTEM_INFO sSysInfo; // Useful information about the system
+ GetSystemInfo(&sSysInfo); // Initialize the structure.
+
+ const DWORD kPageSize = sSysInfo.dwPageSize;
+ // This crashes with SIGILL on x86/x86-64/arm.
+ const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
+ const int kOffset = kPageSize - sizeof(instructions);
+ // Get some executable memory. Specifically, reserve two pages,
+ // but only commit the first.
+ char* memory = reinterpret_cast<char*>(VirtualAlloc(NULL,
+ kPageSize * 2,
+ MEM_RESERVE,
+ PAGE_NOACCESS));
+ ASSERT_TRUE(memory);
+ ASSERT_TRUE(VirtualAlloc(memory, kPageSize,
+ MEM_COMMIT, PAGE_EXECUTE_READWRITE));
+
+ // Write some instructions that will crash.
+ memcpy(memory + kOffset, instructions, sizeof(instructions));
+
+ // Now execute the instructions, which should crash.
+ typedef void (*void_function)(void);
+ void_function memory_function =
+ reinterpret_cast<void_function>(memory + kOffset);
+ ASSERT_DEATH(memory_function(), "");
+
+ // free the memory.
+ VirtualFree(memory, 0, MEM_RELEASE);
+
+ // Verify that the resulting minidump contains the memory around the IP
+ wstring minidump_filename_wide = find_minidump_in_directory(temp_path_);
+ ASSERT_FALSE(minidump_filename_wide.empty());
+ string minidump_filename;
+ ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(minidump_filename_wide,
+ &minidump_filename));
+
+ // Read the minidump. Locate the exception record and the
+ // memory list, and then ensure that there is a memory region
+ // in the memory list that covers the instruction pointer from
+ // the exception record.
+ {
+ Minidump minidump(minidump_filename);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpException* exception = minidump.GetException();
+ MinidumpMemoryList* memory_list = minidump.GetMemoryList();
+ ASSERT_TRUE(exception);
+ ASSERT_TRUE(memory_list);
+ ASSERT_LT((unsigned)0, memory_list->region_count());
+
+ MinidumpContext* context = exception->GetContext();
+ ASSERT_TRUE(context);
+
+ uint64_t instruction_pointer;
+ ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer));
+
+ MinidumpMemoryRegion* region =
+ memory_list->GetMemoryRegionForAddress(instruction_pointer);
+ ASSERT_TRUE(region);
+
+ const size_t kPrefixSize = 128; // bytes
+ EXPECT_EQ(kPrefixSize + sizeof(instructions), region->GetSize());
+ const uint8_t* bytes = region->GetMemory();
+ ASSERT_TRUE(bytes);
+
+ uint8_t prefix_bytes[kPrefixSize];
+ memset(prefix_bytes, 0, sizeof(prefix_bytes));
+ EXPECT_EQ(0, memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)));
+ EXPECT_EQ(0, memcmp(bytes + kPrefixSize,
+ instructions, sizeof(instructions)));
+ }
+
+ DeleteFileW(minidump_filename_wide.c_str());
+}
+
+#endif // !ADDRESS_SANITIZER
+
+} // namespace
diff --git a/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_nesting_test.cc b/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_nesting_test.cc
new file mode 100644
index 0000000000..3ae1d7cd05
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_nesting_test.cc
@@ -0,0 +1,327 @@
+// Copyright 2012, 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 <windows.h>
+
+#include <string>
+
+#include "breakpad_googletest_includes.h"
+#include "client/windows/handler/exception_handler.h"
+#include "client/windows/unittests/exception_handler_test.h"
+
+namespace {
+
+const char kFoo[] = "foo";
+const char kBar[] = "bar";
+
+const char kStartOfLine[] = "^";
+const char kEndOfLine[] = "$";
+
+const char kFilterReturnsTrue[] = "filter_returns_true";
+const char kFilterReturnsFalse[] = "filter_returns_false";
+
+const char kCallbackReturnsTrue[] = "callback_returns_true";
+const char kCallbackReturnsFalse[] = "callback_returns_false";
+
+bool DoesPathExist(const wchar_t *path_name) {
+ DWORD flags = GetFileAttributes(path_name);
+ if (flags == INVALID_FILE_ATTRIBUTES) {
+ return false;
+ }
+ return true;
+}
+
+// A callback function to run before Breakpad performs any substantial
+// processing of an exception. A FilterCallback is called before writing
+// a minidump. context is the parameter supplied by the user as
+// callback_context when the handler was created. exinfo points to the
+// exception record, if any; assertion points to assertion information,
+// if any.
+//
+// If a FilterCallback returns true, Breakpad will continue processing,
+// attempting to write a minidump. If a FilterCallback returns false,
+// Breakpad will immediately report the exception as unhandled without
+// writing a minidump, allowing another handler the opportunity to handle it.
+template <bool filter_return_value>
+bool CrashHandlerFilter(void* context,
+ EXCEPTION_POINTERS* exinfo,
+ MDRawAssertionInfo* assertion) {
+ if (filter_return_value) {
+ fprintf(stderr, kFilterReturnsTrue);
+ } else {
+ fprintf(stderr, kFilterReturnsFalse);
+ }
+ fflush(stderr);
+
+ return filter_return_value;
+}
+
+// A callback function to run after the minidump has been written.
+// minidump_id is a unique id for the dump, so the minidump
+// file is <dump_path>\<minidump_id>.dmp. context is the parameter supplied
+// by the user as callback_context when the handler was created. exinfo
+// points to the exception record, or NULL if no exception occurred.
+// succeeded indicates whether a minidump file was successfully written.
+// assertion points to information about an assertion if the handler was
+// invoked by an assertion.
+//
+// If an exception occurred and the callback returns true, Breakpad will treat
+// the exception as fully-handled, suppressing any other handlers from being
+// notified of the exception. If the callback returns false, Breakpad will
+// treat the exception as unhandled, and allow another handler to handle it.
+// If there are no other handlers, Breakpad will report the exception to the
+// system as unhandled, allowing a debugger or native crash dialog the
+// opportunity to handle the exception. Most callback implementations
+// should normally return the value of |succeeded|, or when they wish to
+// not report an exception of handled, false. Callbacks will rarely want to
+// return true directly (unless |succeeded| is true).
+//
+// For out-of-process dump generation, dump path and minidump ID will always
+// be NULL. In case of out-of-process dump generation, the dump path and
+// minidump id are controlled by the server process and are not communicated
+// back to the crashing process.
+template <bool callback_return_value>
+bool MinidumpWrittenCallback(const wchar_t* dump_path,
+ const wchar_t* minidump_id,
+ void* context,
+ EXCEPTION_POINTERS* exinfo,
+ MDRawAssertionInfo* assertion,
+ bool succeeded) {
+ bool rv = false;
+ if (callback_return_value &&
+ succeeded &&
+ DoesPathExist(dump_path)) {
+ rv = true;
+ fprintf(stderr, kCallbackReturnsTrue);
+ } else {
+ fprintf(stderr, kCallbackReturnsFalse);
+ }
+ fflush(stderr);
+
+ return rv;
+}
+
+
+void DoCrash(const char *message) {
+ if (message) {
+ fprintf(stderr, "%s", message);
+ fflush(stderr);
+ }
+ int *i = NULL;
+ (*i)++;
+
+ ASSERT_TRUE(false);
+}
+
+void InstallExceptionHandlerAndCrash(bool install_filter,
+ bool filter_return_value,
+ bool install_callback,
+ bool callback_return_value) {
+ wchar_t temp_path[MAX_PATH] = { '\0' };
+ GetTempPath(MAX_PATH, temp_path);
+
+ ASSERT_TRUE(DoesPathExist(temp_path));
+ google_breakpad::ExceptionHandler exc(
+ temp_path,
+ install_filter ?
+ (filter_return_value ?
+ &CrashHandlerFilter<true> :
+ &CrashHandlerFilter<false>) :
+ NULL,
+ install_callback ?
+ (callback_return_value ?
+ &MinidumpWrittenCallback<true> :
+ &MinidumpWrittenCallback<false>) :
+ NULL,
+ NULL, // callback_context
+ google_breakpad::ExceptionHandler::HANDLER_EXCEPTION);
+
+ // Disable GTest SEH handler
+ testing::DisableExceptionHandlerInScope disable_exception_handler;
+
+ DoCrash(NULL);
+}
+
+TEST(AssertDeathSanity, Simple) {
+ ASSERT_DEATH(DoCrash(NULL), "");
+}
+
+TEST(AssertDeathSanity, Regex) {
+ ASSERT_DEATH(DoCrash(kFoo),
+ std::string(kStartOfLine) +
+ std::string(kFoo) +
+ std::string(kEndOfLine));
+
+ ASSERT_DEATH(DoCrash(kBar),
+ std::string(kStartOfLine) +
+ std::string(kBar) +
+ std::string(kEndOfLine));
+}
+
+TEST(ExceptionHandlerCallbacks, FilterTrue_No_Callback) {
+ ASSERT_DEATH(
+ InstallExceptionHandlerAndCrash(true, // install_filter
+ true, // filter_return_value
+ false, // install_callback
+ false), // callback_return_value
+ std::string(kStartOfLine) +
+ std::string(kFilterReturnsTrue) +
+ std::string(kEndOfLine));
+}
+
+TEST(ExceptionHandlerCallbacks, FilterTrue_Callback) {
+ ASSERT_DEATH(
+ InstallExceptionHandlerAndCrash(true, // install_filter
+ true, // filter_return_value
+ true, // install_callback
+ false), // callback_return_value
+ std::string(kStartOfLine) +
+ std::string(kFilterReturnsTrue) +
+ std::string(kCallbackReturnsFalse) +
+ std::string(kEndOfLine));
+}
+
+TEST(ExceptionHandlerCallbacks, FilterFalse_No_Callback) {
+ ASSERT_DEATH(
+ InstallExceptionHandlerAndCrash(true, // install_filter
+ false, // filter_return_value
+ false, // install_callback
+ false), // callback_return_value
+ std::string(kStartOfLine) +
+ std::string(kFilterReturnsFalse) +
+ std::string(kEndOfLine));
+}
+
+// Callback shouldn't be executed when filter returns false
+TEST(ExceptionHandlerCallbacks, FilterFalse_Callback) {
+ ASSERT_DEATH(
+ InstallExceptionHandlerAndCrash(true, // install_filter
+ false, // filter_return_value
+ true, // install_callback
+ false), // callback_return_value
+ std::string(kStartOfLine) +
+ std::string(kFilterReturnsFalse) +
+ std::string(kEndOfLine));
+}
+
+TEST(ExceptionHandlerCallbacks, No_Filter_No_Callback) {
+ ASSERT_DEATH(
+ InstallExceptionHandlerAndCrash(false, // install_filter
+ true, // filter_return_value
+ false, // install_callback
+ false), // callback_return_value
+ std::string(kStartOfLine) +
+ std::string(kEndOfLine));
+}
+
+TEST(ExceptionHandlerCallbacks, No_Filter_Callback) {
+ ASSERT_DEATH(
+ InstallExceptionHandlerAndCrash(false, // install_filter
+ true, // filter_return_value
+ true, // install_callback
+ false), // callback_return_value
+ std::string(kStartOfLine) +
+ std::string(kCallbackReturnsFalse) +
+ std::string(kEndOfLine));
+}
+
+
+TEST(ExceptionHandlerNesting, Skip_From_Inner_Filter) {
+ wchar_t temp_path[MAX_PATH] = { '\0' };
+ GetTempPath(MAX_PATH, temp_path);
+
+ ASSERT_TRUE(DoesPathExist(temp_path));
+ google_breakpad::ExceptionHandler exc(
+ temp_path,
+ &CrashHandlerFilter<true>,
+ &MinidumpWrittenCallback<false>,
+ NULL, // callback_context
+ google_breakpad::ExceptionHandler::HANDLER_EXCEPTION);
+
+ ASSERT_DEATH(
+ InstallExceptionHandlerAndCrash(true, // install_filter
+ false, // filter_return_value
+ true, // install_callback
+ true), // callback_return_value
+ std::string(kStartOfLine) +
+ std::string(kFilterReturnsFalse) + // inner filter
+ std::string(kFilterReturnsTrue) + // outer filter
+ std::string(kCallbackReturnsFalse) + // outer callback
+ std::string(kEndOfLine));
+}
+
+TEST(ExceptionHandlerNesting, Skip_From_Inner_Callback) {
+ wchar_t temp_path[MAX_PATH] = { '\0' };
+ GetTempPath(MAX_PATH, temp_path);
+
+ ASSERT_TRUE(DoesPathExist(temp_path));
+ google_breakpad::ExceptionHandler exc(
+ temp_path,
+ &CrashHandlerFilter<true>,
+ &MinidumpWrittenCallback<false>,
+ NULL, // callback_context
+ google_breakpad::ExceptionHandler::HANDLER_EXCEPTION);
+
+ ASSERT_DEATH(
+ InstallExceptionHandlerAndCrash(true, // install_filter
+ true, // filter_return_value
+ true, // install_callback
+ false), // callback_return_value
+ std::string(kStartOfLine) +
+ std::string(kFilterReturnsTrue) + // inner filter
+ std::string(kCallbackReturnsFalse) + // inner callback
+ std::string(kFilterReturnsTrue) + // outer filter
+ std::string(kCallbackReturnsFalse) + // outer callback
+ std::string(kEndOfLine));
+}
+
+TEST(ExceptionHandlerNesting, Handled_By_Inner_Handler) {
+ wchar_t temp_path[MAX_PATH] = { '\0' };
+ GetTempPath(MAX_PATH, temp_path);
+
+ ASSERT_TRUE(DoesPathExist(temp_path));
+ google_breakpad::ExceptionHandler exc(
+ temp_path,
+ &CrashHandlerFilter<true>,
+ &MinidumpWrittenCallback<true>,
+ NULL, // callback_context
+ google_breakpad::ExceptionHandler::HANDLER_EXCEPTION);
+
+ ASSERT_DEATH(
+ InstallExceptionHandlerAndCrash(true, // install_filter
+ true, // filter_return_value
+ true, // install_callback
+ true), // callback_return_value
+ std::string(kStartOfLine) +
+ std::string(kFilterReturnsTrue) + // inner filter
+ std::string(kCallbackReturnsTrue) + // inner callback
+ std::string(kEndOfLine));
+}
+
+} // namespace
diff --git a/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_test.cc b/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_test.cc
new file mode 100644
index 0000000000..a4ce12a8aa
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_test.cc
@@ -0,0 +1,503 @@
+// 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.
+
+#include "client/windows/unittests/exception_handler_test.h"
+
+#include <windows.h>
+#include <dbghelp.h>
+#include <strsafe.h>
+#include <objbase.h>
+#include <shellapi.h>
+
+#include <string>
+
+#include "breakpad_googletest_includes.h"
+#include "client/windows/crash_generation/crash_generation_server.h"
+#include "client/windows/handler/exception_handler.h"
+#include "client/windows/unittests/dump_analysis.h" // NOLINT
+#include "common/windows/string_utils-inl.h"
+#include "google_breakpad/processor/minidump.h"
+
+namespace testing {
+
+DisableExceptionHandlerInScope::DisableExceptionHandlerInScope() {
+ catch_exceptions_ = GTEST_FLAG(catch_exceptions);
+ GTEST_FLAG(catch_exceptions) = false;
+}
+
+DisableExceptionHandlerInScope::~DisableExceptionHandlerInScope() {
+ GTEST_FLAG(catch_exceptions) = catch_exceptions_;
+}
+
+} // namespace testing
+
+namespace {
+
+using std::wstring;
+using namespace google_breakpad;
+
+const wchar_t kPipeName[] = L"\\\\.\\pipe\\BreakpadCrashTest\\TestCaseServer";
+const char kSuccessIndicator[] = "success";
+const char kFailureIndicator[] = "failure";
+
+const MINIDUMP_TYPE kFullDumpType = static_cast<MINIDUMP_TYPE>(
+ MiniDumpWithFullMemory | // Full memory from process.
+ MiniDumpWithProcessThreadData | // Get PEB and TEB.
+ MiniDumpWithHandleData); // Get all handle information.
+
+class ExceptionHandlerTest : public ::testing::Test {
+ protected:
+ // Member variable for each test that they can use
+ // for temporary storage.
+ TCHAR temp_path_[MAX_PATH];
+
+ // Actually constructs a temp path name.
+ virtual void SetUp();
+
+ // Deletes temporary files.
+ virtual void TearDown();
+
+ void DoCrashInvalidParameter();
+ void DoCrashPureVirtualCall();
+
+ // Utility function to test for a path's existence.
+ static BOOL DoesPathExist(const TCHAR *path_name);
+
+ // Client callback.
+ static void ClientDumpCallback(
+ void *dump_context,
+ const google_breakpad::ClientInfo *client_info,
+ const std::wstring *dump_path);
+
+ static bool DumpCallback(const wchar_t* dump_path,
+ const wchar_t* minidump_id,
+ void* context,
+ EXCEPTION_POINTERS* exinfo,
+ MDRawAssertionInfo* assertion,
+ bool succeeded);
+
+ static std::wstring dump_file;
+ static std::wstring full_dump_file;
+};
+
+std::wstring ExceptionHandlerTest::dump_file;
+std::wstring ExceptionHandlerTest::full_dump_file;
+
+void ExceptionHandlerTest::SetUp() {
+ const ::testing::TestInfo* const test_info =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+ TCHAR temp_path[MAX_PATH] = { '\0' };
+ TCHAR test_name_wide[MAX_PATH] = { '\0' };
+ // We want the temporary directory to be what the OS returns
+ // to us, + the test case name.
+ GetTempPath(MAX_PATH, temp_path);
+ // THe test case name is exposed to use as a c-style string,
+ // But we might be working in UNICODE here on Windows.
+ int dwRet = MultiByteToWideChar(CP_ACP, 0, test_info->name(),
+ static_cast<int>(strlen(test_info->name())),
+ test_name_wide,
+ MAX_PATH);
+ if (!dwRet) {
+ assert(false);
+ }
+ StringCchPrintfW(temp_path_, MAX_PATH, L"%s%s", temp_path, test_name_wide);
+ CreateDirectory(temp_path_, NULL);
+}
+
+void ExceptionHandlerTest::TearDown() {
+ if (!dump_file.empty()) {
+ ::DeleteFile(dump_file.c_str());
+ dump_file = L"";
+ }
+ if (!full_dump_file.empty()) {
+ ::DeleteFile(full_dump_file.c_str());
+ full_dump_file = L"";
+ }
+}
+
+BOOL ExceptionHandlerTest::DoesPathExist(const TCHAR *path_name) {
+ DWORD flags = GetFileAttributes(path_name);
+ if (flags == INVALID_FILE_ATTRIBUTES) {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+// static
+void ExceptionHandlerTest::ClientDumpCallback(
+ void *dump_context,
+ const google_breakpad::ClientInfo *client_info,
+ const wstring *dump_path) {
+ dump_file = *dump_path;
+ // Create the full dump file name from the dump path.
+ full_dump_file = dump_file.substr(0, dump_file.length() - 4) + L"-full.dmp";
+}
+
+// static
+bool ExceptionHandlerTest::DumpCallback(const wchar_t* dump_path,
+ const wchar_t* minidump_id,
+ void* context,
+ EXCEPTION_POINTERS* exinfo,
+ MDRawAssertionInfo* assertion,
+ bool succeeded) {
+ dump_file = dump_path;
+ dump_file += L"\\";
+ dump_file += minidump_id;
+ dump_file += L".dmp";
+ return succeeded;
+}
+
+void ExceptionHandlerTest::DoCrashInvalidParameter() {
+ google_breakpad::ExceptionHandler *exc =
+ new google_breakpad::ExceptionHandler(
+ temp_path_, NULL, NULL, NULL,
+ google_breakpad::ExceptionHandler::HANDLER_INVALID_PARAMETER,
+ kFullDumpType, kPipeName, NULL);
+
+ // Disable the message box for assertions
+ _CrtSetReportMode(_CRT_ASSERT, 0);
+
+ // Although this is executing in the child process of the death test,
+ // if it's not true we'll still get an error rather than the crash
+ // being expected.
+ ASSERT_TRUE(exc->IsOutOfProcess());
+ printf(NULL);
+}
+
+
+struct PureVirtualCallBase {
+ PureVirtualCallBase() {
+ // We have to reinterpret so the linker doesn't get confused because the
+ // method isn't defined.
+ reinterpret_cast<PureVirtualCallBase*>(this)->PureFunction();
+ }
+ virtual ~PureVirtualCallBase() {}
+ virtual void PureFunction() const = 0;
+};
+struct PureVirtualCall : public PureVirtualCallBase {
+ PureVirtualCall() { PureFunction(); }
+ virtual void PureFunction() const {}
+};
+
+void ExceptionHandlerTest::DoCrashPureVirtualCall() {
+ google_breakpad::ExceptionHandler *exc =
+ new google_breakpad::ExceptionHandler(
+ temp_path_, NULL, NULL, NULL,
+ google_breakpad::ExceptionHandler::HANDLER_PURECALL,
+ kFullDumpType, kPipeName, NULL);
+
+ // Disable the message box for assertions
+ _CrtSetReportMode(_CRT_ASSERT, 0);
+
+ // Although this is executing in the child process of the death test,
+ // if it's not true we'll still get an error rather than the crash
+ // being expected.
+ ASSERT_TRUE(exc->IsOutOfProcess());
+
+ // Create a new frame to ensure PureVirtualCall is not optimized to some
+ // other line in this function.
+ {
+ PureVirtualCall instance;
+ }
+}
+
+// This test validates that the minidump is written correctly.
+TEST_F(ExceptionHandlerTest, InvalidParameterMiniDumpTest) {
+ ASSERT_TRUE(DoesPathExist(temp_path_));
+
+ // Call with a bad argument
+ ASSERT_TRUE(DoesPathExist(temp_path_));
+ wstring dump_path(temp_path_);
+ google_breakpad::CrashGenerationServer server(
+ kPipeName, NULL, NULL, NULL, ClientDumpCallback, NULL, NULL, NULL, NULL,
+ NULL, true, &dump_path);
+
+ ASSERT_TRUE(dump_file.empty() && full_dump_file.empty());
+
+ // This HAS to be EXPECT_, because when this test case is executed in the
+ // child process, the server registration will fail due to the named pipe
+ // being the same.
+ EXPECT_TRUE(server.Start());
+ EXPECT_EXIT(DoCrashInvalidParameter(), ::testing::ExitedWithCode(0), "");
+ ASSERT_TRUE(!dump_file.empty() && !full_dump_file.empty());
+ ASSERT_TRUE(DoesPathExist(dump_file.c_str()));
+ ASSERT_TRUE(DoesPathExist(full_dump_file.c_str()));
+
+ // Verify the dump for infos.
+ DumpAnalysis mini(dump_file);
+ DumpAnalysis full(full_dump_file);
+
+ // The dump should have all of these streams.
+ EXPECT_TRUE(mini.HasStream(ThreadListStream));
+ EXPECT_TRUE(full.HasStream(ThreadListStream));
+ EXPECT_TRUE(mini.HasStream(ModuleListStream));
+ EXPECT_TRUE(full.HasStream(ModuleListStream));
+ EXPECT_TRUE(mini.HasStream(ExceptionStream));
+ EXPECT_TRUE(full.HasStream(ExceptionStream));
+ EXPECT_TRUE(mini.HasStream(SystemInfoStream));
+ EXPECT_TRUE(full.HasStream(SystemInfoStream));
+ EXPECT_TRUE(mini.HasStream(MiscInfoStream));
+ EXPECT_TRUE(full.HasStream(MiscInfoStream));
+ EXPECT_TRUE(mini.HasStream(HandleDataStream));
+ EXPECT_TRUE(full.HasStream(HandleDataStream));
+
+ // We expect PEB and TEBs in this dump.
+ EXPECT_TRUE(mini.HasTebs() || full.HasTebs());
+ EXPECT_TRUE(mini.HasPeb() || full.HasPeb());
+
+ // Minidump should have a memory listing, but no 64-bit memory.
+ EXPECT_TRUE(mini.HasStream(MemoryListStream));
+ EXPECT_FALSE(mini.HasStream(Memory64ListStream));
+
+ EXPECT_FALSE(full.HasStream(MemoryListStream));
+ EXPECT_TRUE(full.HasStream(Memory64ListStream));
+
+ // This is the only place we don't use OR because we want both not
+ // to have the streams.
+ EXPECT_FALSE(mini.HasStream(ThreadExListStream));
+ EXPECT_FALSE(full.HasStream(ThreadExListStream));
+ EXPECT_FALSE(mini.HasStream(CommentStreamA));
+ EXPECT_FALSE(full.HasStream(CommentStreamA));
+ EXPECT_FALSE(mini.HasStream(CommentStreamW));
+ EXPECT_FALSE(full.HasStream(CommentStreamW));
+ EXPECT_FALSE(mini.HasStream(FunctionTableStream));
+ EXPECT_FALSE(full.HasStream(FunctionTableStream));
+ EXPECT_FALSE(mini.HasStream(MemoryInfoListStream));
+ EXPECT_FALSE(full.HasStream(MemoryInfoListStream));
+ EXPECT_FALSE(mini.HasStream(ThreadInfoListStream));
+ EXPECT_FALSE(full.HasStream(ThreadInfoListStream));
+ EXPECT_FALSE(mini.HasStream(HandleOperationListStream));
+ EXPECT_FALSE(full.HasStream(HandleOperationListStream));
+ EXPECT_FALSE(mini.HasStream(TokenStream));
+ EXPECT_FALSE(full.HasStream(TokenStream));
+}
+
+
+// This test validates that the minidump is written correctly.
+TEST_F(ExceptionHandlerTest, PureVirtualCallMiniDumpTest) {
+ ASSERT_TRUE(DoesPathExist(temp_path_));
+
+ // Call with a bad argument
+ ASSERT_TRUE(DoesPathExist(temp_path_));
+ wstring dump_path(temp_path_);
+ google_breakpad::CrashGenerationServer server(
+ kPipeName, NULL, NULL, NULL, ClientDumpCallback, NULL, NULL, NULL, NULL,
+ NULL, true, &dump_path);
+
+ ASSERT_TRUE(dump_file.empty() && full_dump_file.empty());
+
+ // This HAS to be EXPECT_, because when this test case is executed in the
+ // child process, the server registration will fail due to the named pipe
+ // being the same.
+ EXPECT_TRUE(server.Start());
+ EXPECT_EXIT(DoCrashPureVirtualCall(), ::testing::ExitedWithCode(0), "");
+ ASSERT_TRUE(!dump_file.empty() && !full_dump_file.empty());
+ ASSERT_TRUE(DoesPathExist(dump_file.c_str()));
+ ASSERT_TRUE(DoesPathExist(full_dump_file.c_str()));
+
+ // Verify the dump for infos.
+ DumpAnalysis mini(dump_file);
+ DumpAnalysis full(full_dump_file);
+
+ // The dump should have all of these streams.
+ EXPECT_TRUE(mini.HasStream(ThreadListStream));
+ EXPECT_TRUE(full.HasStream(ThreadListStream));
+ EXPECT_TRUE(mini.HasStream(ModuleListStream));
+ EXPECT_TRUE(full.HasStream(ModuleListStream));
+ EXPECT_TRUE(mini.HasStream(ExceptionStream));
+ EXPECT_TRUE(full.HasStream(ExceptionStream));
+ EXPECT_TRUE(mini.HasStream(SystemInfoStream));
+ EXPECT_TRUE(full.HasStream(SystemInfoStream));
+ EXPECT_TRUE(mini.HasStream(MiscInfoStream));
+ EXPECT_TRUE(full.HasStream(MiscInfoStream));
+ EXPECT_TRUE(mini.HasStream(HandleDataStream));
+ EXPECT_TRUE(full.HasStream(HandleDataStream));
+
+ // We expect PEB and TEBs in this dump.
+ EXPECT_TRUE(mini.HasTebs() || full.HasTebs());
+ EXPECT_TRUE(mini.HasPeb() || full.HasPeb());
+
+ // Minidump should have a memory listing, but no 64-bit memory.
+ EXPECT_TRUE(mini.HasStream(MemoryListStream));
+ EXPECT_FALSE(mini.HasStream(Memory64ListStream));
+
+ EXPECT_FALSE(full.HasStream(MemoryListStream));
+ EXPECT_TRUE(full.HasStream(Memory64ListStream));
+
+ // This is the only place we don't use OR because we want both not
+ // to have the streams.
+ EXPECT_FALSE(mini.HasStream(ThreadExListStream));
+ EXPECT_FALSE(full.HasStream(ThreadExListStream));
+ EXPECT_FALSE(mini.HasStream(CommentStreamA));
+ EXPECT_FALSE(full.HasStream(CommentStreamA));
+ EXPECT_FALSE(mini.HasStream(CommentStreamW));
+ EXPECT_FALSE(full.HasStream(CommentStreamW));
+ EXPECT_FALSE(mini.HasStream(FunctionTableStream));
+ EXPECT_FALSE(full.HasStream(FunctionTableStream));
+ EXPECT_FALSE(mini.HasStream(MemoryInfoListStream));
+ EXPECT_FALSE(full.HasStream(MemoryInfoListStream));
+ EXPECT_FALSE(mini.HasStream(ThreadInfoListStream));
+ EXPECT_FALSE(full.HasStream(ThreadInfoListStream));
+ EXPECT_FALSE(mini.HasStream(HandleOperationListStream));
+ EXPECT_FALSE(full.HasStream(HandleOperationListStream));
+ EXPECT_FALSE(mini.HasStream(TokenStream));
+ EXPECT_FALSE(full.HasStream(TokenStream));
+}
+
+// Test that writing a minidump produces a valid minidump containing
+// some expected structures.
+TEST_F(ExceptionHandlerTest, WriteMinidumpTest) {
+ ExceptionHandler handler(temp_path_,
+ NULL,
+ DumpCallback,
+ NULL,
+ ExceptionHandler::HANDLER_ALL);
+
+ // Disable GTest SEH handler
+ testing::DisableExceptionHandlerInScope disable_exception_handler;
+
+ ASSERT_TRUE(handler.WriteMinidump());
+ ASSERT_FALSE(dump_file.empty());
+
+ string minidump_filename;
+ ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(dump_file,
+ &minidump_filename));
+
+ // Read the minidump and verify some info.
+ Minidump minidump(minidump_filename);
+ ASSERT_TRUE(minidump.Read());
+ // TODO(ted): more comprehensive tests...
+}
+
+// Test that an additional memory region can be included in the minidump.
+TEST_F(ExceptionHandlerTest, AdditionalMemory) {
+ SYSTEM_INFO si;
+ GetSystemInfo(&si);
+ const uint32_t kMemorySize = si.dwPageSize;
+
+ // Get some heap memory.
+ uint8_t* memory = new uint8_t[kMemorySize];
+ const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
+ ASSERT_TRUE(memory);
+
+ // Stick some data into the memory so the contents can be verified.
+ for (uint32_t i = 0; i < kMemorySize; ++i) {
+ memory[i] = i % 255;
+ }
+
+ ExceptionHandler handler(temp_path_,
+ NULL,
+ DumpCallback,
+ NULL,
+ ExceptionHandler::HANDLER_ALL);
+
+ // Disable GTest SEH handler
+ testing::DisableExceptionHandlerInScope disable_exception_handler;
+
+ // Add the memory region to the list of memory to be included.
+ handler.RegisterAppMemory(memory, kMemorySize);
+ ASSERT_TRUE(handler.WriteMinidump());
+ ASSERT_FALSE(dump_file.empty());
+
+ string minidump_filename;
+ ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(dump_file,
+ &minidump_filename));
+
+ // Read the minidump. Ensure that the memory region is present
+ Minidump minidump(minidump_filename);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList();
+ ASSERT_TRUE(dump_memory_list);
+ const MinidumpMemoryRegion* region =
+ dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress);
+ ASSERT_TRUE(region);
+
+ EXPECT_EQ(kMemoryAddress, region->GetBase());
+ EXPECT_EQ(kMemorySize, region->GetSize());
+
+ // Verify memory contents.
+ EXPECT_EQ(0, memcmp(region->GetMemory(), memory, kMemorySize));
+
+ delete[] memory;
+}
+
+// Test that a memory region that was previously registered
+// can be unregistered.
+TEST_F(ExceptionHandlerTest, AdditionalMemoryRemove) {
+ SYSTEM_INFO si;
+ GetSystemInfo(&si);
+ const uint32_t kMemorySize = si.dwPageSize;
+
+ // Get some heap memory.
+ uint8_t* memory = new uint8_t[kMemorySize];
+ const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory);
+ ASSERT_TRUE(memory);
+
+ // Stick some data into the memory so the contents can be verified.
+ for (uint32_t i = 0; i < kMemorySize; ++i) {
+ memory[i] = i % 255;
+ }
+
+ ExceptionHandler handler(temp_path_,
+ NULL,
+ DumpCallback,
+ NULL,
+ ExceptionHandler::HANDLER_ALL);
+
+ // Disable GTest SEH handler
+ testing::DisableExceptionHandlerInScope disable_exception_handler;
+
+ // Add the memory region to the list of memory to be included.
+ handler.RegisterAppMemory(memory, kMemorySize);
+
+ // ...and then remove it
+ handler.UnregisterAppMemory(memory);
+
+ ASSERT_TRUE(handler.WriteMinidump());
+ ASSERT_FALSE(dump_file.empty());
+
+ string minidump_filename;
+ ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(dump_file,
+ &minidump_filename));
+
+ // Read the minidump. Ensure that the memory region is not present.
+ Minidump minidump(minidump_filename);
+ ASSERT_TRUE(minidump.Read());
+
+ MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList();
+ ASSERT_TRUE(dump_memory_list);
+ const MinidumpMemoryRegion* region =
+ dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress);
+ EXPECT_FALSE(region);
+
+ delete[] memory;
+}
+
+} // namespace
diff --git a/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_test.h b/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_test.h
new file mode 100644
index 0000000000..ef973e5392
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_test.h
@@ -0,0 +1,61 @@
+// Copyright 2012, 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.
+
+#ifndef CLIENT_WINDOWS_UNITTESTS_EXCEPTION_HANDLER_TEST_H_
+#define CLIENT_WINDOWS_UNITTESTS_EXCEPTION_HANDLER_TEST_H_
+
+namespace testing {
+
+// By default, GTest (on Windows) installs a SEH filter (and a handler) before
+// starting to run all the tests in order to avoid test interruptions is some
+// of the tests are crashing. Unfortunately, this functionality prevents the
+// execution to reach the UnhandledExceptionFilter installed by Google-Breakpad
+// ExceptionHandler so in order to test the Google-Breakpad exception handling
+// code the exception handling done by GTest must be disabled.
+// Usage:
+//
+// google_breakpad::ExceptionHandler exc(...);
+//
+// // Disable GTest SEH handler
+// testing::DisableExceptionHandlerInScope disable_exception_handler;
+// ...
+// ASSERT_DEATH( ... some crash ...);
+//
+class DisableExceptionHandlerInScope {
+ public:
+ DisableExceptionHandlerInScope();
+ ~DisableExceptionHandlerInScope();
+
+ private:
+ bool catch_exceptions_;
+};
+
+} // namespace testing
+
+#endif // CLIENT_WINDOWS_UNITTESTS_EXCEPTION_HANDLER_TEST_H_
diff --git a/toolkit/crashreporter/breakpad-client/windows/unittests/minidump_test.cc b/toolkit/crashreporter/breakpad-client/windows/unittests/minidump_test.cc
new file mode 100644
index 0000000000..82641125c1
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/windows/unittests/minidump_test.cc
@@ -0,0 +1,332 @@
+// Copyright (c) 2010, 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 <windows.h>
+#include <objbase.h>
+#include <dbghelp.h>
+
+#include "breakpad_googletest_includes.h"
+#include "client/windows/crash_generation/minidump_generator.h"
+#include "client/windows/unittests/dump_analysis.h" // NOLINT
+
+namespace {
+
+// Minidump with stacks, PEB, TEB, and unloaded module list.
+const MINIDUMP_TYPE kSmallDumpType = static_cast<MINIDUMP_TYPE>(
+ MiniDumpWithProcessThreadData | // Get PEB and TEB.
+ MiniDumpWithUnloadedModules); // Get unloaded modules when available.
+
+// Minidump with all of the above, plus memory referenced from stack.
+const MINIDUMP_TYPE kLargerDumpType = static_cast<MINIDUMP_TYPE>(
+ MiniDumpWithProcessThreadData | // Get PEB and TEB.
+ MiniDumpWithUnloadedModules | // Get unloaded modules when available.
+ MiniDumpWithIndirectlyReferencedMemory); // Get memory referenced by stack.
+
+// Large dump with all process memory.
+const MINIDUMP_TYPE kFullDumpType = static_cast<MINIDUMP_TYPE>(
+ MiniDumpWithFullMemory | // Full memory from process.
+ MiniDumpWithProcessThreadData | // Get PEB and TEB.
+ MiniDumpWithHandleData | // Get all handle information.
+ MiniDumpWithUnloadedModules); // Get unloaded modules when available.
+
+class MinidumpTest: public testing::Test {
+ public:
+ MinidumpTest() {
+ wchar_t temp_dir_path[ MAX_PATH ] = {0};
+ ::GetTempPath(MAX_PATH, temp_dir_path);
+ dump_path_ = temp_dir_path;
+ }
+
+ virtual void SetUp() {
+ // Make sure URLMon isn't loaded into our process.
+ ASSERT_EQ(NULL, ::GetModuleHandle(L"urlmon.dll"));
+
+ // Then load and unload it to ensure we have something to
+ // stock the unloaded module list with.
+ HMODULE urlmon = ::LoadLibrary(L"urlmon.dll");
+ ASSERT_TRUE(urlmon != NULL);
+ ASSERT_TRUE(::FreeLibrary(urlmon));
+ }
+
+ virtual void TearDown() {
+ if (!dump_file_.empty()) {
+ ::DeleteFile(dump_file_.c_str());
+ dump_file_ = L"";
+ }
+ if (!full_dump_file_.empty()) {
+ ::DeleteFile(full_dump_file_.c_str());
+ full_dump_file_ = L"";
+ }
+ }
+
+ bool WriteDump(ULONG flags) {
+ using google_breakpad::MinidumpGenerator;
+
+ // Fake exception is access violation on write to this.
+ EXCEPTION_RECORD ex_record = {
+ STATUS_ACCESS_VIOLATION, // ExceptionCode
+ 0, // ExceptionFlags
+ NULL, // ExceptionRecord;
+ reinterpret_cast<void*>(static_cast<uintptr_t>(0xCAFEBABE)), // ExceptionAddress;
+ 2, // NumberParameters;
+ { EXCEPTION_WRITE_FAULT, reinterpret_cast<ULONG_PTR>(this) }
+ };
+ CONTEXT ctx_record = {};
+ EXCEPTION_POINTERS ex_ptrs = {
+ &ex_record,
+ &ctx_record,
+ };
+
+ MinidumpGenerator generator(dump_path_,
+ ::GetCurrentProcess(),
+ ::GetCurrentProcessId(),
+ ::GetCurrentThreadId(),
+ ::GetCurrentThreadId(),
+ &ex_ptrs,
+ NULL,
+ static_cast<MINIDUMP_TYPE>(flags),
+ TRUE);
+ generator.GenerateDumpFile(&dump_file_);
+ generator.GenerateFullDumpFile(&full_dump_file_);
+ // And write a dump
+ bool result = generator.WriteMinidump();
+ return result == TRUE;
+ }
+
+ protected:
+ std::wstring dump_file_;
+ std::wstring full_dump_file_;
+
+ std::wstring dump_path_;
+};
+
+// We need to be able to get file information from Windows
+bool HasFileInfo(const std::wstring& file_path) {
+ DWORD dummy;
+ const wchar_t* path = file_path.c_str();
+ DWORD length = ::GetFileVersionInfoSize(path, &dummy);
+ if (length == 0)
+ return NULL;
+
+ void* data = calloc(length, 1);
+ if (!data)
+ return false;
+
+ if (!::GetFileVersionInfo(path, dummy, length, data)) {
+ free(data);
+ return false;
+ }
+
+ void* translate = NULL;
+ UINT page_count;
+ BOOL query_result = VerQueryValue(
+ data,
+ L"\\VarFileInfo\\Translation",
+ static_cast<void**>(&translate),
+ &page_count);
+
+ free(data);
+ if (query_result && translate) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+TEST_F(MinidumpTest, Version) {
+ // Loads DbgHelp.dll in process
+ ImagehlpApiVersion();
+
+ HMODULE dbg_help = ::GetModuleHandle(L"dbghelp.dll");
+ ASSERT_TRUE(dbg_help != NULL);
+
+ wchar_t dbg_help_file[1024] = {};
+ ASSERT_TRUE(::GetModuleFileName(dbg_help,
+ dbg_help_file,
+ sizeof(dbg_help_file) /
+ sizeof(*dbg_help_file)));
+ ASSERT_TRUE(HasFileInfo(std::wstring(dbg_help_file)) != NULL);
+
+// LOG(INFO) << "DbgHelp.dll version: " << file_info->file_version();
+}
+
+TEST_F(MinidumpTest, Normal) {
+ EXPECT_TRUE(WriteDump(MiniDumpNormal));
+ DumpAnalysis mini(dump_file_);
+
+ // We expect threads, modules and some memory.
+ EXPECT_TRUE(mini.HasStream(ThreadListStream));
+ EXPECT_TRUE(mini.HasStream(ModuleListStream));
+ EXPECT_TRUE(mini.HasStream(MemoryListStream));
+ EXPECT_TRUE(mini.HasStream(ExceptionStream));
+ EXPECT_TRUE(mini.HasStream(SystemInfoStream));
+ EXPECT_TRUE(mini.HasStream(MiscInfoStream));
+
+ EXPECT_FALSE(mini.HasStream(ThreadExListStream));
+ EXPECT_FALSE(mini.HasStream(Memory64ListStream));
+ EXPECT_FALSE(mini.HasStream(CommentStreamA));
+ EXPECT_FALSE(mini.HasStream(CommentStreamW));
+ EXPECT_FALSE(mini.HasStream(HandleDataStream));
+ EXPECT_FALSE(mini.HasStream(FunctionTableStream));
+ EXPECT_FALSE(mini.HasStream(UnloadedModuleListStream));
+ EXPECT_FALSE(mini.HasStream(MemoryInfoListStream));
+ EXPECT_FALSE(mini.HasStream(ThreadInfoListStream));
+ EXPECT_FALSE(mini.HasStream(HandleOperationListStream));
+ EXPECT_FALSE(mini.HasStream(TokenStream));
+
+ // We expect no PEB nor TEBs in this dump.
+ EXPECT_FALSE(mini.HasTebs());
+ EXPECT_FALSE(mini.HasPeb());
+
+ // We expect no off-stack memory in this dump.
+ EXPECT_FALSE(mini.HasMemory(this));
+}
+
+TEST_F(MinidumpTest, SmallDump) {
+ ASSERT_TRUE(WriteDump(kSmallDumpType));
+ DumpAnalysis mini(dump_file_);
+
+ EXPECT_TRUE(mini.HasStream(ThreadListStream));
+ EXPECT_TRUE(mini.HasStream(ModuleListStream));
+ EXPECT_TRUE(mini.HasStream(MemoryListStream));
+ EXPECT_TRUE(mini.HasStream(ExceptionStream));
+ EXPECT_TRUE(mini.HasStream(SystemInfoStream));
+ EXPECT_TRUE(mini.HasStream(UnloadedModuleListStream));
+ EXPECT_TRUE(mini.HasStream(MiscInfoStream));
+
+ // We expect PEB and TEBs in this dump.
+ EXPECT_TRUE(mini.HasTebs());
+ EXPECT_TRUE(mini.HasPeb());
+
+ EXPECT_FALSE(mini.HasStream(ThreadExListStream));
+ EXPECT_FALSE(mini.HasStream(Memory64ListStream));
+ EXPECT_FALSE(mini.HasStream(CommentStreamA));
+ EXPECT_FALSE(mini.HasStream(CommentStreamW));
+ EXPECT_FALSE(mini.HasStream(HandleDataStream));
+ EXPECT_FALSE(mini.HasStream(FunctionTableStream));
+ EXPECT_FALSE(mini.HasStream(MemoryInfoListStream));
+ EXPECT_FALSE(mini.HasStream(ThreadInfoListStream));
+ EXPECT_FALSE(mini.HasStream(HandleOperationListStream));
+ EXPECT_FALSE(mini.HasStream(TokenStream));
+
+ // We expect no off-stack memory in this dump.
+ EXPECT_FALSE(mini.HasMemory(this));
+}
+
+TEST_F(MinidumpTest, LargerDump) {
+ ASSERT_TRUE(WriteDump(kLargerDumpType));
+ DumpAnalysis mini(dump_file_);
+
+ // The dump should have all of these streams.
+ EXPECT_TRUE(mini.HasStream(ThreadListStream));
+ EXPECT_TRUE(mini.HasStream(ModuleListStream));
+ EXPECT_TRUE(mini.HasStream(MemoryListStream));
+ EXPECT_TRUE(mini.HasStream(ExceptionStream));
+ EXPECT_TRUE(mini.HasStream(SystemInfoStream));
+ EXPECT_TRUE(mini.HasStream(UnloadedModuleListStream));
+ EXPECT_TRUE(mini.HasStream(MiscInfoStream));
+
+ // We expect memory referenced by stack in this dump.
+ EXPECT_TRUE(mini.HasMemory(this));
+
+ // We expect PEB and TEBs in this dump.
+ EXPECT_TRUE(mini.HasTebs());
+ EXPECT_TRUE(mini.HasPeb());
+
+ EXPECT_FALSE(mini.HasStream(ThreadExListStream));
+ EXPECT_FALSE(mini.HasStream(Memory64ListStream));
+ EXPECT_FALSE(mini.HasStream(CommentStreamA));
+ EXPECT_FALSE(mini.HasStream(CommentStreamW));
+ EXPECT_FALSE(mini.HasStream(HandleDataStream));
+ EXPECT_FALSE(mini.HasStream(FunctionTableStream));
+ EXPECT_FALSE(mini.HasStream(MemoryInfoListStream));
+ EXPECT_FALSE(mini.HasStream(ThreadInfoListStream));
+ EXPECT_FALSE(mini.HasStream(HandleOperationListStream));
+ EXPECT_FALSE(mini.HasStream(TokenStream));
+}
+
+TEST_F(MinidumpTest, FullDump) {
+ ASSERT_TRUE(WriteDump(kFullDumpType));
+ ASSERT_TRUE(dump_file_ != L"");
+ ASSERT_TRUE(full_dump_file_ != L"");
+ DumpAnalysis mini(dump_file_);
+ DumpAnalysis full(full_dump_file_);
+
+ // Either dumps can contain part of the information.
+
+ // The dump should have all of these streams.
+ EXPECT_TRUE(mini.HasStream(ThreadListStream));
+ EXPECT_TRUE(full.HasStream(ThreadListStream));
+ EXPECT_TRUE(mini.HasStream(ModuleListStream));
+ EXPECT_TRUE(full.HasStream(ModuleListStream));
+ EXPECT_TRUE(mini.HasStream(ExceptionStream));
+ EXPECT_TRUE(full.HasStream(ExceptionStream));
+ EXPECT_TRUE(mini.HasStream(SystemInfoStream));
+ EXPECT_TRUE(full.HasStream(SystemInfoStream));
+ EXPECT_TRUE(mini.HasStream(UnloadedModuleListStream));
+ EXPECT_TRUE(full.HasStream(UnloadedModuleListStream));
+ EXPECT_TRUE(mini.HasStream(MiscInfoStream));
+ EXPECT_TRUE(full.HasStream(MiscInfoStream));
+ EXPECT_TRUE(mini.HasStream(HandleDataStream));
+ EXPECT_TRUE(full.HasStream(HandleDataStream));
+
+ // We expect memory referenced by stack in this dump.
+ EXPECT_FALSE(mini.HasMemory(this));
+ EXPECT_TRUE(full.HasMemory(this));
+
+ // We expect PEB and TEBs in this dump.
+ EXPECT_TRUE(mini.HasTebs() || full.HasTebs());
+ EXPECT_TRUE(mini.HasPeb() || full.HasPeb());
+
+ EXPECT_TRUE(mini.HasStream(MemoryListStream));
+ EXPECT_TRUE(full.HasStream(Memory64ListStream));
+ EXPECT_FALSE(mini.HasStream(Memory64ListStream));
+ EXPECT_FALSE(full.HasStream(MemoryListStream));
+
+ // This is the only place we don't use OR because we want both not
+ // to have the streams.
+ EXPECT_FALSE(mini.HasStream(ThreadExListStream));
+ EXPECT_FALSE(full.HasStream(ThreadExListStream));
+ EXPECT_FALSE(mini.HasStream(CommentStreamA));
+ EXPECT_FALSE(full.HasStream(CommentStreamA));
+ EXPECT_FALSE(mini.HasStream(CommentStreamW));
+ EXPECT_FALSE(full.HasStream(CommentStreamW));
+ EXPECT_FALSE(mini.HasStream(FunctionTableStream));
+ EXPECT_FALSE(full.HasStream(FunctionTableStream));
+ EXPECT_FALSE(mini.HasStream(MemoryInfoListStream));
+ EXPECT_FALSE(full.HasStream(MemoryInfoListStream));
+ EXPECT_FALSE(mini.HasStream(ThreadInfoListStream));
+ EXPECT_FALSE(full.HasStream(ThreadInfoListStream));
+ EXPECT_FALSE(mini.HasStream(HandleOperationListStream));
+ EXPECT_FALSE(full.HasStream(HandleOperationListStream));
+ EXPECT_FALSE(mini.HasStream(TokenStream));
+ EXPECT_FALSE(full.HasStream(TokenStream));
+}
+
+} // namespace