From ca28e201c6ae5c06272b61861072f66bca172ed0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 7 Mar 2012 19:50:31 +0100 Subject: initial commit --- .gitignore | 5 + Makefile | 15 ++ coredump.c | 179 +++++++++++++++++++++++ coredump.h | 14 ++ format.h | 6 + generate.c | 79 +++++++++++ minidump.c | 470 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ minidump.h | 12 ++ segfault.c | 38 +++++ 9 files changed, 818 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 coredump.c create mode 100644 coredump.h create mode 100644 format.h create mode 100644 generate.c create mode 100644 minidump.c create mode 100644 minidump.h create mode 100644 segfault.c 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 +#include +#include +#include +#include +#include +#include + +#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 +#include + +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 +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + +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 +#include +#include +#include +#include +#include + +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; +} -- cgit v1.2.3