1// Copyright (c) 2009, 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// linux_ptrace_dumper_unittest.cc:
31// Unit tests for google_breakpad::LinuxPtraceDumper.
32//
33// This file was renamed from linux_dumper_unittest.cc and modified due
34// to LinuxDumper being splitted into two classes.
35
36#include <errno.h>
37#include <fcntl.h>
38#include <limits.h>
39#include <unistd.h>
40#include <signal.h>
41#include <stdint.h>
42#include <string.h>
43#include <sys/mman.h>
44#include <sys/prctl.h>
45#include <sys/poll.h>
46#include <sys/stat.h>
47#include <sys/types.h>
48
49#include <string>
50
51#include "breakpad_googletest_includes.h"
52#include "client/linux/minidump_writer/linux_ptrace_dumper.h"
53#include "client/linux/minidump_writer/minidump_writer_unittest_utils.h"
54#include "common/linux/eintr_wrapper.h"
55#include "common/linux/file_id.h"
56#include "common/linux/ignore_ret.h"
57#include "common/linux/safe_readlink.h"
58#include "common/memory.h"
59#include "common/using_std_string.h"
60
61#ifndef PR_SET_PTRACER
62#define PR_SET_PTRACER 0x59616d61
63#endif
64
65using namespace google_breakpad;
66
67namespace {
68
69typedef testing::Test LinuxPtraceDumperTest;
70
71/* Fixture for running tests in a child process. */
72class LinuxPtraceDumperChildTest : public testing::Test {
73 protected:
74  virtual void SetUp() {
75    child_pid_ = fork();
76#ifndef __ANDROID__
77    prctl(PR_SET_PTRACER, child_pid_);
78#endif
79  }
80
81  /* Gtest is calling TestBody from this class, which sets up a child
82   * process in which the RealTestBody virtual member is called.
83   * As such, TestBody is not supposed to be overridden in derived classes.
84   */
85  virtual void TestBody() /* final */ {
86    if (child_pid_ == 0) {
87      // child process
88      RealTestBody();
89      exit(HasFatalFailure() ? kFatalFailure :
90           (HasNonfatalFailure() ? kNonFatalFailure : 0));
91    }
92
93    ASSERT_TRUE(child_pid_ > 0);
94    int status;
95    waitpid(child_pid_, &status, 0);
96    if (WEXITSTATUS(status) == kFatalFailure) {
97      GTEST_FATAL_FAILURE_("Test failed in child process");
98    } else if (WEXITSTATUS(status) == kNonFatalFailure) {
99      GTEST_NONFATAL_FAILURE_("Test failed in child process");
100    }
101  }
102
103  /* Gtest defines TestBody functions through its macros, but classes
104   * derived from this one need to define RealTestBody instead.
105   * This is achieved by defining a TestBody macro further below.
106   */
107  virtual void RealTestBody() = 0;
108 private:
109  static const int kFatalFailure = 1;
110  static const int kNonFatalFailure = 2;
111
112  pid_t child_pid_;
113};
114
115}  // namespace
116
117/* Replace TestBody declarations within TEST*() with RealTestBody
118 * declarations */
119#define TestBody RealTestBody
120
121TEST_F(LinuxPtraceDumperChildTest, Setup) {
122  LinuxPtraceDumper dumper(getppid());
123}
124
125TEST_F(LinuxPtraceDumperChildTest, FindMappings) {
126  LinuxPtraceDumper dumper(getppid());
127  ASSERT_TRUE(dumper.Init());
128
129  ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(getpid)));
130  ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(printf)));
131  ASSERT_FALSE(dumper.FindMapping(NULL));
132}
133
134TEST_F(LinuxPtraceDumperChildTest, ThreadList) {
135  LinuxPtraceDumper dumper(getppid());
136  ASSERT_TRUE(dumper.Init());
137
138  ASSERT_GE(dumper.threads().size(), (size_t)1);
139  bool found = false;
140  for (size_t i = 0; i < dumper.threads().size(); ++i) {
141    if (dumper.threads()[i] == getppid()) {
142      ASSERT_FALSE(found);
143      found = true;
144    }
145  }
146  ASSERT_TRUE(found);
147}
148
149// Helper stack class to close a file descriptor and unmap
150// a mmap'ed mapping.
151class StackHelper {
152 public:
153  StackHelper()
154    : fd_(-1), mapping_(NULL), size_(0) {}
155  ~StackHelper() {
156    if (size_)
157      munmap(mapping_, size_);
158    if (fd_ >= 0)
159      close(fd_);
160  }
161  void Init(int fd, char* mapping, size_t size) {
162    fd_ = fd;
163    mapping_ = mapping;
164    size_ = size;
165  }
166
167  char* mapping() const { return mapping_; }
168  size_t size() const { return size_; }
169
170 private:
171  int fd_;
172  char* mapping_;
173  size_t size_;
174};
175
176class LinuxPtraceDumperMappingsTest : public LinuxPtraceDumperChildTest {
177 protected:
178  virtual void SetUp();
179
180  string helper_path_;
181  size_t page_size_;
182  StackHelper helper_;
183};
184
185void LinuxPtraceDumperMappingsTest::SetUp() {
186  helper_path_ = GetHelperBinary();
187  if (helper_path_.empty()) {
188    FAIL() << "Couldn't find helper binary";
189    exit(1);
190  }
191
192  // mmap two segments out of the helper binary, one
193  // enclosed in the other, but with different protections.
194  page_size_ = sysconf(_SC_PAGESIZE);
195  const size_t kMappingSize = 3 * page_size_;
196  int fd = open(helper_path_.c_str(), O_RDONLY);
197  ASSERT_NE(-1, fd) << "Failed to open file: " << helper_path_
198                    << ", Error: " << strerror(errno);
199  char* mapping =
200    reinterpret_cast<char*>(mmap(NULL,
201                                 kMappingSize,
202                                 PROT_READ,
203                                 MAP_SHARED,
204                                 fd,
205                                 0));
206  ASSERT_TRUE(mapping);
207
208  // Ensure that things get cleaned up.
209  helper_.Init(fd, mapping, kMappingSize);
210
211  // Carve a page out of the first mapping with different permissions.
212  char* inside_mapping =  reinterpret_cast<char*>(
213      mmap(mapping + 2 * page_size_,
214           page_size_,
215           PROT_NONE,
216           MAP_SHARED | MAP_FIXED,
217           fd,
218           // Map a different offset just to
219           // better test real-world conditions.
220           page_size_));
221  ASSERT_TRUE(inside_mapping);
222
223  LinuxPtraceDumperChildTest::SetUp();
224}
225
226TEST_F(LinuxPtraceDumperMappingsTest, MergedMappings) {
227  // Now check that LinuxPtraceDumper interpreted the mappings properly.
228  LinuxPtraceDumper dumper(getppid());
229  ASSERT_TRUE(dumper.Init());
230  int mapping_count = 0;
231  for (unsigned i = 0; i < dumper.mappings().size(); ++i) {
232    const MappingInfo& mapping = *dumper.mappings()[i];
233    if (strcmp(mapping.name, this->helper_path_.c_str()) == 0) {
234      // This mapping should encompass the entire original mapped
235      // range.
236      EXPECT_EQ(reinterpret_cast<uintptr_t>(this->helper_.mapping()),
237                mapping.start_addr);
238      EXPECT_EQ(this->helper_.size(), mapping.size);
239      EXPECT_EQ(0U, mapping.offset);
240      mapping_count++;
241    }
242  }
243  EXPECT_EQ(1, mapping_count);
244}
245
246TEST_F(LinuxPtraceDumperChildTest, BuildProcPath) {
247  const pid_t pid = getppid();
248  LinuxPtraceDumper dumper(pid);
249
250  char maps_path[NAME_MAX] = "";
251  char maps_path_expected[NAME_MAX];
252  snprintf(maps_path_expected, sizeof(maps_path_expected),
253           "/proc/%d/maps", pid);
254  EXPECT_TRUE(dumper.BuildProcPath(maps_path, pid, "maps"));
255  EXPECT_STREQ(maps_path_expected, maps_path);
256
257  EXPECT_FALSE(dumper.BuildProcPath(NULL, pid, "maps"));
258  EXPECT_FALSE(dumper.BuildProcPath(maps_path, 0, "maps"));
259  EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, ""));
260  EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, NULL));
261
262  char long_node[NAME_MAX];
263  size_t long_node_len = NAME_MAX - strlen("/proc/123") - 1;
264  memset(long_node, 'a', long_node_len);
265  long_node[long_node_len] = '\0';
266  EXPECT_FALSE(dumper.BuildProcPath(maps_path, 123, long_node));
267}
268
269#if !defined(__ARM_EABI__) && !defined(__mips__)
270// Ensure that the linux-gate VDSO is included in the mapping list.
271TEST_F(LinuxPtraceDumperChildTest, MappingsIncludeLinuxGate) {
272  LinuxPtraceDumper dumper(getppid());
273  ASSERT_TRUE(dumper.Init());
274
275  void* linux_gate_loc =
276    reinterpret_cast<void *>(dumper.auxv()[AT_SYSINFO_EHDR]);
277  ASSERT_TRUE(linux_gate_loc);
278  bool found_linux_gate = false;
279
280  const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
281  const MappingInfo* mapping;
282  for (unsigned i = 0; i < mappings.size(); ++i) {
283    mapping = mappings[i];
284    if (!strcmp(mapping->name, kLinuxGateLibraryName)) {
285      found_linux_gate = true;
286      break;
287    }
288  }
289  EXPECT_TRUE(found_linux_gate);
290  EXPECT_EQ(linux_gate_loc, reinterpret_cast<void*>(mapping->start_addr));
291  EXPECT_EQ(0, memcmp(linux_gate_loc, ELFMAG, SELFMAG));
292}
293
294// Ensure that the linux-gate VDSO can generate a non-zeroed File ID.
295TEST_F(LinuxPtraceDumperChildTest, LinuxGateMappingID) {
296  LinuxPtraceDumper dumper(getppid());
297  ASSERT_TRUE(dumper.Init());
298
299  bool found_linux_gate = false;
300  const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
301  unsigned index = 0;
302  for (unsigned i = 0; i < mappings.size(); ++i) {
303    if (!strcmp(mappings[i]->name, kLinuxGateLibraryName)) {
304      found_linux_gate = true;
305      index = i;
306      break;
307    }
308  }
309  ASSERT_TRUE(found_linux_gate);
310
311  // Need to suspend the child so ptrace actually works.
312  ASSERT_TRUE(dumper.ThreadsSuspend());
313  uint8_t identifier[sizeof(MDGUID)];
314  ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index],
315                                                 true,
316                                                 index,
317                                                 identifier));
318  uint8_t empty_identifier[sizeof(MDGUID)];
319  memset(empty_identifier, 0, sizeof(empty_identifier));
320  EXPECT_NE(0, memcmp(empty_identifier, identifier, sizeof(identifier)));
321  EXPECT_TRUE(dumper.ThreadsResume());
322}
323#endif
324
325TEST_F(LinuxPtraceDumperChildTest, FileIDsMatch) {
326  // Calculate the File ID of our binary using both
327  // FileID::ElfFileIdentifier and LinuxDumper::ElfFileIdentifierForMapping
328  // and ensure that we get the same result from both.
329  char exe_name[PATH_MAX];
330  ASSERT_TRUE(SafeReadLink("/proc/self/exe", exe_name));
331
332  LinuxPtraceDumper dumper(getppid());
333  ASSERT_TRUE(dumper.Init());
334  const wasteful_vector<MappingInfo*> mappings = dumper.mappings();
335  bool found_exe = false;
336  unsigned i;
337  for (i = 0; i < mappings.size(); ++i) {
338    const MappingInfo* mapping = mappings[i];
339    if (!strcmp(mapping->name, exe_name)) {
340      found_exe = true;
341      break;
342    }
343  }
344  ASSERT_TRUE(found_exe);
345
346  uint8_t identifier1[sizeof(MDGUID)];
347  uint8_t identifier2[sizeof(MDGUID)];
348  EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[i], true, i,
349                                                 identifier1));
350  FileID fileid(exe_name);
351  EXPECT_TRUE(fileid.ElfFileIdentifier(identifier2));
352  char identifier_string1[37];
353  char identifier_string2[37];
354  FileID::ConvertIdentifierToString(identifier1, identifier_string1,
355                                    37);
356  FileID::ConvertIdentifierToString(identifier2, identifier_string2,
357                                    37);
358  EXPECT_STREQ(identifier_string1, identifier_string2);
359}
360
361/* Get back to normal behavior of TEST*() macros wrt TestBody. */
362#undef TestBody
363
364// Comment out this test due to crosbug.com/6757.  Only seems to
365// fail on heavily loaded buildbots and is written with timing
366// assumptions.
367#if 0
368TEST(LinuxPtraceDumperTest, VerifyStackReadWithMultipleThreads) {
369  static const int kNumberOfThreadsInHelperProgram = 5;
370  char kNumberOfThreadsArgument[2];
371  sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram);
372
373  int fds[2];
374  ASSERT_NE(-1, pipe(fds));
375
376  pid_t child_pid = fork();
377  if (child_pid == 0) {
378    // In child process.
379    close(fds[0]);
380
381    string helper_path(GetHelperBinary());
382    if (helper_path.empty()) {
383      FAIL() << "Couldn't find helper binary";
384      exit(1);
385    }
386
387    // Pass the pipe fd and the number of threads as arguments.
388    char pipe_fd_string[8];
389    sprintf(pipe_fd_string, "%d", fds[1]);
390    execl(helper_path.c_str(),
391          "linux_dumper_unittest_helper",
392          pipe_fd_string,
393          kNumberOfThreadsArgument,
394          NULL);
395    // Kill if we get here.
396    printf("Errno from exec: %d", errno);
397    FAIL() << "Exec of " << helper_path << " failed: " << strerror(errno);
398    exit(0);
399  }
400  close(fds[1]);
401
402  // Wait for all child threads to indicate that they have started
403  for (int threads = 0; threads < kNumberOfThreadsInHelperProgram; threads++) {
404    struct pollfd pfd;
405    memset(&pfd, 0, sizeof(pfd));
406    pfd.fd = fds[0];
407    pfd.events = POLLIN | POLLERR;
408
409    const int r = HANDLE_EINTR(poll(&pfd, 1, 1000));
410    ASSERT_EQ(1, r);
411    ASSERT_TRUE(pfd.revents & POLLIN);
412    uint8_t junk;
413    ASSERT_EQ(read(fds[0], &junk, sizeof(junk)),
414              static_cast<ssize_t>(sizeof(junk)));
415  }
416  close(fds[0]);
417
418  // There is a race here because we may stop a child thread before
419  // it is actually running the busy loop. Empirically this sleep
420  // is sufficient to avoid the race.
421  usleep(100000);
422
423  // Children are ready now.
424  LinuxPtraceDumper dumper(child_pid);
425  ASSERT_TRUE(dumper.Init());
426  EXPECT_EQ((size_t)kNumberOfThreadsInHelperProgram, dumper.threads().size());
427  EXPECT_TRUE(dumper.ThreadsSuspend());
428
429  ThreadInfo one_thread;
430  for (size_t i = 0; i < dumper.threads().size(); ++i) {
431    EXPECT_TRUE(dumper.GetThreadInfoByIndex(i, &one_thread));
432    const void* stack;
433    size_t stack_len;
434    EXPECT_TRUE(dumper.GetStackInfo(&stack, &stack_len,
435        one_thread.stack_pointer));
436    // In the helper program, we stored a pointer to the thread id in a
437    // specific register. Check that we can recover its value.
438#if defined(__ARM_EABI__)
439    pid_t* process_tid_location = (pid_t*)(one_thread.regs.uregs[3]);
440#elif defined(__aarch64__)
441    pid_t* process_tid_location = (pid_t*)(one_thread.regs.regs[3]);
442#elif defined(__i386)
443    pid_t* process_tid_location = (pid_t*)(one_thread.regs.ecx);
444#elif defined(__x86_64)
445    pid_t* process_tid_location = (pid_t*)(one_thread.regs.rcx);
446#elif defined(__mips__)
447    pid_t* process_tid_location =
448        reinterpret_cast<pid_t*>(one_thread.regs.regs[1]);
449#else
450#error This test has not been ported to this platform.
451#endif
452    pid_t one_thread_id;
453    dumper.CopyFromProcess(&one_thread_id,
454                           dumper.threads()[i],
455                           process_tid_location,
456                           4);
457    EXPECT_EQ(dumper.threads()[i], one_thread_id);
458  }
459  EXPECT_TRUE(dumper.ThreadsResume());
460  kill(child_pid, SIGKILL);
461
462  // Reap child
463  int status;
464  ASSERT_NE(-1, HANDLE_EINTR(waitpid(child_pid, &status, 0)));
465  ASSERT_TRUE(WIFSIGNALED(status));
466  ASSERT_EQ(SIGKILL, WTERMSIG(status));
467}
468#endif
469