summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2012-03-07 19:50:31 +0100
committerLennart Poettering <lennart@poettering.net>2012-03-07 19:50:31 +0100
commitca28e201c6ae5c06272b61861072f66bca172ed0 (patch)
treeae56fa42a340052c7b1f375f15a342d951f2eddb
initial commit
-rw-r--r--.gitignore5
-rw-r--r--Makefile15
-rw-r--r--coredump.c179
-rw-r--r--coredump.h14
-rw-r--r--format.h6
-rw-r--r--generate.c79
-rw-r--r--minidump.c470
-rw-r--r--minidump.h12
-rw-r--r--segfault.c38
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, &note, &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;
+}