minidump_generator.cc revision 60a883212fbe065f37da2ffdeca2bbe22742c7e8
1// Copyright (c) 2006, 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 <algorithm> 31#include <cstdio> 32 33#include <mach/host_info.h> 34#include <mach/i386/thread_status.h> 35#include <mach/mach_vm.h> 36#include <mach/vm_statistics.h> 37#include <mach-o/dyld.h> 38#include <mach-o/loader.h> 39#include <sys/sysctl.h> 40#include <sys/resource.h> 41 42#include <CoreFoundation/CoreFoundation.h> 43 44#include "client/mac/handler/minidump_generator.h" 45 46#ifdef HAS_PPC_SUPPORT 47#include <mach/ppc/thread_status.h> 48#endif 49 50#include "client/minidump_file_writer-inl.h" 51#include "common/mac/file_id.h" 52#include "common/mac/string_utilities.h" 53 54using MacStringUtils::ConvertToString; 55using MacStringUtils::IntegerValueAtIndex; 56 57namespace google_breakpad { 58 59#if __LP64__ 60#define LC_SEGMENT_ARCH LC_SEGMENT_64 61#else 62#define LC_SEGMENT_ARCH LC_SEGMENT 63#endif 64 65// constructor when generating from within the crashed process 66MinidumpGenerator::MinidumpGenerator() 67 : writer_(), 68 exception_type_(0), 69 exception_code_(0), 70 exception_subcode_(0), 71 exception_thread_(0), 72 crashing_task_(mach_task_self()), 73 handler_thread_(mach_thread_self()), 74 cpu_type_(DynamicImages::GetNativeCPUType()), 75 dynamic_images_(NULL), 76 memory_blocks_(&allocator_) { 77 GatherSystemInformation(); 78} 79 80// constructor when generating from a different process than the 81// crashed process 82MinidumpGenerator::MinidumpGenerator(mach_port_t crashing_task, 83 mach_port_t handler_thread) 84 : writer_(), 85 exception_type_(0), 86 exception_code_(0), 87 exception_subcode_(0), 88 exception_thread_(0), 89 crashing_task_(crashing_task), 90 handler_thread_(handler_thread), 91 cpu_type_(DynamicImages::GetNativeCPUType()), 92 dynamic_images_(NULL), 93 memory_blocks_(&allocator_) { 94 if (crashing_task != mach_task_self()) { 95 dynamic_images_ = new DynamicImages(crashing_task_); 96 cpu_type_ = dynamic_images_->GetCPUType(); 97 } else { 98 dynamic_images_ = NULL; 99 cpu_type_ = DynamicImages::GetNativeCPUType(); 100 } 101 102 GatherSystemInformation(); 103} 104 105MinidumpGenerator::~MinidumpGenerator() { 106 delete dynamic_images_; 107} 108 109char MinidumpGenerator::build_string_[16]; 110int MinidumpGenerator::os_major_version_ = 0; 111int MinidumpGenerator::os_minor_version_ = 0; 112int MinidumpGenerator::os_build_number_ = 0; 113 114// static 115void MinidumpGenerator::GatherSystemInformation() { 116 // If this is non-zero, then we've already gathered the information 117 if (os_major_version_) 118 return; 119 120 // This code extracts the version and build information from the OS 121 CFStringRef vers_path = 122 CFSTR("/System/Library/CoreServices/SystemVersion.plist"); 123 CFURLRef sys_vers = 124 CFURLCreateWithFileSystemPath(NULL, 125 vers_path, 126 kCFURLPOSIXPathStyle, 127 false); 128 CFDataRef data; 129 SInt32 error; 130 CFURLCreateDataAndPropertiesFromResource(NULL, sys_vers, &data, NULL, NULL, 131 &error); 132 133 if (!data) 134 return; 135 136 CFDictionaryRef list = static_cast<CFDictionaryRef> 137 (CFPropertyListCreateFromXMLData(NULL, data, kCFPropertyListImmutable, 138 NULL)); 139 if (!list) 140 return; 141 142 CFStringRef build_version = static_cast<CFStringRef> 143 (CFDictionaryGetValue(list, CFSTR("ProductBuildVersion"))); 144 CFStringRef product_version = static_cast<CFStringRef> 145 (CFDictionaryGetValue(list, CFSTR("ProductVersion"))); 146 string build_str = ConvertToString(build_version); 147 string product_str = ConvertToString(product_version); 148 149 CFRelease(list); 150 CFRelease(sys_vers); 151 CFRelease(data); 152 153 strlcpy(build_string_, build_str.c_str(), sizeof(build_string_)); 154 155 // Parse the string that looks like "10.4.8" 156 os_major_version_ = IntegerValueAtIndex(product_str, 0); 157 os_minor_version_ = IntegerValueAtIndex(product_str, 1); 158 os_build_number_ = IntegerValueAtIndex(product_str, 2); 159} 160 161string MinidumpGenerator::UniqueNameInDirectory(const string &dir, 162 string *unique_name) { 163 CFUUIDRef uuid = CFUUIDCreate(NULL); 164 CFStringRef uuid_cfstr = CFUUIDCreateString(NULL, uuid); 165 CFRelease(uuid); 166 string file_name(ConvertToString(uuid_cfstr)); 167 CFRelease(uuid_cfstr); 168 string path(dir); 169 170 // Ensure that the directory (if non-empty) has a trailing slash so that 171 // we can append the file name and have a valid pathname. 172 if (!dir.empty()) { 173 if (dir.at(dir.size() - 1) != '/') 174 path.append(1, '/'); 175 } 176 177 path.append(file_name); 178 path.append(".dmp"); 179 180 if (unique_name) 181 *unique_name = file_name; 182 183 return path; 184} 185 186bool MinidumpGenerator::Write(const char *path) { 187 WriteStreamFN writers[] = { 188 &MinidumpGenerator::WriteThreadListStream, 189 &MinidumpGenerator::WriteMemoryListStream, 190 &MinidumpGenerator::WriteSystemInfoStream, 191 &MinidumpGenerator::WriteModuleListStream, 192 &MinidumpGenerator::WriteMiscInfoStream, 193 &MinidumpGenerator::WriteBreakpadInfoStream, 194 // Exception stream needs to be the last entry in this array as it may 195 // be omitted in the case where the minidump is written without an 196 // exception. 197 &MinidumpGenerator::WriteExceptionStream, 198 }; 199 bool result = false; 200 201 // If opening was successful, create the header, directory, and call each 202 // writer. The destructor for the TypedMDRVAs will cause the data to be 203 // flushed. The destructor for the MinidumpFileWriter will close the file. 204 if (writer_.Open(path)) { 205 TypedMDRVA<MDRawHeader> header(&writer_); 206 TypedMDRVA<MDRawDirectory> dir(&writer_); 207 208 if (!header.Allocate()) 209 return false; 210 211 int writer_count = static_cast<int>(sizeof(writers) / sizeof(writers[0])); 212 213 // If we don't have exception information, don't write out the 214 // exception stream 215 if (!exception_thread_ && !exception_type_) 216 --writer_count; 217 218 // Add space for all writers 219 if (!dir.AllocateArray(writer_count)) 220 return false; 221 222 MDRawHeader *header_ptr = header.get(); 223 header_ptr->signature = MD_HEADER_SIGNATURE; 224 header_ptr->version = MD_HEADER_VERSION; 225 time(reinterpret_cast<time_t *>(&(header_ptr->time_date_stamp))); 226 header_ptr->stream_count = writer_count; 227 header_ptr->stream_directory_rva = dir.position(); 228 229 MDRawDirectory local_dir; 230 result = true; 231 for (int i = 0; (result) && (i < writer_count); ++i) { 232 result = (this->*writers[i])(&local_dir); 233 234 if (result) 235 dir.CopyIndex(i, &local_dir); 236 } 237 } 238 return result; 239} 240 241size_t MinidumpGenerator::CalculateStackSize(mach_vm_address_t start_addr) { 242 mach_vm_address_t stack_region_base = start_addr; 243 mach_vm_size_t stack_region_size; 244 natural_t nesting_level = 0; 245 vm_region_submap_info_64 submap_info; 246 mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT_64; 247 248 vm_region_recurse_info_t region_info; 249 region_info = reinterpret_cast<vm_region_recurse_info_t>(&submap_info); 250 251 if (start_addr == 0) { 252 return 0; 253 } 254 255 kern_return_t result = 256 mach_vm_region_recurse(crashing_task_, &stack_region_base, 257 &stack_region_size, &nesting_level, 258 region_info, &info_count); 259 260 if (result != KERN_SUCCESS || start_addr < stack_region_base) { 261 // Failure or stack corruption, since mach_vm_region had to go 262 // higher in the process address space to find a valid region. 263 return 0; 264 } 265 266 unsigned int tag = submap_info.user_tag; 267 268 // If the user tag is VM_MEMORY_STACK, look for more readable regions with 269 // the same tag placed immediately above the computed stack region. Under 270 // some circumstances, the stack for thread 0 winds up broken up into 271 // multiple distinct abutting regions. This can happen for several reasons, 272 // including user code that calls setrlimit(RLIMIT_STACK, ...) or changes 273 // the access on stack pages by calling mprotect. 274 if (tag == VM_MEMORY_STACK) { 275 while (true) { 276 mach_vm_address_t next_region_base = stack_region_base + 277 stack_region_size; 278 mach_vm_address_t proposed_next_region_base = next_region_base; 279 mach_vm_size_t next_region_size; 280 nesting_level = 0; 281 mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT_64; 282 result = mach_vm_region_recurse(crashing_task_, &next_region_base, 283 &next_region_size, &nesting_level, 284 region_info, &info_count); 285 if (result != KERN_SUCCESS || 286 next_region_base != proposed_next_region_base || 287 submap_info.user_tag != tag || 288 submap_info.protection & VM_PROT_READ == 0) { 289 break; 290 } 291 292 stack_region_size += next_region_size; 293 } 294 } 295 296 return stack_region_base + stack_region_size - start_addr; 297} 298 299bool MinidumpGenerator::WriteStackFromStartAddress( 300 mach_vm_address_t start_addr, 301 MDMemoryDescriptor *stack_location) { 302 UntypedMDRVA memory(&writer_); 303 304 bool result = false; 305 size_t size = CalculateStackSize(start_addr); 306 307 if (size == 0) { 308 // In some situations the stack address for the thread can come back 0. 309 // In these cases we skip over the threads in question and stuff the 310 // stack with a clearly borked value. 311 start_addr = 0xDEADBEEF; 312 size = 16; 313 if (!memory.Allocate(size)) 314 return false; 315 316 unsigned long long dummy_stack[2]; // Fill dummy stack with 16 bytes of 317 // junk. 318 dummy_stack[0] = 0xDEADBEEF; 319 dummy_stack[1] = 0xDEADBEEF; 320 321 result = memory.Copy(dummy_stack, size); 322 } else { 323 324 if (!memory.Allocate(size)) 325 return false; 326 327 if (dynamic_images_) { 328 vector<uint8_t> stack_memory; 329 if (ReadTaskMemory(crashing_task_, 330 start_addr, 331 size, 332 stack_memory) != KERN_SUCCESS) { 333 return false; 334 } 335 336 result = memory.Copy(&stack_memory[0], size); 337 } else { 338 result = memory.Copy(reinterpret_cast<const void *>(start_addr), size); 339 } 340 } 341 342 stack_location->start_of_memory_range = start_addr; 343 stack_location->memory = memory.location(); 344 345 return result; 346} 347 348bool MinidumpGenerator::WriteStack(breakpad_thread_state_data_t state, 349 MDMemoryDescriptor *stack_location) { 350 switch (cpu_type_) { 351#ifdef HAS_PPC_SUPPORT 352 case CPU_TYPE_POWERPC: 353 return WriteStackPPC(state, stack_location); 354 case CPU_TYPE_POWERPC64: 355 return WriteStackPPC64(state, stack_location); 356#endif 357 case CPU_TYPE_I386: 358 return WriteStackX86(state, stack_location); 359 case CPU_TYPE_X86_64: 360 return WriteStackX86_64(state, stack_location); 361 default: 362 return false; 363 } 364} 365 366bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state, 367 MDLocationDescriptor *register_location) { 368 switch (cpu_type_) { 369#ifdef HAS_PPC_SUPPORT 370 case CPU_TYPE_POWERPC: 371 return WriteContextPPC(state, register_location); 372 case CPU_TYPE_POWERPC64: 373 return WriteContextPPC64(state, register_location); 374#endif 375 case CPU_TYPE_I386: 376 return WriteContextX86(state, register_location); 377 case CPU_TYPE_X86_64: 378 return WriteContextX86_64(state, register_location); 379 default: 380 return false; 381 } 382} 383 384u_int64_t MinidumpGenerator::CurrentPCForStack( 385 breakpad_thread_state_data_t state) { 386 switch (cpu_type_) { 387#ifdef HAS_PPC_SUPPORT 388 case CPU_TYPE_POWERPC: 389 return CurrentPCForStackPPC(state); 390 case CPU_TYPE_POWERPC64: 391 return CurrentPCForStackPPC64(state); 392#endif 393 case CPU_TYPE_I386: 394 return CurrentPCForStackX86(state); 395 case CPU_TYPE_X86_64: 396 return CurrentPCForStackX86_64(state); 397 default: 398 assert("Unknown CPU type!"); 399 return 0; 400 } 401} 402 403#ifdef HAS_PCC_SUPPORT 404bool MinidumpGenerator::WriteStackPPC(breakpad_thread_state_data_t state, 405 MDMemoryDescriptor *stack_location) { 406 ppc_thread_state_t *machine_state = 407 reinterpret_cast<ppc_thread_state_t *>(state); 408 mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, r1); 409 return WriteStackFromStartAddress(start_addr, stack_location); 410} 411 412bool MinidumpGenerator::WriteStackPPC64(breakpad_thread_state_data_t state, 413 MDMemoryDescriptor *stack_location) { 414 ppc_thread_state64_t *machine_state = 415 reinterpret_cast<ppc_thread_state64_t *>(state); 416 mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, r1); 417 return WriteStackFromStartAddress(start_addr, stack_location); 418} 419 420u_int64_t 421MinidumpGenerator::CurrentPCForStackPPC(breakpad_thread_state_data_t state) { 422 ppc_thread_state_t *machine_state = 423 reinterpret_cast<ppc_thread_state_t *>(state); 424 425 return REGISTER_FROM_THREADSTATE(machine_state, srr0); 426} 427 428u_int64_t 429MinidumpGenerator::CurrentPCForStackPPC64(breakpad_thread_state_data_t state) { 430 ppc_thread_state64_t *machine_state = 431 reinterpret_cast<ppc_thread_state64_t *>(state); 432 433 return REGISTER_FROM_THREADSTATE(machine_state, srr0); 434} 435 436bool MinidumpGenerator::WriteContextPPC(breakpad_thread_state_data_t state, 437 MDLocationDescriptor *register_location) 438{ 439 TypedMDRVA<MDRawContextPPC> context(&writer_); 440 ppc_thread_state_t *machine_state = 441 reinterpret_cast<ppc_thread_state_t *>(state); 442 443 if (!context.Allocate()) 444 return false; 445 446 *register_location = context.location(); 447 MDRawContextPPC *context_ptr = context.get(); 448 context_ptr->context_flags = MD_CONTEXT_PPC_BASE; 449 450#define AddReg(a) context_ptr->a = REGISTER_FROM_THREADSTATE(machine_state, a) 451#define AddGPR(a) context_ptr->gpr[a] = REGISTER_FROM_THREADSTATE(machine_state, r ## a) 452 453 AddReg(srr0); 454 AddReg(cr); 455 AddReg(xer); 456 AddReg(ctr); 457 AddReg(lr); 458 AddReg(vrsave); 459 460 AddGPR(0); 461 AddGPR(1); 462 AddGPR(2); 463 AddGPR(3); 464 AddGPR(4); 465 AddGPR(5); 466 AddGPR(6); 467 AddGPR(7); 468 AddGPR(8); 469 AddGPR(9); 470 AddGPR(10); 471 AddGPR(11); 472 AddGPR(12); 473 AddGPR(13); 474 AddGPR(14); 475 AddGPR(15); 476 AddGPR(16); 477 AddGPR(17); 478 AddGPR(18); 479 AddGPR(19); 480 AddGPR(20); 481 AddGPR(21); 482 AddGPR(22); 483 AddGPR(23); 484 AddGPR(24); 485 AddGPR(25); 486 AddGPR(26); 487 AddGPR(27); 488 AddGPR(28); 489 AddGPR(29); 490 AddGPR(30); 491 AddGPR(31); 492 AddReg(mq); 493#undef AddReg 494#undef AddGPR 495 496 return true; 497} 498 499bool MinidumpGenerator::WriteContextPPC64( 500 breakpad_thread_state_data_t state, 501 MDLocationDescriptor *register_location) { 502 TypedMDRVA<MDRawContextPPC64> context(&writer_); 503 ppc_thread_state64_t *machine_state = 504 reinterpret_cast<ppc_thread_state64_t *>(state); 505 506 if (!context.Allocate()) 507 return false; 508 509 *register_location = context.location(); 510 MDRawContextPPC64 *context_ptr = context.get(); 511 context_ptr->context_flags = MD_CONTEXT_PPC_BASE; 512 513#define AddReg(a) context_ptr->a = REGISTER_FROM_THREADSTATE(machine_state, a) 514#define AddGPR(a) context_ptr->gpr[a] = REGISTER_FROM_THREADSTATE(machine_state, r ## a) 515 516 AddReg(srr0); 517 AddReg(cr); 518 AddReg(xer); 519 AddReg(ctr); 520 AddReg(lr); 521 AddReg(vrsave); 522 523 AddGPR(0); 524 AddGPR(1); 525 AddGPR(2); 526 AddGPR(3); 527 AddGPR(4); 528 AddGPR(5); 529 AddGPR(6); 530 AddGPR(7); 531 AddGPR(8); 532 AddGPR(9); 533 AddGPR(10); 534 AddGPR(11); 535 AddGPR(12); 536 AddGPR(13); 537 AddGPR(14); 538 AddGPR(15); 539 AddGPR(16); 540 AddGPR(17); 541 AddGPR(18); 542 AddGPR(19); 543 AddGPR(20); 544 AddGPR(21); 545 AddGPR(22); 546 AddGPR(23); 547 AddGPR(24); 548 AddGPR(25); 549 AddGPR(26); 550 AddGPR(27); 551 AddGPR(28); 552 AddGPR(29); 553 AddGPR(30); 554 AddGPR(31); 555#undef AddReg 556#undef AddGPR 557 558 return true; 559} 560 561#endif 562 563bool MinidumpGenerator::WriteStackX86(breakpad_thread_state_data_t state, 564 MDMemoryDescriptor *stack_location) { 565 i386_thread_state_t *machine_state = 566 reinterpret_cast<i386_thread_state_t *>(state); 567 568 mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, esp); 569 return WriteStackFromStartAddress(start_addr, stack_location); 570} 571 572bool MinidumpGenerator::WriteStackX86_64(breakpad_thread_state_data_t state, 573 MDMemoryDescriptor *stack_location) { 574 x86_thread_state64_t *machine_state = 575 reinterpret_cast<x86_thread_state64_t *>(state); 576 577 mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, rsp); 578 return WriteStackFromStartAddress(start_addr, stack_location); 579} 580 581u_int64_t 582MinidumpGenerator::CurrentPCForStackX86(breakpad_thread_state_data_t state) { 583 i386_thread_state_t *machine_state = 584 reinterpret_cast<i386_thread_state_t *>(state); 585 586 return REGISTER_FROM_THREADSTATE(machine_state, eip); 587} 588 589u_int64_t 590MinidumpGenerator::CurrentPCForStackX86_64(breakpad_thread_state_data_t state) { 591 x86_thread_state64_t *machine_state = 592 reinterpret_cast<x86_thread_state64_t *>(state); 593 594 return REGISTER_FROM_THREADSTATE(machine_state, rip); 595} 596 597bool MinidumpGenerator::WriteContextX86(breakpad_thread_state_data_t state, 598 MDLocationDescriptor *register_location) 599{ 600 TypedMDRVA<MDRawContextX86> context(&writer_); 601 i386_thread_state_t *machine_state = 602 reinterpret_cast<i386_thread_state_t *>(state); 603 604 if (!context.Allocate()) 605 return false; 606 607 *register_location = context.location(); 608 MDRawContextX86 *context_ptr = context.get(); 609 610#define AddReg(a) context_ptr->a = REGISTER_FROM_THREADSTATE(machine_state, a) 611 612 context_ptr->context_flags = MD_CONTEXT_X86; 613 AddReg(eax); 614 AddReg(ebx); 615 AddReg(ecx); 616 AddReg(edx); 617 AddReg(esi); 618 AddReg(edi); 619 AddReg(ebp); 620 AddReg(esp); 621 622 AddReg(cs); 623 AddReg(ds); 624 AddReg(ss); 625 AddReg(es); 626 AddReg(fs); 627 AddReg(gs); 628 AddReg(eflags); 629 630 AddReg(eip); 631#undef AddReg 632 633 return true; 634} 635 636bool MinidumpGenerator::WriteContextX86_64( 637 breakpad_thread_state_data_t state, 638 MDLocationDescriptor *register_location) { 639 TypedMDRVA<MDRawContextAMD64> context(&writer_); 640 x86_thread_state64_t *machine_state = 641 reinterpret_cast<x86_thread_state64_t *>(state); 642 643 if (!context.Allocate()) 644 return false; 645 646 *register_location = context.location(); 647 MDRawContextAMD64 *context_ptr = context.get(); 648 649#define AddReg(a) context_ptr->a = REGISTER_FROM_THREADSTATE(machine_state, a) 650 651 context_ptr->context_flags = MD_CONTEXT_AMD64; 652 AddReg(rax); 653 AddReg(rbx); 654 AddReg(rcx); 655 AddReg(rdx); 656 AddReg(rdi); 657 AddReg(rsi); 658 AddReg(rbp); 659 AddReg(rsp); 660 AddReg(r8); 661 AddReg(r9); 662 AddReg(r10); 663 AddReg(r11); 664 AddReg(r12); 665 AddReg(r13); 666 AddReg(r14); 667 AddReg(r15); 668 AddReg(rip); 669 // according to AMD's software developer guide, bits above 18 are 670 // not used in the flags register. Since the minidump format 671 // specifies 32 bits for the flags register, we can truncate safely 672 // with no loss. 673 context_ptr->eflags = static_cast<u_int32_t>(REGISTER_FROM_THREADSTATE(machine_state, rflags)); 674 AddReg(cs); 675 AddReg(fs); 676 AddReg(gs); 677#undef AddReg 678 679 return true; 680} 681 682bool MinidumpGenerator::GetThreadState(thread_act_t target_thread, 683 thread_state_t state, 684 mach_msg_type_number_t *count) { 685 thread_state_flavor_t flavor; 686 switch (cpu_type_) { 687#ifdef HAS_PPC_SUPPORT 688 case CPU_TYPE_POWERPC: 689 flavor = PPC_THREAD_STATE; 690 break; 691 case CPU_TYPE_POWERPC64: 692 flavor = PPC_THREAD_STATE64; 693 break; 694#endif 695 case CPU_TYPE_I386: 696 flavor = i386_THREAD_STATE; 697 break; 698 case CPU_TYPE_X86_64: 699 flavor = x86_THREAD_STATE64; 700 break; 701 default: 702 return false; 703 } 704 return thread_get_state(target_thread, flavor, 705 state, count) == KERN_SUCCESS; 706} 707 708bool MinidumpGenerator::WriteThreadStream(mach_port_t thread_id, 709 MDRawThread *thread) { 710 breakpad_thread_state_data_t state; 711 mach_msg_type_number_t state_count 712 = static_cast<mach_msg_type_number_t>(sizeof(state)); 713 714 if (GetThreadState(thread_id, state, &state_count)) { 715 if (!WriteStack(state, &thread->stack)) 716 return false; 717 718 memory_blocks_.push_back(thread->stack); 719 720 if (!WriteContext(state, &thread->thread_context)) 721 return false; 722 723 thread->thread_id = thread_id; 724 } else { 725 return false; 726 } 727 728 return true; 729} 730 731bool MinidumpGenerator::WriteThreadListStream( 732 MDRawDirectory *thread_list_stream) { 733 TypedMDRVA<MDRawThreadList> list(&writer_); 734 thread_act_port_array_t threads_for_task; 735 mach_msg_type_number_t thread_count; 736 int non_generator_thread_count; 737 738 if (task_threads(crashing_task_, &threads_for_task, &thread_count)) 739 return false; 740 741 // Don't include the generator thread 742 if (handler_thread_ != MACH_PORT_NULL) 743 non_generator_thread_count = thread_count - 1; 744 else 745 non_generator_thread_count = thread_count; 746 if (!list.AllocateObjectAndArray(non_generator_thread_count, 747 sizeof(MDRawThread))) 748 return false; 749 750 thread_list_stream->stream_type = MD_THREAD_LIST_STREAM; 751 thread_list_stream->location = list.location(); 752 753 list.get()->number_of_threads = non_generator_thread_count; 754 755 MDRawThread thread; 756 int thread_idx = 0; 757 758 for (unsigned int i = 0; i < thread_count; ++i) { 759 memset(&thread, 0, sizeof(MDRawThread)); 760 761 if (threads_for_task[i] != handler_thread_) { 762 if (!WriteThreadStream(threads_for_task[i], &thread)) 763 return false; 764 765 list.CopyIndexAfterObject(thread_idx++, &thread, sizeof(MDRawThread)); 766 } 767 } 768 769 return true; 770} 771 772bool MinidumpGenerator::WriteMemoryListStream( 773 MDRawDirectory *memory_list_stream) { 774 TypedMDRVA<MDRawMemoryList> list(&writer_); 775 776 // If the dump has an exception, include some memory around the 777 // instruction pointer. 778 const size_t kIPMemorySize = 256; // bytes 779 bool have_ip_memory = false; 780 MDMemoryDescriptor ip_memory_d; 781 if (exception_thread_ && exception_type_) { 782 breakpad_thread_state_data_t state; 783 mach_msg_type_number_t stateCount 784 = static_cast<mach_msg_type_number_t>(sizeof(state)); 785 786 if (thread_get_state(exception_thread_, 787 BREAKPAD_MACHINE_THREAD_STATE, 788 state, 789 &stateCount) == KERN_SUCCESS) { 790 u_int64_t ip = CurrentPCForStack(state); 791 // Bound it to the upper and lower bounds of the region 792 // it's contained within. If it's not in a known memory region, 793 // don't bother trying to write it. 794 mach_vm_address_t addr = ip; 795 mach_vm_size_t size; 796 natural_t nesting_level = 0; 797 vm_region_submap_info_64 info; 798 mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT_64; 799 800 kern_return_t ret = 801 mach_vm_region_recurse(crashing_task_, 802 &addr, 803 &size, 804 &nesting_level, 805 (vm_region_recurse_info_t)&info, 806 &info_count); 807 if (ret == KERN_SUCCESS && ip >= addr && ip < (addr + size)) { 808 // Try to get 128 bytes before and after the IP, but 809 // settle for whatever's available. 810 ip_memory_d.start_of_memory_range = 811 std::max(uintptr_t(addr), 812 uintptr_t(ip - (kIPMemorySize / 2))); 813 uintptr_t end_of_range = 814 std::min(uintptr_t(ip + (kIPMemorySize / 2)), 815 uintptr_t(addr + size)); 816 ip_memory_d.memory.data_size = 817 end_of_range - ip_memory_d.start_of_memory_range; 818 have_ip_memory = true; 819 // This needs to get appended to the list even though 820 // the memory bytes aren't filled in yet so the entire 821 // list can be written first. The memory bytes will get filled 822 // in after the memory list is written. 823 memory_blocks_.push_back(ip_memory_d); 824 } 825 } 826 } 827 828 // Now fill in the memory list and write it. 829 unsigned memory_count = memory_blocks_.size(); 830 if (!list.AllocateObjectAndArray(memory_count, 831 sizeof(MDMemoryDescriptor))) 832 return false; 833 834 memory_list_stream->stream_type = MD_MEMORY_LIST_STREAM; 835 memory_list_stream->location = list.location(); 836 837 list.get()->number_of_memory_ranges = memory_count; 838 839 unsigned int i; 840 for (i = 0; i < memory_count; ++i) { 841 list.CopyIndexAfterObject(i, &memory_blocks_[i], 842 sizeof(MDMemoryDescriptor)); 843 } 844 845 if (have_ip_memory) { 846 // Now read the memory around the instruction pointer. 847 UntypedMDRVA ip_memory(&writer_); 848 if (!ip_memory.Allocate(ip_memory_d.memory.data_size)) 849 return false; 850 851 if (dynamic_images_) { 852 // Out-of-process. 853 vector<uint8_t> memory; 854 if (ReadTaskMemory(crashing_task_, 855 ip_memory_d.start_of_memory_range, 856 ip_memory_d.memory.data_size, 857 memory) != KERN_SUCCESS) { 858 return false; 859 } 860 861 ip_memory.Copy(&memory[0], ip_memory_d.memory.data_size); 862 } else { 863 // In-process, just copy from local memory. 864 ip_memory.Copy( 865 reinterpret_cast<const void *>(ip_memory_d.start_of_memory_range), 866 ip_memory_d.memory.data_size); 867 } 868 869 ip_memory_d.memory = ip_memory.location(); 870 // Write this again now that the data location is filled in. 871 list.CopyIndexAfterObject(i - 1, &ip_memory_d, 872 sizeof(MDMemoryDescriptor)); 873 } 874 875 return true; 876} 877 878bool 879MinidumpGenerator::WriteExceptionStream(MDRawDirectory *exception_stream) { 880 TypedMDRVA<MDRawExceptionStream> exception(&writer_); 881 882 if (!exception.Allocate()) 883 return false; 884 885 exception_stream->stream_type = MD_EXCEPTION_STREAM; 886 exception_stream->location = exception.location(); 887 MDRawExceptionStream *exception_ptr = exception.get(); 888 exception_ptr->thread_id = exception_thread_; 889 890 // This naming is confusing, but it is the proper translation from 891 // mach naming to minidump naming. 892 exception_ptr->exception_record.exception_code = exception_type_; 893 exception_ptr->exception_record.exception_flags = exception_code_; 894 895 breakpad_thread_state_data_t state; 896 mach_msg_type_number_t state_count 897 = static_cast<mach_msg_type_number_t>(sizeof(state)); 898 899 if (!GetThreadState(exception_thread_, state, &state_count)) 900 return false; 901 902 if (!WriteContext(state, &exception_ptr->thread_context)) 903 return false; 904 905 if (exception_type_ == EXC_BAD_ACCESS) 906 exception_ptr->exception_record.exception_address = exception_subcode_; 907 else 908 exception_ptr->exception_record.exception_address = CurrentPCForStack(state); 909 910 return true; 911} 912 913bool MinidumpGenerator::WriteSystemInfoStream( 914 MDRawDirectory *system_info_stream) { 915 TypedMDRVA<MDRawSystemInfo> info(&writer_); 916 917 if (!info.Allocate()) 918 return false; 919 920 system_info_stream->stream_type = MD_SYSTEM_INFO_STREAM; 921 system_info_stream->location = info.location(); 922 923 // CPU Information 924 uint32_t number_of_processors; 925 size_t len = sizeof(number_of_processors); 926 sysctlbyname("hw.ncpu", &number_of_processors, &len, NULL, 0); 927 MDRawSystemInfo *info_ptr = info.get(); 928 929 switch (cpu_type_) { 930 case CPU_TYPE_POWERPC: 931 case CPU_TYPE_POWERPC64: 932 info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_PPC; 933 break; 934 case CPU_TYPE_I386: 935 case CPU_TYPE_X86_64: 936 if (cpu_type_ == CPU_TYPE_I386) 937 info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_X86; 938 else 939 info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_AMD64; 940#ifdef __i386__ 941 // ebx is used for PIC code, so we need 942 // to preserve it. 943#define cpuid(op,eax,ebx,ecx,edx) \ 944 asm ("pushl %%ebx \n\t" \ 945 "cpuid \n\t" \ 946 "movl %%ebx,%1 \n\t" \ 947 "popl %%ebx" \ 948 : "=a" (eax), \ 949 "=g" (ebx), \ 950 "=c" (ecx), \ 951 "=d" (edx) \ 952 : "0" (op)) 953#elif defined(__x86_64__) 954 955#define cpuid(op,eax,ebx,ecx,edx) \ 956 asm ("cpuid \n\t" \ 957 : "=a" (eax), \ 958 "=b" (ebx), \ 959 "=c" (ecx), \ 960 "=d" (edx) \ 961 : "0" (op)) 962#endif 963 964#if defined(__i386__) || defined(__x86_64__) 965 int unused, unused2; 966 // get vendor id 967 cpuid(0, unused, info_ptr->cpu.x86_cpu_info.vendor_id[0], 968 info_ptr->cpu.x86_cpu_info.vendor_id[2], 969 info_ptr->cpu.x86_cpu_info.vendor_id[1]); 970 // get version and feature info 971 cpuid(1, info_ptr->cpu.x86_cpu_info.version_information, unused, unused2, 972 info_ptr->cpu.x86_cpu_info.feature_information); 973 974 // family 975 info_ptr->processor_level = 976 (info_ptr->cpu.x86_cpu_info.version_information & 0xF00) >> 8; 977 // 0xMMSS (Model, Stepping) 978 info_ptr->processor_revision = 979 (info_ptr->cpu.x86_cpu_info.version_information & 0xF) | 980 ((info_ptr->cpu.x86_cpu_info.version_information & 0xF0) << 4); 981 982 // decode extended model info 983 if (info_ptr->processor_level == 0xF || 984 info_ptr->processor_level == 0x6) { 985 info_ptr->processor_revision |= 986 ((info_ptr->cpu.x86_cpu_info.version_information & 0xF0000) >> 4); 987 } 988 989 // decode extended family info 990 if (info_ptr->processor_level == 0xF) { 991 info_ptr->processor_level += 992 ((info_ptr->cpu.x86_cpu_info.version_information & 0xFF00000) >> 20); 993 } 994 995#endif // __i386__ || __x86_64_ 996 break; 997 default: 998 info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_UNKNOWN; 999 break; 1000 } 1001 1002 info_ptr->number_of_processors = number_of_processors; 1003 info_ptr->platform_id = MD_OS_MAC_OS_X; 1004 1005 MDLocationDescriptor build_string_loc; 1006 1007 if (!writer_.WriteString(build_string_, 0, 1008 &build_string_loc)) 1009 return false; 1010 1011 info_ptr->csd_version_rva = build_string_loc.rva; 1012 info_ptr->major_version = os_major_version_; 1013 info_ptr->minor_version = os_minor_version_; 1014 info_ptr->build_number = os_build_number_; 1015 1016 return true; 1017} 1018 1019bool MinidumpGenerator::WriteModuleStream(unsigned int index, 1020 MDRawModule *module) { 1021 if (dynamic_images_) { 1022 // we're in a different process than the crashed process 1023 DynamicImage *image = dynamic_images_->GetImage(index); 1024 1025 if (!image) 1026 return false; 1027 1028 memset(module, 0, sizeof(MDRawModule)); 1029 1030 MDLocationDescriptor string_location; 1031 1032 string name = image->GetFilePath(); 1033 if (!writer_.WriteString(name.c_str(), 0, &string_location)) 1034 return false; 1035 1036 module->base_of_image = image->GetVMAddr() + image->GetVMAddrSlide(); 1037 module->size_of_image = static_cast<u_int32_t>(image->GetVMSize()); 1038 module->module_name_rva = string_location.rva; 1039 1040 // We'll skip the executable module, because they don't have 1041 // LC_ID_DYLIB load commands, and the crash processing server gets 1042 // version information from the Plist file, anyway. 1043 if (index != (uint32_t)FindExecutableModule()) { 1044 module->version_info.signature = MD_VSFIXEDFILEINFO_SIGNATURE; 1045 module->version_info.struct_version |= MD_VSFIXEDFILEINFO_VERSION; 1046 // Convert MAC dylib version format, which is a 32 bit number, to the 1047 // format used by minidump. The mac format is <16 bits>.<8 bits>.<8 bits> 1048 // so it fits nicely into the windows version with some massaging 1049 // The mapping is: 1050 // 1) upper 16 bits of MAC version go to lower 16 bits of product HI 1051 // 2) Next most significant 8 bits go to upper 16 bits of product LO 1052 // 3) Least significant 8 bits go to lower 16 bits of product LO 1053 uint32_t modVersion = image->GetVersion(); 1054 module->version_info.file_version_hi = 0; 1055 module->version_info.file_version_hi = modVersion >> 16; 1056 module->version_info.file_version_lo |= (modVersion & 0xff00) << 8; 1057 module->version_info.file_version_lo |= (modVersion & 0xff); 1058 } 1059 1060 if (!WriteCVRecord(module, image->GetCPUType(), name.c_str())) { 1061 return false; 1062 } 1063 } else { 1064 // Getting module info in the crashed process 1065 const breakpad_mach_header *header; 1066 header = (breakpad_mach_header*)_dyld_get_image_header(index); 1067 if (!header) 1068 return false; 1069 1070#ifdef __LP64__ 1071 assert(header->magic == MH_MAGIC_64); 1072 1073 if(header->magic != MH_MAGIC_64) 1074 return false; 1075#else 1076 assert(header->magic == MH_MAGIC); 1077 1078 if(header->magic != MH_MAGIC) 1079 return false; 1080#endif 1081 1082 int cpu_type = header->cputype; 1083 unsigned long slide = _dyld_get_image_vmaddr_slide(index); 1084 const char* name = _dyld_get_image_name(index); 1085 const struct load_command *cmd = 1086 reinterpret_cast<const struct load_command *>(header + 1); 1087 1088 memset(module, 0, sizeof(MDRawModule)); 1089 1090 for (unsigned int i = 0; cmd && (i < header->ncmds); i++) { 1091 if (cmd->cmd == LC_SEGMENT_ARCH) { 1092 1093 const breakpad_mach_segment_command *seg = 1094 reinterpret_cast<const breakpad_mach_segment_command *>(cmd); 1095 1096 if (!strcmp(seg->segname, "__TEXT")) { 1097 MDLocationDescriptor string_location; 1098 1099 if (!writer_.WriteString(name, 0, &string_location)) 1100 return false; 1101 1102 module->base_of_image = seg->vmaddr + slide; 1103 module->size_of_image = static_cast<u_int32_t>(seg->vmsize); 1104 module->module_name_rva = string_location.rva; 1105 1106 if (!WriteCVRecord(module, cpu_type, name)) 1107 return false; 1108 1109 return true; 1110 } 1111 } 1112 1113 cmd = reinterpret_cast<struct load_command*>((char *)cmd + cmd->cmdsize); 1114 } 1115 } 1116 1117 return true; 1118} 1119 1120int MinidumpGenerator::FindExecutableModule() { 1121 if (dynamic_images_) { 1122 int index = dynamic_images_->GetExecutableImageIndex(); 1123 1124 if (index >= 0) { 1125 return index; 1126 } 1127 } else { 1128 int image_count = _dyld_image_count(); 1129 const struct mach_header *header; 1130 1131 for (int index = 0; index < image_count; ++index) { 1132 header = _dyld_get_image_header(index); 1133 1134 if (header->filetype == MH_EXECUTE) 1135 return index; 1136 } 1137 } 1138 1139 // failed - just use the first image 1140 return 0; 1141} 1142 1143bool MinidumpGenerator::WriteCVRecord(MDRawModule *module, int cpu_type, 1144 const char *module_path) { 1145 TypedMDRVA<MDCVInfoPDB70> cv(&writer_); 1146 1147 // Only return the last path component of the full module path 1148 const char *module_name = strrchr(module_path, '/'); 1149 1150 // Increment past the slash 1151 if (module_name) 1152 ++module_name; 1153 else 1154 module_name = "<Unknown>"; 1155 1156 size_t module_name_length = strlen(module_name); 1157 1158 if (!cv.AllocateObjectAndArray(module_name_length + 1, sizeof(u_int8_t))) 1159 return false; 1160 1161 if (!cv.CopyIndexAfterObject(0, module_name, module_name_length)) 1162 return false; 1163 1164 module->cv_record = cv.location(); 1165 MDCVInfoPDB70 *cv_ptr = cv.get(); 1166 cv_ptr->cv_signature = MD_CVINFOPDB70_SIGNATURE; 1167 cv_ptr->age = 0; 1168 1169 // Get the module identifier 1170 FileID file_id(module_path); 1171 unsigned char identifier[16]; 1172 1173 if (file_id.MachoIdentifier(cpu_type, identifier)) { 1174 cv_ptr->signature.data1 = (uint32_t)identifier[0] << 24 | 1175 (uint32_t)identifier[1] << 16 | (uint32_t)identifier[2] << 8 | 1176 (uint32_t)identifier[3]; 1177 cv_ptr->signature.data2 = (uint32_t)identifier[4] << 8 | identifier[5]; 1178 cv_ptr->signature.data3 = (uint32_t)identifier[6] << 8 | identifier[7]; 1179 cv_ptr->signature.data4[0] = identifier[8]; 1180 cv_ptr->signature.data4[1] = identifier[9]; 1181 cv_ptr->signature.data4[2] = identifier[10]; 1182 cv_ptr->signature.data4[3] = identifier[11]; 1183 cv_ptr->signature.data4[4] = identifier[12]; 1184 cv_ptr->signature.data4[5] = identifier[13]; 1185 cv_ptr->signature.data4[6] = identifier[14]; 1186 cv_ptr->signature.data4[7] = identifier[15]; 1187 } 1188 1189 return true; 1190} 1191 1192bool MinidumpGenerator::WriteModuleListStream( 1193 MDRawDirectory *module_list_stream) { 1194 TypedMDRVA<MDRawModuleList> list(&writer_); 1195 1196 int image_count = dynamic_images_ ? 1197 dynamic_images_->GetImageCount() : _dyld_image_count(); 1198 1199 if (!list.AllocateObjectAndArray(image_count, MD_MODULE_SIZE)) 1200 return false; 1201 1202 module_list_stream->stream_type = MD_MODULE_LIST_STREAM; 1203 module_list_stream->location = list.location(); 1204 list.get()->number_of_modules = image_count; 1205 1206 // Write out the executable module as the first one 1207 MDRawModule module; 1208 int executableIndex = FindExecutableModule(); 1209 1210 if (!WriteModuleStream(executableIndex, &module)) { 1211 return false; 1212 } 1213 1214 list.CopyIndexAfterObject(0, &module, MD_MODULE_SIZE); 1215 int destinationIndex = 1; // Write all other modules after this one 1216 1217 for (int i = 0; i < image_count; ++i) { 1218 if (i != executableIndex) { 1219 if (!WriteModuleStream(i, &module)) { 1220 return false; 1221 } 1222 1223 list.CopyIndexAfterObject(destinationIndex++, &module, MD_MODULE_SIZE); 1224 } 1225 } 1226 1227 return true; 1228} 1229 1230bool MinidumpGenerator::WriteMiscInfoStream(MDRawDirectory *misc_info_stream) { 1231 TypedMDRVA<MDRawMiscInfo> info(&writer_); 1232 1233 if (!info.Allocate()) 1234 return false; 1235 1236 misc_info_stream->stream_type = MD_MISC_INFO_STREAM; 1237 misc_info_stream->location = info.location(); 1238 1239 MDRawMiscInfo *info_ptr = info.get(); 1240 info_ptr->size_of_info = static_cast<u_int32_t>(sizeof(MDRawMiscInfo)); 1241 info_ptr->flags1 = MD_MISCINFO_FLAGS1_PROCESS_ID | 1242 MD_MISCINFO_FLAGS1_PROCESS_TIMES | 1243 MD_MISCINFO_FLAGS1_PROCESSOR_POWER_INFO; 1244 1245 // Process ID 1246 info_ptr->process_id = getpid(); 1247 1248 // Times 1249 struct rusage usage; 1250 if (getrusage(RUSAGE_SELF, &usage) != -1) { 1251 // Omit the fractional time since the MDRawMiscInfo only wants seconds 1252 info_ptr->process_user_time = 1253 static_cast<u_int32_t>(usage.ru_utime.tv_sec); 1254 info_ptr->process_kernel_time = 1255 static_cast<u_int32_t>(usage.ru_stime.tv_sec); 1256 } 1257 int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, 1258 static_cast<int>(info_ptr->process_id) }; 1259 u_int mibsize = static_cast<u_int>(sizeof(mib) / sizeof(mib[0])); 1260 size_t size; 1261 if (!sysctl(mib, mibsize, NULL, &size, NULL, 0)) { 1262 mach_vm_address_t addr; 1263 if (mach_vm_allocate(mach_task_self(), 1264 &addr, 1265 size, 1266 true) == KERN_SUCCESS) { 1267 struct kinfo_proc *proc = (struct kinfo_proc *)addr; 1268 if (!sysctl(mib, mibsize, proc, &size, NULL, 0)) 1269 info_ptr->process_create_time = 1270 static_cast<u_int32_t>(proc->kp_proc.p_starttime.tv_sec); 1271 mach_vm_deallocate(mach_task_self(), addr, size); 1272 } 1273 } 1274 1275 // Speed 1276 uint64_t speed; 1277 const uint64_t kOneMillion = 1000 * 1000; 1278 size = sizeof(speed); 1279 sysctlbyname("hw.cpufrequency_max", &speed, &size, NULL, 0); 1280 info_ptr->processor_max_mhz = static_cast<u_int32_t>(speed / kOneMillion); 1281 info_ptr->processor_mhz_limit = static_cast<u_int32_t>(speed / kOneMillion); 1282 size = sizeof(speed); 1283 sysctlbyname("hw.cpufrequency", &speed, &size, NULL, 0); 1284 info_ptr->processor_current_mhz = static_cast<u_int32_t>(speed / kOneMillion); 1285 1286 return true; 1287} 1288 1289bool MinidumpGenerator::WriteBreakpadInfoStream( 1290 MDRawDirectory *breakpad_info_stream) { 1291 TypedMDRVA<MDRawBreakpadInfo> info(&writer_); 1292 1293 if (!info.Allocate()) 1294 return false; 1295 1296 breakpad_info_stream->stream_type = MD_BREAKPAD_INFO_STREAM; 1297 breakpad_info_stream->location = info.location(); 1298 MDRawBreakpadInfo *info_ptr = info.get(); 1299 1300 if (exception_thread_ && exception_type_) { 1301 info_ptr->validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID | 1302 MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID; 1303 info_ptr->dump_thread_id = handler_thread_; 1304 info_ptr->requesting_thread_id = exception_thread_; 1305 } else { 1306 info_ptr->validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID; 1307 info_ptr->dump_thread_id = handler_thread_; 1308 info_ptr->requesting_thread_id = 0; 1309 } 1310 1311 return true; 1312} 1313 1314} // namespace google_breakpad 1315