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
diff --git a/src/client/windows/unittests/exception_handler_death_test.cc b/src/client/windows/unittests/exception_handler_death_test.cc
index 3613140..adea044 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 @@
   // 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 @@
   // 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