Commit abab2999 authored by Ted Mielczarek's avatar Ted Mielczarek
Browse files

bug 599301 - Make Breakpad include memory around instruction pointer in...

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
parent d183c2f3
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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 \
+93 −1
Original line number Diff line number Diff line
@@ -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);
    }
+7 −0
Original line number Diff line number Diff line
@@ -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
+316 −2
Original line number Diff line number Diff line
@@ -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
+7 −0
Original line number Diff line number Diff line
@@ -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);
Loading