summaryrefslogtreecommitdiffstats
path: root/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_death_test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_death_test.cc')
-rw-r--r--toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_death_test.cc587
1 files changed, 587 insertions, 0 deletions
diff --git a/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_death_test.cc b/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_death_test.cc
new file mode 100644
index 0000000000..5ef9e64d1d
--- /dev/null
+++ b/toolkit/crashreporter/breakpad-client/windows/unittests/exception_handler_death_test.cc
@@ -0,0 +1,587 @@
+// Copyright 2009, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// 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 "client/windows/crash_generation/crash_generation_server.h"
+#include "client/windows/handler/exception_handler.h"
+#include "client/windows/unittests/exception_handler_test.h"
+#include "common/windows/string_utils-inl.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);
+
+enum OutOfProcGuarantee {
+ OUT_OF_PROC_GUARANTEED,
+ OUT_OF_PROC_BEST_EFFORT,
+};
+
+class ExceptionHandlerDeathTest : public ::testing::Test {
+ protected:
+ // Member variable for each test that they can use
+ // for temporary storage.
+ TCHAR temp_path_[MAX_PATH];
+ // Actually constructs a temp path name.
+ virtual void SetUp();
+ // A helper method that tests can use to crash.
+ void DoCrashAccessViolation(const OutOfProcGuarantee out_of_proc_guarantee);
+ void DoCrashPureVirtualCall();
+};
+
+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 as a c-style string,
+ // convert it to a wchar_t string.
+ int dwRet = MultiByteToWideChar(CP_ACP, 0, test_info->name(),
+ static_cast<int>(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);
+ CreateDirectory(temp_path_, NULL);
+}
+
+BOOL DoesPathExist(const TCHAR *path_name) {
+ DWORD flags = GetFileAttributes(path_name);
+ if (flags == INVALID_FILE_ATTRIBUTES) {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+bool MinidumpWrittenCallback(const wchar_t* dump_path,
+ const wchar_t* minidump_id,
+ void* context,
+ EXCEPTION_POINTERS* exinfo,
+ MDRawAssertionInfo* assertion,
+ bool succeeded) {
+ if (succeeded && DoesPathExist(dump_path)) {
+ fprintf(stderr, kSuccessIndicator);
+ } else {
+ fprintf(stderr, kFailureIndicator);
+ }
+ // If we don't flush, the output doesn't get sent before
+ // this process dies.
+ fflush(stderr);
+ return succeeded;
+}
+
+TEST_F(ExceptionHandlerDeathTest, InProcTest) {
+ // For the in-proc test, we just need to instantiate an exception
+ // handler in in-proc mode, and crash. Since the entire test is
+ // reexecuted in the child process, we don't have to worry about
+ // the semantics of the exception handler being inherited/not
+ // inherited across CreateProcess().
+ ASSERT_TRUE(DoesPathExist(temp_path_));
+ scoped_ptr<google_breakpad::ExceptionHandler> exc(
+ new google_breakpad::ExceptionHandler(
+ temp_path_,
+ NULL,
+ &MinidumpWrittenCallback,
+ NULL,
+ google_breakpad::ExceptionHandler::HANDLER_ALL));
+
+ // Disable GTest SEH handler
+ testing::DisableExceptionHandlerInScope disable_exception_handler;
+
+ int *i = NULL;
+ ASSERT_DEATH((*i)++, kSuccessIndicator);
+}
+
+static bool gDumpCallbackCalled = false;
+
+void clientDumpCallback(void *dump_context,
+ const google_breakpad::ClientInfo *client_info,
+ const std::wstring *dump_path) {
+ gDumpCallbackCalled = true;
+}
+
+void ExceptionHandlerDeathTest::DoCrashAccessViolation(
+ const OutOfProcGuarantee out_of_proc_guarantee) {
+ scoped_ptr<google_breakpad::ExceptionHandler> exc;
+
+ if (out_of_proc_guarantee == OUT_OF_PROC_GUARANTEED) {
+ google_breakpad::CrashGenerationClient *client =
+ new google_breakpad::CrashGenerationClient(kPipeName,
+ MiniDumpNormal,
+ NULL); // custom_info
+ ASSERT_TRUE(client->Register());
+ exc.reset(new google_breakpad::ExceptionHandler(
+ temp_path_,
+ NULL, // filter
+ NULL, // callback
+ NULL, // callback_context
+ google_breakpad::ExceptionHandler::HANDLER_ALL,
+ client));
+ } else {
+ ASSERT_TRUE(out_of_proc_guarantee == OUT_OF_PROC_BEST_EFFORT);
+ exc.reset(new google_breakpad::ExceptionHandler(
+ temp_path_,
+ NULL, // filter
+ NULL, // callback
+ NULL, // callback_context
+ google_breakpad::ExceptionHandler::HANDLER_ALL,
+ MiniDumpNormal,
+ kPipeName,
+ NULL)); // custom_info
+ }
+
+ // Disable GTest SEH handler
+ testing::DisableExceptionHandlerInScope disable_exception_handler;
+
+ // Although this is executing in the child process of the death test,
+ // if it's not true we'll still get an error rather than the crash
+ // being expected.
+ ASSERT_TRUE(exc->IsOutOfProcess());
+ int *i = NULL;
+ printf("%d\n", (*i)++);
+}
+
+TEST_F(ExceptionHandlerDeathTest, OutOfProcTest) {
+ // We can take advantage of a detail of google test here to save some
+ // complexity in testing: when you do a death test, it actually forks.
+ // So we can make the main test harness the crash generation server,
+ // and call ASSERT_DEATH on a NULL dereference, it to expecting test
+ // the out of process scenario, since it's happening in a different
+ // process! This is different from the above because, above, we pass
+ // a NULL pipe name, and we also don't start a crash generation server.
+
+ ASSERT_TRUE(DoesPathExist(temp_path_));
+ std::wstring dump_path(temp_path_);
+ google_breakpad::CrashGenerationServer server(
+ kPipeName, NULL, NULL, NULL, &clientDumpCallback, NULL, NULL, NULL, NULL,
+ NULL, true, &dump_path);
+
+ // This HAS to be EXPECT_, because when this test case is executed in the
+ // child process, the server registration will fail due to the named pipe
+ // being the same.
+ EXPECT_TRUE(server.Start());
+ gDumpCallbackCalled = false;
+ ASSERT_DEATH(this->DoCrashAccessViolation(OUT_OF_PROC_BEST_EFFORT), "");
+ EXPECT_TRUE(gDumpCallbackCalled);
+}
+
+TEST_F(ExceptionHandlerDeathTest, OutOfProcGuaranteedTest) {
+ // This is similar to the previous test (OutOfProcTest). The only difference
+ // is that in this test, the crash generation client is created and registered
+ // with the crash generation server outside of the ExceptionHandler
+ // constructor which allows breakpad users to opt out of the default
+ // in-process dump generation when the registration with the crash generation
+ // server fails.
+
+ ASSERT_TRUE(DoesPathExist(temp_path_));
+ std::wstring dump_path(temp_path_);
+ google_breakpad::CrashGenerationServer server(
+ kPipeName, NULL, NULL, NULL, &clientDumpCallback, NULL, NULL, NULL, NULL,
+ NULL, true, &dump_path);
+
+ // This HAS to be EXPECT_, because when this test case is executed in the
+ // child process, the server registration will fail due to the named pipe
+ // being the same.
+ EXPECT_TRUE(server.Start());
+ gDumpCallbackCalled = false;
+ ASSERT_DEATH(this->DoCrashAccessViolation(OUT_OF_PROC_GUARANTEED), "");
+ EXPECT_TRUE(gDumpCallbackCalled);
+}
+
+TEST_F(ExceptionHandlerDeathTest, InvalidParameterTest) {
+ using google_breakpad::ExceptionHandler;
+
+ ASSERT_TRUE(DoesPathExist(temp_path_));
+ ExceptionHandler handler(temp_path_, NULL, NULL, NULL,
+ ExceptionHandler::HANDLER_INVALID_PARAMETER);
+
+ // Disable the message box for assertions
+ _CrtSetReportMode(_CRT_ASSERT, 0);
+
+ // Call with a bad argument. The invalid parameter will be swallowed
+ // and a dump will be generated, the process will exit(0).
+ ASSERT_EXIT(printf(NULL), ::testing::ExitedWithCode(0), "");
+}
+
+
+struct PureVirtualCallBase {
+ PureVirtualCallBase() {
+ // We have to reinterpret so the linker doesn't get confused because the
+ // method isn't defined.
+ reinterpret_cast<PureVirtualCallBase*>(this)->PureFunction();
+ }
+ virtual ~PureVirtualCallBase() {}
+ virtual void PureFunction() const = 0;
+};
+struct PureVirtualCall : public PureVirtualCallBase {
+ PureVirtualCall() { PureFunction(); }
+ virtual void PureFunction() const {}
+};
+
+void ExceptionHandlerDeathTest::DoCrashPureVirtualCall() {
+ PureVirtualCall instance;
+}
+
+TEST_F(ExceptionHandlerDeathTest, PureVirtualCallTest) {
+ using google_breakpad::ExceptionHandler;
+
+ ASSERT_TRUE(DoesPathExist(temp_path_));
+ ExceptionHandler handler(temp_path_, NULL, NULL, NULL,
+ 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 size_t extension_length = sizeof(extension) / sizeof(extension[0]) - 1;
+ const size_t 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;
+}
+
+#ifndef ADDRESS_SANITIZER
+
+TEST_F(ExceptionHandlerDeathTest, InstructionPointerMemory) {
+ ASSERT_TRUE(DoesPathExist(temp_path_));
+ scoped_ptr<google_breakpad::ExceptionHandler> exc(
+ new google_breakpad::ExceptionHandler(
+ temp_path_,
+ NULL,
+ NULL,
+ NULL,
+ google_breakpad::ExceptionHandler::HANDLER_ALL));
+
+ // Disable GTest SEH handler
+ testing::DisableExceptionHandlerInScope disable_exception_handler;
+
+ // Get some executable memory.
+ const uint32_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 at least 128 bytes on either
+ // side of 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);
+
+ uint64_t instruction_pointer;
+ ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer));
+
+ MinidumpMemoryRegion* region =
+ memory_list->GetMemoryRegionForAddress(instruction_pointer);
+ ASSERT_TRUE(region);
+
+ EXPECT_LE(kMemorySize, region->GetSize());
+ const uint8_t* bytes = region->GetMemory();
+ ASSERT_TRUE(bytes);
+
+ uint64_t ip_offset = instruction_pointer - region->GetBase();
+ EXPECT_GE(region->GetSize() - kOffset, ip_offset);
+ EXPECT_LE(kOffset, ip_offset);
+
+ uint8_t prefix_bytes[kOffset];
+ uint8_t suffix_bytes[kMemorySize - kOffset - sizeof(instructions)];
+ memset(prefix_bytes, 0, sizeof(prefix_bytes));
+ memset(suffix_bytes, 0, sizeof(suffix_bytes));
+ EXPECT_EQ(0, memcmp(bytes + ip_offset - kOffset, prefix_bytes,
+ sizeof(prefix_bytes)));
+ EXPECT_EQ(0, memcmp(bytes + ip_offset, instructions, sizeof(instructions)));
+ EXPECT_EQ(0, memcmp(bytes + ip_offset + sizeof(instructions), suffix_bytes,
+ sizeof(suffix_bytes)));
+ }
+
+ DeleteFileW(minidump_filename_wide.c_str());
+}
+
+TEST_F(ExceptionHandlerDeathTest, InstructionPointerMemoryMinBound) {
+ ASSERT_TRUE(DoesPathExist(temp_path_));
+ scoped_ptr<google_breakpad::ExceptionHandler> exc(
+ new google_breakpad::ExceptionHandler(
+ temp_path_,
+ NULL,
+ NULL,
+ NULL,
+ google_breakpad::ExceptionHandler::HANDLER_ALL));
+
+ // Disable GTest SEH handler
+ testing::DisableExceptionHandlerInScope disable_exception_handler;
+
+ SYSTEM_INFO sSysInfo; // Useful information about the system
+ GetSystemInfo(&sSysInfo); // Initialize the structure.
+
+ const uint32_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);
+
+ uint64_t instruction_pointer;
+ ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer));
+
+ MinidumpMemoryRegion* region =
+ memory_list->GetMemoryRegionForAddress(instruction_pointer);
+ ASSERT_TRUE(region);
+
+ EXPECT_EQ(kMemorySize / 2, region->GetSize());
+ const uint8_t* bytes = region->GetMemory();
+ ASSERT_TRUE(bytes);
+
+ uint8_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_));
+ scoped_ptr<google_breakpad::ExceptionHandler> exc(
+ new google_breakpad::ExceptionHandler(
+ temp_path_,
+ NULL,
+ NULL,
+ NULL,
+ google_breakpad::ExceptionHandler::HANDLER_ALL));
+
+ // Disable GTest SEH handler
+ testing::DisableExceptionHandlerInScope disable_exception_handler;
+
+ 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);
+
+ uint64_t instruction_pointer;
+ ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer));
+
+ MinidumpMemoryRegion* region =
+ memory_list->GetMemoryRegionForAddress(instruction_pointer);
+ ASSERT_TRUE(region);
+
+ const size_t kPrefixSize = 128; // bytes
+ EXPECT_EQ(kPrefixSize + sizeof(instructions), region->GetSize());
+ const uint8_t* bytes = region->GetMemory();
+ ASSERT_TRUE(bytes);
+
+ uint8_t prefix_bytes[kPrefixSize];
+ memset(prefix_bytes, 0, sizeof(prefix_bytes));
+ EXPECT_EQ(0, memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)));
+ EXPECT_EQ(0, memcmp(bytes + kPrefixSize,
+ instructions, sizeof(instructions)));
+ }
+
+ DeleteFileW(minidump_filename_wide.c_str());
+}
+
+#endif // !ADDRESS_SANITIZER
+
+} // namespace