summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_test.cc')
-rw-r--r--toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_test.cc503
1 files changed, 503 insertions, 0 deletions
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