| // 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. |
| |
| // minidump_generator_test.cc: Unit tests for google_breakpad::MinidumpGenerator |
| |
| #include <AvailabilityMacros.h> |
| #ifndef MAC_OS_X_VERSION_10_6 |
| #define MAC_OS_X_VERSION_10_6 1060 |
| #endif |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include <string> |
| #include <vector> |
| |
| #include "breakpad_googletest_includes.h" |
| #include "client/mac/handler/minidump_generator.h" |
| #include "client/mac/tests/spawn_child_process.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 std::vector; |
| using google_breakpad::AutoTempDir; |
| using google_breakpad::MinidumpGenerator; |
| 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::MinidumpModule; |
| using google_breakpad::MinidumpModuleList; |
| using google_breakpad::MinidumpSystemInfo; |
| using google_breakpad::MinidumpThread; |
| using google_breakpad::MinidumpThreadList; |
| using google_breakpad::ReceivePort; |
| using testing::Test; |
| using namespace google_breakpad_test; |
| |
| class MinidumpGeneratorTest : public Test { |
| public: |
| AutoTempDir tempDir; |
| }; |
| |
| static void* Junk(void* data) { |
| bool* wait = reinterpret_cast<bool*>(data); |
| while (!*wait) { |
| usleep(10000); |
| } |
| return NULL; |
| } |
| |
| TEST_F(MinidumpGeneratorTest, InProcess) { |
| MinidumpGenerator generator; |
| string dump_filename = |
| MinidumpGenerator::UniqueNameInDirectory(tempDir.path(), NULL); |
| |
| // Run an extra thread since MinidumpGenerator assumes there |
| // are 2 or more threads. |
| pthread_t junk_thread; |
| bool quit = false; |
| ASSERT_EQ(0, pthread_create(&junk_thread, NULL, Junk, &quit)); |
| |
| ASSERT_TRUE(generator.Write(dump_filename.c_str())); |
| // Ensure that minidump file exists and is > 0 bytes. |
| struct stat st; |
| ASSERT_EQ(0, stat(dump_filename.c_str(), &st)); |
| ASSERT_LT(0, st.st_size); |
| |
| // join the background thread |
| quit = true; |
| pthread_join(junk_thread, NULL); |
| |
| // Read the minidump, sanity check some data. |
| Minidump minidump(dump_filename.c_str()); |
| ASSERT_TRUE(minidump.Read()); |
| |
| MinidumpSystemInfo* system_info = minidump.GetSystemInfo(); |
| ASSERT_TRUE(system_info); |
| const MDRawSystemInfo* raw_info = system_info->system_info(); |
| ASSERT_TRUE(raw_info); |
| EXPECT_EQ(kNativeArchitecture, raw_info->processor_architecture); |
| |
| MinidumpThreadList* thread_list = minidump.GetThreadList(); |
| ASSERT_TRUE(thread_list); |
| ASSERT_EQ((unsigned int)1, thread_list->thread_count()); |
| |
| MinidumpThread* main_thread = thread_list->GetThreadAtIndex(0); |
| ASSERT_TRUE(main_thread); |
| MinidumpContext* context = main_thread->GetContext(); |
| ASSERT_TRUE(context); |
| EXPECT_EQ(kNativeContext, context->GetContextCPU()); |
| |
| MinidumpModuleList* module_list = minidump.GetModuleList(); |
| ASSERT_TRUE(module_list); |
| const MinidumpModule* main_module = module_list->GetMainModule(); |
| ASSERT_TRUE(main_module); |
| EXPECT_EQ(GetExecutablePath(), main_module->code_file()); |
| } |
| |
| TEST_F(MinidumpGeneratorTest, OutOfProcess) { |
| const int kTimeoutMs = 2000; |
| // Create a mach port to receive the child task on. |
| char machPortName[128]; |
| sprintf(machPortName, "MinidumpGeneratorTest.OutOfProcess.%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 port. |
| MachSendMessage child_message(0); |
| child_message.AddDescriptor(mach_task_self()); |
| |
| MachPortSender child_sender(machPortName); |
| if (child_sender.SendMessage(child_message, kTimeoutMs) != KERN_SUCCESS) { |
| fprintf(stderr, "Error sending message from child process!\n"); |
| 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 port. |
| MachReceiveMessage child_message; |
| ASSERT_EQ(KERN_SUCCESS, |
| parent_recv_port.WaitForMessage(&child_message, kTimeoutMs)); |
| mach_port_t child_task = child_message.GetTranslatedPort(0); |
| ASSERT_NE((mach_port_t)MACH_PORT_NULL, child_task); |
| |
| // Write a minidump of the child process. |
| MinidumpGenerator generator(child_task, MACH_PORT_NULL); |
| string dump_filename = |
| MinidumpGenerator::UniqueNameInDirectory(tempDir.path(), NULL); |
| ASSERT_TRUE(generator.Write(dump_filename.c_str())); |
| |
| // Ensure that minidump file exists and is > 0 bytes. |
| struct stat st; |
| ASSERT_EQ(0, stat(dump_filename.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)); |
| |
| // Read the minidump, sanity check some data. |
| Minidump minidump(dump_filename.c_str()); |
| ASSERT_TRUE(minidump.Read()); |
| |
| MinidumpSystemInfo* system_info = minidump.GetSystemInfo(); |
| ASSERT_TRUE(system_info); |
| const MDRawSystemInfo* raw_info = system_info->system_info(); |
| ASSERT_TRUE(raw_info); |
| EXPECT_EQ(kNativeArchitecture, raw_info->processor_architecture); |
| |
| MinidumpThreadList* thread_list = minidump.GetThreadList(); |
| ASSERT_TRUE(thread_list); |
| ASSERT_EQ((unsigned int)1, thread_list->thread_count()); |
| |
| MinidumpThread* main_thread = thread_list->GetThreadAtIndex(0); |
| ASSERT_TRUE(main_thread); |
| MinidumpContext* context = main_thread->GetContext(); |
| ASSERT_TRUE(context); |
| EXPECT_EQ(kNativeContext, context->GetContextCPU()); |
| |
| MinidumpModuleList* module_list = minidump.GetModuleList(); |
| ASSERT_TRUE(module_list); |
| const MinidumpModule* main_module = module_list->GetMainModule(); |
| ASSERT_TRUE(main_module); |
| EXPECT_EQ(GetExecutablePath(), main_module->code_file()); |
| } |
| |
| // This test fails on 10.5, but I don't have easy access to a 10.5 machine, |
| // so it's simpler to just limit it to 10.6 for now. |
| #if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6) && \ |
| (defined(__x86_64__) || defined(__i386__)) |
| |
| TEST_F(MinidumpGeneratorTest, CrossArchitectureDump) { |
| const int kTimeoutMs = 5000; |
| // Create a mach port to receive the child task on. |
| char machPortName[128]; |
| sprintf(machPortName, |
| "MinidumpGeneratorTest.CrossArchitectureDump.%d", getpid()); |
| |
| ReceivePort parent_recv_port(machPortName); |
| |
| // Spawn a child process to dump. |
| string helper_path = GetHelperPath(); |
| const char* argv[] = { |
| helper_path.c_str(), |
| machPortName, |
| NULL |
| }; |
| pid_t pid = spawn_child_process(argv); |
| ASSERT_NE(-1, pid); |
| |
| // Read the child's task port. |
| MachReceiveMessage child_message; |
| ASSERT_EQ(KERN_SUCCESS, |
| parent_recv_port.WaitForMessage(&child_message, kTimeoutMs)); |
| mach_port_t child_task = child_message.GetTranslatedPort(0); |
| ASSERT_NE((mach_port_t)MACH_PORT_NULL, child_task); |
| |
| // Write a minidump of the child process. |
| MinidumpGenerator generator(child_task, MACH_PORT_NULL); |
| string dump_filename = |
| MinidumpGenerator::UniqueNameInDirectory(tempDir.path(), NULL); |
| ASSERT_TRUE(generator.Write(dump_filename.c_str())); |
| |
| // Ensure that minidump file exists and is > 0 bytes. |
| struct stat st; |
| ASSERT_EQ(0, stat(dump_filename.c_str(), &st)); |
| ASSERT_LT(0, st.st_size); |
| |
| // Kill child process. |
| kill(pid, SIGKILL); |
| |
| int ret; |
| ASSERT_EQ(pid, waitpid(pid, &ret, 0)); |
| |
| const MDCPUArchitecture kExpectedArchitecture = |
| #if defined(__x86_64__) |
| MD_CPU_ARCHITECTURE_X86 |
| #elif defined(__i386__) |
| MD_CPU_ARCHITECTURE_AMD64 |
| #endif |
| ; |
| const uint32_t kExpectedContext = |
| #if defined(__i386__) |
| MD_CONTEXT_AMD64 |
| #elif defined(__x86_64__) |
| MD_CONTEXT_X86 |
| #endif |
| ; |
| |
| // Read the minidump, sanity check some data. |
| Minidump minidump(dump_filename.c_str()); |
| ASSERT_TRUE(minidump.Read()); |
| |
| MinidumpSystemInfo* system_info = minidump.GetSystemInfo(); |
| ASSERT_TRUE(system_info); |
| const MDRawSystemInfo* raw_info = system_info->system_info(); |
| ASSERT_TRUE(raw_info); |
| EXPECT_EQ(kExpectedArchitecture, raw_info->processor_architecture); |
| |
| MinidumpThreadList* thread_list = minidump.GetThreadList(); |
| ASSERT_TRUE(thread_list); |
| ASSERT_EQ((unsigned int)1, thread_list->thread_count()); |
| |
| MinidumpThread* main_thread = thread_list->GetThreadAtIndex(0); |
| ASSERT_TRUE(main_thread); |
| MinidumpContext* context = main_thread->GetContext(); |
| ASSERT_TRUE(context); |
| EXPECT_EQ(kExpectedContext, context->GetContextCPU()); |
| |
| MinidumpModuleList* module_list = minidump.GetModuleList(); |
| ASSERT_TRUE(module_list); |
| const MinidumpModule* main_module = module_list->GetMainModule(); |
| ASSERT_TRUE(main_module); |
| EXPECT_EQ(helper_path, main_module->code_file()); |
| } |
| #endif // 10.6 && (x86-64 || i386) |
| |
| } |