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
65 using namespace google_breakpad;
66
67 namespace {
68
69 typedef testing::Test LinuxPtraceDumperTest;
70
71 /* Fixture for running tests in a child process. */
72 class LinuxPtraceDumperChildTest : public testing::Test {
73 protected:
SetUp()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 */
TestBody()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
TEST_F(LinuxPtraceDumperChildTest,Setup)121 TEST_F(LinuxPtraceDumperChildTest, Setup) {
122 LinuxPtraceDumper dumper(getppid());
123 }
124
TEST_F(LinuxPtraceDumperChildTest,FindMappings)125 TEST_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
TEST_F(LinuxPtraceDumperChildTest,ThreadList)134 TEST_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.
151 class StackHelper {
152 public:
StackHelper()153 StackHelper()
154 : fd_(-1), mapping_(NULL), size_(0) {}
~StackHelper()155 ~StackHelper() {
156 if (size_)
157 munmap(mapping_, size_);
158 if (fd_ >= 0)
159 close(fd_);
160 }
Init(int fd,char * mapping,size_t size)161 void Init(int fd, char* mapping, size_t size) {
162 fd_ = fd;
163 mapping_ = mapping;
164 size_ = size;
165 }
166
mapping() const167 char* mapping() const { return mapping_; }
size() const168 size_t size() const { return size_; }
169
170 private:
171 int fd_;
172 char* mapping_;
173 size_t size_;
174 };
175
176 class LinuxPtraceDumperMappingsTest : public LinuxPtraceDumperChildTest {
177 protected:
178 virtual void SetUp();
179
180 string helper_path_;
181 size_t page_size_;
182 StackHelper helper_;
183 };
184
SetUp()185 void 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
TEST_F(LinuxPtraceDumperMappingsTest,MergedMappings)226 TEST_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
TEST_F(LinuxPtraceDumperChildTest,BuildProcPath)246 TEST_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.
TEST_F(LinuxPtraceDumperChildTest,MappingsIncludeLinuxGate)271 TEST_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.
TEST_F(LinuxPtraceDumperChildTest,LinuxGateMappingID)295 TEST_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
TEST_F(LinuxPtraceDumperChildTest,FileIDsMatch)325 TEST_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
368 TEST(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