LCOV - code coverage report
Current view: directory - toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer - linux_dumper.cc (source / functions) Found Hit Coverage
Test: app.info Lines: 223 0 0.0 %
Date: 2012-06-02 Functions: 17 0 0.0 %

       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                 : // This code deals with the mechanics of getting information about a crashed
      31                 : // process. Since this code may run in a compromised address space, the same
      32                 : // rules apply as detailed at the top of minidump_writer.h: no libc calls and
      33                 : // use the alternative allocator.
      34                 : 
      35                 : #include "client/linux/minidump_writer/linux_dumper.h"
      36                 : 
      37                 : #include <assert.h>
      38                 : #include <limits.h>
      39                 : #include <stddef.h>
      40                 : #include <stdlib.h>
      41                 : #include <stdio.h>
      42                 : #include <string.h>
      43                 : 
      44                 : #include <unistd.h>
      45                 : #include <elf.h>
      46                 : #include <errno.h>
      47                 : #include <fcntl.h>
      48                 : #if !defined(__ANDROID__)
      49                 : #include <link.h>
      50                 : #endif
      51                 : 
      52                 : #include <sys/types.h>
      53                 : #include <sys/ptrace.h>
      54                 : #include <sys/wait.h>
      55                 : 
      56                 : #include <algorithm>
      57                 : 
      58                 : #include "client/linux/minidump_writer/directory_reader.h"
      59                 : #include "client/linux/minidump_writer/line_reader.h"
      60                 : #include "common/linux/file_id.h"
      61                 : #include "common/linux/linux_libc_support.h"
      62                 : #include "common/linux/linux_syscall_support.h"
      63                 : 
      64                 : static const char kMappedFileUnsafePrefix[] = "/dev/";
      65                 : 
      66                 : namespace google_breakpad {
      67                 : 
      68               0 : bool AttachThread(pid_t pid) {
      69                 :   // This may fail if the thread has just died or debugged.
      70               0 :   errno = 0;
      71               0 :   if (sys_ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0 &&
      72               0 :       errno != 0) {
      73               0 :     return false;
      74                 :   }
      75               0 :   while (sys_waitpid(pid, NULL, __WALL) < 0) {
      76               0 :     if (errno != EINTR) {
      77               0 :       sys_ptrace(PTRACE_DETACH, pid, NULL, NULL);
      78               0 :       return false;
      79                 :     }
      80                 :   }
      81                 : #if defined(__i386) || defined(__x86_64)
      82                 :   // On x86, the stack pointer is NULL or -1, when executing trusted code in
      83                 :   // the seccomp sandbox. Not only does this cause difficulties down the line
      84                 :   // when trying to dump the thread's stack, it also results in the minidumps
      85                 :   // containing information about the trusted threads. This information is
      86                 :   // generally completely meaningless and just pollutes the minidumps.
      87                 :   // We thus test the stack pointer and exclude any threads that are part of
      88                 :   // the seccomp sandbox's trusted code.
      89                 :   user_regs_struct regs;
      90               0 :   if (sys_ptrace(PTRACE_GETREGS, pid, NULL, &regs) == -1 ||
      91                 : #if defined(__i386)
      92               0 :       !regs.esp
      93                 : #elif defined(__x86_64)
      94                 :       !regs.rsp
      95                 : #endif
      96                 :       ) {
      97               0 :     sys_ptrace(PTRACE_DETACH, pid, NULL, NULL);
      98               0 :     return false;
      99                 :   }
     100                 : #endif
     101               0 :   return true;
     102                 : }
     103                 : 
     104               0 : bool DetachThread(pid_t pid) {
     105               0 :   return sys_ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0;
     106                 : }
     107                 : 
     108               0 : inline bool IsMappedFileOpenUnsafe(
     109                 :     const google_breakpad::MappingInfo& mapping) {
     110                 :   // It is unsafe to attempt to open a mapped file that lives under /dev,
     111                 :   // because the semantics of the open may be driver-specific so we'd risk
     112                 :   // hanging the crash dumper. And a file in /dev/ almost certainly has no
     113                 :   // ELF file identifier anyways.
     114                 :   return my_strncmp(mapping.name,
     115                 :                     kMappedFileUnsafePrefix,
     116               0 :                     sizeof(kMappedFileUnsafePrefix) - 1) == 0;
     117                 : }
     118                 : 
     119               0 : bool GetThreadRegisters(ThreadInfo* info) {
     120               0 :   pid_t tid = info->tid;
     121                 : 
     122               0 :   if (sys_ptrace(PTRACE_GETREGS, tid, NULL, &info->regs) == -1) {
     123               0 :     return false;
     124                 :   }
     125                 : 
     126                 : #if !defined(__ANDROID__)
     127               0 :   if (sys_ptrace(PTRACE_GETFPREGS, tid, NULL, &info->fpregs) == -1) {
     128               0 :     return false;
     129                 :   }
     130                 : #endif
     131                 : 
     132                 : #if defined(__i386)
     133               0 :   if (sys_ptrace(PTRACE_GETFPXREGS, tid, NULL, &info->fpxregs) == -1)
     134               0 :     return false;
     135                 : #endif
     136                 : 
     137                 : #if defined(__i386) || defined(__x86_64)
     138               0 :   for (unsigned i = 0; i < ThreadInfo::kNumDebugRegisters; ++i) {
     139               0 :     if (sys_ptrace(
     140                 :         PTRACE_PEEKUSER, tid,
     141                 :         reinterpret_cast<void*> (offsetof(struct user,
     142                 :                                           u_debugreg[0]) + i *
     143                 :                                  sizeof(debugreg_t)),
     144               0 :         &info->dregs[i]) == -1) {
     145               0 :       return false;
     146                 :     }
     147                 :   }
     148                 : #endif
     149                 : 
     150               0 :   return true;
     151                 : }
     152                 : 
     153               0 : LinuxDumper::LinuxDumper(int pid)
     154                 :     : pid_(pid),
     155                 :       threads_suspended_(false),
     156                 :       threads_(&allocator_, 8),
     157               0 :       mappings_(&allocator_) {
     158               0 : }
     159                 : 
     160               0 : bool LinuxDumper::Init() {
     161               0 :   return EnumerateThreads(&threads_) &&
     162               0 :          EnumerateMappings(&mappings_);
     163                 : }
     164                 : 
     165               0 : bool LinuxDumper::ThreadsAttach() {
     166               0 :   if (threads_suspended_)
     167               0 :     return true;
     168               0 :   for (size_t i = 0; i < threads_.size(); ++i) {
     169               0 :     if (!AttachThread(threads_[i])) {
     170                 :       // If the thread either disappeared before we could attach to it, or if
     171                 :       // it was part of the seccomp sandbox's trusted code, it is OK to
     172                 :       // silently drop it from the minidump.
     173               0 :       memmove(&threads_[i], &threads_[i+1],
     174               0 :               (threads_.size() - i - 1) * sizeof(threads_[i]));
     175               0 :       threads_.resize(threads_.size() - 1);
     176               0 :       --i;
     177                 :     }
     178                 :   }
     179               0 :   threads_suspended_ = true;
     180               0 :   return threads_.size() > 0;
     181                 : }
     182                 : 
     183               0 : bool LinuxDumper::ThreadsDetach() {
     184               0 :   if (!threads_suspended_)
     185               0 :     return false;
     186               0 :   bool good = true;
     187               0 :   for (size_t i = 0; i < threads_.size(); ++i)
     188               0 :     good &= DetachThread(threads_[i]);
     189               0 :   threads_suspended_ = false;
     190               0 :   return good;
     191                 : }
     192                 : 
     193                 : void
     194               0 : LinuxDumper::BuildProcPath(char* path, pid_t pid, const char* node) const {
     195               0 :   assert(path);
     196               0 :   if (!path) {
     197               0 :     return;
     198                 :   }
     199                 : 
     200               0 :   path[0] = '\0';
     201                 : 
     202               0 :   const unsigned pid_len = my_int_len(pid);
     203                 : 
     204               0 :   assert(node);
     205               0 :   if (!node) {
     206               0 :     return;
     207                 :   }
     208                 : 
     209               0 :   size_t node_len = my_strlen(node);
     210               0 :   assert(node_len < NAME_MAX);
     211               0 :   if (node_len >= NAME_MAX) {
     212               0 :     return;
     213                 :   }
     214                 : 
     215               0 :   assert(node_len > 0);
     216               0 :   if (node_len == 0) {
     217               0 :     return;
     218                 :   }
     219                 : 
     220               0 :   assert(pid > 0);
     221               0 :   if (pid <= 0) {
     222               0 :     return;
     223                 :   }
     224                 : 
     225               0 :   const size_t total_length = 6 + pid_len + 1 + node_len;
     226                 : 
     227               0 :   assert(total_length < NAME_MAX);
     228               0 :   if (total_length >= NAME_MAX) {
     229               0 :     return;
     230                 :   }
     231                 : 
     232               0 :   memcpy(path, "/proc/", 6);
     233               0 :   my_itos(path + 6, pid, pid_len);
     234               0 :   memcpy(path + 6 + pid_len, "/", 1);
     235               0 :   memcpy(path + 6 + pid_len + 1, node, node_len);
     236               0 :   memcpy(path + total_length, "\0", 1);
     237                 : }
     238                 : 
     239                 : bool
     240               0 : LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping,
     241                 :                                          uint8_t identifier[sizeof(MDGUID)])
     242                 : {
     243               0 :   my_memset(identifier, 0, sizeof(MDGUID));
     244               0 :   if (IsMappedFileOpenUnsafe(mapping)) {
     245               0 :     return false;
     246                 :   }
     247               0 :   int fd = sys_open(mapping.name, O_RDONLY, 0);
     248               0 :   if (fd < 0)
     249               0 :     return false;
     250                 :   struct kernel_stat st;
     251               0 :   if (sys_fstat(fd, &st) != 0) {
     252               0 :     sys_close(fd);
     253               0 :     return false;
     254                 :   }
     255                 : #if defined(__x86_64)
     256                 : #define sys_mmap2 sys_mmap
     257                 : #endif
     258               0 :   void* base = sys_mmap2(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
     259               0 :   sys_close(fd);
     260               0 :   if (base == MAP_FAILED)
     261               0 :     return false;
     262                 : 
     263               0 :   bool success = FileID::ElfFileIdentifierFromMappedFile(base, identifier);
     264               0 :   sys_munmap(base, st.st_size);
     265               0 :   return success;
     266                 : }
     267                 : 
     268                 : void*
     269               0 : LinuxDumper::FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const {
     270                 :   char auxv_path[80];
     271               0 :   BuildProcPath(auxv_path, pid, "auxv");
     272                 : 
     273                 :   // If BuildProcPath errors out due to invalid input, we'll handle it when
     274                 :   // we try to sys_open the file.
     275                 : 
     276                 :   // Find the AT_SYSINFO_EHDR entry for linux-gate.so
     277                 :   // See http://www.trilithium.com/johan/2005/08/linux-gate/ for more
     278                 :   // information.
     279               0 :   int fd = sys_open(auxv_path, O_RDONLY, 0);
     280               0 :   if (fd < 0) {
     281               0 :     return NULL;
     282                 :   }
     283                 : 
     284                 :   elf_aux_entry one_aux_entry;
     285               0 :   while (sys_read(fd,
     286                 :                   &one_aux_entry,
     287               0 :                   sizeof(elf_aux_entry)) == sizeof(elf_aux_entry) &&
     288                 :          one_aux_entry.a_type != AT_NULL) {
     289               0 :     if (one_aux_entry.a_type == AT_SYSINFO_EHDR) {
     290               0 :       close(fd);
     291               0 :       return reinterpret_cast<void*>(one_aux_entry.a_un.a_val);
     292                 :     }
     293                 :   }
     294               0 :   close(fd);
     295               0 :   return NULL;
     296                 : }
     297                 : 
     298                 : bool
     299               0 : LinuxDumper::EnumerateMappings(wasteful_vector<MappingInfo*>* result) const {
     300                 :   char maps_path[80];
     301               0 :   BuildProcPath(maps_path, pid_, "maps");
     302                 : 
     303                 :   // linux_gate_loc is the beginning of the kernel's mapping of
     304                 :   // linux-gate.so in the process.  It doesn't actually show up in the
     305                 :   // maps list as a filename, so we use the aux vector to find it's
     306                 :   // load location and special case it's entry when creating the list
     307                 :   // of mappings.
     308                 :   const void* linux_gate_loc;
     309               0 :   linux_gate_loc = FindBeginningOfLinuxGateSharedLibrary(pid_);
     310                 : 
     311               0 :   const int fd = sys_open(maps_path, O_RDONLY, 0);
     312               0 :   if (fd < 0)
     313               0 :     return false;
     314               0 :   LineReader* const line_reader = new(allocator_) LineReader(fd);
     315                 : 
     316                 :   const char* line;
     317                 :   unsigned line_len;
     318               0 :   while (line_reader->GetNextLine(&line, &line_len)) {
     319                 :     uintptr_t start_addr, end_addr, offset;
     320                 : 
     321               0 :     const char* i1 = my_read_hex_ptr(&start_addr, line);
     322               0 :     if (*i1 == '-') {
     323               0 :       const char* i2 = my_read_hex_ptr(&end_addr, i1 + 1);
     324               0 :       if (*i2 == ' ') {
     325               0 :         const char* i3 = my_read_hex_ptr(&offset, i2 + 6 /* skip ' rwxp ' */);
     326               0 :         if (*i3 == ' ') {
     327               0 :           const char* name = NULL;
     328                 :           // Only copy name if the name is a valid path name, or if
     329                 :           // it's the VDSO image.
     330               0 :           if (((name = my_strchr(line, '/')) == NULL) &&
     331                 :               linux_gate_loc &&
     332                 :               reinterpret_cast<void*>(start_addr) == linux_gate_loc) {
     333               0 :             name = kLinuxGateLibraryName;
     334               0 :             offset = 0;
     335                 :           }
     336                 :           // Merge adjacent mappings with the same name into one module,
     337                 :           // assuming they're a single library mapped by the dynamic linker
     338               0 :           if (name && result->size()) {
     339               0 :             MappingInfo* module = (*result)[result->size() - 1];
     340               0 :             if ((start_addr == module->start_addr + module->size) &&
     341               0 :                 (my_strlen(name) == my_strlen(module->name)) &&
     342               0 :                 (my_strncmp(name, module->name, my_strlen(name)) == 0)) {
     343               0 :               module->size = end_addr - module->start_addr;
     344               0 :               line_reader->PopLine(line_len);
     345               0 :               continue;
     346                 :             }
     347                 :           }
     348               0 :           MappingInfo* const module = new(allocator_) MappingInfo;
     349               0 :           memset(module, 0, sizeof(MappingInfo));
     350               0 :           module->start_addr = start_addr;
     351               0 :           module->size = end_addr - start_addr;
     352               0 :           module->offset = offset;
     353               0 :           if (name != NULL) {
     354               0 :             const unsigned l = my_strlen(name);
     355               0 :             if (l < sizeof(module->name))
     356               0 :               memcpy(module->name, name, l);
     357                 :           }
     358               0 :           result->push_back(module);
     359                 :         }
     360                 :       }
     361                 :     }
     362               0 :     line_reader->PopLine(line_len);
     363                 :   }
     364                 : 
     365               0 :   sys_close(fd);
     366                 : 
     367               0 :   return result->size() > 0;
     368                 : }
     369                 : 
     370                 : // Parse /proc/$pid/task to list all the threads of the process identified by
     371                 : // pid.
     372               0 : bool LinuxDumper::EnumerateThreads(wasteful_vector<pid_t>* result) const {
     373                 :   char task_path[80];
     374               0 :   BuildProcPath(task_path, pid_, "task");
     375                 : 
     376               0 :   const int fd = sys_open(task_path, O_RDONLY | O_DIRECTORY, 0);
     377               0 :   if (fd < 0)
     378               0 :     return false;
     379               0 :   DirectoryReader* dir_reader = new(allocator_) DirectoryReader(fd);
     380                 : 
     381                 :   // The directory may contain duplicate entries which we filter by assuming
     382                 :   // that they are consecutive.
     383               0 :   int last_tid = -1;
     384                 :   const char* dent_name;
     385               0 :   while (dir_reader->GetNextEntry(&dent_name)) {
     386               0 :     if (my_strcmp(dent_name, ".") &&
     387               0 :         my_strcmp(dent_name, "..")) {
     388               0 :       int tid = 0;
     389               0 :       if (my_strtoui(&tid, dent_name) &&
     390                 :           last_tid != tid) {
     391               0 :         last_tid = tid;
     392               0 :         result->push_back(tid);
     393                 :       }
     394                 :     }
     395               0 :     dir_reader->PopEntry();
     396                 :   }
     397                 : 
     398               0 :   sys_close(fd);
     399               0 :   return true;
     400                 : }
     401                 : 
     402                 : // Read thread info from /proc/$pid/status.
     403                 : // Fill out the |tgid|, |ppid| and |pid| members of |info|. If unavailable,
     404                 : // these members are set to -1. Returns true iff all three members are
     405                 : // available.
     406               0 : bool LinuxDumper::ThreadInfoGet(ThreadInfo* info) {
     407               0 :   assert(info != NULL);
     408               0 :   pid_t tid = info->tid;
     409                 :   char status_path[80];
     410               0 :   BuildProcPath(status_path, tid, "status");
     411                 : 
     412               0 :   const int fd = open(status_path, O_RDONLY);
     413               0 :   if (fd < 0)
     414               0 :     return false;
     415                 : 
     416               0 :   LineReader* const line_reader = new(allocator_) LineReader(fd);
     417                 :   const char* line;
     418                 :   unsigned line_len;
     419                 : 
     420               0 :   info->ppid = info->tgid = -1;
     421                 : 
     422               0 :   while (line_reader->GetNextLine(&line, &line_len)) {
     423               0 :     if (my_strncmp("Tgid:\t", line, 6) == 0) {
     424               0 :       my_strtoui(&info->tgid, line + 6);
     425               0 :     } else if (my_strncmp("PPid:\t", line, 6) == 0) {
     426               0 :       my_strtoui(&info->ppid, line + 6);
     427                 :     }
     428                 : 
     429               0 :     line_reader->PopLine(line_len);
     430                 :   }
     431                 : 
     432               0 :   if (info->ppid == -1 || info->tgid == -1)
     433               0 :     return false;
     434                 : 
     435               0 :   if (!GetThreadRegisters(info))
     436               0 :       return false;
     437                 : 
     438                 :   const uint8_t* stack_pointer;
     439                 : #if defined(__i386)
     440               0 :   memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp));
     441                 : #elif defined(__x86_64)
     442                 :   memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp));
     443                 : #elif defined(__ARM_EABI__)
     444                 :   memcpy(&stack_pointer, &info->regs.ARM_sp, sizeof(info->regs.ARM_sp));
     445                 : #else
     446                 : #error "This code hasn't been ported to your platform yet."
     447                 : #endif
     448                 : 
     449               0 :   if (!GetStackInfo(&info->stack, &info->stack_len,
     450               0 :                     (uintptr_t) stack_pointer))
     451               0 :     return false;
     452                 : 
     453               0 :   return true;
     454                 : }
     455                 : 
     456                 : // Get information about the stack, given the stack pointer. We don't try to
     457                 : // walk the stack since we might not have all the information needed to do
     458                 : // unwind. So we just grab, up to, 32k of stack.
     459               0 : bool LinuxDumper::GetStackInfo(const void** stack, size_t* stack_len,
     460                 :                                uintptr_t int_stack_pointer) {
     461                 :   // Move the stack pointer to the bottom of the page that it's in.
     462               0 :   const uintptr_t page_size = getpagesize();
     463                 : 
     464                 :   uint8_t* const stack_pointer =
     465               0 :       reinterpret_cast<uint8_t*>(int_stack_pointer & ~(page_size - 1));
     466                 : 
     467                 :   // The number of bytes of stack which we try to capture.
     468                 :   static ptrdiff_t kStackToCapture = 32 * 1024;
     469                 : 
     470               0 :   const MappingInfo* mapping = FindMapping(stack_pointer);
     471               0 :   if (!mapping)
     472               0 :     return false;
     473               0 :   const ptrdiff_t offset = stack_pointer - (uint8_t*) mapping->start_addr;
     474                 :   const ptrdiff_t distance_to_end =
     475               0 :       static_cast<ptrdiff_t>(mapping->size) - offset;
     476                 :   *stack_len = distance_to_end > kStackToCapture ?
     477               0 :       kStackToCapture : distance_to_end;
     478               0 :   *stack = stack_pointer;
     479               0 :   return true;
     480                 : }
     481                 : 
     482                 : // static
     483               0 : void LinuxDumper::CopyFromProcess(void* dest, pid_t child, const void* src,
     484                 :                                   size_t length) {
     485               0 :   unsigned long tmp = 55;
     486               0 :   size_t done = 0;
     487                 :   static const size_t word_size = sizeof(tmp);
     488               0 :   uint8_t* const local = (uint8_t*) dest;
     489               0 :   uint8_t* const remote = (uint8_t*) src;
     490                 : 
     491               0 :   while (done < length) {
     492               0 :     const size_t l = length - done > word_size ? word_size : length - done;
     493               0 :     if (sys_ptrace(PTRACE_PEEKDATA, child, remote + done, &tmp) == -1) {
     494               0 :       tmp = 0;
     495                 :     }
     496               0 :     memcpy(local + done, &tmp, l);
     497               0 :     done += l;
     498                 :   }
     499               0 : }
     500                 : 
     501                 : // Find the mapping which the given memory address falls in.
     502               0 : const MappingInfo* LinuxDumper::FindMapping(const void* address) const {
     503               0 :   const uintptr_t addr = (uintptr_t) address;
     504                 : 
     505               0 :   for (size_t i = 0; i < mappings_.size(); ++i) {
     506               0 :     const uintptr_t start = static_cast<uintptr_t>(mappings_[i]->start_addr);
     507               0 :     if (addr >= start && addr - start < mappings_[i]->size)
     508               0 :       return mappings_[i];
     509                 :   }
     510                 : 
     511               0 :   return NULL;
     512                 : }
     513                 : 
     514                 : }  // namespace google_breakpad

Generated by: LCOV version 1.7