bug 599301 - Make Breakpad include memory around instruction pointer in minidumps on older versions of Windows. r=mento, a=beltzner

--HG--
extra : rebase_source : c5fd538fdfb532eeed4c4265eb93a69bfac485aa
This commit is contained in:
Ted Mielczarek 2011-01-28 11:57:08 -05:00
parent d183c2f337
commit abab29991a
7 changed files with 466 additions and 4 deletions

View File

@ -46,7 +46,7 @@ LIBRARY_NAME = exception_handler_s
XPI_NAME = crashreporter
LOCAL_INCLUDES = -I$(topsrcdir)/toolkit/crashreporter/google-breakpad/src
DEFINES += -DUNICODE -D_UNICODE -DBREAKPAD_NO_TERMINATE_THREAD
DEFINES += -DUNICODE -D_UNICODE -DBREAKPAD_NO_TERMINATE_THREAD -DNOMINMAX
CPPSRCS = \
exception_handler.cc \

View File

@ -33,6 +33,8 @@
#include <stdio.h>
#include <winternl.h>
#include <algorithm>
#include "common/windows/string_utils-inl.h"
#include "client/windows/common/ipc_protocol.h"
@ -129,6 +131,13 @@ namespace google_breakpad {
static const int kWaitForHandlerThreadMs = 60000;
static const int kExceptionHandlerThreadInitialStackSize = 64 * 1024;
// This is passed as the context to the MinidumpWriteDump callback.
typedef struct {
ULONG64 memory_base;
ULONG memory_size;
bool finished;
} MinidumpCallbackContext;
vector<ExceptionHandler*>* ExceptionHandler::handler_stack_ = NULL;
LONG ExceptionHandler::handler_stack_index_ = 0;
CRITICAL_SECTION ExceptionHandler::handler_stack_critical_section_;
@ -908,6 +917,45 @@ bool ExceptionHandler::WriteMinidumpWithException(
return success;
}
// static
BOOL CALLBACK ExceptionHandler::MinidumpWriteDumpCallback(
PVOID context,
const PMINIDUMP_CALLBACK_INPUT callback_input,
PMINIDUMP_CALLBACK_OUTPUT callback_output) {
switch (callback_input->CallbackType) {
case MemoryCallback: {
MinidumpCallbackContext* callback_context =
reinterpret_cast<MinidumpCallbackContext*>(context);
if (callback_context->finished)
return FALSE;
// Include the specified memory region.
callback_output->MemoryBase = callback_context->memory_base;
callback_output->MemorySize = callback_context->memory_size;
callback_context->finished = true;
return TRUE;
}
// Include all modules.
case IncludeModuleCallback:
case ModuleCallback:
return TRUE;
// Include all threads.
case IncludeThreadCallback:
case ThreadCallback:
return TRUE;
// Stop receiving cancel callbacks.
case CancelCallback:
callback_output->CheckCancel = FALSE;
callback_output->Cancel = FALSE;
return TRUE;
}
// Ignore other callback types.
return FALSE;
}
bool ExceptionHandler::WriteMinidumpWithExceptionForProcess(
DWORD requesting_thread_id,
EXCEPTION_POINTERS* exinfo,
@ -967,6 +1015,50 @@ bool ExceptionHandler::WriteMinidumpWithExceptionForProcess(
++user_streams.UserStreamCount;
}
MINIDUMP_CALLBACK_INFORMATION callback;
MinidumpCallbackContext context;
MINIDUMP_CALLBACK_INFORMATION* callback_pointer = NULL;
// Older versions of DbgHelp.dll don't correctly put the memory around
// the faulting instruction pointer into the minidump. This
// callback will ensure that it gets included.
if (exinfo) {
// Find a memory region of 256 bytes centered on the
// faulting instruction pointer.
const ULONG64 instruction_pointer =
#if defined(_M_IX86)
exinfo->ContextRecord->Eip;
#elif defined(_M_AMD64)
exinfo->ContextRecord->Rip;
#else
#error Unsupported platform
#endif
MEMORY_BASIC_INFORMATION info;
if (VirtualQuery(reinterpret_cast<LPCVOID>(instruction_pointer),
&info,
sizeof(MEMORY_BASIC_INFORMATION)) != 0 &&
info.State == MEM_COMMIT) {
// Attempt to get 128 bytes before and after the instruction
// pointer, but settle for whatever's available up to the
// boundaries of the memory region.
const ULONG64 kIPMemorySize = 256;
context.memory_base =
std::max(reinterpret_cast<ULONG64>(info.BaseAddress),
instruction_pointer - (kIPMemorySize / 2));
ULONG64 end_of_range =
std::min(instruction_pointer + (kIPMemorySize / 2),
reinterpret_cast<ULONG64>(info.BaseAddress)
+ info.RegionSize);
context.memory_size =
static_cast<ULONG>(end_of_range - context.memory_base);
context.finished = false;
callback.CallbackRoutine = MinidumpWriteDumpCallback;
callback.CallbackParam = reinterpret_cast<void*>(&context);
callback_pointer = &callback;
}
}
// The explicit comparison to TRUE avoids a warning (C4800).
success = (minidump_write_dump_(process,
processId,
@ -974,7 +1066,7 @@ bool ExceptionHandler::WriteMinidumpWithExceptionForProcess(
dump_type_,
exinfo ? &except_info : NULL,
&user_streams,
NULL) == TRUE);
callback_pointer) == TRUE);
CloseHandle(dump_file);
}

