bug 599301 - Make Breakpad include memory around instruction pointer in minidumps on older versions of Windows. r=mento, a=beltzner
authorTed Mielczarek <ted.mielczarek@gmail.com>
Fri, 28 Jan 2011 11:57:08 -0500
changeset 61551 846f2ea91e63392221ea1944ae8c1156e1f06139
parent 61550 a39147a05a1e384aaa70a5c382c509a1e0034260
child 61552 3bba6b2caabd6be9b8b4a40c63d6592669230074
push idunknown
push userunknown
push dateunknown
reviewersmento, beltzner
bugs599301
milestone2.0b11pre
bug 599301 - Make Breakpad include memory around instruction pointer in minidumps on older versions of Windows. r=mento, a=beltzner
toolkit/crashreporter/google-breakpad/src/client/windows/handler/Makefile.in
toolkit/crashreporter/google-breakpad/src/client/windows/handler/exception_handler.cc
toolkit/crashreporter/google-breakpad/src/client/windows/handler/exception_handler.h
toolkit/crashreporter/google-breakpad/src/client/windows/unittests/exception_handler_death_test.cc
toolkit/crashreporter/test/CrashTestUtils.jsm
toolkit/crashreporter/test/dumputils.cpp
toolkit/crashreporter/test/unit/test_crashreporter_crash.js
--- a/toolkit/crashreporter/google-breakpad/src/client/windows/handler/Makefile.in
+++ b/toolkit/crashreporter/google-breakpad/src/client/windows/handler/Makefile.in
@@ -41,17 +41,17 @@ VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE		= handler
 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 \
 		$(NULL)
 
 # need static lib
 FORCE_STATIC_LIB = 1
 
--- a/toolkit/crashreporter/google-breakpad/src/client/windows/handler/exception_handler.cc
+++ b/toolkit/crashreporter/google-breakpad/src/client/windows/handler/exception_handler.cc
@@ -28,16 +28,18 @@
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include <assert.h>
 #include <ObjBase.h>
 #include <psapi.h>
 #include <stdio.h>
 #include <winternl.h>
 
+#include <algorithm>
+
 #include "common/windows/string_utils-inl.h"
 
 #include "client/windows/common/ipc_protocol.h"
 #include "client/windows/handler/exception_handler.h"
 #include "common/windows/guid_string.h"
 
 typedef VOID (WINAPI *RtlCaptureContextPtr) (PCONTEXT pContextRecord);
 
@@ -124,16 +126,23 @@ DWORD GetProcId(HANDLE process) {
 
 } // namespace
 
 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_;
 volatile LONG ExceptionHandler::instance_count_ = 0;
 
 ExceptionHandler::ExceptionHandler(const wstring& dump_path,
                                    FilterCallback filter,
                                    MinidumpCallback callback,
@@ -903,16 +912,55 @@ bool ExceptionHandler::WriteMinidumpWith
     // id so they are not known to the client.
     success = callback_(dump_path_c_, next_minidump_id_c_, callback_context_,
                         exinfo, assertion, success);
   }
 
   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,
     MDRawAssertionInfo* assertion,
     HANDLE process,
     DWORD processId,
     bool write_requester_stream) {
   bool success = false;
@@ -962,24 +1010,68 @@ bool ExceptionHandler::WriteMinidumpWith
       if (assertion) {
         int idx = user_streams.UserStreamCount;
         user_stream_array[idx].Type = MD_ASSERTION_INFO_STREAM;
         user_stream_array[idx].BufferSize = sizeof(MDRawAssertionInfo);
         user_stream_array[idx].Buffer = assertion;
         ++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,
                                       dump_file,
                                       dump_type_,
                                       exinfo ? &except_info : NULL,
                                       &user_streams,
-                                      NULL) == TRUE);
+                                      callback_pointer) == TRUE);
 
       CloseHandle(dump_file);
     }
   }
 
   return success;
 }
 
