1// Copyright (c) 2010, Google Inc. 2// All rights reserved. 3// 4// Redistribution and use in source and binary forms, with or without 5// modification, are permitted provided that the following conditions are 6// met: 7// 8// * Redistributions of source code must retain the above copyright 9// notice, this list of conditions and the following disclaimer. 10// * Redistributions in binary form must reproduce the above 11// copyright notice, this list of conditions and the following disclaimer 12// in the documentation and/or other materials provided with the 13// distribution. 14// * Neither the name of Google Inc. nor the names of its 15// contributors may be used to endorse or promote products derived from 16// this software without specific prior written permission. 17// 18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29// 30// crash_generation_server_test.cc 31// Unit tests for CrashGenerationServer 32 33#include <dirent.h> 34#include <glob.h> 35#include <stdint.h> 36#include <sys/wait.h> 37#include <unistd.h> 38 39#include <string> 40 41#include "breakpad_googletest_includes.h" 42#include "client/mac/crash_generation/client_info.h" 43#include "client/mac/crash_generation/crash_generation_client.h" 44#include "client/mac/crash_generation/crash_generation_server.h" 45#include "client/mac/handler/exception_handler.h" 46#include "client/mac/tests/spawn_child_process.h" 47#include "common/tests/auto_tempdir.h" 48#include "google_breakpad/processor/minidump.h" 49 50namespace google_breakpad { 51// This acts as the log sink for INFO logging from the processor 52// logging code. The logging output confuses XCode and makes it think 53// there are unit test failures. testlogging.h handles the overriding. 54std::ostringstream info_log; 55} 56 57namespace { 58using std::string; 59using google_breakpad::AutoTempDir; 60using google_breakpad::ClientInfo; 61using google_breakpad::CrashGenerationClient; 62using google_breakpad::CrashGenerationServer; 63using google_breakpad::ExceptionHandler; 64using google_breakpad::Minidump; 65using google_breakpad::MinidumpContext; 66using google_breakpad::MinidumpException; 67using google_breakpad::MinidumpModule; 68using google_breakpad::MinidumpModuleList; 69using google_breakpad::MinidumpSystemInfo; 70using google_breakpad::MinidumpThread; 71using google_breakpad::MinidumpThreadList; 72using testing::Test; 73using namespace google_breakpad_test; 74 75class CrashGenerationServerTest : public Test { 76public: 77 // The port name to receive messages on 78 char mach_port_name[128]; 79 // Filename of the last dump that was generated 80 string last_dump_name; 81 // PID of the child process 82 pid_t child_pid; 83 // A temp dir 84 AutoTempDir temp_dir; 85 // Counter just to ensure that we don't hit the same port again 86 static int i; 87 bool filter_callback_called; 88 89 void SetUp() { 90 sprintf(mach_port_name, 91 "com.google.breakpad.ServerTest.%d.%d", getpid(), 92 CrashGenerationServerTest::i++); 93 child_pid = (pid_t)-1; 94 filter_callback_called = false; 95 } 96}; 97int CrashGenerationServerTest::i = 0; 98 99// Test that starting and stopping a server works 100TEST_F(CrashGenerationServerTest, testStartStopServer) { 101 CrashGenerationServer server(mach_port_name, 102 NULL, // filter callback 103 NULL, // filter context 104 NULL, // dump callback 105 NULL, // dump context 106 NULL, // exit callback 107 NULL, // exit context 108 false, // generate dumps 109 ""); // dump path 110 ASSERT_TRUE(server.Start()); 111 ASSERT_TRUE(server.Stop()); 112} 113 114// Test that requesting a dump via CrashGenerationClient works 115// Test without actually dumping 116TEST_F(CrashGenerationServerTest, testRequestDumpNoDump) { 117 CrashGenerationServer server(mach_port_name, 118 NULL, // filter callback 119 NULL, // filter context 120 NULL, // dump callback 121 NULL, // dump context 122 NULL, // exit callback 123 NULL, // exit context 124 false, // don't generate dumps 125 temp_dir.path()); // dump path 126 ASSERT_TRUE(server.Start()); 127 128 pid_t pid = fork(); 129 ASSERT_NE(-1, pid); 130 if (pid == 0) { 131 CrashGenerationClient client(mach_port_name); 132 bool result = client.RequestDump(); 133 exit(result ? 0 : 1); 134 } 135 136 int ret; 137 ASSERT_EQ(pid, waitpid(pid, &ret, 0)); 138 EXPECT_TRUE(WIFEXITED(ret)); 139 EXPECT_EQ(0, WEXITSTATUS(ret)); 140 EXPECT_TRUE(server.Stop()); 141 // check that no minidump was written 142 string pattern = temp_dir.path() + "/*"; 143 glob_t dirContents; 144 ret = glob(pattern.c_str(), GLOB_NOSORT, NULL, &dirContents); 145 EXPECT_EQ(GLOB_NOMATCH, ret); 146 if (ret != GLOB_NOMATCH) 147 globfree(&dirContents); 148} 149 150void dumpCallback(void *context, const ClientInfo &client_info, 151 const std::string &file_path) { 152 if (context) { 153 CrashGenerationServerTest* self = 154 reinterpret_cast<CrashGenerationServerTest*>(context); 155 if (!file_path.empty()) 156 self->last_dump_name = file_path; 157 self->child_pid = client_info.pid(); 158 } 159} 160 161void *RequestDump(void *context) { 162 CrashGenerationClient client((const char*)context); 163 bool result = client.RequestDump(); 164 return (void*)(result ? 0 : 1); 165} 166 167// Test that actually writing a minidump works 168TEST_F(CrashGenerationServerTest, testRequestDump) { 169 CrashGenerationServer server(mach_port_name, 170 NULL, // filter callback 171 NULL, // filter context 172 dumpCallback, // dump callback 173 this, // dump context 174 NULL, // exit callback 175 NULL, // exit context 176 true, // generate dumps 177 temp_dir.path()); // dump path 178 ASSERT_TRUE(server.Start()); 179 180 pid_t pid = fork(); 181 ASSERT_NE(-1, pid); 182 if (pid == 0) { 183 // Have to spawn off a separate thread to request the dump, 184 // because MinidumpGenerator assumes the handler thread is not 185 // the only thread 186 pthread_t thread; 187 if (pthread_create(&thread, NULL, RequestDump, (void*)mach_port_name) != 0) 188 exit(1); 189 void* result; 190 pthread_join(thread, &result); 191 exit(reinterpret_cast<intptr_t>(result)); 192 } 193 194 int ret; 195 ASSERT_EQ(pid, waitpid(pid, &ret, 0)); 196 EXPECT_TRUE(WIFEXITED(ret)); 197 EXPECT_EQ(0, WEXITSTATUS(ret)); 198 EXPECT_TRUE(server.Stop()); 199 // check that minidump was written 200 ASSERT_FALSE(last_dump_name.empty()); 201 struct stat st; 202 EXPECT_EQ(0, stat(last_dump_name.c_str(), &st)); 203 EXPECT_LT(0, st.st_size); 204 // check client's PID 205 ASSERT_EQ(pid, child_pid); 206} 207 208static void Crasher() { 209 int *a = (int*)0x42; 210 211 fprintf(stdout, "Going to crash...\n"); 212 fprintf(stdout, "A = %d", *a); 213} 214 215// Test that crashing a child process with an OOP ExceptionHandler installed 216// results in a minidump being written by the CrashGenerationServer in 217// the parent. 218TEST_F(CrashGenerationServerTest, testChildProcessCrash) { 219 CrashGenerationServer server(mach_port_name, 220 NULL, // filter callback 221 NULL, // filter context 222 dumpCallback, // dump callback 223 this, // dump context 224 NULL, // exit callback 225 NULL, // exit context 226 true, // generate dumps 227 temp_dir.path()); // dump path 228 ASSERT_TRUE(server.Start()); 229 230 pid_t pid = fork(); 231 ASSERT_NE(-1, pid); 232 if (pid == 0) { 233 // Instantiate an OOP exception handler. 234 ExceptionHandler eh("", NULL, NULL, NULL, true, mach_port_name); 235 Crasher(); 236 // not reached 237 exit(0); 238 } 239 240 int ret; 241 ASSERT_EQ(pid, waitpid(pid, &ret, 0)); 242 EXPECT_FALSE(WIFEXITED(ret)); 243 EXPECT_TRUE(server.Stop()); 244 // check that minidump was written 245 ASSERT_FALSE(last_dump_name.empty()); 246 struct stat st; 247 EXPECT_EQ(0, stat(last_dump_name.c_str(), &st)); 248 EXPECT_LT(0, st.st_size); 249 250 // Read the minidump, sanity check some data. 251 Minidump minidump(last_dump_name.c_str()); 252 ASSERT_TRUE(minidump.Read()); 253 254 MinidumpSystemInfo* system_info = minidump.GetSystemInfo(); 255 ASSERT_TRUE(system_info); 256 const MDRawSystemInfo* raw_info = system_info->system_info(); 257 ASSERT_TRUE(raw_info); 258 EXPECT_EQ(kNativeArchitecture, raw_info->processor_architecture); 259 260 MinidumpThreadList* thread_list = minidump.GetThreadList(); 261 ASSERT_TRUE(thread_list); 262 ASSERT_EQ((unsigned int)1, thread_list->thread_count()); 263 264 MinidumpThread* main_thread = thread_list->GetThreadAtIndex(0); 265 ASSERT_TRUE(main_thread); 266 MinidumpContext* context = main_thread->GetContext(); 267 ASSERT_TRUE(context); 268 EXPECT_EQ(kNativeContext, context->GetContextCPU()); 269 270 MinidumpModuleList* module_list = minidump.GetModuleList(); 271 ASSERT_TRUE(module_list); 272 const MinidumpModule* main_module = module_list->GetMainModule(); 273 ASSERT_TRUE(main_module); 274 EXPECT_EQ(GetExecutablePath(), main_module->code_file()); 275} 276 277#if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6) && \ 278 (defined(__x86_64__) || defined(__i386__)) 279// Test that crashing a child process of a different architecture 280// produces a valid minidump. 281TEST_F(CrashGenerationServerTest, testChildProcessCrashCrossArchitecture) { 282 CrashGenerationServer server(mach_port_name, 283 NULL, // filter callback 284 NULL, // filter context 285 dumpCallback, // dump callback 286 this, // dump context 287 NULL, // exit callback 288 NULL, // exit context 289 true, // generate dumps 290 temp_dir.path()); // dump path 291 ASSERT_TRUE(server.Start()); 292 293 // Spawn a child process 294 string helper_path = GetHelperPath(); 295 const char* argv[] = { 296 helper_path.c_str(), 297 "crash", 298 mach_port_name, 299 NULL 300 }; 301 pid_t pid = spawn_child_process(argv); 302 ASSERT_NE(-1, pid); 303 304 int ret; 305 ASSERT_EQ(pid, waitpid(pid, &ret, 0)); 306 EXPECT_FALSE(WIFEXITED(ret)); 307 EXPECT_TRUE(server.Stop()); 308 // check that minidump was written 309 ASSERT_FALSE(last_dump_name.empty()); 310 struct stat st; 311 EXPECT_EQ(0, stat(last_dump_name.c_str(), &st)); 312 EXPECT_LT(0, st.st_size); 313 314const MDCPUArchitecture kExpectedArchitecture = 315#if defined(__x86_64__) 316 MD_CPU_ARCHITECTURE_X86 317#elif defined(__i386__) 318 MD_CPU_ARCHITECTURE_AMD64 319#endif 320 ; 321const uint32_t kExpectedContext = 322#if defined(__i386__) 323 MD_CONTEXT_AMD64 324#elif defined(__x86_64__) 325 MD_CONTEXT_X86 326#endif 327 ; 328 329 // Read the minidump, sanity check some data. 330 Minidump minidump(last_dump_name.c_str()); 331 ASSERT_TRUE(minidump.Read()); 332 333 MinidumpSystemInfo* system_info = minidump.GetSystemInfo(); 334 ASSERT_TRUE(system_info); 335 const MDRawSystemInfo* raw_info = system_info->system_info(); 336 ASSERT_TRUE(raw_info); 337 EXPECT_EQ(kExpectedArchitecture, raw_info->processor_architecture); 338 339 MinidumpThreadList* thread_list = minidump.GetThreadList(); 340 ASSERT_TRUE(thread_list); 341 ASSERT_EQ((unsigned int)1, thread_list->thread_count()); 342 343 MinidumpThread* main_thread = thread_list->GetThreadAtIndex(0); 344 ASSERT_TRUE(main_thread); 345 MinidumpContext* context = main_thread->GetContext(); 346 ASSERT_TRUE(context); 347 EXPECT_EQ(kExpectedContext, context->GetContextCPU()); 348 349 MinidumpModuleList* module_list = minidump.GetModuleList(); 350 ASSERT_TRUE(module_list); 351 const MinidumpModule* main_module = module_list->GetMainModule(); 352 ASSERT_TRUE(main_module); 353 EXPECT_EQ(helper_path, main_module->code_file()); 354} 355#endif 356 357bool filter_callback(void* context) { 358 CrashGenerationServerTest* self = 359 reinterpret_cast<CrashGenerationServerTest*>(context); 360 self->filter_callback_called = true; 361 // veto dump generation 362 return false; 363} 364 365// Test that a filter callback can veto minidump writing. 366TEST_F(CrashGenerationServerTest, testFilter) { 367 CrashGenerationServer server(mach_port_name, 368 filter_callback, // filter callback 369 this, // filter context 370 dumpCallback, // dump callback 371 this, // dump context 372 NULL, // exit callback 373 NULL, // exit context 374 true, // generate dumps 375 temp_dir.path()); // dump path 376 ASSERT_TRUE(server.Start()); 377 378 pid_t pid = fork(); 379 ASSERT_NE(-1, pid); 380 if (pid == 0) { 381 // Instantiate an OOP exception handler. 382 ExceptionHandler eh("", NULL, NULL, NULL, true, mach_port_name); 383 Crasher(); 384 // not reached 385 exit(0); 386 } 387 388 int ret; 389 ASSERT_EQ(pid, waitpid(pid, &ret, 0)); 390 EXPECT_FALSE(WIFEXITED(ret)); 391 EXPECT_TRUE(server.Stop()); 392 393 // check that no minidump was written 394 EXPECT_TRUE(last_dump_name.empty()); 395 EXPECT_TRUE(filter_callback_called); 396} 397 398} // namespace 399