View File

@ -295,6 +295,13 @@ class ExceptionHandler {
EXCEPTION_POINTERS* exinfo,
MDRawAssertionInfo* assertion);
// This function is used as a callback when calling MinidumpWriteDump,
// in order to add additional memory regions to the dump.
static BOOL CALLBACK MinidumpWriteDumpCallback(
PVOID context,
const PMINIDUMP_CALLBACK_INPUT callback_input,
PMINIDUMP_CALLBACK_OUTPUT callback_output);
// This function does the actual writing of a minidump. It is
// called on the handler thread. requesting_thread_id is the ID of
// the thread that requested the dump, if that information is

View File

@ -33,11 +33,19 @@
#include <objbase.h>
#include <shellapi.h>
#include <string>
#include "../../../breakpad_googletest_includes.h"
#include "../../../../common/windows/string_utils-inl.h"
#include "../crash_generation/crash_generation_server.h"
#include "../handler/exception_handler.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";
@ -65,8 +73,8 @@ void ExceptionHandlerDeathTest::SetUp() {
// 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.
// 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,
@ -212,4 +220,310 @@ TEST_F(ExceptionHandlerDeathTest, PureVirtualCallTest) {
// 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;
}
TEST_F(ExceptionHandlerDeathTest, InstructionPointerMemory) {
ASSERT_TRUE(DoesPathExist(temp_path_));
google_breakpad::ExceptionHandler *exc =
new google_breakpad::ExceptionHandler(
temp_path_, NULL, NULL, NULL,
google_breakpad::ExceptionHandler::HANDLER_ALL);
// Get some executable memory.
const u_int32_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);
u_int64_t instruction_pointer;
switch (context->GetContextCPU()) {
case MD_CONTEXT_X86:
instruction_pointer = context->GetContextX86()->eip;
break;
case MD_CONTEXT_AMD64:
instruction_pointer = context->GetContextAMD64()->rip;
break;
default:
FAIL() << "Unknown context CPU: " << context->GetContextCPU();
break;
}
MinidumpMemoryRegion* region =
memory_list->GetMemoryRegionForAddress(instruction_pointer);
ASSERT_TRUE(region);
EXPECT_EQ(kMemorySize, region->GetSize());
const u_int8_t* bytes = region->GetMemory();
ASSERT_TRUE(bytes);
u_int8_t prefix_bytes[kOffset];
u_int8_t suffix_bytes[kMemorySize - kOffset - sizeof(instructions)];
memset(prefix_bytes, 0, sizeof(prefix_bytes));
memset(suffix_bytes, 0, sizeof(suffix_bytes));
EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0);
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, InstructionPointerMemoryMinBound) {
ASSERT_TRUE(DoesPathExist(temp_path_));
google_breakpad::ExceptionHandler *exc =
new google_breakpad::ExceptionHandler(
temp_path_, NULL, NULL, NULL,
google_breakpad::ExceptionHandler::HANDLER_ALL);
SYSTEM_INFO sSysInfo; // Useful information about the system
GetSystemInfo(&sSysInfo); // Initialize the structure.
const u_int32_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);
u_int64_t instruction_pointer;
switch (context->GetContextCPU()) {
case MD_CONTEXT_X86:
instruction_pointer = context->GetContextX86()->eip;
break;
case MD_CONTEXT_AMD64:
instruction_pointer = context->GetContextAMD64()->rip;
break;
default:
FAIL() << "Unknown context CPU: " << context->GetContextCPU();
break;
}
MinidumpMemoryRegion* region =
memory_list->GetMemoryRegionForAddress(instruction_pointer);
ASSERT_TRUE(region);
EXPECT_EQ(kMemorySize / 2, region->GetSize());
const u_int8_t* bytes = region->GetMemory();
ASSERT_TRUE(bytes);
u_int8_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_));
google_breakpad::ExceptionHandler *exc =
new google_breakpad::ExceptionHandler(
temp_path_, NULL, NULL, NULL,
google_breakpad::ExceptionHandler::HANDLER_ALL);
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);
u_int64_t instruction_pointer;
switch (context->GetContextCPU()) {
case MD_CONTEXT_X86:
instruction_pointer = context->GetContextX86()->eip;
break;
case MD_CONTEXT_AMD64:
instruction_pointer = context->GetContextAMD64()->rip;
break;
default:
FAIL() << "Unknown context CPU: " << context->GetContextCPU();
break;
}
MinidumpMemoryRegion* region =
memory_list->GetMemoryRegionForAddress(instruction_pointer);
ASSERT_TRUE(region);
const size_t kPrefixSize = 128; // bytes
EXPECT_EQ(kPrefixSize + sizeof(instructions), region->GetSize());
const u_int8_t* bytes = region->GetMemory();
ASSERT_TRUE(bytes);
u_int8_t prefix_bytes[kPrefixSize];
memset(prefix_bytes, 0, sizeof(prefix_bytes));
EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0);
EXPECT_TRUE(memcmp(bytes + kPrefixSize,
instructions, sizeof(instructions)) == 0);
}
DeleteFileW(minidump_filename_wide.c_str());
}
} // namespace

