// 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(),
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()));
// 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()));
// 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