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