--- a/toolkit/crashreporter/google-breakpad/src/client/windows/handler/exception_handler.h
+++ b/toolkit/crashreporter/google-breakpad/src/client/windows/handler/exception_handler.h
@@ -290,16 +290,23 @@ class ExceptionHandler {
   // current process.  requesting_thread_id is the ID of the thread
   // that requested the dump.  If the dump is requested as a result of
   // an exception, exinfo contains exception information, otherwise,
   // it is NULL.
   bool WriteMinidumpWithException(DWORD requesting_thread_id,
                                   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
   // meaningful.  If the dump is requested as a result of an
   // exception, exinfo contains exception information, otherwise, it
   // is NULL.  process is the one that will be dumped.  If
   // requesting_thread_id is meaningful and should be added to the
   // minidump, write_requester_stream is |true|.
--- a/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/exception_handler_death_test.cc
+++ b/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/exception_handler_death_test.cc
@@ -28,21 +28,29 @@
 // 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 "../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";
 
 // Utility function to test for a path's existence.
 BOOL DoesPathExist(const TCHAR *path_name);
 
 class ExceptionHandlerDeathTest : public ::testing::Test {
@@ -60,18 +68,18 @@ class ExceptionHandlerDeathTest : public
 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 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,
                                   MAX_PATH);
   if (!dwRet) {
     assert(false);
   }
   StringCchPrintfW(temp_path_, MAX_PATH, L"%s%s", temp_path, test_name_wide);
@@ -207,9 +215,315 @@ TEST_F(ExceptionHandlerDeathTest, PureVi
                            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;
 }
+
+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
--- a/toolkit/crashreporter/test/CrashTestUtils.jsm
+++ b/toolkit/crashreporter/test/CrashTestUtils.jsm
@@ -1,15 +1,16 @@
 var EXPORTED_SYMBOLS = ["CrashTestUtils"];
 
 let CrashTestUtils = {
   // These will be defined using ctypes APIs below.
   crash: null,
   lockDir: null,
   dumpHasStream: null,
+  dumpHasInstructionPointerMemory: null,
 
   // Constants for crash()
   // Keep these in sync with nsTestCrasher.cpp!
   CRASH_INVALID_POINTER_DEREF: 0,
   CRASH_PURE_VIRTUAL_CALL:     1,
   CRASH_RUNTIMEABORT:          2,
 
   // Constants for dumpHasStream()
@@ -35,8 +36,14 @@ CrashTestUtils.lockDir = lib.declare("Lo
                                      ctypes.voidptr_t);  // nsISupports*
 
 
 CrashTestUtils.dumpHasStream = lib.declare("DumpHasStream",
                                            ctypes.default_abi,
                                            ctypes.bool,
                                            ctypes.char.ptr,
                                            ctypes.uint32_t);
+
+CrashTestUtils.dumpHasInstructionPointerMemory =
+  lib.declare("DumpHasInstructionPointerMemory",
+              ctypes.default_abi,
+              ctypes.bool,
+              ctypes.char.ptr);
--- a/toolkit/crashreporter/test/dumputils.cpp
+++ b/toolkit/crashreporter/test/dumputils.cpp
@@ -1,19 +1,60 @@
 #include "google_breakpad/processor/minidump.h"
 #include "nscore.h"
 
 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)
 {
   Minidump dump(dump_file);
   if (!dump.Read())
     return false;
 
   u_int32_t length;
   if (!dump.SeekToStreamType(stream_type, &length) || length == 0)
     return false;
 
   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;
+}
--- a/toolkit/crashreporter/test/unit/test_crashreporter_crash.js
+++ b/toolkit/crashreporter/test/unit/test_crashreporter_crash.js
@@ -16,16 +16,17 @@ function run_test()
 
   // try a basic crash
   do_crash(null, function(mdump, extra) {
              do_check_true(mdump.exists());
              do_check_true(mdump.fileSize > 0);
              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));
            });
 
   // check setting some basic data
   do_crash(function() {
              crashReporter.annotateCrashReport("TestKey", "TestValue");
              crashReporter.appendAppNotesToCrashReport("Junk");