status_t TombstoneSection::BlockingCall(int pipeWriteFd) const { std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir); if (proc.get() == nullptr) { ALOGE("opendir /proc failed: %s\n", strerror(errno)); return -errno; } const std::set<int> hal_pids = get_interesting_hal_pids(); ProtoOutputStream proto; struct dirent* d; status_t err = NO_ERROR; while ((d = readdir(proc.get()))) { int pid = atoi(d->d_name); if (pid <= 0) { continue; } const std::string link_name = android::base::StringPrintf("/proc/%d/exe", pid); std::string exe; if (!android::base::Readlink(link_name, &exe)) { ALOGE("Can't read '%s': %s\n", link_name.c_str(), strerror(errno)); continue; } bool is_java_process; if (exe == "/system/bin/app_process32" || exe == "/system/bin/app_process64") { if (mType != "java") continue; // Don't bother dumping backtraces for the zygote. if (IsZygote(pid)) { VLOG("Skipping Zygote"); continue; } is_java_process = true; } else if (should_dump_native_traces(exe.c_str())) { if (mType != "native") continue; is_java_process = false; } else if (hal_pids.find(pid) != hal_pids.end()) { if (mType != "hal") continue; is_java_process = false; } else { // Probably a native process we don't care about, continue. VLOG("Skipping %d", pid); continue; } Fpipe dumpPipe; if (!dumpPipe.init()) { ALOGW("TombstoneSection '%s' failed to setup dump pipe", this->name.string()); err = -errno; break; } const uint64_t start = Nanotime(); pid_t child = fork(); if (child < 0) { ALOGE("Failed to fork child process"); break; } else if (child == 0) { // This is the child process. dumpPipe.readFd().reset(); const int ret = dump_backtrace_to_file_timeout( pid, is_java_process ? kDebuggerdJavaBacktrace : kDebuggerdNativeBacktrace, is_java_process ? 5 : 20, dumpPipe.writeFd().get()); if (ret == -1) { if (errno == 0) { ALOGW("Dumping failed for pid '%d', likely due to a timeout\n", pid); } else { ALOGE("Dumping failed for pid '%d': %s\n", pid, strerror(errno)); } } dumpPipe.writeFd().reset(); _exit(EXIT_SUCCESS); } dumpPipe.writeFd().reset(); // Parent process. // Read from the pipe concurrently to avoid blocking the child. FdBuffer buffer; err = buffer.readFully(dumpPipe.readFd().get()); // Wait on the child to avoid it becoming a zombie process. status_t cStatus = wait_child(child); if (err != NO_ERROR) { ALOGW("TombstoneSection '%s' failed to read stack dump: %d", this->name.string(), err); dumpPipe.readFd().reset(); break; } if (cStatus != NO_ERROR) { ALOGE("[%s] child had an issue: %s\n", this->name.string(), strerror(-cStatus)); } auto dump = std::make_unique<char[]>(buffer.size()); auto iterator = buffer.data(); int i = 0; while (iterator.hasNext()) { dump[i] = iterator.next(); i++; } uint64_t token = proto.start(android::os::BackTraceProto::TRACES); proto.write(android::os::BackTraceProto::Stack::PID, pid); proto.write(android::os::BackTraceProto::Stack::DUMP, dump.get(), i); proto.write(android::os::BackTraceProto::Stack::DUMP_DURATION_NS, static_cast<long long>(Nanotime() - start)); proto.end(token); dumpPipe.readFd().reset(); } if (!proto.flush(pipeWriteFd) && errno == EPIPE) { ALOGE("[%s] wrote to a broken pipe\n", this->name.string()); if (err != NO_ERROR) { return EPIPE; } } return err; }
/* dump Dalvik and native stack traces, return the trace file location (NULL if none) */ const char *dump_traces() { const char* result = NULL; char traces_path[PROPERTY_VALUE_MAX] = ""; property_get("dalvik.vm.stack-trace-file", traces_path, ""); if (!traces_path[0]) return NULL; /* move the old traces.txt (if any) out of the way temporarily */ char anr_traces_path[PATH_MAX]; strlcpy(anr_traces_path, traces_path, sizeof(anr_traces_path)); strlcat(anr_traces_path, ".anr", sizeof(anr_traces_path)); if (rename(traces_path, anr_traces_path) && errno != ENOENT) { fprintf(stderr, "rename(%s, %s): %s\n", traces_path, anr_traces_path, strerror(errno)); return NULL; // Can't rename old traces.txt -- no permission? -- leave it alone instead } /* make the directory if necessary */ char anr_traces_dir[PATH_MAX]; strlcpy(anr_traces_dir, traces_path, sizeof(anr_traces_dir)); char *slash = strrchr(anr_traces_dir, '/'); if (slash != NULL) { *slash = '\0'; if (!mkdir(anr_traces_dir, 0775)) { chown(anr_traces_dir, AID_SYSTEM, AID_SYSTEM); chmod(anr_traces_dir, 0775); } else if (errno != EEXIST) { fprintf(stderr, "mkdir(%s): %s\n", anr_traces_dir, strerror(errno)); return NULL; } } /* create a new, empty traces.txt file to receive stack dumps */ int fd = open(traces_path, O_CREAT | O_WRONLY | O_TRUNC | O_NOFOLLOW, 0666); /* -rw-rw-rw- */ if (fd < 0) { fprintf(stderr, "%s: %s\n", traces_path, strerror(errno)); return NULL; } int chmod_ret = fchmod(fd, 0666); if (chmod_ret < 0) { fprintf(stderr, "fchmod on %s failed: %s\n", traces_path, strerror(errno)); close(fd); return NULL; } /* walk /proc and kill -QUIT all Dalvik processes */ DIR *proc = opendir("/proc"); if (proc == NULL) { fprintf(stderr, "/proc: %s\n", strerror(errno)); goto error_close_fd; } /* use inotify to find when processes are done dumping */ int ifd = inotify_init(); if (ifd < 0) { fprintf(stderr, "inotify_init: %s\n", strerror(errno)); goto error_close_fd; } int wfd = inotify_add_watch(ifd, traces_path, IN_CLOSE_WRITE); if (wfd < 0) { fprintf(stderr, "inotify_add_watch(%s): %s\n", traces_path, strerror(errno)); goto error_close_ifd; } struct dirent *d; int dalvik_found = 0; while ((d = readdir(proc))) { int pid = atoi(d->d_name); if (pid <= 0) continue; char path[PATH_MAX]; char data[PATH_MAX]; snprintf(path, sizeof(path), "/proc/%d/exe", pid); ssize_t len = readlink(path, data, sizeof(data) - 1); if (len <= 0) { continue; } data[len] = '\0'; if (!strcmp(data, "/system/bin/app_process")) { /* skip zygote -- it won't dump its stack anyway */ snprintf(path, sizeof(path), "/proc/%d/cmdline", pid); int fd = open(path, O_RDONLY); len = read(fd, data, sizeof(data) - 1); close(fd); if (len <= 0) { continue; } data[len] = '\0'; if (!strcmp(data, "zygote")) { continue; } ++dalvik_found; if (kill(pid, SIGQUIT)) { fprintf(stderr, "kill(%d, SIGQUIT): %s\n", pid, strerror(errno)); continue; } /* wait for the writable-close notification from inotify */ struct pollfd pfd = { ifd, POLLIN, 0 }; int ret = poll(&pfd, 1, 200); /* 200 msec timeout */ if (ret < 0) { fprintf(stderr, "poll: %s\n", strerror(errno)); } else if (ret == 0) { fprintf(stderr, "warning: timed out dumping pid %d\n", pid); } else { struct inotify_event ie; read(ifd, &ie, sizeof(ie)); } } else if (should_dump_native_traces(data)) { /* dump native process if appropriate */ if (lseek(fd, 0, SEEK_END) < 0) { fprintf(stderr, "lseek: %s\n", strerror(errno)); } else { dump_backtrace_to_file(pid, fd); } } } if (dalvik_found == 0) { fprintf(stderr, "Warning: no Dalvik processes found to dump stacks\n"); } static char dump_traces_path[PATH_MAX]; strlcpy(dump_traces_path, traces_path, sizeof(dump_traces_path)); strlcat(dump_traces_path, ".bugreport", sizeof(dump_traces_path)); if (rename(traces_path, dump_traces_path)) { fprintf(stderr, "rename(%s, %s): %s\n", traces_path, dump_traces_path, strerror(errno)); goto error_close_ifd; } result = dump_traces_path; /* replace the saved [ANR] traces.txt file */ rename(anr_traces_path, traces_path); error_close_ifd: close(ifd); error_close_fd: close(fd); return result; }