// 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 #include #include #include #include #include #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(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(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(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