| // 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. |
| // |
| // crash_generation_server_test.cc |
| // Unit tests for CrashGenerationServer |
| |
| #include <dirent.h> |
| #include <glob.h> |
| #include <stdint.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| |
| #include <string> |
| |
| #include "breakpad_googletest_includes.h" |
| #include "client/mac/crash_generation/client_info.h" |
| #include "client/mac/crash_generation/crash_generation_client.h" |
| #include "client/mac/crash_generation/crash_generation_server.h" |
| #include "client/mac/handler/exception_handler.h" |
| #include "client/mac/tests/spawn_child_process.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::ClientInfo; |
| using google_breakpad::CrashGenerationClient; |
| using google_breakpad::CrashGenerationServer; |
| using google_breakpad::ExceptionHandler; |
| 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 testing::Test; |
| using namespace google_breakpad_test; |
| |
| class CrashGenerationServerTest : public Test { |
| public: |
| // The port name to receive messages on |
| char mach_port_name[128]; |
| // Filename of the last dump that was generated |
| string last_dump_name; |
| // PID of the child process |
| pid_t child_pid; |
| // A temp dir |
| AutoTempDir temp_dir; |
| // Counter just to ensure that we don't hit the same port again |
| static int i; |
| bool filter_callback_called; |
| |
| void SetUp() { |
| sprintf(mach_port_name, |
| "com.google.breakpad.ServerTest.%d.%d", getpid(), |
| CrashGenerationServerTest::i++); |
| child_pid = (pid_t)-1; |
| filter_callback_called = false; |
| } |
| }; |
| int CrashGenerationServerTest::i = 0; |
| |
| // Test that starting and stopping a server works |
| TEST_F(CrashGenerationServerTest, testStartStopServer) { |
| CrashGenerationServer server(mach_port_name, |
| NULL, // filter callback |
| NULL, // filter context |
| NULL, // dump callback |
| NULL, // dump context |
| NULL, // exit callback |
| NULL, // exit context |
| false, // generate dumps |
| ""); // dump path |
| ASSERT_TRUE(server.Start()); |
| ASSERT_TRUE(server.Stop()); |
| } |
| |
| // Test that requesting a dump via CrashGenerationClient works |
| // Test without actually dumping |
| TEST_F(CrashGenerationServerTest, testRequestDumpNoDump) { |
| CrashGenerationServer server(mach_port_name, |
| NULL, // filter callback |
| NULL, // filter context |
| NULL, // dump callback |
| NULL, // dump context |
| NULL, // exit callback |
| NULL, // exit context |
| false, // don't generate dumps |
| temp_dir.path()); // dump path |
| ASSERT_TRUE(server.Start()); |
| |
| pid_t pid = fork(); |
| ASSERT_NE(-1, pid); |
| if (pid == 0) { |
| CrashGenerationClient client(mach_port_name); |
| bool result = client.RequestDump(); |
| exit(result ? 0 : 1); |
| } |
| |
| int ret; |
| ASSERT_EQ(pid, waitpid(pid, &ret, 0)); |
| EXPECT_TRUE(WIFEXITED(ret)); |
| EXPECT_EQ(0, WEXITSTATUS(ret)); |
| EXPECT_TRUE(server.Stop()); |
| // check that no minidump was written |
| string pattern = temp_dir.path() + "/*"; |
| glob_t dirContents; |
| ret = glob(pattern.c_str(), GLOB_NOSORT, NULL, &dirContents); |
| EXPECT_EQ(GLOB_NOMATCH, ret); |
| if (ret != GLOB_NOMATCH) |
| globfree(&dirContents); |
| } |
| |
| void dumpCallback(void* context, const ClientInfo& client_info, |
| const std::string& file_path) { |
| if (context) { |
| CrashGenerationServerTest* self = |
| reinterpret_cast<CrashGenerationServerTest*>(context); |
| if (!file_path.empty()) |
| self->last_dump_name = file_path; |
| self->child_pid = client_info.pid(); |
| } |
| } |
| |
| void* RequestDump(void* context) { |
| CrashGenerationClient client((const char*)context); |
| bool result = client.RequestDump(); |
| return (void*)(result ? 0 : 1); |
| } |
| |
| // Test that actually writing a minidump works |
| TEST_F(CrashGenerationServerTest, testRequestDump) { |
| CrashGenerationServer server(mach_port_name, |
| NULL, // filter callback |
| NULL, // filter context |
| dumpCallback, // dump callback |
| this, // dump context |
| NULL, // exit callback |
| NULL, // exit context |
| true, // generate dumps |
| temp_dir.path()); // dump path |
| ASSERT_TRUE(server.Start()); |
| |
| pid_t pid = fork(); |
| ASSERT_NE(-1, pid); |
| if (pid == 0) { |
| // Have to spawn off a separate thread to request the dump, |
| // because MinidumpGenerator assumes the handler thread is not |
| // the only thread |
| pthread_t thread; |
| if (pthread_create(&thread, NULL, RequestDump, (void*)mach_port_name) != 0) |
| exit(1); |
| void* result; |
| pthread_join(thread, &result); |
| exit(reinterpret_cast<intptr_t>(result)); |
| } |
| |
| int ret; |
| ASSERT_EQ(pid, waitpid(pid, &ret, 0)); |
| EXPECT_TRUE(WIFEXITED(ret)); |
| EXPECT_EQ(0, WEXITSTATUS(ret)); |
| EXPECT_TRUE(server.Stop()); |
| // check that minidump was written |
| ASSERT_FALSE(last_dump_name.empty()); |
| struct stat st; |
| EXPECT_EQ(0, stat(last_dump_name.c_str(), &st)); |
| EXPECT_LT(0, st.st_size); |
| // check client's PID |
| ASSERT_EQ(pid, child_pid); |
| } |
| |
| static void Crasher() { |
| int* a = (int*)0x42; |
| |
| fprintf(stdout, "Going to crash...\n"); |
| fprintf(stdout, "A = %d", *a); |
| } |
| |
| // Test that crashing a child process with an OOP ExceptionHandler installed |
| // results in a minidump being written by the CrashGenerationServer in |
| // the parent. |
| TEST_F(CrashGenerationServerTest, testChildProcessCrash) { |
| CrashGenerationServer server(mach_port_name, |
| NULL, // filter callback |
| NULL, // filter context |
| dumpCallback, // dump callback |
| this, // dump context |
| NULL, // exit callback |
| NULL, // exit context |
| true, // generate dumps |
| temp_dir.path()); // dump path |
| ASSERT_TRUE(server.Start()); |
| |
| pid_t pid = fork(); |
| ASSERT_NE(-1, pid); |
| if (pid == 0) { |
| // Instantiate an OOP exception handler. |
| ExceptionHandler eh("", NULL, NULL, NULL, true, mach_port_name); |
| Crasher(); |
| // not reached |
| exit(0); |
| } |
| |
| int ret; |
| ASSERT_EQ(pid, waitpid(pid, &ret, 0)); |
| EXPECT_FALSE(WIFEXITED(ret)); |
| EXPECT_TRUE(server.Stop()); |
| // check that minidump was written |
| ASSERT_FALSE(last_dump_name.empty()); |
| struct stat st; |
| EXPECT_EQ(0, stat(last_dump_name.c_str(), &st)); |
| EXPECT_LT(0, st.st_size); |
| |
| // Read the minidump, sanity check some data. |
| Minidump minidump(last_dump_name.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()); |
| } |
| |
| #if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6) && \ |
| (defined(__x86_64__) || defined(__i386__)) |
| // Test that crashing a child process of a different architecture |
| // produces a valid minidump. |
| TEST_F(CrashGenerationServerTest, testChildProcessCrashCrossArchitecture) { |
| CrashGenerationServer server(mach_port_name, |
| NULL, // filter callback |
| NULL, // filter context |
| dumpCallback, // dump callback |
| this, // dump context |
| NULL, // exit callback |
| NULL, // exit context |
| true, // generate dumps |
| temp_dir.path()); // dump path |
| ASSERT_TRUE(server.Start()); |
| |
| // Spawn a child process |
| string helper_path = GetHelperPath(); |
| const char* argv[] = { |
| helper_path.c_str(), |
| "crash", |
| mach_port_name, |
| NULL |
| }; |
| pid_t pid = spawn_child_process(argv); |
| ASSERT_NE(-1, pid); |
| |
| int ret; |
| ASSERT_EQ(pid, waitpid(pid, &ret, 0)); |
| EXPECT_FALSE(WIFEXITED(ret)); |
| EXPECT_TRUE(server.Stop()); |
| // check that minidump was written |
| ASSERT_FALSE(last_dump_name.empty()); |
| struct stat st; |
| EXPECT_EQ(0, stat(last_dump_name.c_str(), &st)); |
| EXPECT_LT(0, st.st_size); |
| |
| 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(last_dump_name.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 |
| |
| bool filter_callback(void* context) { |
| CrashGenerationServerTest* self = |
| reinterpret_cast<CrashGenerationServerTest*>(context); |
| self->filter_callback_called = true; |
| // veto dump generation |
| return false; |
| } |
| |
| // Test that a filter callback can veto minidump writing. |
| TEST_F(CrashGenerationServerTest, testFilter) { |
| CrashGenerationServer server(mach_port_name, |
| filter_callback, // filter callback |
| this, // filter context |
| dumpCallback, // dump callback |
| this, // dump context |
| NULL, // exit callback |
| NULL, // exit context |
| true, // generate dumps |
| temp_dir.path()); // dump path |
| ASSERT_TRUE(server.Start()); |
| |
| pid_t pid = fork(); |
| ASSERT_NE(-1, pid); |
| if (pid == 0) { |
| // Instantiate an OOP exception handler. |
| ExceptionHandler eh("", NULL, NULL, NULL, true, mach_port_name); |
| Crasher(); |
| // not reached |
| exit(0); |
| } |
| |
| int ret; |
| ASSERT_EQ(pid, waitpid(pid, &ret, 0)); |
| EXPECT_FALSE(WIFEXITED(ret)); |
| EXPECT_TRUE(server.Stop()); |
| |
| // check that no minidump was written |
| EXPECT_TRUE(last_dump_name.empty()); |
| EXPECT_TRUE(filter_callback_called); |
| } |
| |
| } // namespace |