blob: 079ca3d6fd37ac19632e04dfda4974ac20181464 [file] [log] [blame]
// 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(),
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 int extension_length = sizeof(extension) / sizeof(extension[0]) - 1;
const int 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 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, region->GetSize());
const uint8_t* bytes = region->GetMemory();
ASSERT_TRUE(bytes);
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, prefix_bytes, sizeof(prefix_bytes)));
EXPECT_EQ(0, memcmp(bytes + kOffset, instructions, sizeof(instructions)));
EXPECT_EQ(0, memcmp(bytes + kOffset + 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