| OLD | NEW |
| (Empty) |
| 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 "breakpad/linux/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 <errno.h> | |
| 46 #include <fcntl.h> | |
| 47 | |
| 48 #include <sys/types.h> | |
| 49 #include <sys/ptrace.h> | |
| 50 #include <sys/wait.h> | |
| 51 | |
| 52 #include "breakpad/linux/directory_reader.h" | |
| 53 #include "breakpad/linux/line_reader.h" | |
| 54 #include "breakpad/linux/linux_libc_support.h" | |
| 55 #include "breakpad/linux/linux_syscall_support.h" | |
| 56 | |
| 57 // Suspend a thread by attaching to it. | |
| 58 static bool SuspendThread(pid_t pid) { | |
| 59 // This may fail if the thread has just died or debugged. | |
| 60 errno = 0; | |
| 61 if (sys_ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0 && | |
| 62 errno != 0) { | |
| 63 return false; | |
| 64 } | |
| 65 while (sys_waitpid(pid, NULL, __WALL) < 0) { | |
| 66 if (errno != EINTR) { | |
| 67 sys_ptrace(PTRACE_DETACH, pid, NULL, NULL); | |
| 68 return false; | |
| 69 } | |
| 70 } | |
| 71 return true; | |
| 72 } | |
| 73 | |
| 74 // Resume a thread by detaching from it. | |
| 75 static bool ResumeThread(pid_t pid) { | |
| 76 return sys_ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0; | |
| 77 } | |
| 78 | |
| 79 namespace google_breakpad { | |
| 80 | |
| 81 LinuxDumper::LinuxDumper(int pid) | |
| 82 : pid_(pid), | |
| 83 threads_suspened_(false), | |
| 84 threads_(&allocator_, 8), | |
| 85 mappings_(&allocator_) { | |
| 86 } | |
| 87 | |
| 88 bool LinuxDumper::Init() { | |
| 89 return EnumerateThreads(&threads_) && | |
| 90 EnumerateMappings(&mappings_); | |
| 91 } | |
| 92 | |
| 93 bool LinuxDumper::ThreadsSuspend() { | |
| 94 if (threads_suspened_) | |
| 95 return true; | |
| 96 bool good = true; | |
| 97 for (size_t i = 0; i < threads_.size(); ++i) | |
| 98 good &= SuspendThread(threads_[i]); | |
| 99 threads_suspened_ = true; | |
| 100 return good; | |
| 101 } | |
| 102 | |
| 103 bool LinuxDumper::ThreadsResume() { | |
| 104 if (!threads_suspened_) | |
| 105 return false; | |
| 106 bool good = true; | |
| 107 for (size_t i = 0; i < threads_.size(); ++i) | |
| 108 good &= ResumeThread(threads_[i]); | |
| 109 threads_suspened_ = false; | |
| 110 return good; | |
| 111 } | |
| 112 | |
| 113 void | |
| 114 LinuxDumper::BuildProcPath(char* path, pid_t pid, const char* node) const { | |
| 115 assert(path); | |
| 116 if (!path) { | |
| 117 return; | |
| 118 } | |
| 119 | |
| 120 path[0] = '\0'; | |
| 121 | |
| 122 const unsigned pid_len = my_int_len(pid); | |
| 123 | |
| 124 assert(node); | |
| 125 if (!node) { | |
| 126 return; | |
| 127 } | |
| 128 | |
| 129 size_t node_len = my_strlen(node); | |
| 130 assert(node_len < NAME_MAX); | |
| 131 if (node_len >= NAME_MAX) { | |
| 132 return; | |
| 133 } | |
| 134 | |
| 135 assert(node_len > 0); | |
| 136 if (node_len == 0) { | |
| 137 return; | |
| 138 } | |
| 139 | |
| 140 assert(pid > 0); | |
| 141 if (pid <= 0) { | |
| 142 return; | |
| 143 } | |
| 144 | |
| 145 const size_t total_length = 6 + pid_len + 1 + node_len; | |
| 146 | |
| 147 assert(total_length < NAME_MAX); | |
| 148 if (total_length >= NAME_MAX) { | |
| 149 return; | |
| 150 } | |
| 151 | |
| 152 memcpy(path, "/proc/", 6); | |
| 153 my_itos(path + 6, pid, pid_len); | |
| 154 memcpy(path + 6 + pid_len, "/", 1); | |
| 155 memcpy(path + 6 + pid_len + 1, node, node_len); | |
| 156 memcpy(path + total_length, "\0", 1); | |
| 157 } | |
| 158 | |
| 159 void* | |
| 160 LinuxDumper::FindBeginningOfLinuxGateSharedLibrary(const pid_t pid) const { | |
| 161 char auxv_path[80]; | |
| 162 BuildProcPath(auxv_path, pid, "auxv"); | |
| 163 | |
| 164 // If BuildProcPath errors out due to invalid input, we'll handle it when | |
| 165 // we try to sys_open the file. | |
| 166 | |
| 167 // Find the AT_SYSINFO_EHDR entry for linux-gate.so | |
| 168 // See http://www.trilithium.com/johan/2005/08/linux-gate/ for more | |
| 169 // information. | |
| 170 int fd = sys_open(auxv_path, O_RDONLY, 0); | |
| 171 if (fd < 0) { | |
| 172 return NULL; | |
| 173 } | |
| 174 | |
| 175 elf_aux_entry one_aux_entry; | |
| 176 while (sys_read(fd, | |
| 177 &one_aux_entry, | |
| 178 sizeof(elf_aux_entry)) == sizeof(elf_aux_entry) && | |
| 179 one_aux_entry.a_type != AT_NULL) { | |
| 180 if (one_aux_entry.a_type == AT_SYSINFO_EHDR) { | |
| 181 close(fd); | |
| 182 return reinterpret_cast<void*>(one_aux_entry.a_un.a_val); | |
| 183 } | |
| 184 } | |
| 185 close(fd); | |
| 186 return NULL; | |
| 187 } | |
| 188 | |
| 189 bool | |
| 190 LinuxDumper::EnumerateMappings(wasteful_vector<MappingInfo*>* result) const { | |
| 191 char maps_path[80]; | |
| 192 BuildProcPath(maps_path, pid_, "maps"); | |
| 193 | |
| 194 // linux_gate_loc is the beginning of the kernel's mapping of | |
| 195 // linux-gate.so in the process. It doesn't actually show up in the | |
| 196 // maps list as a filename, so we use the aux vector to find it's | |
| 197 // load location and special case it's entry when creating the list | |
| 198 // of mappings. | |
| 199 const void* linux_gate_loc; | |
| 200 linux_gate_loc = FindBeginningOfLinuxGateSharedLibrary(pid_); | |
| 201 | |
| 202 const int fd = sys_open(maps_path, O_RDONLY, 0); | |
| 203 if (fd < 0) | |
| 204 return false; | |
| 205 LineReader* const line_reader = new(allocator_) LineReader(fd); | |
| 206 | |
| 207 const char* line; | |
| 208 unsigned line_len; | |
| 209 while (line_reader->GetNextLine(&line, &line_len)) { | |
| 210 uintptr_t start_addr, end_addr, offset; | |
| 211 | |
| 212 const char* i1 = my_read_hex_ptr(&start_addr, line); | |
| 213 if (*i1 == '-') { | |
| 214 const char* i2 = my_read_hex_ptr(&end_addr, i1 + 1); | |
| 215 if (*i2 == ' ') { | |
| 216 const char* i3 = my_read_hex_ptr(&offset, i2 + 6 /* skip ' rwxp ' */); | |
| 217 if (*i3 == ' ') { | |
| 218 MappingInfo* const module = new(allocator_) MappingInfo; | |
| 219 memset(module, 0, sizeof(MappingInfo)); | |
| 220 module->start_addr = start_addr; | |
| 221 module->size = end_addr - start_addr; | |
| 222 module->offset = offset; | |
| 223 const char* name = NULL; | |
| 224 // Only copy name if the name is a valid path name, or if | |
| 225 // we've found the VDSO image | |
| 226 if ((name = my_strchr(line, '/')) != NULL) { | |
| 227 const unsigned l = my_strlen(name); | |
| 228 if (l < sizeof(module->name)) | |
| 229 memcpy(module->name, name, l); | |
| 230 } else if (linux_gate_loc && | |
| 231 reinterpret_cast<void*>(module->start_addr) == | |
| 232 linux_gate_loc) { | |
| 233 memcpy(module->name, | |
| 234 kLinuxGateLibraryName, | |
| 235 my_strlen(kLinuxGateLibraryName)); | |
| 236 module->offset = 0; | |
| 237 } | |
| 238 result->push_back(module); | |
| 239 } | |
| 240 } | |
| 241 } | |
| 242 line_reader->PopLine(line_len); | |
| 243 } | |
| 244 | |
| 245 sys_close(fd); | |
| 246 | |
| 247 return result->size() > 0; | |
| 248 } | |
| 249 | |
| 250 // Parse /proc/$pid/task to list all the threads of the process identified by | |
| 251 // pid. | |
| 252 bool LinuxDumper::EnumerateThreads(wasteful_vector<pid_t>* result) const { | |
| 253 char task_path[80]; | |
| 254 BuildProcPath(task_path, pid_, "task"); | |
| 255 | |
| 256 const int fd = sys_open(task_path, O_RDONLY | O_DIRECTORY, 0); | |
| 257 if (fd < 0) | |
| 258 return false; | |
| 259 DirectoryReader* dir_reader = new(allocator_) DirectoryReader(fd); | |
| 260 | |
| 261 // The directory may contain duplicate entries which we filter by assuming | |
| 262 // that they are consecutive. | |
| 263 int last_tid = -1; | |
| 264 const char* dent_name; | |
| 265 while (dir_reader->GetNextEntry(&dent_name)) { | |
| 266 if (my_strcmp(dent_name, ".") && | |
| 267 my_strcmp(dent_name, "..")) { | |
| 268 int tid = 0; | |
| 269 if (my_strtoui(&tid, dent_name) && | |
| 270 last_tid != tid) { | |
| 271 last_tid = tid; | |
| 272 result->push_back(tid); | |
| 273 } | |
| 274 } | |
| 275 dir_reader->PopEntry(); | |
| 276 } | |
| 277 | |
| 278 sys_close(fd); | |
| 279 return true; | |
| 280 } | |
| 281 | |
| 282 // Read thread info from /proc/$pid/status. | |
| 283 // Fill out the |tgid|, |ppid| and |pid| members of |info|. If unavailible, | |
| 284 // these members are set to -1. Returns true iff all three members are | |
| 285 // availible. | |
| 286 bool LinuxDumper::ThreadInfoGet(pid_t tid, ThreadInfo* info) { | |
| 287 assert(info != NULL); | |
| 288 char status_path[80]; | |
| 289 BuildProcPath(status_path, tid, "status"); | |
| 290 | |
| 291 const int fd = open(status_path, O_RDONLY); | |
| 292 if (fd < 0) | |
| 293 return false; | |
| 294 | |
| 295 LineReader* const line_reader = new(allocator_) LineReader(fd); | |
| 296 const char* line; | |
| 297 unsigned line_len; | |
| 298 | |
| 299 info->ppid = info->tgid = -1; | |
| 300 | |
| 301 while (line_reader->GetNextLine(&line, &line_len)) { | |
| 302 if (my_strncmp("Tgid:\t", line, 6) == 0) { | |
| 303 my_strtoui(&info->tgid, line + 6); | |
| 304 } else if (my_strncmp("PPid:\t", line, 6) == 0) { | |
| 305 my_strtoui(&info->ppid, line + 6); | |
| 306 } | |
| 307 | |
| 308 line_reader->PopLine(line_len); | |
| 309 } | |
| 310 | |
| 311 if (info->ppid == -1 || info->tgid == -1) | |
| 312 return false; | |
| 313 | |
| 314 if (sys_ptrace(PTRACE_GETREGS, tid, NULL, &info->regs) == -1 || | |
| 315 sys_ptrace(PTRACE_GETFPREGS, tid, NULL, &info->fpregs) == -1) { | |
| 316 return false; | |
| 317 } | |
| 318 | |
| 319 #if defined(__i386) | |
| 320 if (sys_ptrace(PTRACE_GETFPXREGS, tid, NULL, &info->fpxregs) == -1) | |
| 321 return false; | |
| 322 #endif | |
| 323 | |
| 324 #if defined(__i386) || defined(__x86_64) | |
| 325 for (unsigned i = 0; i < ThreadInfo::kNumDebugRegisters; ++i) { | |
| 326 if (sys_ptrace( | |
| 327 PTRACE_PEEKUSER, tid, | |
| 328 reinterpret_cast<void*> (offsetof(struct user, | |
| 329 u_debugreg[0]) + i * | |
| 330 sizeof(debugreg_t)), | |
| 331 &info->dregs[i]) == -1) { | |
| 332 return false; | |
| 333 } | |
| 334 } | |
| 335 #endif | |
| 336 | |
| 337 const uint8_t* stack_pointer; | |
| 338 #if defined(__i386) | |
| 339 memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp)); | |
| 340 #elif defined(__x86_64) | |
| 341 memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp)); | |
| 342 #else | |
| 343 #error "This code hasn't been ported to your platform yet." | |
| 344 #endif | |
| 345 | |
| 346 if (!GetStackInfo(&info->stack, &info->stack_len, | |
| 347 (uintptr_t) stack_pointer)) | |
| 348 return false; | |
| 349 | |
| 350 return true; | |
| 351 } | |
| 352 | |
| 353 // Get information about the stack, given the stack pointer. We don't try to | |
| 354 // walk the stack since we might not have all the information needed to do | |
| 355 // unwind. So we just grab, up to, 32k of stack. | |
| 356 bool LinuxDumper::GetStackInfo(const void** stack, size_t* stack_len, | |
| 357 uintptr_t int_stack_pointer) { | |
| 358 #if defined(__i386) || defined(__x86_64) | |
| 359 static const bool stack_grows_down = true; | |
| 360 static const uintptr_t page_size = 4096; | |
| 361 #else | |
| 362 #error "This code has not been ported to your platform yet." | |
| 363 #endif | |
| 364 // Move the stack pointer to the bottom of the page that it's in. | |
| 365 uint8_t* const stack_pointer = | |
| 366 reinterpret_cast<uint8_t*>(int_stack_pointer & ~(page_size - 1)); | |
| 367 | |
| 368 // The number of bytes of stack which we try to capture. | |
| 369 static unsigned kStackToCapture = 32 * 1024; | |
| 370 | |
| 371 const MappingInfo* mapping = FindMapping(stack_pointer); | |
| 372 if (!mapping) | |
| 373 return false; | |
| 374 if (stack_grows_down) { | |
| 375 const ptrdiff_t offset = stack_pointer - (uint8_t*) mapping->start_addr; | |
| 376 const ptrdiff_t distance_to_end = | |
| 377 static_cast<ptrdiff_t>(mapping->size) - offset; | |
| 378 *stack_len = distance_to_end > kStackToCapture ? | |
| 379 kStackToCapture : distance_to_end; | |
| 380 *stack = stack_pointer; | |
| 381 } else { | |
| 382 const ptrdiff_t offset = stack_pointer - (uint8_t*) mapping->start_addr; | |
| 383 *stack_len = offset > kStackToCapture ? kStackToCapture : offset; | |
| 384 *stack = stack_pointer - *stack_len; | |
| 385 } | |
| 386 | |
| 387 return true; | |
| 388 } | |
| 389 | |
| 390 // static | |
| 391 void LinuxDumper::CopyFromProcess(void* dest, pid_t child, const void* src, | |
| 392 size_t length) { | |
| 393 unsigned long tmp; | |
| 394 size_t done = 0; | |
| 395 static const size_t word_size = sizeof(tmp); | |
| 396 uint8_t* const local = (uint8_t*) dest; | |
| 397 uint8_t* const remote = (uint8_t*) src; | |
| 398 | |
| 399 while (done < length) { | |
| 400 const size_t l = length - done > word_size ? word_size : length - done; | |
| 401 if (sys_ptrace(PTRACE_PEEKDATA, child, remote + done, &tmp) == -1) | |
| 402 tmp = 0; | |
| 403 memcpy(local + done, &tmp, l); | |
| 404 done += l; | |
| 405 } | |
| 406 } | |
| 407 | |
| 408 // Find the mapping which the given memory address falls in. | |
| 409 const MappingInfo* LinuxDumper::FindMapping(const void* address) const { | |
| 410 const uintptr_t addr = (uintptr_t) address; | |
| 411 | |
| 412 for (size_t i = 0; i < mappings_.size(); ++i) { | |
| 413 const uintptr_t start = static_cast<uintptr_t>(mappings_[i]->start_addr); | |
| 414 if (addr >= start && addr - start < mappings_[i]->size) | |
| 415 return mappings_[i]; | |
| 416 } | |
| 417 | |
| 418 return NULL; | |
| 419 } | |
| 420 | |
| 421 } // namespace google_breakpad | |
| OLD | NEW |