View File

@ -5,6 +5,7 @@ let CrashTestUtils = {
crash: null,
lockDir: null,
dumpHasStream: null,
dumpHasInstructionPointerMemory: null,
// Constants for crash()
// Keep these in sync with nsTestCrasher.cpp!
@ -40,3 +41,9 @@ CrashTestUtils.dumpHasStream = lib.declare("DumpHasStream",
ctypes.bool,
ctypes.char.ptr,
ctypes.uint32_t);
CrashTestUtils.dumpHasInstructionPointerMemory =
lib.declare("DumpHasInstructionPointerMemory",
ctypes.default_abi,
ctypes.bool,
ctypes.char.ptr);

View File

@ -3,6 +3,7 @@
using namespace google_breakpad;
// Return true if the specified minidump contains a stream of |stream_type|.
extern "C"
NS_EXPORT bool
DumpHasStream(const char* dump_file, u_int32_t stream_type)
@ -17,3 +18,43 @@ DumpHasStream(const char* dump_file, u_int32_t stream_type)
return true;
}
// Return true if the specified minidump contains a memory region
// that contains the instruction pointer from the exception record.
extern "C"
NS_EXPORT bool
DumpHasInstructionPointerMemory(const char* dump_file)
{
Minidump minidump(dump_file);
if (!minidump.Read())
return false;
MinidumpException* exception = minidump.GetException();
MinidumpMemoryList* memory_list = minidump.GetMemoryList();
if (!exception || !memory_list) {
return false;
}
MinidumpContext* context = exception->GetContext();
if (!context)
return false;
u_int64_t instruction_pointer;
switch (context->GetContextCPU()) {
case MD_CONTEXT_X86:
instruction_pointer = context->GetContextX86()->eip;
break;
case MD_CONTEXT_AMD64:
instruction_pointer = context->GetContextAMD64()->rip;
break;
case MD_CONTEXT_ARM:
instruction_pointer = context->GetContextARM()->iregs[15];
break;
default:
return false;
}
MinidumpMemoryRegion* region =
memory_list->GetMemoryRegionForAddress(instruction_pointer);
return region != NULL;
}

View File

@ -21,6 +21,7 @@ function run_test()
do_check_true('StartupTime' in extra);
do_check_true('CrashTime' in extra);
do_check_true(CrashTestUtils.dumpHasStream(mdump.path, CrashTestUtils.MD_THREAD_LIST_STREAM));
do_check_true(CrashTestUtils.dumpHasInstructionPointerMemory(mdump.path));
if (is_win7_or_newer)
do_check_true(CrashTestUtils.dumpHasStream(mdump.path, CrashTestUtils.MD_MEMORY_INFO_LIST_STREAM));
});