diff options
author | Lennart Poettering <lennart@poettering.net> | 2012-03-07 19:50:31 +0100 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2012-03-07 19:50:31 +0100 |
commit | ca28e201c6ae5c06272b61861072f66bca172ed0 (patch) | |
tree | ae56fa42a340052c7b1f375f15a342d951f2eddb |
initial commit
-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | Makefile | 15 | ||||
-rw-r--r-- | coredump.c | 179 | ||||
-rw-r--r-- | coredump.h | 14 | ||||
-rw-r--r-- | format.h | 6 | ||||
-rw-r--r-- | generate.c | 79 | ||||
-rw-r--r-- | minidump.c | 470 | ||||
-rw-r--r-- | minidump.h | 12 | ||||
-rw-r--r-- | segfault.c | 38 |
9 files changed, 818 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6bd832d --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +core +core.* +*.o +segfault +generate diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6fe65ea --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +CFLAGS=-Wextra -Wall -O0 -g -D_GNU_SOURCE + +all: segfault generate core + +generate: generate.o coredump.h coredump.o minidump.o minidump.h + $(CC) $(CFLAGS) $(LIBS) $^ -o $@ + +segfault: segfault.c + $(CC) $(CFLAGS) $(LIBS) $^ -o $@ + +core: segfault + ( ./segfault ||: ) > /dev/null 2>&1 + +clean: + rm -f core.* core segfault generate diff --git a/coredump.c b/coredump.c new file mode 100644 index 0000000..7d70f74 --- /dev/null +++ b/coredump.c @@ -0,0 +1,179 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/param.h> + +#include "coredump.h" + +int coredump_read_header(int fd, ElfW(Ehdr) *header) { + ssize_t l; + + assert(fd >= 0); + assert(header); + + l = pread(fd, header, sizeof(*header), 0); + if (l < 0) + return -errno; + if (l != sizeof(*header)) + return -EIO; + + if (memcmp(header->e_ident, ELFMAG, SELFMAG) != 0) + return -EINVAL; + + if (header->e_type != ET_CORE) + return -EINVAL; + + if (header->e_ehsize != sizeof(ElfW(Ehdr))) + return -EINVAL; + + if (header->e_phentsize != sizeof(ElfW(Phdr))) + return -EINVAL; + +#if __WORDSIZE == 32 + if (header->e_ident[EI_CLASS] != ELFCLASS32) + return -EINVAL; +#elif __WORDSIZE == 64 + if (header->e_ident[EI_CLASS] != ELFCLASS64) + return -EINVAL; +#else +#error "Unknown word size." +#endif + + return 0; +} + +int coredump_read_segment_header(int fd, const ElfW(Ehdr) *header, unsigned long i, ElfW(Phdr) *segment) { + ssize_t l; + + assert(fd >= 0); + assert(header); + assert(segment); + + if (header->e_phoff == 0) + return -E2BIG; + + if (i >= header->e_phnum) + return -E2BIG; + + l = pread(fd, segment, sizeof(*segment), + header->e_phoff + i * header->e_phentsize); + + if (l < 0) + return -errno; + if (l != sizeof(*segment)) + return -EIO; + + return 0; +} + +int coredump_find_note_segment(int fd, const ElfW(Ehdr) *header, off_t *offset, off_t *length) { + unsigned long i; + int r; + + assert(fd >= 0); + assert(header); + assert(offset); + assert(length); + + for (i = 0; i < header->e_phnum; i++) { + ElfW(Phdr) segment; + + r = coredump_read_segment_header(fd, header, i, &segment); + if (r < 0) + return r; + + if (segment.p_type == PT_NOTE) { + *offset = segment.p_offset; + *length = segment.p_filesz; + + return 1; + } + } + + return 0; +} + +int coredump_read_memory(int fd, const ElfW(Ehdr) *header, unsigned long source, void *destination, size_t length) { + unsigned long i; + int r; + + assert(fd >= 0); + assert(header); + assert(destination); + assert(length > 0); + + for (i = 0; i < header->e_phnum; i++) { + ElfW(Phdr) segment; + ssize_t l; + + r = coredump_read_segment_header(fd, header, i, &segment); + if (r < 0) + return r; + + if (segment.p_type != PT_LOAD) + continue; + + if (source >= segment.p_vaddr + segment.p_filesz) + continue; + + if (source + length < segment.p_vaddr) + continue; + + /* We assume that what we are looking for lies + * entirely within one segment. */ + + if (source < segment.p_vaddr || + source + length > segment.p_vaddr + segment.p_filesz) + return -EIO; + + l = pread(fd, + (uint8_t*) destination, length, + segment.p_offset + (source - segment.p_vaddr)); + if (l < 0) + return -errno; + if ((size_t) l != length) + return -EIO; + + return 1; + } + + return 0; +} + +int coredump_next_note(int fd, off_t *offset, off_t *length, ElfW(Nhdr) *n, off_t *name, off_t *descriptor) { + ssize_t l; + off_t j; + + assert(fd >= 0); + assert(offset); + assert(length); + assert(n); + assert(name); + assert(descriptor); + + l = pread(fd, n, sizeof(*n), *offset); + if (l < 0) + return -errno; + if (l != sizeof(*n)) + return -EIO; + + j = sizeof(*n) + + roundup(n->n_namesz, sizeof(long)) + + roundup(n->n_descsz, sizeof(long)); + + if (j > *length) + return -EIO; + + *name = *offset + sizeof(*n); + *descriptor = *offset + sizeof(*n) + roundup(n->n_namesz, sizeof(long)); + + *offset += j; + *length -= j; + + return 0; +} diff --git a/coredump.h b/coredump.h new file mode 100644 index 0000000..34cb902 --- /dev/null +++ b/coredump.h @@ -0,0 +1,14 @@ +#ifndef foocoredumphfoo +#define foocoredumphfoo + +#include <elf.h> +#include <link.h> + +int coredump_read_header(int fd, ElfW(Ehdr) *header); +int coredump_read_segment_header(int fd, const ElfW(Ehdr) *header, unsigned long i, ElfW(Phdr) *segment); +int coredump_read_memory(int fd, const ElfW(Ehdr) *header, unsigned long source, void *destination, size_t length); + +int coredump_find_note_segment(int fd, const ElfW(Ehdr) *header, off_t *offset, off_t *length); +int coredump_next_note(int fd, off_t *offset, off_t *length, ElfW(Nhdr) *n, off_t *name, off_t *descriptor); + +#endif diff --git a/format.h b/format.h new file mode 100644 index 0000000..6bd1c80 --- /dev/null +++ b/format.h @@ -0,0 +1,6 @@ +#ifndef fooformathfoo +#define fooformathfoo + + + +#endif diff --git a/generate.c b/generate.c new file mode 100644 index 0000000..ec73b58 --- /dev/null +++ b/generate.c @@ -0,0 +1,79 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/mman.h> +#include <string.h> + +#include "coredump.h" +#include "minidump.h" + +static int write_cb(const void *data, size_t length, void *userdata) { + fwrite(data, 1, length, userdata); + + return 0; +} + +int main(int argc, char *argv[]) { + int r; + int fd = -1; + unsigned long l; + char *p = NULL; + ElfW(Ehdr) header; + ElfW(Phdr) segment; + + if (argc != 2) { + fprintf(stderr, "Expecting file name as sole argument.\n"); + return EXIT_FAILURE; + } + + errno = 0; + l = strtoul(argv[1], &p, 10); + if (errno == 0 && p && *p == 0 && l > 0) + r = minidump_make((pid_t) l, -1, write_cb, stdout); + else { + fd = open(argc >= 2 ? argv[1] : "core", O_RDONLY|O_CLOEXEC); + + if (fd < 0) { + fprintf(stderr, "Failed to open core dump: %m\n"); + r = -errno; + goto fail; + } + + r = minidump_make(0, fd, write_cb, stdout); + } + + if (r < 0) { + fprintf(stderr, "Failed to generate minidump: %s\n", strerror(-r)); + goto fail; + } + + /* r = coredump_read_header(fd, &header); */ + /* if (r < 0) { */ + /* fprintf(stderr, "Failed to read ELF header: %s\n", strerror(-r)); */ + /* goto fail; */ + /* } */ + + /* printf("segments = %lu\n", (unsigned long) header.e_phnum); */ + + /* for (i = 0; i < header.e_phnum; i++) { */ + + /* r = coredump_read_segment_header(fd, &header, i, &segment); */ + /* if (r < 0) { */ + /* fprintf(stderr, "Failed to read ELF segment: %s\n", strerror(-r)); */ + /* goto fail; */ + /* } */ + + /* printf("segment type = %lu\n", (unsigned long) segment.p_type); */ + /* } */ + + +fail: + if (fd >= 0) + close(fd); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/minidump.c b/minidump.c new file mode 100644 index 0000000..fbe9ee5 --- /dev/null +++ b/minidump.c @@ -0,0 +1,470 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#include <assert.h> +#include <stdbool.h> +#include <dirent.h> +#include <stdlib.h> +#include <errno.h> +#include <stdio.h> +#include <sys/ptrace.h> +#include <sys/wait.h> +#include <string.h> +#include <sys/param.h> +#include <sys/user.h> +#include <sys/procfs.h> +#include <sys/uio.h> +#include <unistd.h> + +#include "minidump.h" +#include "coredump.h" + +struct thread_info { + pid_t tid; + unsigned long stack_pointer; + + struct elf_prstatus prstatus; /* only available on coredumps */ + siginfo_t siginfo; /* only available on ptrace */ + struct user user; /* only available on ptrace */ + + struct user_regs_struct regs; + struct user_fpregs_struct fpregs; +#ifdef __i386 + struct user_fpxregs_struct fpxregs; +#endif +}; + +struct context { + pid_t pid; + int fd; + + ElfW(Ehdr) header; + + minidump_write_t write; + void *userdata; +}; + +#define HAVE_PROCESS(c) ((c)->pid > 0) +#define HAVE_COREDUMP(c) ((c)->fd >= 0) + +static int threads_begin(pid_t pid, DIR **_d) { + char *path; + DIR *d; + + assert(pid > 0); + + if (asprintf(&path, "/proc/%lu/task", (unsigned long) pid) < 0) + return -ENOMEM; + + d = opendir(path); + free(path); + + if (!d) + return -errno; + + *_d = d; + return 0; +} + +static int threads_next(DIR *d, pid_t *pid) { + struct dirent buf, *de; + int k; + long l; + char *p; + + for (;;) { + k = readdir_r(d, &buf, &de); + if (k != 0) + return -k; + + if (!de) + return 0; + + if (de->d_name[0] == '.') + continue; + + if (de->d_type != DT_DIR) + continue; + + errno = 0; + l = strtol(de->d_name, &p, 10); + if (errno != 0) + continue; + + if (p && *p != 0) + continue; + + if (l <= 0) + continue; + + *pid = (pid_t) l; + return 1; + } +} + +static int attach_threads(struct context *c, bool b) { + DIR* d = NULL; + int r; + + assert(c); + assert(HAVE_PROCESS(c)); + + r = threads_begin(c->pid, &d); + if (r < 0) + return r; + + for (;;) { + pid_t tid; + + r = threads_next(d, &tid); + if (r < 0) + goto finish; + + if (r == 0) + break; + + if (ptrace(b ? PTRACE_ATTACH : PTRACE_DETACH, tid, NULL, NULL) < 0) { + + if (errno == ESRCH) + continue; + + r = -errno; + goto finish; + } + + if (b) { + /* Wait until the thread is actually stopped */ + + for (;;) { + int status; + + if (waitpid(tid, &status, __WALL) < 0) { + if (errno == EINTR) + continue; + + r = -errno; + goto finish; + } + + if (WIFSTOPPED(status)) + break; + } + } + } + + r = 0; + +finish: + if (d) + closedir(d); + + return r; +} + +static int ptrace_copy(enum __ptrace_request req, pid_t pid, unsigned long source, void *destination, size_t length) { + long l; + + assert(req == PTRACE_PEEKTEXT || + req == PTRACE_PEEKDATA || + req == PTRACE_PEEKUSER); + + assert(pid > 0); + assert(destination); + assert(length > 0); + + while (length > 0) { + + errno = 0; + l = ptrace(req, pid, (void*) source, NULL); + if (errno != 0) + return -errno; + + memcpy(destination, &l, MIN(length, sizeof(l))); + + if (length <= sizeof(l)) + break; + + length -= sizeof(l); + source += sizeof(l); + destination = (uint8_t*) destination + sizeof(l); + } + + return 0; +} + +static int read_memory(struct context *c, unsigned long source, void *destination, size_t length) { + assert(c); + assert(destination); + assert(length > 0); + + if (HAVE_COREDUMP(c)) + return coredump_read_memory(c->fd, &c->header, source, destination, length); + + return ptrace_copy(PTRACE_PEEKDATA, c->pid, source, destination, length); +} + +static int read_thread_info_ptrace(struct context *c, pid_t tid, struct thread_info *i) { + int r; + struct iovec iovec; + + assert(c); + assert(HAVE_PROCESS(c)); + assert(tid > 0); + assert(i); + + memset(i, 0, sizeof(*i)); + + i->tid = tid; + + r = ptrace_copy(PTRACE_PEEKUSER, tid, 0, &i->user, sizeof(i->user)); + if (r < 0) + return r; + + r = ptrace(PTRACE_GETSIGINFO, tid, NULL, &i->siginfo, sizeof(i->siginfo)); + if (r < 0) + return r; + + iovec.iov_base = &i->regs; + iovec.iov_len = sizeof(i->regs); + r = ptrace(PTRACE_GETREGSET, tid, NT_PRSTATUS, &iovec); + if (r < 0) + return r; + if (iovec.iov_len != sizeof(i->regs)) + return -EIO; + + iovec.iov_base = &i->fpregs; + iovec.iov_len = sizeof(i->fpregs); + r = ptrace(PTRACE_GETREGSET, tid, NT_FPREGSET, &iovec); + if (r < 0) + return r; + if (iovec.iov_len != sizeof(i->fpregs)) + return -EIO; + +#ifdef __i386 + iovec.iov_base = &i->fpxregs; + iovec.iov_len = sizeof(i->fpxregs); + r = ptrace(PTRACE_GETREGSET, tid, NT_PRXFPREG, &iovec); + if (r < 0) + return r; + if (iovec.iov_len != sizeof(i->fpxregs)) + return -EIO; +#endif + + return 0; +} + +static int read_thread_info_core(struct context *c, pid_t tid, struct thread_info *i) { + int r; + + assert(c); + assert(c->fd >= 0); + assert(tid > 0); + assert(i); + + return 0; +} + +static int work_thread_info(struct context *c, struct thread_info *i) { + assert(c); + assert(i); + +#if defined(__i386) + i->stack_pointer = (unsigned long) i->regs.esp; +#elif defined(__x86_64) + i->stack_pointer = (unsigned long) i->regs.rsp; +#else +#error "I need porting to your architecture" +#endif + + printf("thread %lu (sp=%0lx)\n", (unsigned long) i->tid, (unsigned long) i->stack_pointer); + return 0; +} + +static int foreach_thread_ptrace(struct context *c) { + DIR *d = NULL; + int r; + + assert(c); + assert(HAVE_PROCESS(c)); + + r = threads_begin(c->pid, &d); + if (r < 0) + return r; + + for (;;) { + pid_t tid; + struct thread_info i; + + r = threads_next(d, &tid); + if (r < 0) + goto finish; + + if (r == 0) + break; + + r = read_thread_info_ptrace(c, tid, &i); + if (r < 0) + goto finish; + + r = work_thread_info(c, &i); + if (r < 0) + goto finish; + } + + r = 0; + +finish: + if (d) + closedir(d); + + return r; +} + +static int foreach_thread_core(struct context *c) { + off_t offset, length; + int r; + struct thread_info i; + unsigned thread_count; + + assert(c); + assert(HAVE_COREDUMP(c)); + + r = coredump_find_note_segment(c->fd, &c->header, &offset, &length); + if (r < 0) + return r; + if (r == 0) + return -EIO; + + memset(&i, 0, sizeof(i)); + thread_count = 0; + + while (length > 0) { + off_t name_offset, descriptor_offset; + char name[16]; + ssize_t l; + ElfW(Nhdr) note; + + + r = coredump_next_note(c->fd, &offset, &length, ¬e, &name_offset, &descriptor_offset); + if (r < 0) + return r; + + if (note.n_namesz >= sizeof(name)) + continue; + + l = pread(c->fd, name, note.n_namesz, name_offset); + if (l < 0) + return -errno; + if (l != note.n_namesz) + return -EIO; + + name[l] = 0; + + if (strcmp(name, "CORE") == 0 && + note.n_type == NT_PRSTATUS) { + + if (thread_count > 0) { + work_thread_info(c, &i); + memset(&i, 0, sizeof(i)); + } + + thread_count ++; + + if (note.n_descsz != sizeof(i.prstatus)) + return -EIO; + + l = pread(c->fd, &i.prstatus, sizeof(i.prstatus), descriptor_offset); + if (l < 0) + return -errno; + if (l != sizeof(i.prstatus)) + return -EIO; + + i.tid = i.prstatus.pr_pid; + memcpy(&i.regs, i.prstatus.pr_reg, sizeof(i.regs)); + + } else if (strcmp(name, "CORE") == 0 && + note.n_type == NT_FPREGSET) { + + if (note.n_descsz != sizeof(i.fpregs)) + return -EIO; + + l = pread(c->fd, &i.fpregs, sizeof(i.fpregs), descriptor_offset); + if (l < 0) + return -errno; + if (l != sizeof(i.fpregs)) + return -EIO; +#ifdef __i386 + } else if (strcmp(name, "LINUX") == 0 && + note.n_type == NT_PRXFPREG) { + + if (note.n_descsz != sizeof(i.fpxregs)) + return -EIO; + + l = pread(c->fd, &i.fpxregs, sizeof(i.fpxregs), descriptor_offset); + if (l < 0) + return -errno; + if (l != sizeof(i.fpxregs)) + return -EIO; +#endif + } + } + + if (thread_count > 0) + work_thread_info(c, &i); + + return 0; +} + +static int foreach_thread(struct context *c) { + assert(c); + + if (HAVE_COREDUMP(c)) + return foreach_thread_core(c); + + return foreach_thread_ptrace(c); +} + +int minidump_make(pid_t pid, int fd, minidump_write_t cb, void *userdata) { + struct context c; + int r; + + if (pid <= 0 && fd < 0) + return -EINVAL; + + if (!cb) + return -EINVAL; + + if (pid > 0) + if (kill(pid, 0) < 0) + return -errno; + + memset(&c, 0, sizeof(c)); + + c.pid = pid; + c.fd = fd; + c.write = cb; + c.userdata = userdata; + + if (HAVE_PROCESS(&c)) { + r = attach_threads(&c, true); + if (r < 0) + goto finish; + } + + if (HAVE_COREDUMP(&c)) { + r = coredump_read_header(fd, &c.header); + if (r < 0) + return r; + } + + r = foreach_thread(&c); + if (r < 0) + goto finish; + + r = 0; + +finish: + if (HAVE_PROCESS(&c)) + attach_threads(&c, false); + + return r; +} diff --git a/minidump.h b/minidump.h new file mode 100644 index 0000000..4dcc6a8 --- /dev/null +++ b/minidump.h @@ -0,0 +1,12 @@ +#ifndef foominidumphfoo +#define foominidumphfoo + +#include <sys/types.h> + +typedef int (*minidump_write_t)(const void *data, size_t length, void *userdata); + +/* Pass the PID and/or a seekable fd for the coredump. */ + +int minidump_make(pid_t pid, int coredump_fd, minidump_write_t cb, void *userdata); + +#endif diff --git a/segfault.c b/segfault.c new file mode 100644 index 0000000..e0e759c --- /dev/null +++ b/segfault.c @@ -0,0 +1,38 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#include <sys/time.h> +#include <sys/resource.h> +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <limits.h> + +int main(int argc, char *argv[]) { + int *p = 0; + struct rlimit rl; + char path[PATH_MAX]; + + if (getrlimit(RLIMIT_CORE, &rl) < 0) { + fprintf(stderr, "getrlimit() failed: %m"); + return EXIT_FAILURE; + } + + rl.rlim_cur = 1024UL*1024UL*1024UL; + if (setrlimit(RLIMIT_CORE, &rl) < 0) { + fprintf(stderr, "setrlimit() failed: %m"); + return EXIT_FAILURE; + } + + unlink("coredump"); + + snprintf(path, sizeof(path), "core.%lu", (unsigned long) getpid()); + path[sizeof(path)-1] = 0; + + if (symlink(path, "core") < 0) { + fprintf(stderr, "symlink() failed: %m"); + return EXIT_FAILURE; + } + + *p = 1; + return 0; +} |