diff options
author | ted.mielczarek <ted.mielczarek@4c0a9323-5329-0410-9bdc-e9ce6186880e> | 2011-01-25 19:19:19 +0000 |
---|---|---|
committer | ted.mielczarek <ted.mielczarek@4c0a9323-5329-0410-9bdc-e9ce6186880e> | 2011-01-25 19:19:19 +0000 |
commit | 0df0555e75214af91a9ee01a09fa24e1ff3c92c4 (patch) | |
tree | 7fc8400ea6bcc727d016f4797deee10773d3a52d | |
parent | Round-trip client/processor unittests for Windows (diff) | |
download | breakpad-0df0555e75214af91a9ee01a09fa24e1ff3c92c4.tar.xz |
Use a MinidumpCallback to force minidumps on Windows to include memory around the faulting instruction pointer. Older versions of DbgHelp don't seem to do this correctly (on Windows XP, for example)
R=mark at http://breakpad.appspot.com/259001
git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@763 4c0a9323-5329-0410-9bdc-e9ce6186880e
-rw-r--r-- | src/client/windows/handler/exception_handler.cc | 94 | ||||
-rw-r--r-- | src/client/windows/handler/exception_handler.h | 7 | ||||
-rwxr-xr-x | src/client/windows/unittests/exception_handler_death_test.cc | 318 |
3 files changed, 416 insertions, 3 deletions
diff --git a/src/client/windows/handler/exception_handler.cc b/src/client/windows/handler/exception_handler.cc index f02544cd..9c0593b4 100644 --- a/src/client/windows/handler/exception_handler.cc +++ b/src/client/windows/handler/exception_handler.cc @@ -28,6 +28,8 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <ObjBase.h> + +#include <algorithm> #include <cassert> #include <cstdio> @@ -44,6 +46,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_; @@ -757,6 +766,50 @@ bool ExceptionHandler::WriteMinidumpWithException( ++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_(GetCurrentProcess(), GetCurrentProcessId(), @@ -764,7 +817,7 @@ bool ExceptionHandler::WriteMinidumpWithException( dump_type_, exinfo ? &except_info : NULL, &user_streams, - NULL) == TRUE); + callback_pointer) == TRUE); CloseHandle(dump_file); } @@ -783,6 +836,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; +} + void ExceptionHandler::UpdateNextID() { assert(uuid_create_); UUID id = {0}; diff --git a/src/client/windows/handler/exception_handler.h b/src/client/windows/handler/exception_handler.h index 2cacdc38..2c2e7b76 100644 --- a/src/client/windows/handler/exception_handler.h +++ b/src/client/windows/handler/exception_handler.h @@ -277,6 +277,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); + // Generates a new ID and stores it in next_minidump_id_, and stores the // path of the next minidump to be written in next_minidump_path_. void UpdateNextID(); diff --git a/src/client/windows/unittests/exception_handler_death_test.cc b/src/client/windows/unittests/exception_handler_death_test.cc index 36131403..adea044f 100755 --- a/src/client/windows/unittests/exception_handler_death_test.cc +++ b/src/client/windows/unittests/exception_handler_death_test.cc @@ -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 |