diff options
Diffstat (limited to 'cmds/dumpstate')
-rw-r--r-- | cmds/dumpstate/dumpstate.c | 27 | ||||
-rw-r--r-- | cmds/dumpstate/dumpstate.h | 8 | ||||
-rw-r--r-- | cmds/dumpstate/utils.c | 249 |
3 files changed, 171 insertions, 113 deletions
diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c index dc1ada0..a33dcf3 100644 --- a/cmds/dumpstate/dumpstate.c +++ b/cmds/dumpstate/dumpstate.c @@ -64,7 +64,8 @@ static void get_tombstone_fds(tombstone_data_t data[NUM_TOMBSTONES]) { time_t thirty_minutes_ago = time(NULL) - 60*30; for (size_t i = 0; i < NUM_TOMBSTONES; i++) { snprintf(data[i].name, sizeof(data[i].name), "%s%02zu", TOMBSTONE_FILE_PREFIX, i); - int fd = open(data[i].name, O_RDONLY | O_CLOEXEC | O_NOFOLLOW); + int fd = TEMP_FAILURE_RETRY(open(data[i].name, + O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK)); struct stat st; if (fstat(fd, &st) == 0 && S_ISREG(st.st_mode) && (time_t) st.st_mtime >= thirty_minutes_ago) { @@ -184,7 +185,8 @@ static void dumpstate() { if (!anr_traces_path[0]) { printf("*** NO VM TRACES FILE DEFINED (dalvik.vm.stack-trace-file)\n\n"); } else { - int fd = open(anr_traces_path, O_RDONLY | O_CLOEXEC | O_NOFOLLOW); + int fd = TEMP_FAILURE_RETRY(open(anr_traces_path, + O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK)); if (fd < 0) { printf("*** NO ANR VM TRACES FILE (%s): %s\n\n", anr_traces_path, strerror(errno)); } else { @@ -236,13 +238,15 @@ static void dumpstate() { dump_file("LAST KMSG", "/proc/last_kmsg"); } - dump_file("LAST PANIC CONSOLE", "/data/dontpanic/apanic_console"); - dump_file("LAST PANIC THREADS", "/data/dontpanic/apanic_threads"); + /* kernels must set CONFIG_PSTORE_PMSG, slice up pstore with device tree */ + run_command("LAST LOGCAT", 10, "logcat", "-L", "-v", "threadtime", + "-b", "all", "-d", "*:v", NULL); for_each_userid(do_dump_settings, NULL); /* The following have a tendency to get wedged when wifi drivers/fw goes belly-up. */ - run_command("NETWORK INTERFACES", 10, SU_PATH, "root", "netcfg", NULL); + + run_command("NETWORK INTERFACES", 10, "ip", "link", NULL); run_command("IPv4 ADDRESSES", 10, "ip", "-4", "addr", "show", NULL); run_command("IPv6 ADDRESSES", 10, "ip", "-6", "addr", "show", NULL); @@ -387,7 +391,6 @@ static void usage() { fprintf(stderr, "usage: dumpstate [-b soundfile] [-e soundfile] [-o file [-d] [-p] [-z]] [-s] [-q]\n" " -o: write to file (instead of stdout)\n" " -d: append date to filename (requires -o)\n" - " -z: gzip output (requires -o)\n" " -p: capture screenshot to filename.png (requires -o)\n" " -s: write output to control socket (for init)\n" " -b: play sound file instead of vibrate, at beginning of job\n" @@ -410,7 +413,6 @@ static void vibrate(FILE* vibrator, int ms) { int main(int argc, char *argv[]) { struct sigaction sigact; int do_add_date = 0; - int do_compress = 0; int do_vibrate = 1; char* use_outfile = 0; int use_socket = 0; @@ -435,7 +437,7 @@ int main(int argc, char *argv[]) { /* set as high priority, and protect from OOM killer */ setpriority(PRIO_PROCESS, 0, -20); - FILE *oom_adj = fopen("/proc/self/oom_adj", "w"); + FILE *oom_adj = fopen("/proc/self/oom_adj", "we"); if (oom_adj) { fputs("-17", oom_adj); fclose(oom_adj); @@ -450,7 +452,6 @@ int main(int argc, char *argv[]) { case 's': use_socket = 1; break; case 'v': break; // compatibility no-op case 'q': do_vibrate = 0; break; - case 'z': do_compress = 6; break; case 'p': do_fb = 1; break; case 'B': do_broadcast = 1; break; case '?': printf("\n"); @@ -469,15 +470,14 @@ int main(int argc, char *argv[]) { /* open the vibrator before dropping root */ FILE *vibrator = 0; if (do_vibrate) { - vibrator = fopen("/sys/class/timed_output/vibrator/enable", "w"); + vibrator = fopen("/sys/class/timed_output/vibrator/enable", "we"); if (vibrator) { - fcntl(fileno(vibrator), F_SETFD, FD_CLOEXEC); vibrate(vibrator, 150); } } /* read /proc/cmdline before dropping root */ - FILE *cmdline = fopen("/proc/cmdline", "r"); + FILE *cmdline = fopen("/proc/cmdline", "re"); if (cmdline != NULL) { fgets(cmdline_buf, sizeof(cmdline_buf), cmdline); fclose(cmdline); @@ -545,10 +545,9 @@ int main(int argc, char *argv[]) { strlcat(screenshot_path, ".png", sizeof(screenshot_path)); } strlcat(path, ".txt", sizeof(path)); - if (do_compress) strlcat(path, ".gz", sizeof(path)); strlcpy(tmp_path, path, sizeof(tmp_path)); strlcat(tmp_path, ".tmp", sizeof(tmp_path)); - gzip_pid = redirect_to_file(stdout, tmp_path, do_compress); + redirect_to_file(stdout, tmp_path); } dumpstate(); diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h index 53bfff6..d17a677 100644 --- a/cmds/dumpstate/dumpstate.h +++ b/cmds/dumpstate/dumpstate.h @@ -31,7 +31,9 @@ typedef void (for_each_userid_func)(int); /* prints the contents of a file */ int dump_file(const char *title, const char *path); -/* prints the contents of the fd */ +/* prints the contents of the fd + * fd must have been opened with the flag O_NONBLOCK. + */ int dump_file_from_fd(const char *title, const char *path, int fd); /* forks a command and waits for it to finish -- terminate args with NULL */ @@ -43,8 +45,8 @@ void print_properties(); /* redirect output to a service control socket */ void redirect_to_socket(FILE *redirect, const char *service); -/* redirect output to a file, optionally gzipping; returns gzip pid */ -pid_t redirect_to_file(FILE *redirect, char *path, int gzip_level); +/* redirect output to a file */ +void redirect_to_file(FILE *redirect, char *path); /* dump Dalvik and native stack traces, return the trace file location (NULL if none) */ const char *dump_traces(); diff --git a/cmds/dumpstate/utils.c b/cmds/dumpstate/utils.c index 3e0b24b..cf14c8b 100644 --- a/cmds/dumpstate/utils.c +++ b/cmds/dumpstate/utils.c @@ -53,6 +53,12 @@ static const char* native_processes_to_dump[] = { NULL, }; +static uint64_t nanotime() { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * NANOS_PER_SEC + ts.tv_nsec; +} + void for_each_userid(void (*func)(int), const char *header) { DIR *d; struct dirent *de; @@ -98,7 +104,7 @@ static void __for_each_pid(void (*helper)(int, const char *, void *), const char sprintf(cmdpath,"/proc/%d/cmdline", pid); memset(cmdline, 0, sizeof(cmdline)); - if ((fd = open(cmdpath, O_RDONLY)) < 0) { + if ((fd = TEMP_FAILURE_RETRY(open(cmdpath, O_RDONLY | O_CLOEXEC))) < 0) { strcpy(cmdline, "N/A"); } else { read(fd, cmdline, sizeof(cmdline) - 1); @@ -149,7 +155,7 @@ static void for_each_tid_helper(int pid, const char *cmdline, void *arg) { sprintf(commpath,"/proc/%d/comm", tid); memset(comm, 0, sizeof(comm)); - if ((fd = open(commpath, O_RDONLY)) < 0) { + if ((fd = TEMP_FAILURE_RETRY(open(commpath, O_RDONLY | O_CLOEXEC))) < 0) { strcpy(comm, "N/A"); } else { char *c; @@ -180,7 +186,7 @@ void show_wchan(int pid, int tid, const char *name) { memset(buffer, 0, sizeof(buffer)); sprintf(path, "/proc/%d/wchan", tid); - if ((fd = open(path, O_RDONLY)) < 0) { + if ((fd = TEMP_FAILURE_RETRY(open(path, O_RDONLY | O_CLOEXEC))) < 0) { printf("Failed to open '%s' (%s)\n", path, strerror(errno)); return; } @@ -250,22 +256,7 @@ void do_showmap(int pid, const char *name) { run_command(title, 10, SU_PATH, "root", "showmap", arg, NULL); } -/* prints the contents of a file */ -int dump_file(const char *title, const char *path) { - int fd = open(path, O_RDONLY); - if (fd < 0) { - int err = errno; - if (title) printf("------ %s (%s) ------\n", title, path); - printf("*** %s: %s\n", path, strerror(err)); - if (title) printf("\n"); - return -1; - } - return dump_file_from_fd(title, path, fd); -} - -int dump_file_from_fd(const char *title, const char *path, int fd) { - char buffer[32768]; - +static int _dump_file_from_fd(const char *title, const char *path, int fd) { if (title) printf("------ %s (%s", title, path); if (title) { @@ -279,32 +270,126 @@ int dump_file_from_fd(const char *title, const char *path, int fd) { printf(") ------\n"); } - int newline = 0; - for (;;) { - int ret = read(fd, buffer, sizeof(buffer)); - if (ret > 0) { - newline = (buffer[ret - 1] == '\n'); - ret = fwrite(buffer, ret, 1, stdout); + bool newline = false; + fd_set read_set; + struct timeval tm; + while (1) { + FD_ZERO(&read_set); + FD_SET(fd, &read_set); + /* Timeout if no data is read for 30 seconds. */ + tm.tv_sec = 30; + tm.tv_usec = 0; + uint64_t elapsed = nanotime(); + int ret = TEMP_FAILURE_RETRY(select(fd + 1, &read_set, NULL, NULL, &tm)); + if (ret == -1) { + printf("*** %s: select failed: %s\n", path, strerror(errno)); + newline = true; + break; + } else if (ret == 0) { + elapsed = nanotime() - elapsed; + printf("*** %s: Timed out after %.3fs\n", path, + (float) elapsed / NANOS_PER_SEC); + newline = true; + break; + } else { + char buffer[65536]; + ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer, sizeof(buffer))); + if (bytes_read > 0) { + fwrite(buffer, bytes_read, 1, stdout); + newline = (buffer[bytes_read-1] == '\n'); + } else { + if (bytes_read == -1) { + printf("*** %s: Failed to read from fd: %s", path, strerror(errno)); + newline = true; + } + break; + } } - if (ret <= 0) break; } - close(fd); + TEMP_FAILURE_RETRY(close(fd)); if (!newline) printf("\n"); if (title) printf("\n"); return 0; } -static int64_t nanotime() { +/* prints the contents of a file */ +int dump_file(const char *title, const char *path) { + int fd = TEMP_FAILURE_RETRY(open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC)); + if (fd < 0) { + int err = errno; + if (title) printf("------ %s (%s) ------\n", title, path); + printf("*** %s: %s\n", path, strerror(err)); + if (title) printf("\n"); + return -1; + } + return _dump_file_from_fd(title, path, fd); +} + +/* fd must have been opened with the flag O_NONBLOCK. With this flag set, + * it's possible to avoid issues where opening the file itself can get + * stuck. + */ +int dump_file_from_fd(const char *title, const char *path, int fd) { + int flags = fcntl(fd, F_GETFL); + if (flags == -1) { + printf("*** %s: failed to get flags on fd %d: %s\n", path, fd, strerror(errno)); + return -1; + } else if (!(flags & O_NONBLOCK)) { + printf("*** %s: fd must have O_NONBLOCK set.\n", path); + return -1; + } + return _dump_file_from_fd(title, path, fd); +} + +bool waitpid_with_timeout(pid_t pid, int timeout_seconds, int* status) { + sigset_t child_mask, old_mask; + sigemptyset(&child_mask); + sigaddset(&child_mask, SIGCHLD); + + if (sigprocmask(SIG_BLOCK, &child_mask, &old_mask) == -1) { + printf("*** sigprocmask failed: %s\n", strerror(errno)); + return false; + } + struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return (int64_t)ts.tv_sec * NANOS_PER_SEC + ts.tv_nsec; + ts.tv_sec = timeout_seconds; + ts.tv_nsec = 0; + int ret = TEMP_FAILURE_RETRY(sigtimedwait(&child_mask, NULL, &ts)); + int saved_errno = errno; + // Set the signals back the way they were. + if (sigprocmask(SIG_SETMASK, &old_mask, NULL) == -1) { + printf("*** sigprocmask failed: %s\n", strerror(errno)); + if (ret == 0) { + return false; + } + } + if (ret == -1) { + errno = saved_errno; + if (errno == EAGAIN) { + errno = ETIMEDOUT; + } else { + printf("*** sigtimedwait failed: %s\n", strerror(errno)); + } + return false; + } + + pid_t child_pid = waitpid(pid, status, WNOHANG); + if (child_pid != pid) { + if (child_pid != -1) { + printf("*** Waiting for pid %d, got pid %d instead\n", pid, child_pid); + } else { + printf("*** waitpid failed: %s\n", strerror(errno)); + } + return false; + } + return true; } /* forks a command and waits for it to finish */ int run_command(const char *title, int timeout_seconds, const char *command, ...) { fflush(stdout); - int64_t start = nanotime(); + uint64_t start = nanotime(); pid_t pid = fork(); /* handle error case */ @@ -345,28 +430,35 @@ int run_command(const char *title, int timeout_seconds, const char *command, ... } /* handle parent case */ - for (;;) { - int status; - pid_t p = waitpid(pid, &status, WNOHANG); - int64_t elapsed = nanotime() - start; - if (p == pid) { - if (WIFSIGNALED(status)) { - printf("*** %s: Killed by signal %d\n", command, WTERMSIG(status)); - } else if (WIFEXITED(status) && WEXITSTATUS(status) > 0) { - printf("*** %s: Exit code %d\n", command, WEXITSTATUS(status)); - } - if (title) printf("[%s: %.3fs elapsed]\n\n", command, (float)elapsed / NANOS_PER_SEC); - return status; + int status; + bool ret = waitpid_with_timeout(pid, timeout_seconds, &status); + uint64_t elapsed = nanotime() - start; + if (!ret) { + if (errno == ETIMEDOUT) { + printf("*** %s: Timed out after %.3fs (killing pid %d)\n", command, + (float) elapsed / NANOS_PER_SEC, pid); + } else { + printf("*** %s: Error after %.4fs (killing pid %d)\n", command, + (float) elapsed / NANOS_PER_SEC, pid); } - - if (timeout_seconds && elapsed / NANOS_PER_SEC > timeout_seconds) { - printf("*** %s: Timed out after %.3fs (killing pid %d)\n", command, (float) elapsed / NANOS_PER_SEC, pid); - kill(pid, SIGTERM); - return -1; + kill(pid, SIGTERM); + if (!waitpid_with_timeout(pid, 5, NULL)) { + kill(pid, SIGKILL); + if (!waitpid_with_timeout(pid, 5, NULL)) { + printf("*** %s: Cannot kill %d even with SIGKILL.\n", command, pid); + } } + return -1; + } - usleep(100000); // poll every 0.1 sec + if (WIFSIGNALED(status)) { + printf("*** %s: Killed by signal %d\n", command, WTERMSIG(status)); + } else if (WIFEXITED(status) && WEXITSTATUS(status) > 0) { + printf("*** %s: Exit code %d\n", command, WEXITSTATUS(status)); } + if (title) printf("[%s: %.3fs elapsed]\n\n", command, (float)elapsed / NANOS_PER_SEC); + + return status; } size_t num_props = 0; @@ -407,6 +499,7 @@ void redirect_to_socket(FILE *redirect, const char *service) { fprintf(stderr, "android_get_control_socket(%s): %s\n", service, strerror(errno)); exit(1); } + fcntl(s, F_SETFD, FD_CLOEXEC); if (listen(s, 4) < 0) { fprintf(stderr, "listen(control socket): %s\n", strerror(errno)); exit(1); @@ -425,8 +518,8 @@ void redirect_to_socket(FILE *redirect, const char *service) { close(fd); } -/* redirect output to a file, optionally gzipping; returns gzip pid (or -1) */ -pid_t redirect_to_file(FILE *redirect, char *path, int gzip_level) { +/* redirect output to a file */ +void redirect_to_file(FILE *redirect, char *path) { char *chp = path; /* skip initial slash */ @@ -443,52 +536,15 @@ pid_t redirect_to_file(FILE *redirect, char *path, int gzip_level) { } } - int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + int fd = TEMP_FAILURE_RETRY(open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)); if (fd < 0) { fprintf(stderr, "%s: %s\n", path, strerror(errno)); exit(1); } - pid_t gzip_pid = -1; - if (gzip_level > 0) { - int fds[2]; - if (pipe(fds)) { - fprintf(stderr, "pipe: %s\n", strerror(errno)); - exit(1); - } - - fflush(redirect); - fflush(stdout); - - gzip_pid = fork(); - if (gzip_pid < 0) { - fprintf(stderr, "fork: %s\n", strerror(errno)); - exit(1); - } - - if (gzip_pid == 0) { - dup2(fds[0], STDIN_FILENO); - dup2(fd, STDOUT_FILENO); - - close(fd); - close(fds[0]); - close(fds[1]); - - char level[10]; - snprintf(level, sizeof(level), "-%d", gzip_level); - execlp("gzip", "gzip", level, NULL); - fprintf(stderr, "exec(gzip): %s\n", strerror(errno)); - _exit(-1); - } - - close(fd); - close(fds[0]); - fd = fds[1]; - } - - dup2(fd, fileno(redirect)); + TEMP_FAILURE_RETRY(dup2(fd, fileno(redirect))); close(fd); - return gzip_pid; } static bool should_dump_native_traces(const char* path) { @@ -536,7 +592,8 @@ const char *dump_traces() { } /* 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- */ + int fd = TEMP_FAILURE_RETRY(open(traces_path, O_CREAT | O_WRONLY | O_TRUNC | O_NOFOLLOW | O_CLOEXEC, + 0666)); /* -rw-rw-rw- */ if (fd < 0) { fprintf(stderr, "%s: %s\n", traces_path, strerror(errno)); return NULL; @@ -586,7 +643,7 @@ const char *dump_traces() { if (!strncmp(data, "/system/bin/app_process", strlen("/system/bin/app_process"))) { /* skip zygote -- it won't dump its stack anyway */ snprintf(path, sizeof(path), "/proc/%d/cmdline", pid); - int cfd = open(path, O_RDONLY); + int cfd = TEMP_FAILURE_RETRY(open(path, O_RDONLY | O_CLOEXEC)); len = read(cfd, data, sizeof(data) - 1); close(cfd); if (len <= 0) { @@ -598,7 +655,7 @@ const char *dump_traces() { } ++dalvik_found; - int64_t start = nanotime(); + uint64_t start = nanotime(); if (kill(pid, SIGQUIT)) { fprintf(stderr, "kill(%d, SIGQUIT): %s\n", pid, strerror(errno)); continue; @@ -628,7 +685,7 @@ const char *dump_traces() { fprintf(stderr, "lseek: %s\n", strerror(errno)); } else { static uint16_t timeout_failures = 0; - int64_t start = nanotime(); + uint64_t start = nanotime(); /* If 3 backtrace dumps fail in a row, consider debuggerd dead. */ if (timeout_failures == 3) { @@ -671,7 +728,7 @@ error_close_fd: void dump_route_tables() { const char* const RT_TABLES_PATH = "/data/misc/net/rt_tables"; dump_file("RT_TABLES", RT_TABLES_PATH); - FILE* fp = fopen(RT_TABLES_PATH, "r"); + FILE* fp = fopen(RT_TABLES_PATH, "re"); if (!fp) { printf("*** %s: %s\n", RT_TABLES_PATH, strerror(errno)); return; |