mirror of
https://github.com/yuzu-emu/breakpad.git
synced 2025-04-17 00:51:48 +00:00
A=Bill McCloskey <wmccloskey@mozilla.com> R=ted at https://bugzilla.mozilla.org/show_bug.cgi?id=662646 git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@989 4c0a9323-5329-0410-9bdc-e9ce6186880e
424 lines
15 KiB
C++
Executable file
424 lines
15 KiB
C++
Executable file
// 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 "../../../../common/windows/string_utils-inl.h"
|
|
#include "../../../../google_breakpad/processor/minidump.h"
|
|
#include "../crash_generation/crash_generation_server.h"
|
|
#include "../handler/exception_handler.h"
|
|
#include "dump_analysis.h" // NOLINT
|
|
|
|
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);
|
|
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_F(ExceptionHandlerTest, AdditionalMemory) {
|
|
SYSTEM_INFO si;
|
|
GetSystemInfo(&si);
|
|
const u_int32_t kMemorySize = si.dwPageSize;
|
|
// Get some heap memory.
|
|
u_int8_t* memory = new u_int8_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 (unsigned int i = 0; i < kMemorySize; ++i) {
|
|
memory[i] = i % 255;
|
|
}
|
|
|
|
ExceptionHandler handler(temp_path_,
|
|
NULL,
|
|
DumpCallback,
|
|
NULL,
|
|
ExceptionHandler::HANDLER_ALL);
|
|
// 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;
|
|
}
|
|
|
|
} // namespace
|