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