| // 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()); |
| } |
| |
| } |