|  | // Copyright (c) 2010, 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. | 
|  |  | 
|  | // exception_handler_test.cc: Unit tests for google_breakpad::ExceptionHandler | 
|  |  | 
|  | #include <pthread.h> | 
|  | #include <sys/mman.h> | 
|  | #include <sys/stat.h> | 
|  | #include <unistd.h> | 
|  |  | 
|  | #include "breakpad_googletest_includes.h" | 
|  | #include "client/mac/handler/exception_handler.h" | 
|  | #include "common/linux/ignore_ret.h" | 
|  | #include "common/mac/MachIPC.h" | 
|  | #include "common/tests/auto_tempdir.h" | 
|  | #include "google_breakpad/processor/minidump.h" | 
|  |  | 
|  | namespace google_breakpad { | 
|  | // This acts as the log sink for INFO logging from the processor | 
|  | // logging code. The logging output confuses XCode and makes it think | 
|  | // there are unit test failures. testlogging.h handles the overriding. | 
|  | std::ostringstream info_log; | 
|  | } | 
|  |  | 
|  | namespace { | 
|  | using std::string; | 
|  | using google_breakpad::AutoTempDir; | 
|  | using google_breakpad::ExceptionHandler; | 
|  | using google_breakpad::MachPortSender; | 
|  | using google_breakpad::MachReceiveMessage; | 
|  | using google_breakpad::MachSendMessage; | 
|  | using google_breakpad::Minidump; | 
|  | using google_breakpad::MinidumpContext; | 
|  | using google_breakpad::MinidumpException; | 
|  | using google_breakpad::MinidumpMemoryList; | 
|  | using google_breakpad::MinidumpMemoryRegion; | 
|  | using google_breakpad::ReceivePort; | 
|  | using testing::Test; | 
|  |  | 
|  | class ExceptionHandlerTest : public Test { | 
|  | public: | 
|  | void InProcessCrash(bool aborting); | 
|  | AutoTempDir tempDir; | 
|  | string lastDumpName; | 
|  | }; | 
|  |  | 
|  | static void Crasher() { | 
|  | int *a = (int*)0x42; | 
|  |  | 
|  | fprintf(stdout, "Going to crash...\n"); | 
|  | fprintf(stdout, "A = %d", *a); | 
|  | } | 
|  |  | 
|  | static void AbortCrasher() { | 
|  | fprintf(stdout, "Going to crash...\n"); | 
|  | abort(); | 
|  | } | 
|  |  | 
|  | static void SoonToCrash(void(*crasher)()) { | 
|  | crasher(); | 
|  | } | 
|  |  | 
|  | static bool MDCallback(const char *dump_dir, const char *file_name, | 
|  | void *context, bool success) { | 
|  | string path(dump_dir); | 
|  | path.append("/"); | 
|  | path.append(file_name); | 
|  | path.append(".dmp"); | 
|  |  | 
|  | int fd = *reinterpret_cast<int*>(context); | 
|  | IGNORE_RET(write(fd, path.c_str(), path.length() + 1)); | 
|  | close(fd); | 
|  | exit(0); | 
|  | // not reached | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void ExceptionHandlerTest::InProcessCrash(bool aborting) { | 
|  | // Give the child process a pipe to report back on. | 
|  | int fds[2]; | 
|  | ASSERT_EQ(0, pipe(fds)); | 
|  | // Fork off a child process so it can crash. | 
|  | pid_t pid = fork(); | 
|  | if (pid == 0) { | 
|  | // In the child process. | 
|  | close(fds[0]); | 
|  | ExceptionHandler eh(tempDir.path(), NULL, MDCallback, &fds[1], true, NULL); | 
|  | // crash | 
|  | SoonToCrash(aborting ? &AbortCrasher : &Crasher); | 
|  | // not reached | 
|  | exit(1); | 
|  | } | 
|  | // In the parent process. | 
|  | ASSERT_NE(-1, pid); | 
|  | // Wait for the background process to return the minidump file. | 
|  | close(fds[1]); | 
|  | char minidump_file[PATH_MAX]; | 
|  | ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file)); | 
|  | ASSERT_NE(0, nbytes); | 
|  |  | 
|  | Minidump minidump(minidump_file); | 
|  | ASSERT_TRUE(minidump.Read()); | 
|  |  | 
|  | MinidumpException* exception = minidump.GetException(); | 
|  | ASSERT_TRUE(exception); | 
|  |  | 
|  | const MDRawExceptionStream* raw_exception = exception->exception(); | 
|  | ASSERT_TRUE(raw_exception); | 
|  |  | 
|  | if (aborting) { | 
|  | EXPECT_EQ(MD_EXCEPTION_MAC_SOFTWARE, | 
|  | raw_exception->exception_record.exception_code); | 
|  | EXPECT_EQ(MD_EXCEPTION_CODE_MAC_ABORT, | 
|  | raw_exception->exception_record.exception_flags); | 
|  | } else { | 
|  | EXPECT_EQ(MD_EXCEPTION_MAC_BAD_ACCESS, | 
|  | raw_exception->exception_record.exception_code); | 
|  | #if defined(__x86_64__) | 
|  | EXPECT_EQ(MD_EXCEPTION_CODE_MAC_INVALID_ADDRESS, | 
|  | raw_exception->exception_record.exception_flags); | 
|  | #elif defined(__i386__) | 
|  | EXPECT_EQ(MD_EXCEPTION_CODE_MAC_PROTECTION_FAILURE, | 
|  | raw_exception->exception_record.exception_flags); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | const MinidumpContext* context = exception->GetContext(); | 
|  | ASSERT_TRUE(context); | 
|  |  | 
|  | uint64_t instruction_pointer; | 
|  | ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer)); | 
|  |  | 
|  | // Ideally would like to sanity check that abort() is on the stack | 
|  | // but that's hard. | 
|  | MinidumpMemoryList* memory_list = minidump.GetMemoryList(); | 
|  | ASSERT_TRUE(memory_list); | 
|  | MinidumpMemoryRegion* region = | 
|  | memory_list->GetMemoryRegionForAddress(instruction_pointer); | 
|  | EXPECT_TRUE(region); | 
|  |  | 
|  | // Child process should have exited with a zero status. | 
|  | int ret; | 
|  | ASSERT_EQ(pid, waitpid(pid, &ret, 0)); | 
|  | EXPECT_NE(0, WIFEXITED(ret)); | 
|  | EXPECT_EQ(0, WEXITSTATUS(ret)); | 
|  | } | 
|  |  | 
|  | TEST_F(ExceptionHandlerTest, InProcess) { | 
|  | InProcessCrash(false); | 
|  | } | 
|  |  | 
|  | TEST_F(ExceptionHandlerTest, InProcessAbort) { | 
|  | InProcessCrash(true); | 
|  | } | 
|  |  | 
|  | static bool DumpNameMDCallback(const char *dump_dir, const char *file_name, | 
|  | void *context, bool success) { | 
|  | ExceptionHandlerTest *self = reinterpret_cast<ExceptionHandlerTest*>(context); | 
|  | if (dump_dir && file_name) { | 
|  | self->lastDumpName = dump_dir; | 
|  | self->lastDumpName += "/"; | 
|  | self->lastDumpName += file_name; | 
|  | self->lastDumpName += ".dmp"; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | TEST_F(ExceptionHandlerTest, WriteMinidump) { | 
|  | ExceptionHandler eh(tempDir.path(), NULL, DumpNameMDCallback, this, true, | 
|  | NULL); | 
|  | ASSERT_TRUE(eh.WriteMinidump()); | 
|  |  | 
|  | // Ensure that minidump file exists and is > 0 bytes. | 
|  | ASSERT_FALSE(lastDumpName.empty()); | 
|  | struct stat st; | 
|  | ASSERT_EQ(0, stat(lastDumpName.c_str(), &st)); | 
|  | ASSERT_LT(0, st.st_size); | 
|  |  | 
|  | // The minidump should not contain an exception stream. | 
|  | Minidump minidump(lastDumpName); | 
|  | ASSERT_TRUE(minidump.Read()); | 
|  |  | 
|  | MinidumpException* exception = minidump.GetException(); | 
|  | EXPECT_FALSE(exception); | 
|  | } | 
|  |  | 
|  | TEST_F(ExceptionHandlerTest, WriteMinidumpWithException) { | 
|  | ExceptionHandler eh(tempDir.path(), NULL, DumpNameMDCallback, this, true, | 
|  | NULL); | 
|  | ASSERT_TRUE(eh.WriteMinidump(true)); | 
|  |  | 
|  | // Ensure that minidump file exists and is > 0 bytes. | 
|  | ASSERT_FALSE(lastDumpName.empty()); | 
|  | struct stat st; | 
|  | ASSERT_EQ(0, stat(lastDumpName.c_str(), &st)); | 
|  | ASSERT_LT(0, st.st_size); | 
|  |  | 
|  | // The minidump should contain an exception stream. | 
|  | Minidump minidump(lastDumpName); | 
|  | ASSERT_TRUE(minidump.Read()); | 
|  |  | 
|  | MinidumpException* exception = minidump.GetException(); | 
|  | ASSERT_TRUE(exception); | 
|  | const MDRawExceptionStream* raw_exception = exception->exception(); | 
|  | ASSERT_TRUE(raw_exception); | 
|  |  | 
|  | EXPECT_EQ(MD_EXCEPTION_MAC_BREAKPOINT, | 
|  | raw_exception->exception_record.exception_code); | 
|  | } | 
|  |  | 
|  | TEST_F(ExceptionHandlerTest, DumpChildProcess) { | 
|  | const int kTimeoutMs = 2000; | 
|  | // Create a mach port to receive the child task on. | 
|  | char machPortName[128]; | 
|  | sprintf(machPortName, "ExceptionHandlerTest.%d", getpid()); | 
|  | ReceivePort parent_recv_port(machPortName); | 
|  |  | 
|  | // Give the child process a pipe to block on. | 
|  | int fds[2]; | 
|  | ASSERT_EQ(0, pipe(fds)); | 
|  |  | 
|  | // Fork off a child process to dump. | 
|  | pid_t pid = fork(); | 
|  | if (pid == 0) { | 
|  | // In the child process | 
|  | close(fds[1]); | 
|  |  | 
|  | // Send parent process the task and thread ports. | 
|  | MachSendMessage child_message(0); | 
|  | child_message.AddDescriptor(mach_task_self()); | 
|  | child_message.AddDescriptor(mach_thread_self()); | 
|  |  | 
|  | MachPortSender child_sender(machPortName); | 
|  | if (child_sender.SendMessage(child_message, kTimeoutMs) != KERN_SUCCESS) | 
|  | exit(1); | 
|  |  | 
|  | // Wait for the parent process. | 
|  | uint8_t data; | 
|  | read(fds[0], &data, 1); | 
|  | exit(0); | 
|  | } | 
|  | // In the parent process. | 
|  | ASSERT_NE(-1, pid); | 
|  | close(fds[0]); | 
|  |  | 
|  | // Read the child's task and thread ports. | 
|  | MachReceiveMessage child_message; | 
|  | ASSERT_EQ(KERN_SUCCESS, | 
|  | parent_recv_port.WaitForMessage(&child_message, kTimeoutMs)); | 
|  | mach_port_t child_task = child_message.GetTranslatedPort(0); | 
|  | mach_port_t child_thread = child_message.GetTranslatedPort(1); | 
|  | ASSERT_NE((mach_port_t)MACH_PORT_NULL, child_task); | 
|  | ASSERT_NE((mach_port_t)MACH_PORT_NULL, child_thread); | 
|  |  | 
|  | // Write a minidump of the child process. | 
|  | bool result = ExceptionHandler::WriteMinidumpForChild(child_task, | 
|  | child_thread, | 
|  | tempDir.path(), | 
|  | DumpNameMDCallback, | 
|  | this); | 
|  | ASSERT_EQ(true, result); | 
|  |  | 
|  | // Ensure that minidump file exists and is > 0 bytes. | 
|  | ASSERT_FALSE(lastDumpName.empty()); | 
|  | struct stat st; | 
|  | ASSERT_EQ(0, stat(lastDumpName.c_str(), &st)); | 
|  | ASSERT_LT(0, st.st_size); | 
|  |  | 
|  | // Unblock child process | 
|  | uint8_t data = 1; | 
|  | IGNORE_RET(write(fds[1], &data, 1)); | 
|  |  | 
|  | // Child process should have exited with a zero status. | 
|  | int ret; | 
|  | ASSERT_EQ(pid, waitpid(pid, &ret, 0)); | 
|  | EXPECT_NE(0, WIFEXITED(ret)); | 
|  | EXPECT_EQ(0, WEXITSTATUS(ret)); | 
|  | } | 
|  |  | 
|  | // Test that memory around the instruction pointer is written | 
|  | // to the dump as a MinidumpMemoryRegion. | 
|  | TEST_F(ExceptionHandlerTest, InstructionPointerMemory) { | 
|  | // Give the child process a pipe to report back on. | 
|  | int fds[2]; | 
|  | ASSERT_EQ(0, pipe(fds)); | 
|  |  | 
|  | // These are defined here so the parent can use them to check the | 
|  | // data from the minidump afterwards. | 
|  | 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 }; | 
|  |  | 
|  | pid_t pid = fork(); | 
|  | if (pid == 0) { | 
|  | close(fds[0]); | 
|  | ExceptionHandler eh(tempDir.path(), NULL, MDCallback, &fds[1], true, NULL); | 
|  | // Get some executable memory. | 
|  | char* memory = | 
|  | reinterpret_cast<char*>(mmap(NULL, | 
|  | kMemorySize, | 
|  | PROT_READ | PROT_WRITE | PROT_EXEC, | 
|  | MAP_PRIVATE | MAP_ANON, | 
|  | -1, | 
|  | 0)); | 
|  | if (!memory) | 
|  | exit(0); | 
|  |  | 
|  | // 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); | 
|  | memory_function(); | 
|  | // not reached | 
|  | exit(1); | 
|  | } | 
|  | // In the parent process. | 
|  | ASSERT_NE(-1, pid); | 
|  | close(fds[1]); | 
|  |  | 
|  | // Wait for the background process to return the minidump file. | 
|  | close(fds[1]); | 
|  | char minidump_file[PATH_MAX]; | 
|  | ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file)); | 
|  | ASSERT_NE(0, nbytes); | 
|  | // Ensure that minidump file exists and is > 0 bytes. | 
|  | struct stat st; | 
|  | ASSERT_EQ(0, stat(minidump_file, &st)); | 
|  | ASSERT_LT(0, st.st_size); | 
|  |  | 
|  | // Child process should have exited with a zero status. | 
|  | int ret; | 
|  | ASSERT_EQ(pid, waitpid(pid, &ret, 0)); | 
|  | EXPECT_NE(0, WIFEXITED(ret)); | 
|  | EXPECT_EQ(0, WEXITSTATUS(ret)); | 
|  |  | 
|  | // 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_file); | 
|  | ASSERT_TRUE(minidump.Read()); | 
|  |  | 
|  | MinidumpException* exception = minidump.GetException(); | 
|  | MinidumpMemoryList* memory_list = minidump.GetMemoryList(); | 
|  | ASSERT_TRUE(exception); | 
|  | ASSERT_TRUE(memory_list); | 
|  | ASSERT_NE((unsigned int)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); | 
|  | EXPECT_TRUE(region); | 
|  |  | 
|  | EXPECT_EQ(kMemorySize, region->GetSize()); | 
|  | const uint8_t* bytes = region->GetMemory(); | 
|  | ASSERT_TRUE(bytes); | 
|  |  | 
|  | 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_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); | 
|  | } | 
|  |  | 
|  | // Test that the memory region around the instruction pointer is | 
|  | // bounded correctly on the low end. | 
|  | TEST_F(ExceptionHandlerTest, InstructionPointerMemoryMinBound) { | 
|  | // Give the child process a pipe to report back on. | 
|  | int fds[2]; | 
|  | ASSERT_EQ(0, pipe(fds)); | 
|  |  | 
|  | // These are defined here so the parent can use them to check the | 
|  | // data from the minidump afterwards. | 
|  | const uint32_t kMemorySize = 256;  // bytes | 
|  | const int kOffset = 0; | 
|  | // This crashes with SIGILL on x86/x86-64/arm. | 
|  | const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff }; | 
|  |  | 
|  | pid_t pid = fork(); | 
|  | if (pid == 0) { | 
|  | close(fds[0]); | 
|  | ExceptionHandler eh(tempDir.path(), NULL, MDCallback, &fds[1], true, NULL); | 
|  | // Get some executable memory. | 
|  | char* memory = | 
|  | reinterpret_cast<char*>(mmap(NULL, | 
|  | kMemorySize, | 
|  | PROT_READ | PROT_WRITE | PROT_EXEC, | 
|  | MAP_PRIVATE | MAP_ANON, | 
|  | -1, | 
|  | 0)); | 
|  | if (!memory) | 
|  | exit(0); | 
|  |  | 
|  | // Write some instructions that will crash. Put them at the start | 
|  | // of the block of memory, to ensure that the memory bounding | 
|  | // works properly. | 
|  | 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); | 
|  | memory_function(); | 
|  | // not reached | 
|  | exit(1); | 
|  | } | 
|  | // In the parent process. | 
|  | ASSERT_NE(-1, pid); | 
|  | close(fds[1]); | 
|  |  | 
|  | // Wait for the background process to return the minidump file. | 
|  | close(fds[1]); | 
|  | char minidump_file[PATH_MAX]; | 
|  | ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file)); | 
|  | ASSERT_NE(0, nbytes); | 
|  | // Ensure that minidump file exists and is > 0 bytes. | 
|  | struct stat st; | 
|  | ASSERT_EQ(0, stat(minidump_file, &st)); | 
|  | ASSERT_LT(0, st.st_size); | 
|  |  | 
|  | // Child process should have exited with a zero status. | 
|  | int ret; | 
|  | ASSERT_EQ(pid, waitpid(pid, &ret, 0)); | 
|  | EXPECT_NE(0, WIFEXITED(ret)); | 
|  | EXPECT_EQ(0, WEXITSTATUS(ret)); | 
|  |  | 
|  | // 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_file); | 
|  | ASSERT_TRUE(minidump.Read()); | 
|  |  | 
|  | MinidumpException* exception = minidump.GetException(); | 
|  | MinidumpMemoryList* memory_list = minidump.GetMemoryList(); | 
|  | ASSERT_TRUE(exception); | 
|  | ASSERT_TRUE(memory_list); | 
|  | ASSERT_NE((unsigned int)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); | 
|  | EXPECT_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); | 
|  | } | 
|  |  | 
|  | // Test that the memory region around the instruction pointer is | 
|  | // bounded correctly on the high end. | 
|  | TEST_F(ExceptionHandlerTest, InstructionPointerMemoryMaxBound) { | 
|  | // Give the child process a pipe to report back on. | 
|  | int fds[2]; | 
|  | ASSERT_EQ(0, pipe(fds)); | 
|  |  | 
|  | // These are defined here so the parent can use them to check the | 
|  | // data from the minidump afterwards. | 
|  | // Use 4k here because the OS will hand out a single page even | 
|  | // if a smaller size is requested, and this test wants to | 
|  | // test the upper bound of the memory range. | 
|  | const uint32_t kMemorySize = 4096;  // bytes | 
|  | // This crashes with SIGILL on x86/x86-64/arm. | 
|  | const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff }; | 
|  | const int kOffset = kMemorySize - sizeof(instructions); | 
|  |  | 
|  | pid_t pid = fork(); | 
|  | if (pid == 0) { | 
|  | close(fds[0]); | 
|  | ExceptionHandler eh(tempDir.path(), NULL, MDCallback, &fds[1], true, NULL); | 
|  | // Get some executable memory. | 
|  | char* memory = | 
|  | reinterpret_cast<char*>(mmap(NULL, | 
|  | kMemorySize, | 
|  | PROT_READ | PROT_WRITE | PROT_EXEC, | 
|  | MAP_PRIVATE | MAP_ANON, | 
|  | -1, | 
|  | 0)); | 
|  | if (!memory) | 
|  | exit(0); | 
|  |  | 
|  | // Write some instructions that will crash. Put them at the start | 
|  | // of the block of memory, to ensure that the memory bounding | 
|  | // works properly. | 
|  | 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); | 
|  | memory_function(); | 
|  | // not reached | 
|  | exit(1); | 
|  | } | 
|  | // In the parent process. | 
|  | ASSERT_NE(-1, pid); | 
|  | close(fds[1]); | 
|  |  | 
|  | // Wait for the background process to return the minidump file. | 
|  | close(fds[1]); | 
|  | char minidump_file[PATH_MAX]; | 
|  | ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file)); | 
|  | ASSERT_NE(0, nbytes); | 
|  | // Ensure that minidump file exists and is > 0 bytes. | 
|  | struct stat st; | 
|  | ASSERT_EQ(0, stat(minidump_file, &st)); | 
|  | ASSERT_LT(0, st.st_size); | 
|  |  | 
|  | // Child process should have exited with a zero status. | 
|  | int ret; | 
|  | ASSERT_EQ(pid, waitpid(pid, &ret, 0)); | 
|  | EXPECT_NE(0, WIFEXITED(ret)); | 
|  | EXPECT_EQ(0, WEXITSTATUS(ret)); | 
|  |  | 
|  | // 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_file); | 
|  | ASSERT_TRUE(minidump.Read()); | 
|  |  | 
|  | MinidumpException* exception = minidump.GetException(); | 
|  | MinidumpMemoryList* memory_list = minidump.GetMemoryList(); | 
|  | ASSERT_TRUE(exception); | 
|  | ASSERT_TRUE(memory_list); | 
|  | ASSERT_NE((unsigned int)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); | 
|  | EXPECT_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_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0); | 
|  | EXPECT_TRUE(memcmp(bytes + kPrefixSize, | 
|  | instructions, sizeof(instructions)) == 0); | 
|  | } | 
|  |  | 
|  | // Ensure that an extra memory block doesn't get added when the | 
|  | // instruction pointer is not in mapped memory. | 
|  | TEST_F(ExceptionHandlerTest, InstructionPointerMemoryNullPointer) { | 
|  | // Give the child process a pipe to report back on. | 
|  | int fds[2]; | 
|  | ASSERT_EQ(0, pipe(fds)); | 
|  |  | 
|  | pid_t pid = fork(); | 
|  | if (pid == 0) { | 
|  | close(fds[0]); | 
|  | ExceptionHandler eh(tempDir.path(), NULL, MDCallback, &fds[1], true, NULL); | 
|  | // Try calling a NULL pointer. | 
|  | typedef void (*void_function)(void); | 
|  | // Volatile markings are needed to keep Clang from generating invalid | 
|  | // opcodes.  See http://crbug.com/498354 for details. | 
|  | volatile void_function memory_function = | 
|  | reinterpret_cast<void_function>(NULL); | 
|  | memory_function(); | 
|  | // not reached | 
|  | exit(1); | 
|  | } | 
|  | // In the parent process. | 
|  | ASSERT_NE(-1, pid); | 
|  | close(fds[1]); | 
|  |  | 
|  | // Wait for the background process to return the minidump file. | 
|  | close(fds[1]); | 
|  | char minidump_file[PATH_MAX]; | 
|  | ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file)); | 
|  | ASSERT_NE(0, nbytes); | 
|  | // Ensure that minidump file exists and is > 0 bytes. | 
|  | struct stat st; | 
|  | ASSERT_EQ(0, stat(minidump_file, &st)); | 
|  | ASSERT_LT(0, st.st_size); | 
|  |  | 
|  | // Child process should have exited with a zero status. | 
|  | int ret; | 
|  | ASSERT_EQ(pid, waitpid(pid, &ret, 0)); | 
|  | EXPECT_NE(0, WIFEXITED(ret)); | 
|  | EXPECT_EQ(0, WEXITSTATUS(ret)); | 
|  |  | 
|  | // Read the minidump. Locate the exception record and the | 
|  | // memory list, and then ensure that there is only one memory region | 
|  | // in the memory list (the thread memory from the single thread). | 
|  | Minidump minidump(minidump_file); | 
|  | ASSERT_TRUE(minidump.Read()); | 
|  |  | 
|  | MinidumpException* exception = minidump.GetException(); | 
|  | MinidumpMemoryList* memory_list = minidump.GetMemoryList(); | 
|  | ASSERT_TRUE(exception); | 
|  | ASSERT_TRUE(memory_list); | 
|  | ASSERT_EQ((unsigned int)1, memory_list->region_count()); | 
|  | } | 
|  |  | 
|  | static void *Junk(void *) { | 
|  | sleep(1000000); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | // Test that the memory list gets written correctly when multiple | 
|  | // threads are running. | 
|  | TEST_F(ExceptionHandlerTest, MemoryListMultipleThreads) { | 
|  | // Give the child process a pipe to report back on. | 
|  | int fds[2]; | 
|  | ASSERT_EQ(0, pipe(fds)); | 
|  |  | 
|  | pid_t pid = fork(); | 
|  | if (pid == 0) { | 
|  | close(fds[0]); | 
|  | ExceptionHandler eh(tempDir.path(), NULL, MDCallback, &fds[1], true, NULL); | 
|  |  | 
|  | // Run an extra thread so >2 memory regions will be written. | 
|  | pthread_t junk_thread; | 
|  | if (pthread_create(&junk_thread, NULL, Junk, NULL) == 0) | 
|  | pthread_detach(junk_thread); | 
|  |  | 
|  | // Just crash. | 
|  | Crasher(); | 
|  |  | 
|  | // not reached | 
|  | exit(1); | 
|  | } | 
|  | // In the parent process. | 
|  | ASSERT_NE(-1, pid); | 
|  | close(fds[1]); | 
|  |  | 
|  | // Wait for the background process to return the minidump file. | 
|  | close(fds[1]); | 
|  | char minidump_file[PATH_MAX]; | 
|  | ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file)); | 
|  | ASSERT_NE(0, nbytes); | 
|  | // Ensure that minidump file exists and is > 0 bytes. | 
|  | struct stat st; | 
|  | ASSERT_EQ(0, stat(minidump_file, &st)); | 
|  | ASSERT_LT(0, st.st_size); | 
|  |  | 
|  | // Child process should have exited with a zero status. | 
|  | int ret; | 
|  | ASSERT_EQ(pid, waitpid(pid, &ret, 0)); | 
|  | EXPECT_NE(0, WIFEXITED(ret)); | 
|  | EXPECT_EQ(0, WEXITSTATUS(ret)); | 
|  |  | 
|  | // Read the minidump, and verify that the memory list can be read. | 
|  | Minidump minidump(minidump_file); | 
|  | ASSERT_TRUE(minidump.Read()); | 
|  |  | 
|  | MinidumpMemoryList* memory_list = minidump.GetMemoryList(); | 
|  | ASSERT_TRUE(memory_list); | 
|  | // Verify that there are three memory regions: | 
|  | // one per thread, and one for the instruction pointer memory. | 
|  | ASSERT_EQ((unsigned int)3, memory_list->region_count()); | 
|  | } | 
|  |  | 
|  | } |