| // Copyright (c) 2011 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 <fcntl.h> |
| #include <sys/poll.h> |
| #include <sys/stat.h> |
| #include <sys/syscall.h> |
| #include <sys/types.h> |
| #include <ucontext.h> |
| #include <unistd.h> |
| |
| #include <string> |
| |
| #include "breakpad_googletest_includes.h" |
| #include "client/linux/handler/exception_handler.h" |
| #include "client/linux/minidump_writer/linux_dumper.h" |
| #include "client/linux/minidump_writer/minidump_writer.h" |
| #include "client/linux/minidump_writer/minidump_writer_unittest_utils.h" |
| #include "common/linux/eintr_wrapper.h" |
| #include "common/linux/file_id.h" |
| #include "common/linux/ignore_ret.h" |
| #include "common/linux/safe_readlink.h" |
| #include "common/scoped_ptr.h" |
| #include "common/tests/auto_tempdir.h" |
| #include "common/tests/file_utils.h" |
| #include "common/using_std_string.h" |
| #include "google_breakpad/processor/minidump.h" |
| |
| using namespace google_breakpad; |
| |
| namespace { |
| |
| typedef testing::Test MinidumpWriterTest; |
| |
| const char kMDWriterUnitTestFileName[] = "/minidump-writer-unittest"; |
| |
| TEST(MinidumpWriterTest, SetupWithPath) { |
| int fds[2]; |
| ASSERT_NE(-1, pipe(fds)); |
| |
| const pid_t child = fork(); |
| if (child == 0) { |
| close(fds[1]); |
| char b; |
| IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b)))); |
| close(fds[0]); |
| syscall(__NR_exit_group); |
| } |
| close(fds[0]); |
| |
| ExceptionHandler::CrashContext context; |
| memset(&context, 0, sizeof(context)); |
| |
| AutoTempDir temp_dir; |
| string templ = temp_dir.path() + kMDWriterUnitTestFileName; |
| // Set a non-zero tid to avoid tripping asserts. |
| context.tid = child; |
| ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context))); |
| struct stat st; |
| ASSERT_EQ(0, stat(templ.c_str(), &st)); |
| ASSERT_GT(st.st_size, 0); |
| |
| close(fds[1]); |
| IGNORE_EINTR(waitpid(child, nullptr, 0)); |
| } |
| |
| TEST(MinidumpWriterTest, SetupWithFD) { |
| int fds[2]; |
| ASSERT_NE(-1, pipe(fds)); |
| |
| const pid_t child = fork(); |
| if (child == 0) { |
| close(fds[1]); |
| char b; |
| HANDLE_EINTR(read(fds[0], &b, sizeof(b))); |
| close(fds[0]); |
| syscall(__NR_exit_group); |
| } |
| close(fds[0]); |
| |
| ExceptionHandler::CrashContext context; |
| memset(&context, 0, sizeof(context)); |
| |
| AutoTempDir temp_dir; |
| string templ = temp_dir.path() + kMDWriterUnitTestFileName; |
| int fd = open(templ.c_str(), O_CREAT | O_WRONLY, S_IRWXU); |
| // Set a non-zero tid to avoid tripping asserts. |
| context.tid = child; |
| ASSERT_TRUE(WriteMinidump(fd, child, &context, sizeof(context))); |
| struct stat st; |
| ASSERT_EQ(0, stat(templ.c_str(), &st)); |
| ASSERT_GT(st.st_size, 0); |
| |
| close(fds[1]); |
| IGNORE_EINTR(waitpid(child, nullptr, 0)); |
| } |
| |
| // Test that mapping info can be specified when writing a minidump, |
| // and that it ends up in the module list of the minidump. |
| TEST(MinidumpWriterTest, MappingInfo) { |
| int fds[2]; |
| ASSERT_NE(-1, pipe(fds)); |
| |
| // These are defined here so the parent can use them to check the |
| // data from the minidump afterwards. |
| const uint32_t memory_size = sysconf(_SC_PAGESIZE); |
| const char* kMemoryName = "a fake module"; |
| const uint8_t kModuleGUID[sizeof(MDGUID)] = { |
| 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, |
| 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF |
| }; |
| const string module_identifier = "33221100554477668899AABBCCDDEEFF0"; |
| |
| // Get some memory. |
| char* memory = |
| reinterpret_cast<char*>(mmap(NULL, |
| memory_size, |
| PROT_READ | PROT_WRITE, |
| MAP_PRIVATE | MAP_ANON, |
| -1, |
| 0)); |
| const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory); |
| ASSERT_TRUE(memory); |
| |
| const pid_t child = fork(); |
| if (child == 0) { |
| close(fds[1]); |
| char b; |
| IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b)))); |
| close(fds[0]); |
| syscall(__NR_exit_group); |
| } |
| close(fds[0]); |
| |
| ExceptionHandler::CrashContext context; |
| memset(&context, 0, sizeof(context)); |
| ASSERT_EQ(0, getcontext(&context.context)); |
| context.tid = child; |
| |
| AutoTempDir temp_dir; |
| string templ = temp_dir.path() + kMDWriterUnitTestFileName; |
| |
| // Add information about the mapped memory. |
| MappingInfo info; |
| info.start_addr = kMemoryAddress; |
| info.size = memory_size; |
| info.offset = 0; |
| info.exec = false; |
| strcpy(info.name, kMemoryName); |
| |
| MappingList mappings; |
| AppMemoryList memory_list; |
| MappingEntry mapping; |
| mapping.first = info; |
| memcpy(mapping.second, kModuleGUID, sizeof(MDGUID)); |
| mappings.push_back(mapping); |
| ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context), |
| mappings, memory_list, false, 0, false)); |
| |
| // Read the minidump. Load the module list, and ensure that |
| // the mmap'ed |memory| is listed with the given module name |
| // and debug ID. |
| Minidump minidump(templ); |
| ASSERT_TRUE(minidump.Read()); |
| |
| MinidumpModuleList* module_list = minidump.GetModuleList(); |
| ASSERT_TRUE(module_list); |
| const MinidumpModule* module = |
| module_list->GetModuleForAddress(kMemoryAddress); |
| ASSERT_TRUE(module); |
| |
| EXPECT_EQ(kMemoryAddress, module->base_address()); |
| EXPECT_EQ(memory_size, module->size()); |
| EXPECT_EQ(kMemoryName, module->code_file()); |
| EXPECT_EQ(module_identifier, module->debug_identifier()); |
| |
| uint32_t len; |
| // These streams are expected to be there |
| EXPECT_TRUE(minidump.SeekToStreamType(MD_THREAD_LIST_STREAM, &len)); |
| EXPECT_TRUE(minidump.SeekToStreamType(MD_MEMORY_LIST_STREAM, &len)); |
| EXPECT_TRUE(minidump.SeekToStreamType(MD_EXCEPTION_STREAM, &len)); |
| EXPECT_TRUE(minidump.SeekToStreamType(MD_SYSTEM_INFO_STREAM, &len)); |
| EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_CPU_INFO, &len)); |
| EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_PROC_STATUS, &len)); |
| EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_CMD_LINE, &len)); |
| EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_ENVIRON, &len)); |
| EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_AUXV, &len)); |
| EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_MAPS, &len)); |
| EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_DSO_DEBUG, &len)); |
| |
| close(fds[1]); |
| IGNORE_EINTR(waitpid(child, nullptr, 0)); |
| } |
| |
| // Test that minidumping is skipped while writing minidumps if principal mapping |
| // is not referenced. |
| TEST(MinidumpWriterTest, MinidumpSkippedIfRequested) { |
| int fds[2]; |
| ASSERT_NE(-1, pipe(fds)); |
| |
| const pid_t child = fork(); |
| if (child == 0) { |
| close(fds[1]); |
| char b; |
| IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b)))); |
| close(fds[0]); |
| syscall(__NR_exit_group); |
| } |
| close(fds[0]); |
| |
| ExceptionHandler::CrashContext context; |
| memset(&context, 0, sizeof(context)); |
| ASSERT_EQ(0, getcontext(&context.context)); |
| context.tid = child; |
| |
| AutoTempDir temp_dir; |
| string templ = temp_dir.path() + kMDWriterUnitTestFileName; |
| |
| // pass an invalid principal mapping address, which will force |
| // WriteMinidump to not write a minidump. |
| ASSERT_FALSE(WriteMinidump(templ.c_str(), child, &context, sizeof(context), |
| true, static_cast<uintptr_t>(0x0102030405060708ull), |
| false)); |
| close(fds[1]); |
| IGNORE_EINTR(waitpid(child, nullptr, 0)); |
| } |
| |
| // Test that minidumping is skipped while writing minidumps if principal mapping |
| // is not referenced. |
| TEST(MinidumpWriterTest, MinidumpStacksSkippedIfRequested) { |
| int fds[2]; |
| ASSERT_NE(-1, pipe(fds)); |
| |
| const pid_t child = fork(); |
| if (child == 0) { |
| close(fds[1]); |
| |
| // Create a thread that does not return, and only references libc (not the |
| // current executable). This thread should not be captured in the minidump. |
| pthread_t thread; |
| pthread_attr_t thread_attributes; |
| pthread_attr_init(&thread_attributes); |
| pthread_attr_setdetachstate(&thread_attributes, PTHREAD_CREATE_DETACHED); |
| sigset_t sigset; |
| sigemptyset(&sigset); |
| pthread_create(&thread, &thread_attributes, |
| reinterpret_cast<void* (*)(void*)>(&sigsuspend), &sigset); |
| |
| char b; |
| IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b)))); |
| close(fds[0]); |
| syscall(__NR_exit_group); |
| } |
| close(fds[0]); |
| |
| ExceptionHandler::CrashContext context; |
| memset(&context, 0, sizeof(context)); |
| ASSERT_EQ(0, getcontext(&context.context)); |
| context.tid = child; |
| |
| AutoTempDir temp_dir; |
| string templ = temp_dir.path() + kMDWriterUnitTestFileName; |
| |
| // Pass an invalid principal mapping address, which will force |
| // WriteMinidump to not dump any thread stacks. |
| ASSERT_TRUE(WriteMinidump( |
| templ.c_str(), child, &context, sizeof(context), true, |
| reinterpret_cast<uintptr_t>(google_breakpad::WriteFile), false)); |
| |
| // Read the minidump. And ensure that thread memory was dumped only for the |
| // main thread. |
| Minidump minidump(templ); |
| ASSERT_TRUE(minidump.Read()); |
| |
| MinidumpThreadList *threads = minidump.GetThreadList(); |
| int threads_with_stacks = 0; |
| for (unsigned int i = 0; i < threads->thread_count(); ++i) { |
| MinidumpThread *thread = threads->GetThreadAtIndex(i); |
| if (thread->GetMemory()) { |
| ++threads_with_stacks; |
| } |
| } |
| ASSERT_EQ(1, threads_with_stacks); |
| close(fds[1]); |
| IGNORE_EINTR(waitpid(child, nullptr, 0)); |
| } |
| |
| // Test that stacks can be sanitized while writing minidumps. |
| TEST(MinidumpWriterTest, StacksAreSanitizedIfRequested) { |
| int fds[2]; |
| ASSERT_NE(-1, pipe(fds)); |
| |
| const pid_t child = fork(); |
| if (child == 0) { |
| close(fds[1]); |
| char b; |
| IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b)))); |
| close(fds[0]); |
| syscall(__NR_exit_group); |
| } |
| close(fds[0]); |
| |
| ExceptionHandler::CrashContext context; |
| memset(&context, 0, sizeof(context)); |
| ASSERT_EQ(0, getcontext(&context.context)); |
| context.tid = child; |
| |
| AutoTempDir temp_dir; |
| string templ = temp_dir.path() + kMDWriterUnitTestFileName; |
| // pass an invalid principal mapping address, which will force |
| // WriteMinidump to not dump any thread stacks. |
| ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context), |
| false, 0, true)); |
| |
| // Read the minidump. And ensure that thread memory contains a defaced value. |
| Minidump minidump(templ); |
| ASSERT_TRUE(minidump.Read()); |
| |
| const uintptr_t defaced = |
| #if defined(__LP64__) |
| 0x0defaced0defaced; |
| #else |
| 0x0defaced; |
| #endif |
| MinidumpThreadList *threads = minidump.GetThreadList(); |
| for (unsigned int i = 0; i < threads->thread_count(); ++i) { |
| MinidumpThread *thread = threads->GetThreadAtIndex(i); |
| MinidumpMemoryRegion *mem = thread->GetMemory(); |
| ASSERT_TRUE(mem != nullptr); |
| uint32_t sz = mem->GetSize(); |
| const uint8_t *data = mem->GetMemory(); |
| ASSERT_TRUE(memmem(data, sz, &defaced, sizeof(defaced)) != nullptr); |
| } |
| close(fds[1]); |
| IGNORE_EINTR(waitpid(child, nullptr, 0)); |
| } |
| |
| // Test that a binary with a longer-than-usual build id note |
| // makes its way all the way through to the minidump unscathed. |
| // The linux_client_unittest is linked with an explicit --build-id |
| // in Makefile.am. |
| TEST(MinidumpWriterTest, BuildIDLong) { |
| int fds[2]; |
| ASSERT_NE(-1, pipe(fds)); |
| |
| const pid_t child = fork(); |
| if (child == 0) { |
| close(fds[1]); |
| char b; |
| IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b)))); |
| close(fds[0]); |
| syscall(__NR_exit_group); |
| } |
| close(fds[0]); |
| |
| ExceptionHandler::CrashContext context; |
| memset(&context, 0, sizeof(context)); |
| ASSERT_EQ(0, getcontext(&context.context)); |
| context.tid = child; |
| |
| AutoTempDir temp_dir; |
| const string dump_path = temp_dir.path() + kMDWriterUnitTestFileName; |
| |
| EXPECT_TRUE(WriteMinidump(dump_path.c_str(), |
| child, &context, sizeof(context))); |
| close(fds[1]); |
| |
| // Read the minidump. Load the module list, and ensure that |
| // the main module has the correct debug id and code id. |
| Minidump minidump(dump_path); |
| ASSERT_TRUE(minidump.Read()); |
| |
| MinidumpModuleList* module_list = minidump.GetModuleList(); |
| ASSERT_TRUE(module_list); |
| const MinidumpModule* module = module_list->GetMainModule(); |
| ASSERT_TRUE(module); |
| const string module_identifier = "030201000504070608090A0B0C0D0E0F0"; |
| // This is passed explicitly to the linker in Makefile.am |
| const string build_id = |
| "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; |
| EXPECT_EQ(module_identifier, module->debug_identifier()); |
| EXPECT_EQ(build_id, module->code_identifier()); |
| |
| IGNORE_EINTR(waitpid(child, nullptr, 0)); |
| } |
| |
| // Test that mapping info can be specified, and that it overrides |
| // existing mappings that are wholly contained within the specified |
| // range. |
| TEST(MinidumpWriterTest, MappingInfoContained) { |
| int fds[2]; |
| ASSERT_NE(-1, pipe(fds)); |
| |
| // These are defined here so the parent can use them to check the |
| // data from the minidump afterwards. |
| const int32_t memory_size = sysconf(_SC_PAGESIZE); |
| const char* kMemoryName = "a fake module"; |
| const uint8_t kModuleGUID[sizeof(MDGUID)] = { |
| 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, |
| 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF |
| }; |
| const string module_identifier = "33221100554477668899AABBCCDDEEFF0"; |
| |
| // mmap a file |
| AutoTempDir temp_dir; |
| string tempfile = temp_dir.path() + "/minidump-writer-unittest-temp"; |
| int fd = open(tempfile.c_str(), O_RDWR | O_CREAT, 0); |
| ASSERT_NE(-1, fd); |
| unlink(tempfile.c_str()); |
| // fill with zeros |
| google_breakpad::scoped_array<char> buffer(new char[memory_size]); |
| memset(buffer.get(), 0, memory_size); |
| ASSERT_EQ(memory_size, write(fd, buffer.get(), memory_size)); |
| lseek(fd, 0, SEEK_SET); |
| |
| char* memory = |
| reinterpret_cast<char*>(mmap(NULL, |
| memory_size, |
| PROT_READ | PROT_WRITE, |
| MAP_PRIVATE, |
| fd, |
| 0)); |
| const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory); |
| ASSERT_TRUE(memory); |
| close(fd); |
| |
| const pid_t child = fork(); |
| if (child == 0) { |
| close(fds[1]); |
| char b; |
| IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b)))); |
| close(fds[0]); |
| syscall(__NR_exit_group); |
| } |
| close(fds[0]); |
| |
| ExceptionHandler::CrashContext context; |
| memset(&context, 0, sizeof(context)); |
| context.tid = 1; |
| |
| string dumpfile = temp_dir.path() + kMDWriterUnitTestFileName; |
| |
| // Add information about the mapped memory. Report it as being larger than |
| // it actually is. |
| MappingInfo info; |
| info.start_addr = kMemoryAddress - memory_size; |
| info.size = memory_size * 3; |
| info.offset = 0; |
| info.exec = false; |
| strcpy(info.name, kMemoryName); |
| |
| MappingList mappings; |
| AppMemoryList memory_list; |
| MappingEntry mapping; |
| mapping.first = info; |
| memcpy(mapping.second, kModuleGUID, sizeof(MDGUID)); |
| mappings.push_back(mapping); |
| ASSERT_TRUE(WriteMinidump(dumpfile.c_str(), child, &context, sizeof(context), |
| mappings, memory_list)); |
| |
| // Read the minidump. Load the module list, and ensure that |
| // the mmap'ed |memory| is listed with the given module name |
| // and debug ID. |
| Minidump minidump(dumpfile); |
| ASSERT_TRUE(minidump.Read()); |
| |
| MinidumpModuleList* module_list = minidump.GetModuleList(); |
| ASSERT_TRUE(module_list); |
| const MinidumpModule* module = |
| module_list->GetModuleForAddress(kMemoryAddress); |
| ASSERT_TRUE(module); |
| |
| EXPECT_EQ(info.start_addr, module->base_address()); |
| EXPECT_EQ(info.size, module->size()); |
| EXPECT_EQ(kMemoryName, module->code_file()); |
| EXPECT_EQ(module_identifier, module->debug_identifier()); |
| |
| close(fds[1]); |
| IGNORE_EINTR(waitpid(child, nullptr, 0)); |
| } |
| |
| TEST(MinidumpWriterTest, DeletedBinary) { |
| const string kNumberOfThreadsArgument = "1"; |
| const string helper_path(GetHelperBinary()); |
| if (helper_path.empty()) { |
| FAIL() << "Couldn't find helper binary"; |
| exit(1); |
| } |
| |
| // Copy binary to a temp file. |
| AutoTempDir temp_dir; |
| string binpath = temp_dir.path() + "/linux-dumper-unittest-helper"; |
| ASSERT_TRUE(CopyFile(helper_path.c_str(), binpath.c_str())) |
| << "Failed to copy " << helper_path << " to " << binpath; |
| ASSERT_EQ(0, chmod(binpath.c_str(), 0755)); |
| |
| int fds[2]; |
| ASSERT_NE(-1, pipe(fds)); |
| |
| pid_t child_pid = fork(); |
| if (child_pid == 0) { |
| // In child process. |
| close(fds[0]); |
| |
| // Pass the pipe fd and the number of threads as arguments. |
| char pipe_fd_string[8]; |
| sprintf(pipe_fd_string, "%d", fds[1]); |
| execl(binpath.c_str(), |
| binpath.c_str(), |
| pipe_fd_string, |
| kNumberOfThreadsArgument.c_str(), |
| NULL); |
| } |
| close(fds[1]); |
| // Wait for the child process to signal that it's ready. |
| struct pollfd pfd; |
| memset(&pfd, 0, sizeof(pfd)); |
| pfd.fd = fds[0]; |
| pfd.events = POLLIN | POLLERR; |
| |
| const int r = HANDLE_EINTR(poll(&pfd, 1, 1000)); |
| ASSERT_EQ(1, r); |
| ASSERT_TRUE(pfd.revents & POLLIN); |
| uint8_t junk; |
| const int nr = HANDLE_EINTR(read(fds[0], &junk, sizeof(junk))); |
| ASSERT_EQ(static_cast<ssize_t>(sizeof(junk)), nr); |
| close(fds[0]); |
| |
| // Child is ready now. |
| // Unlink the test binary. |
| unlink(binpath.c_str()); |
| |
| ExceptionHandler::CrashContext context; |
| memset(&context, 0, sizeof(context)); |
| |
| string templ = temp_dir.path() + kMDWriterUnitTestFileName; |
| // Set a non-zero tid to avoid tripping asserts. |
| context.tid = child_pid; |
| ASSERT_TRUE(WriteMinidump(templ.c_str(), child_pid, &context, |
| sizeof(context))); |
| kill(child_pid, SIGKILL); |
| |
| struct stat st; |
| ASSERT_EQ(0, stat(templ.c_str(), &st)); |
| ASSERT_GT(st.st_size, 0); |
| |
| Minidump minidump(templ); |
| ASSERT_TRUE(minidump.Read()); |
| |
| // Check that the main module filename is correct. |
| MinidumpModuleList* module_list = minidump.GetModuleList(); |
| ASSERT_TRUE(module_list); |
| const MinidumpModule* module = module_list->GetMainModule(); |
| EXPECT_STREQ(binpath.c_str(), module->code_file().c_str()); |
| // Check that the file ID is correct. |
| FileID fileid(helper_path.c_str()); |
| PageAllocator allocator; |
| wasteful_vector<uint8_t> identifier(&allocator, kDefaultBuildIdSize); |
| EXPECT_TRUE(fileid.ElfFileIdentifier(identifier)); |
| string identifier_string = FileID::ConvertIdentifierToUUIDString(identifier); |
| string module_identifier(identifier_string); |
| // Strip out dashes |
| size_t pos; |
| while ((pos = module_identifier.find('-')) != string::npos) { |
| module_identifier.erase(pos, 1); |
| } |
| // And append a zero, because module IDs include an "age" field |
| // which is always zero on Linux. |
| module_identifier += "0"; |
| EXPECT_EQ(module_identifier, module->debug_identifier()); |
| |
| IGNORE_EINTR(waitpid(child_pid, nullptr, 0)); |
| } |
| |
| // Test that an additional memory region can be added to the minidump. |
| TEST(MinidumpWriterTest, AdditionalMemory) { |
| int fds[2]; |
| ASSERT_NE(-1, pipe(fds)); |
| |
| // These are defined here so the parent can use them to check the |
| // data from the minidump afterwards. |
| const uint32_t kMemorySize = sysconf(_SC_PAGESIZE); |
| |
| // Get some heap memory. |
| uint8_t* memory = new uint8_t[kMemorySize]; |
| const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory); |
| ASSERT_TRUE(memory); |
| |
| // Stick some data into the memory so the contents can be verified. |
| for (uint32_t i = 0; i < kMemorySize; ++i) { |
| memory[i] = i % 255; |
| } |
| |
| const pid_t child = fork(); |
| if (child == 0) { |
| close(fds[1]); |
| char b; |
| HANDLE_EINTR(read(fds[0], &b, sizeof(b))); |
| close(fds[0]); |
| syscall(__NR_exit_group); |
| } |
| close(fds[0]); |
| |
| ExceptionHandler::CrashContext context; |
| |
| // This needs a valid context for minidump writing to work, but getting |
| // a useful one from the child is too much work, so just use one from |
| // the parent since the child is just a forked copy anyway. |
| ASSERT_EQ(0, getcontext(&context.context)); |
| context.tid = child; |
| |
| AutoTempDir temp_dir; |
| string templ = temp_dir.path() + kMDWriterUnitTestFileName; |
| unlink(templ.c_str()); |
| |
| MappingList mappings; |
| AppMemoryList memory_list; |
| |
| // Add the memory region to the list of memory to be included. |
| AppMemory app_memory; |
| app_memory.ptr = memory; |
| app_memory.length = kMemorySize; |
| memory_list.push_back(app_memory); |
| ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context), |
| mappings, memory_list)); |
| |
| // Read the minidump. Ensure that the memory region is present |
| Minidump minidump(templ); |
| ASSERT_TRUE(minidump.Read()); |
| |
| MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList(); |
| ASSERT_TRUE(dump_memory_list); |
| const MinidumpMemoryRegion* region = |
| dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress); |
| ASSERT_TRUE(region); |
| |
| EXPECT_EQ(kMemoryAddress, region->GetBase()); |
| EXPECT_EQ(kMemorySize, region->GetSize()); |
| |
| // Verify memory contents. |
| EXPECT_EQ(0, memcmp(region->GetMemory(), memory, kMemorySize)); |
| |
| delete[] memory; |
| close(fds[1]); |
| IGNORE_EINTR(waitpid(child, nullptr, 0)); |
| } |
| |
| // Test that an invalid thread stack pointer still results in a minidump. |
| TEST(MinidumpWriterTest, InvalidStackPointer) { |
| int fds[2]; |
| ASSERT_NE(-1, pipe(fds)); |
| |
| const pid_t child = fork(); |
| if (child == 0) { |
| close(fds[1]); |
| char b; |
| HANDLE_EINTR(read(fds[0], &b, sizeof(b))); |
| close(fds[0]); |
| syscall(__NR_exit_group); |
| } |
| close(fds[0]); |
| |
| ExceptionHandler::CrashContext context; |
| |
| // This needs a valid context for minidump writing to work, but getting |
| // a useful one from the child is too much work, so just use one from |
| // the parent since the child is just a forked copy anyway. |
| ASSERT_EQ(0, getcontext(&context.context)); |
| context.tid = child; |
| |
| // Fake the child's stack pointer for its crashing thread. NOTE: This must |
| // be an invalid memory address for the child process (stack or otherwise). |
| // Try 1MB below the current stack. |
| uintptr_t invalid_stack_pointer = |
| reinterpret_cast<uintptr_t>(&context) - 1024*1024; |
| #if defined(__i386) |
| context.context.uc_mcontext.gregs[REG_ESP] = invalid_stack_pointer; |
| #elif defined(__x86_64) |
| context.context.uc_mcontext.gregs[REG_RSP] = invalid_stack_pointer; |
| #elif defined(__ARM_EABI__) |
| context.context.uc_mcontext.arm_sp = invalid_stack_pointer; |
| #elif defined(__aarch64__) |
| context.context.uc_mcontext.sp = invalid_stack_pointer; |
| #elif defined(__mips__) |
| context.context.uc_mcontext.gregs[MD_CONTEXT_MIPS_REG_SP] = |
| invalid_stack_pointer; |
| #else |
| # error "This code has not been ported to your platform yet." |
| #endif |
| |
| AutoTempDir temp_dir; |
| string templ = temp_dir.path() + kMDWriterUnitTestFileName; |
| // NOTE: In previous versions of Breakpad, WriteMinidump() would fail if |
| // presented with an invalid stack pointer. |
| ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context))); |
| |
| // Read the minidump. Ensure that the memory region is present |
| Minidump minidump(templ); |
| ASSERT_TRUE(minidump.Read()); |
| |
| // TODO(ted.mielczarek,mkrebs): Enable this part of the test once |
| // https://breakpad.appspot.com/413002/ is committed. |
| #if 0 |
| // Make sure there's a thread without a stack. NOTE: It's okay if |
| // GetThreadList() shows the error: "ERROR: MinidumpThread has a memory |
| // region problem". |
| MinidumpThreadList* dump_thread_list = minidump.GetThreadList(); |
| ASSERT_TRUE(dump_thread_list); |
| bool found_empty_stack = false; |
| for (int i = 0; i < dump_thread_list->thread_count(); i++) { |
| MinidumpThread* thread = dump_thread_list->GetThreadAtIndex(i); |
| ASSERT_TRUE(thread->thread() != NULL); |
| // When the stack size is zero bytes, GetMemory() returns NULL. |
| if (thread->GetMemory() == NULL) { |
| found_empty_stack = true; |
| break; |
| } |
| } |
| // NOTE: If you fail this, first make sure that "invalid_stack_pointer" |
| // above is indeed set to an invalid address. |
| ASSERT_TRUE(found_empty_stack); |
| #endif |
| |
| close(fds[1]); |
| IGNORE_EINTR(waitpid(child, nullptr, 0)); |
| } |
| |
| // Test that limiting the size of the minidump works. |
| TEST(MinidumpWriterTest, MinidumpSizeLimit) { |
| static const int kNumberOfThreadsInHelperProgram = 40; |
| |
| char number_of_threads_arg[3]; |
| sprintf(number_of_threads_arg, "%d", kNumberOfThreadsInHelperProgram); |
| |
| string helper_path(GetHelperBinary()); |
| if (helper_path.empty()) { |
| FAIL() << "Couldn't find helper binary"; |
| exit(1); |
| } |
| |
| int fds[2]; |
| ASSERT_NE(-1, pipe(fds)); |
| |
| pid_t child_pid = fork(); |
| if (child_pid == 0) { |
| // In child process. |
| close(fds[0]); |
| |
| // Pass the pipe fd and the number of threads as arguments. |
| char pipe_fd_string[8]; |
| sprintf(pipe_fd_string, "%d", fds[1]); |
| execl(helper_path.c_str(), |
| helper_path.c_str(), |
| pipe_fd_string, |
| number_of_threads_arg, |
| NULL); |
| } |
| close(fds[1]); |
| |
| // Wait for all child threads to indicate that they have started |
| for (int threads = 0; threads < kNumberOfThreadsInHelperProgram; threads++) { |
| struct pollfd pfd; |
| memset(&pfd, 0, sizeof(pfd)); |
| pfd.fd = fds[0]; |
| pfd.events = POLLIN | POLLERR; |
| |
| const int r = HANDLE_EINTR(poll(&pfd, 1, 1000)); |
| ASSERT_EQ(1, r); |
| ASSERT_TRUE(pfd.revents & POLLIN); |
| uint8_t junk; |
| ASSERT_EQ(read(fds[0], &junk, sizeof(junk)), |
| static_cast<ssize_t>(sizeof(junk))); |
| } |
| close(fds[0]); |
| |
| // There is a race here because we may stop a child thread before |
| // it is actually running the busy loop. Empirically this sleep |
| // is sufficient to avoid the race. |
| usleep(100000); |
| |
| // Child and its threads are ready now. |
| |
| |
| off_t normal_file_size; |
| int total_normal_stack_size = 0; |
| AutoTempDir temp_dir; |
| |
| // First, write a minidump with no size limit. |
| { |
| string normal_dump = temp_dir.path() + |
| "/minidump-writer-unittest.dmp"; |
| ASSERT_TRUE(WriteMinidump(normal_dump.c_str(), -1, |
| child_pid, NULL, 0, |
| MappingList(), AppMemoryList())); |
| struct stat st; |
| ASSERT_EQ(0, stat(normal_dump.c_str(), &st)); |
| ASSERT_GT(st.st_size, 0); |
| normal_file_size = st.st_size; |
| |
| Minidump minidump(normal_dump); |
| ASSERT_TRUE(minidump.Read()); |
| MinidumpThreadList* dump_thread_list = minidump.GetThreadList(); |
| ASSERT_TRUE(dump_thread_list); |
| for (unsigned int i = 0; i < dump_thread_list->thread_count(); i++) { |
| MinidumpThread* thread = dump_thread_list->GetThreadAtIndex(i); |
| ASSERT_TRUE(thread->thread() != NULL); |
| // When the stack size is zero bytes, GetMemory() returns NULL. |
| MinidumpMemoryRegion* memory = thread->GetMemory(); |
| ASSERT_TRUE(memory != NULL); |
| total_normal_stack_size += memory->GetSize(); |
| } |
| } |
| |
| // Second, write a minidump with a size limit big enough to not trigger |
| // anything. |
| { |
| // Set size limit arbitrarily 1MB larger than the normal file size -- such |
| // that the limiting code will not kick in. |
| const off_t minidump_size_limit = normal_file_size + 1024*1024; |
| |
| string same_dump = temp_dir.path() + |
| "/minidump-writer-unittest-same.dmp"; |
| ASSERT_TRUE(WriteMinidump(same_dump.c_str(), minidump_size_limit, |
| child_pid, NULL, 0, |
| MappingList(), AppMemoryList())); |
| struct stat st; |
| ASSERT_EQ(0, stat(same_dump.c_str(), &st)); |
| // Make sure limiting wasn't actually triggered. NOTE: If you fail this, |
| // first make sure that "minidump_size_limit" above is indeed set to a |
| // large enough value -- the limit-checking code in minidump_writer.cc |
| // does just a rough estimate. |
| ASSERT_EQ(normal_file_size, st.st_size); |
| } |
| |
| // Third, write a minidump with a size limit small enough to be triggered. |
| { |
| // Set size limit to some arbitrary amount, such that the limiting code |
| // will kick in. The equation used to set this value was determined by |
| // simply reversing the size-limit logic a little bit in order to pick a |
| // size we know will trigger it. The definition of |
| // kLimitAverageThreadStackLength here was copied from class |
| // MinidumpWriter in minidump_writer.cc. |
| static const unsigned kLimitAverageThreadStackLength = 8 * 1024; |
| off_t minidump_size_limit = kNumberOfThreadsInHelperProgram * |
| kLimitAverageThreadStackLength; |
| // If, in reality, each of the threads' stack is *smaller* than |
| // kLimitAverageThreadStackLength, the normal file size could very well be |
| // smaller than the arbitrary limit that was just set. In that case, |
| // either of these numbers should trigger the size-limiting code, but we |
| // might as well pick the smallest. |
| if (normal_file_size < minidump_size_limit) |
| minidump_size_limit = normal_file_size; |
| |
| string limit_dump = temp_dir.path() + |
| "/minidump-writer-unittest-limit.dmp"; |
| ASSERT_TRUE(WriteMinidump(limit_dump.c_str(), minidump_size_limit, |
| child_pid, NULL, 0, |
| MappingList(), AppMemoryList())); |
| struct stat st; |
| ASSERT_EQ(0, stat(limit_dump.c_str(), &st)); |
| ASSERT_GT(st.st_size, 0); |
| // Make sure the file size is at least smaller than the original. If this |
| // fails because it's the same size, then the size-limit logic didn't kick |
| // in like it was supposed to. |
| EXPECT_LT(st.st_size, normal_file_size); |
| |
| Minidump minidump(limit_dump); |
| ASSERT_TRUE(minidump.Read()); |
| MinidumpThreadList* dump_thread_list = minidump.GetThreadList(); |
| ASSERT_TRUE(dump_thread_list); |
| int total_limit_stack_size = 0; |
| for (unsigned int i = 0; i < dump_thread_list->thread_count(); i++) { |
| MinidumpThread* thread = dump_thread_list->GetThreadAtIndex(i); |
| ASSERT_TRUE(thread->thread() != NULL); |
| // When the stack size is zero bytes, GetMemory() returns NULL. |
| MinidumpMemoryRegion* memory = thread->GetMemory(); |
| ASSERT_TRUE(memory != NULL); |
| total_limit_stack_size += memory->GetSize(); |
| } |
| |
| // Make sure stack size shrunk by at least 1KB per extra thread. The |
| // definition of kLimitBaseThreadCount here was copied from class |
| // MinidumpWriter in minidump_writer.cc. |
| // Note: The 1KB is arbitrary, and assumes that the thread stacks are big |
| // enough to shrink by that much. For example, if each thread stack was |
| // originally only 2KB, the current size-limit logic wouldn't actually |
| // shrink them because that's the size to which it tries to shrink. If |
| // you fail this part of the test due to something like that, the test |
| // logic should probably be improved to account for your situation. |
| const unsigned kLimitBaseThreadCount = 20; |
| const unsigned kMinPerExtraThreadStackReduction = 1024; |
| const int min_expected_reduction = (kNumberOfThreadsInHelperProgram - |
| kLimitBaseThreadCount) * kMinPerExtraThreadStackReduction; |
| EXPECT_LT(total_limit_stack_size, |
| total_normal_stack_size - min_expected_reduction); |
| } |
| |
| // Kill the helper program. |
| kill(child_pid, SIGKILL); |
| IGNORE_EINTR(waitpid(child_pid, nullptr, 0)); |
| } |
| |
| } // namespace |