1 // Copyright (c) 2011 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 #include <fcntl.h> 31 #include <sys/poll.h> 32 #include <sys/stat.h> 33 #include <sys/syscall.h> 34 #include <sys/types.h> 35 #include <ucontext.h> 36 #include <unistd.h> 37 38 #include <string> 39 40 #include "breakpad_googletest_includes.h" 41 #include "client/linux/handler/exception_handler.h" 42 #include "client/linux/minidump_writer/linux_dumper.h" 43 #include "client/linux/minidump_writer/minidump_writer.h" 44 #include "client/linux/minidump_writer/minidump_writer_unittest_utils.h" 45 #include "common/linux/eintr_wrapper.h" 46 #include "common/linux/file_id.h" 47 #include "common/linux/ignore_ret.h" 48 #include "common/linux/safe_readlink.h" 49 #include "common/scoped_ptr.h" 50 #include "common/tests/auto_tempdir.h" 51 #include "common/tests/file_utils.h" 52 #include "common/using_std_string.h" 53 #include "google_breakpad/processor/minidump.h" 54 55 using namespace google_breakpad; 56 57 // Length of a formatted GUID string = 58 // sizeof(MDGUID) * 2 + 4 (for dashes) + 1 (null terminator) 59 const int kGUIDStringSize = 37; 60 61 namespace { 62 63 typedef testing::Test MinidumpWriterTest; 64 65 const char kMDWriterUnitTestFileName[] = "/minidump-writer-unittest"; 66 67 TEST(MinidumpWriterTest, SetupWithPath) { 68 int fds[2]; 69 ASSERT_NE(-1, pipe(fds)); 70 71 const pid_t child = fork(); 72 if (child == 0) { 73 close(fds[1]); 74 char b; 75 IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b)))); 76 close(fds[0]); 77 syscall(__NR_exit); 78 } 79 close(fds[0]); 80 81 ExceptionHandler::CrashContext context; 82 memset(&context, 0, sizeof(context)); 83 84 AutoTempDir temp_dir; 85 string templ = temp_dir.path() + kMDWriterUnitTestFileName; 86 // Set a non-zero tid to avoid tripping asserts. 87 context.tid = child; 88 ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context))); 89 struct stat st; 90 ASSERT_EQ(0, stat(templ.c_str(), &st)); 91 ASSERT_GT(st.st_size, 0); 92 93 close(fds[1]); 94 } 95 96 TEST(MinidumpWriterTest, SetupWithFD) { 97 int fds[2]; 98 ASSERT_NE(-1, pipe(fds)); 99 100 const pid_t child = fork(); 101 if (child == 0) { 102 close(fds[1]); 103 char b; 104 HANDLE_EINTR(read(fds[0], &b, sizeof(b))); 105 close(fds[0]); 106 syscall(__NR_exit); 107 } 108 close(fds[0]); 109 110 ExceptionHandler::CrashContext context; 111 memset(&context, 0, sizeof(context)); 112 113 AutoTempDir temp_dir; 114 string templ = temp_dir.path() + kMDWriterUnitTestFileName; 115 int fd = open(templ.c_str(), O_CREAT | O_WRONLY, S_IRWXU); 116 // Set a non-zero tid to avoid tripping asserts. 117 context.tid = child; 118 ASSERT_TRUE(WriteMinidump(fd, child, &context, sizeof(context))); 119 struct stat st; 120 ASSERT_EQ(0, stat(templ.c_str(), &st)); 121 ASSERT_GT(st.st_size, 0); 122 123 close(fds[1]); 124 } 125 126 // Test that mapping info can be specified when writing a minidump, 127 // and that it ends up in the module list of the minidump. 128 TEST(MinidumpWriterTest, MappingInfo) { 129 int fds[2]; 130 ASSERT_NE(-1, pipe(fds)); 131 132 // These are defined here so the parent can use them to check the 133 // data from the minidump afterwards. 134 const uint32_t memory_size = sysconf(_SC_PAGESIZE); 135 const char* kMemoryName = "a fake module"; 136 const uint8_t kModuleGUID[sizeof(MDGUID)] = { 137 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 138 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF 139 }; 140 char module_identifier_buffer[kGUIDStringSize]; 141 FileID::ConvertIdentifierToString(kModuleGUID, 142 module_identifier_buffer, 143 sizeof(module_identifier_buffer)); 144 string module_identifier(module_identifier_buffer); 145 // Strip out dashes 146 size_t pos; 147 while ((pos = module_identifier.find('-')) != string::npos) { 148 module_identifier.erase(pos, 1); 149 } 150 // And append a zero, because module IDs include an "age" field 151 // which is always zero on Linux. 152 module_identifier += "0"; 153 154 // Get some memory. 155 char* memory = 156 reinterpret_cast<char*>(mmap(NULL, 157 memory_size, 158 PROT_READ | PROT_WRITE, 159 MAP_PRIVATE | MAP_ANON, 160 -1, 161 0)); 162 const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory); 163 ASSERT_TRUE(memory); 164 165 const pid_t child = fork(); 166 if (child == 0) { 167 close(fds[1]); 168 char b; 169 IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b)))); 170 close(fds[0]); 171 syscall(__NR_exit); 172 } 173 close(fds[0]); 174 175 ExceptionHandler::CrashContext context; 176 memset(&context, 0, sizeof(context)); 177 ASSERT_EQ(0, getcontext(&context.context)); 178 context.tid = child; 179 180 AutoTempDir temp_dir; 181 string templ = temp_dir.path() + kMDWriterUnitTestFileName; 182 183 // Add information about the mapped memory. 184 MappingInfo info; 185 info.start_addr = kMemoryAddress; 186 info.size = memory_size; 187 info.offset = 0; 188 strcpy(info.name, kMemoryName); 189 190 MappingList mappings; 191 AppMemoryList memory_list; 192 MappingEntry mapping; 193 mapping.first = info; 194 memcpy(mapping.second, kModuleGUID, sizeof(MDGUID)); 195 mappings.push_back(mapping); 196 ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context), 197 mappings, memory_list)); 198 199 // Read the minidump. Load the module list, and ensure that 200 // the mmap'ed |memory| is listed with the given module name 201 // and debug ID. 202 Minidump minidump(templ); 203 ASSERT_TRUE(minidump.Read()); 204 205 MinidumpModuleList* module_list = minidump.GetModuleList(); 206 ASSERT_TRUE(module_list); 207 const MinidumpModule* module = 208 module_list->GetModuleForAddress(kMemoryAddress); 209 ASSERT_TRUE(module); 210 211 EXPECT_EQ(kMemoryAddress, module->base_address()); 212 EXPECT_EQ(memory_size, module->size()); 213 EXPECT_EQ(kMemoryName, module->code_file()); 214 EXPECT_EQ(module_identifier, module->debug_identifier()); 215 216 uint32_t len; 217 // These streams are expected to be there 218 EXPECT_TRUE(minidump.SeekToStreamType(MD_THREAD_LIST_STREAM, &len)); 219 EXPECT_TRUE(minidump.SeekToStreamType(MD_MEMORY_LIST_STREAM, &len)); 220 EXPECT_TRUE(minidump.SeekToStreamType(MD_EXCEPTION_STREAM, &len)); 221 EXPECT_TRUE(minidump.SeekToStreamType(MD_SYSTEM_INFO_STREAM, &len)); 222 EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_CPU_INFO, &len)); 223 EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_PROC_STATUS, &len)); 224 EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_CMD_LINE, &len)); 225 EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_ENVIRON, &len)); 226 EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_AUXV, &len)); 227 EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_MAPS, &len)); 228 EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_DSO_DEBUG, &len)); 229 230 close(fds[1]); 231 } 232 233 // Test that mapping info can be specified, and that it overrides 234 // existing mappings that are wholly contained within the specified 235 // range. 236 #ifdef ENABLE_FLAKY_TESTS 237 TEST(MinidumpWriterTest, MappingInfoContained) { 238 int fds[2]; 239 ASSERT_NE(-1, pipe(fds)); 240 241 // These are defined here so the parent can use them to check the 242 // data from the minidump afterwards. 243 const int32_t memory_size = sysconf(_SC_PAGESIZE); 244 const char* kMemoryName = "a fake module"; 245 const uint8_t kModuleGUID[sizeof(MDGUID)] = { 246 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 247 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF 248 }; 249 char module_identifier_buffer[kGUIDStringSize]; 250 FileID::ConvertIdentifierToString(kModuleGUID, 251 module_identifier_buffer, 252 sizeof(module_identifier_buffer)); 253 string module_identifier(module_identifier_buffer); 254 // Strip out dashes 255 size_t pos; 256 while ((pos = module_identifier.find('-')) != string::npos) { 257 module_identifier.erase(pos, 1); 258 } 259 // And append a zero, because module IDs include an "age" field 260 // which is always zero on Linux. 261 module_identifier += "0"; 262 263 // mmap a file 264 AutoTempDir temp_dir; 265 string tempfile = temp_dir.path() + "/minidump-writer-unittest-temp"; 266 int fd = open(tempfile.c_str(), O_RDWR | O_CREAT, 0); 267 ASSERT_NE(-1, fd); 268 unlink(tempfile.c_str()); 269 // fill with zeros 270 google_breakpad::scoped_array<char> buffer(new char[memory_size]); 271 memset(buffer.get(), 0, memory_size); 272 ASSERT_EQ(memory_size, write(fd, buffer.get(), memory_size)); 273 lseek(fd, 0, SEEK_SET); 274 275 char* memory = 276 reinterpret_cast<char*>(mmap(NULL, 277 memory_size, 278 PROT_READ | PROT_WRITE, 279 MAP_PRIVATE, 280 fd, 281 0)); 282 const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory); 283 ASSERT_TRUE(memory); 284 close(fd); 285 286 const pid_t child = fork(); 287 if (child == 0) { 288 close(fds[1]); 289 char b; 290 IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b)))); 291 close(fds[0]); 292 syscall(__NR_exit); 293 } 294 close(fds[0]); 295 296 ExceptionHandler::CrashContext context; 297 memset(&context, 0, sizeof(context)); 298 context.tid = 1; 299 300 string dumpfile = temp_dir.path() + kMDWriterUnitTestFileName; 301 302 // Add information about the mapped memory. Report it as being larger than 303 // it actually is. 304 MappingInfo info; 305 info.start_addr = kMemoryAddress - memory_size; 306 info.size = memory_size * 3; 307 info.offset = 0; 308 strcpy(info.name, kMemoryName); 309 310 MappingList mappings; 311 AppMemoryList memory_list; 312 MappingEntry mapping; 313 mapping.first = info; 314 memcpy(mapping.second, kModuleGUID, sizeof(MDGUID)); 315 mappings.push_back(mapping); 316 ASSERT_TRUE(WriteMinidump(dumpfile.c_str(), child, &context, sizeof(context), 317 mappings, memory_list)); 318 319 // Read the minidump. Load the module list, and ensure that 320 // the mmap'ed |memory| is listed with the given module name 321 // and debug ID. 322 Minidump minidump(dumpfile); 323 ASSERT_TRUE(minidump.Read()); 324 325 MinidumpModuleList* module_list = minidump.GetModuleList(); 326 ASSERT_TRUE(module_list); 327 const MinidumpModule* module = 328 module_list->GetModuleForAddress(kMemoryAddress); 329 ASSERT_TRUE(module); 330 331 EXPECT_EQ(info.start_addr, module->base_address()); 332 EXPECT_EQ(info.size, module->size()); 333 EXPECT_EQ(kMemoryName, module->code_file()); 334 EXPECT_EQ(module_identifier, module->debug_identifier()); 335 336 close(fds[1]); 337 } 338 #endif 339 340 TEST(MinidumpWriterTest, DeletedBinary) { 341 const string kNumberOfThreadsArgument = "1"; 342 const string helper_path(GetHelperBinary()); 343 if (helper_path.empty()) { 344 FAIL() << "Couldn't find helper binary"; 345 exit(1); 346 } 347 348 // Copy binary to a temp file. 349 AutoTempDir temp_dir; 350 string binpath = temp_dir.path() + "/linux-dumper-unittest-helper"; 351 ASSERT_TRUE(CopyFile(helper_path.c_str(), binpath.c_str())) 352 << "Failed to copy " << helper_path << " to " << binpath; 353 ASSERT_EQ(0, chmod(binpath.c_str(), 0755)); 354 355 int fds[2]; 356 ASSERT_NE(-1, pipe(fds)); 357 358 pid_t child_pid = fork(); 359 if (child_pid == 0) { 360 // In child process. 361 close(fds[0]); 362 363 // Pass the pipe fd and the number of threads as arguments. 364 char pipe_fd_string[8]; 365 sprintf(pipe_fd_string, "%d", fds[1]); 366 execl(binpath.c_str(), 367 binpath.c_str(), 368 pipe_fd_string, 369 kNumberOfThreadsArgument.c_str(), 370 NULL); 371 } 372 close(fds[1]); 373 // Wait for the child process to signal that it's ready. 374 struct pollfd pfd; 375 memset(&pfd, 0, sizeof(pfd)); 376 pfd.fd = fds[0]; 377 pfd.events = POLLIN | POLLERR; 378 379 const int r = HANDLE_EINTR(poll(&pfd, 1, 1000)); 380 ASSERT_EQ(1, r); 381 ASSERT_TRUE(pfd.revents & POLLIN); 382 uint8_t junk; 383 const int nr = HANDLE_EINTR(read(fds[0], &junk, sizeof(junk))); 384 ASSERT_EQ(static_cast<ssize_t>(sizeof(junk)), nr); 385 close(fds[0]); 386 387 // Child is ready now. 388 // Unlink the test binary. 389 unlink(binpath.c_str()); 390 391 ExceptionHandler::CrashContext context; 392 memset(&context, 0, sizeof(context)); 393 394 string templ = temp_dir.path() + kMDWriterUnitTestFileName; 395 // Set a non-zero tid to avoid tripping asserts. 396 context.tid = child_pid; 397 ASSERT_TRUE(WriteMinidump(templ.c_str(), child_pid, &context, 398 sizeof(context))); 399 kill(child_pid, SIGKILL); 400 401 struct stat st; 402 ASSERT_EQ(0, stat(templ.c_str(), &st)); 403 ASSERT_GT(st.st_size, 0); 404 405 Minidump minidump(templ); 406 ASSERT_TRUE(minidump.Read()); 407 408 // Check that the main module filename is correct. 409 MinidumpModuleList* module_list = minidump.GetModuleList(); 410 ASSERT_TRUE(module_list); 411 const MinidumpModule* module = module_list->GetMainModule(); 412 EXPECT_STREQ(binpath.c_str(), module->code_file().c_str()); 413 // Check that the file ID is correct. 414 FileID fileid(helper_path.c_str()); 415 uint8_t identifier[sizeof(MDGUID)]; 416 EXPECT_TRUE(fileid.ElfFileIdentifier(identifier)); 417 char identifier_string[kGUIDStringSize]; 418 FileID::ConvertIdentifierToString(identifier, 419 identifier_string, 420 kGUIDStringSize); 421 string module_identifier(identifier_string); 422 // Strip out dashes 423 size_t pos; 424 while ((pos = module_identifier.find('-')) != string::npos) { 425 module_identifier.erase(pos, 1); 426 } 427 // And append a zero, because module IDs include an "age" field 428 // which is always zero on Linux. 429 module_identifier += "0"; 430 EXPECT_EQ(module_identifier, module->debug_identifier()); 431 } 432 433 // Test that an additional memory region can be added to the minidump. 434 TEST(MinidumpWriterTest, AdditionalMemory) { 435 int fds[2]; 436 ASSERT_NE(-1, pipe(fds)); 437 438 // These are defined here so the parent can use them to check the 439 // data from the minidump afterwards. 440 const uint32_t kMemorySize = sysconf(_SC_PAGESIZE); 441 442 // Get some heap memory. 443 uint8_t* memory = new uint8_t[kMemorySize]; 444 const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory); 445 ASSERT_TRUE(memory); 446 447 // Stick some data into the memory so the contents can be verified. 448 for (uint32_t i = 0; i < kMemorySize; ++i) { 449 memory[i] = i % 255; 450 } 451 452 const pid_t child = fork(); 453 if (child == 0) { 454 close(fds[1]); 455 char b; 456 HANDLE_EINTR(read(fds[0], &b, sizeof(b))); 457 close(fds[0]); 458 syscall(__NR_exit); 459 } 460 close(fds[0]); 461 462 ExceptionHandler::CrashContext context; 463 464 // This needs a valid context for minidump writing to work, but getting 465 // a useful one from the child is too much work, so just use one from 466 // the parent since the child is just a forked copy anyway. 467 ASSERT_EQ(0, getcontext(&context.context)); 468 context.tid = child; 469 470 AutoTempDir temp_dir; 471 string templ = temp_dir.path() + kMDWriterUnitTestFileName; 472 unlink(templ.c_str()); 473 474 MappingList mappings; 475 AppMemoryList memory_list; 476 477 // Add the memory region to the list of memory to be included. 478 AppMemory app_memory; 479 app_memory.ptr = memory; 480 app_memory.length = kMemorySize; 481 memory_list.push_back(app_memory); 482 ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context), 483 mappings, memory_list)); 484 485 // Read the minidump. Ensure that the memory region is present 486 Minidump minidump(templ); 487 ASSERT_TRUE(minidump.Read()); 488 489 MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList(); 490 ASSERT_TRUE(dump_memory_list); 491 const MinidumpMemoryRegion* region = 492 dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress); 493 ASSERT_TRUE(region); 494 495 EXPECT_EQ(kMemoryAddress, region->GetBase()); 496 EXPECT_EQ(kMemorySize, region->GetSize()); 497 498 // Verify memory contents. 499 EXPECT_EQ(0, memcmp(region->GetMemory(), memory, kMemorySize)); 500 501 delete[] memory; 502 close(fds[1]); 503 } 504 505 // Test that an invalid thread stack pointer still results in a minidump. 506 TEST(MinidumpWriterTest, InvalidStackPointer) { 507 int fds[2]; 508 ASSERT_NE(-1, pipe(fds)); 509 510 const pid_t child = fork(); 511 if (child == 0) { 512 close(fds[1]); 513 char b; 514 HANDLE_EINTR(read(fds[0], &b, sizeof(b))); 515 close(fds[0]); 516 syscall(__NR_exit); 517 } 518 close(fds[0]); 519 520 ExceptionHandler::CrashContext context; 521 522 // This needs a valid context for minidump writing to work, but getting 523 // a useful one from the child is too much work, so just use one from 524 // the parent since the child is just a forked copy anyway. 525 ASSERT_EQ(0, getcontext(&context.context)); 526 context.tid = child; 527 528 // Fake the child's stack pointer for its crashing thread. NOTE: This must 529 // be an invalid memory address for the child process (stack or otherwise). 530 // Try 1MB below the current stack. 531 uintptr_t invalid_stack_pointer = 532 reinterpret_cast<uintptr_t>(&context) - 1024*1024; 533 #if defined(__i386) 534 context.context.uc_mcontext.gregs[REG_ESP] = invalid_stack_pointer; 535 #elif defined(__x86_64) 536 context.context.uc_mcontext.gregs[REG_RSP] = invalid_stack_pointer; 537 #elif defined(__ARM_EABI__) 538 context.context.uc_mcontext.arm_sp = invalid_stack_pointer; 539 #elif defined(__aarch64__) 540 context.context.uc_mcontext.sp = invalid_stack_pointer; 541 #elif defined(__mips__) 542 context.context.uc_mcontext.gregs[MD_CONTEXT_MIPS_REG_SP] = 543 invalid_stack_pointer; 544 #else 545 # error "This code has not been ported to your platform yet." 546 #endif 547 548 AutoTempDir temp_dir; 549 string templ = temp_dir.path() + kMDWriterUnitTestFileName; 550 // NOTE: In previous versions of Breakpad, WriteMinidump() would fail if 551 // presented with an invalid stack pointer. 552 ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context))); 553 554 // Read the minidump. Ensure that the memory region is present 555 Minidump minidump(templ); 556 ASSERT_TRUE(minidump.Read()); 557 558 // TODO(ted.mielczarek,mkrebs): Enable this part of the test once 559 // https://breakpad.appspot.com/413002/ is committed. 560 #if 0 561 // Make sure there's a thread without a stack. NOTE: It's okay if 562 // GetThreadList() shows the error: "ERROR: MinidumpThread has a memory 563 // region problem". 564 MinidumpThreadList* dump_thread_list = minidump.GetThreadList(); 565 ASSERT_TRUE(dump_thread_list); 566 bool found_empty_stack = false; 567 for (int i = 0; i < dump_thread_list->thread_count(); i++) { 568 MinidumpThread* thread = dump_thread_list->GetThreadAtIndex(i); 569 ASSERT_TRUE(thread->thread() != NULL); 570 // When the stack size is zero bytes, GetMemory() returns NULL. 571 if (thread->GetMemory() == NULL) { 572 found_empty_stack = true; 573 break; 574 } 575 } 576 // NOTE: If you fail this, first make sure that "invalid_stack_pointer" 577 // above is indeed set to an invalid address. 578 ASSERT_TRUE(found_empty_stack); 579 #endif 580 581 close(fds[1]); 582 } 583 584 // Test that limiting the size of the minidump works. 585 TEST(MinidumpWriterTest, MinidumpSizeLimit) { 586 static const int kNumberOfThreadsInHelperProgram = 40; 587 588 char number_of_threads_arg[3]; 589 sprintf(number_of_threads_arg, "%d", kNumberOfThreadsInHelperProgram); 590 591 string helper_path(GetHelperBinary()); 592 if (helper_path.empty()) { 593 FAIL() << "Couldn't find helper binary"; 594 exit(1); 595 } 596 597 int fds[2]; 598 ASSERT_NE(-1, pipe(fds)); 599 600 pid_t child_pid = fork(); 601 if (child_pid == 0) { 602 // In child process. 603 close(fds[0]); 604 605 // Pass the pipe fd and the number of threads as arguments. 606 char pipe_fd_string[8]; 607 sprintf(pipe_fd_string, "%d", fds[1]); 608 execl(helper_path.c_str(), 609 helper_path.c_str(), 610 pipe_fd_string, 611 number_of_threads_arg, 612 NULL); 613 } 614 close(fds[1]); 615 616 // Wait for all child threads to indicate that they have started 617 for (int threads = 0; threads < kNumberOfThreadsInHelperProgram; threads++) { 618 struct pollfd pfd; 619 memset(&pfd, 0, sizeof(pfd)); 620 pfd.fd = fds[0]; 621 pfd.events = POLLIN | POLLERR; 622 623 const int r = HANDLE_EINTR(poll(&pfd, 1, 1000)); 624 ASSERT_EQ(1, r); 625 ASSERT_TRUE(pfd.revents & POLLIN); 626 uint8_t junk; 627 ASSERT_EQ(read(fds[0], &junk, sizeof(junk)), 628 static_cast<ssize_t>(sizeof(junk))); 629 } 630 close(fds[0]); 631 632 // There is a race here because we may stop a child thread before 633 // it is actually running the busy loop. Empirically this sleep 634 // is sufficient to avoid the race. 635 usleep(100000); 636 637 // Child and its threads are ready now. 638 639 640 off_t normal_file_size; 641 int total_normal_stack_size = 0; 642 AutoTempDir temp_dir; 643 644 // First, write a minidump with no size limit. 645 { 646 string normal_dump = temp_dir.path() + 647 "/minidump-writer-unittest.dmp"; 648 ASSERT_TRUE(WriteMinidump(normal_dump.c_str(), -1, 649 child_pid, NULL, 0, 650 MappingList(), AppMemoryList())); 651 struct stat st; 652 ASSERT_EQ(0, stat(normal_dump.c_str(), &st)); 653 ASSERT_GT(st.st_size, 0); 654 normal_file_size = st.st_size; 655 656 Minidump minidump(normal_dump); 657 ASSERT_TRUE(minidump.Read()); 658 MinidumpThreadList* dump_thread_list = minidump.GetThreadList(); 659 ASSERT_TRUE(dump_thread_list); 660 for (unsigned int i = 0; i < dump_thread_list->thread_count(); i++) { 661 MinidumpThread* thread = dump_thread_list->GetThreadAtIndex(i); 662 ASSERT_TRUE(thread->thread() != NULL); 663 // When the stack size is zero bytes, GetMemory() returns NULL. 664 MinidumpMemoryRegion* memory = thread->GetMemory(); 665 ASSERT_TRUE(memory != NULL); 666 total_normal_stack_size += memory->GetSize(); 667 } 668 } 669 670 // Second, write a minidump with a size limit big enough to not trigger 671 // anything. 672 { 673 // Set size limit arbitrarily 1MB larger than the normal file size -- such 674 // that the limiting code will not kick in. 675 const off_t minidump_size_limit = normal_file_size + 1024*1024; 676 677 string same_dump = temp_dir.path() + 678 "/minidump-writer-unittest-same.dmp"; 679 ASSERT_TRUE(WriteMinidump(same_dump.c_str(), minidump_size_limit, 680 child_pid, NULL, 0, 681 MappingList(), AppMemoryList())); 682 struct stat st; 683 ASSERT_EQ(0, stat(same_dump.c_str(), &st)); 684 // Make sure limiting wasn't actually triggered. NOTE: If you fail this, 685 // first make sure that "minidump_size_limit" above is indeed set to a 686 // large enough value -- the limit-checking code in minidump_writer.cc 687 // does just a rough estimate. 688 ASSERT_EQ(normal_file_size, st.st_size); 689 } 690 691 // Third, write a minidump with a size limit small enough to be triggered. 692 { 693 // Set size limit to some arbitrary amount, such that the limiting code 694 // will kick in. The equation used to set this value was determined by 695 // simply reversing the size-limit logic a little bit in order to pick a 696 // size we know will trigger it. The definition of 697 // kLimitAverageThreadStackLength here was copied from class 698 // MinidumpWriter in minidump_writer.cc. 699 static const unsigned kLimitAverageThreadStackLength = 8 * 1024; 700 off_t minidump_size_limit = kNumberOfThreadsInHelperProgram * 701 kLimitAverageThreadStackLength; 702 // If, in reality, each of the threads' stack is *smaller* than 703 // kLimitAverageThreadStackLength, the normal file size could very well be 704 // smaller than the arbitrary limit that was just set. In that case, 705 // either of these numbers should trigger the size-limiting code, but we 706 // might as well pick the smallest. 707 if (normal_file_size < minidump_size_limit) 708 minidump_size_limit = normal_file_size; 709 710 string limit_dump = temp_dir.path() + 711 "/minidump-writer-unittest-limit.dmp"; 712 ASSERT_TRUE(WriteMinidump(limit_dump.c_str(), minidump_size_limit, 713 child_pid, NULL, 0, 714 MappingList(), AppMemoryList())); 715 struct stat st; 716 ASSERT_EQ(0, stat(limit_dump.c_str(), &st)); 717 ASSERT_GT(st.st_size, 0); 718 // Make sure the file size is at least smaller than the original. If this 719 // fails because it's the same size, then the size-limit logic didn't kick 720 // in like it was supposed to. 721 EXPECT_LT(st.st_size, normal_file_size); 722 723 Minidump minidump(limit_dump); 724 ASSERT_TRUE(minidump.Read()); 725 MinidumpThreadList* dump_thread_list = minidump.GetThreadList(); 726 ASSERT_TRUE(dump_thread_list); 727 int total_limit_stack_size = 0; 728 for (unsigned int i = 0; i < dump_thread_list->thread_count(); i++) { 729 MinidumpThread* thread = dump_thread_list->GetThreadAtIndex(i); 730 ASSERT_TRUE(thread->thread() != NULL); 731 // When the stack size is zero bytes, GetMemory() returns NULL. 732 MinidumpMemoryRegion* memory = thread->GetMemory(); 733 ASSERT_TRUE(memory != NULL); 734 total_limit_stack_size += memory->GetSize(); 735 } 736 737 // Make sure stack size shrunk by at least 1KB per extra thread. The 738 // definition of kLimitBaseThreadCount here was copied from class 739 // MinidumpWriter in minidump_writer.cc. 740 // Note: The 1KB is arbitrary, and assumes that the thread stacks are big 741 // enough to shrink by that much. For example, if each thread stack was 742 // originally only 2KB, the current size-limit logic wouldn't actually 743 // shrink them because that's the size to which it tries to shrink. If 744 // you fail this part of the test due to something like that, the test 745 // logic should probably be improved to account for your situation. 746 const unsigned kLimitBaseThreadCount = 20; 747 const unsigned kMinPerExtraThreadStackReduction = 1024; 748 const int min_expected_reduction = (kNumberOfThreadsInHelperProgram - 749 kLimitBaseThreadCount) * kMinPerExtraThreadStackReduction; 750 EXPECT_LT(total_limit_stack_size, 751 total_normal_stack_size - min_expected_reduction); 752 } 753 754 // Kill the helper program. 755 kill(child_pid, SIGKILL); 756 } 757 758 } // namespace 759