crash-stack: import tbstack sources 40/186140/3
authorŁukasz Stelmach <l.stelmach@samsung.com>
Fri, 27 Jul 2018 11:46:56 +0000 (13:46 +0200)
committerKarol Lewandowski <k.lewandowsk@samsung.com>
Wed, 22 Aug 2018 05:51:34 +0000 (07:51 +0200)
Import files from tbstack project (commit 06b996baf444450dea117296f3b04fe5ba753222)

The code allows access to a crashed process via process_vm_readv()
(or reading /porc/PID/mem) instead of ptrace(2), which is not permited
for crashed processes.

Change-Id: I202454d03bdf5da6c419c921db839a0647c0ec2c
Origin: https://github.com/tbricks/tbstack/commit/06b996baf444450dea117296f3b04fe5ba753222
Origin: https://github.com/tbricks/tbstack/pull/31/commits/d0c7acfaa269e9183c41d120073aadca68b6f79d
Signed-off-by: Łukasz Stelmach <l.stelmach@samsung.com>
[ Changed Origin & Commit after code has been merged to upstream repository
  Commit: d0c7acfaa269e9183c41d120073aadca68b6f79d -> 06b996baf444450dea117296f3b04fe5ba753222
  Origin: ihttps://github.com/tbricks/tbstack/pull/31/commits/d0c7acfaa269e9183c41d120073aadca68b6f79d -> https://github.com/tbricks/tbstack/commit/06b996baf444450dea117296f3b04fe5ba753222 ]
Signed-off-by: Karol Lewandowski <k.lewandowsk@samsung.com>
src/crash-stack/mem_map.c [new file with mode: 0644]
src/crash-stack/mem_map.h [new file with mode: 0644]
src/crash-stack/proc.c [new file with mode: 0644]
src/crash-stack/proc.h [new file with mode: 0644]
src/crash-stack/unwind.c [new file with mode: 0644]

diff --git a/src/crash-stack/mem_map.c b/src/crash-stack/mem_map.c
new file mode 100644 (file)
index 0000000..e330a1d
--- /dev/null
@@ -0,0 +1,722 @@
+/*
+ * tbstack -- fast stack trace utility
+ *
+ * Copyright (c) 2014, Tbricks AB
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TBRICKS
+ * AB BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THISS OFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#include "mem_map.h"
+#include "proc.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#ifndef VSYSCALL_START
+#define VSYSCALL_START (-10UL << 20)
+#endif
+
+extern int opt_verbose;
+extern int opt_ignore_deleted;
+
+static int in(const void *point, const void *start, size_t size)
+{
+    return ((point >= start) &&
+            ((const char *)point < ((const char *)start + size)));
+}
+
+static void mem_data_chunk_init(struct mem_data_chunk *chunk)
+{
+    chunk->start = NULL;
+    chunk->data = NULL;
+    chunk->length = 0;
+    chunk->next = NULL;
+}
+
+static int mem_data_chunk_read_word(struct mem_data_chunk *chunk,
+        void *addr, uintptr_t *value)
+{
+    size_t offset = (size_t)addr - (size_t)chunk->start;
+    assert(offset < chunk->length);
+    *value = *(uintptr_t *)(chunk->data + offset);
+    return 0;
+}
+
+static void mem_data_chunk_destroy(struct mem_data_chunk *chunk, int type)
+{
+    switch (type)
+    {
+    case MEM_REGION_TYPE_MALLOC:
+        free(chunk->data);
+        break;
+
+    case MEM_REGION_TYPE_MMAP:
+        munmap(chunk->data, chunk->length);
+        break;
+
+    default:
+        break;
+    }
+    free(chunk);
+}
+
+static void mem_data_chunk_list_destroy(struct mem_data_chunk *chunk, int type)
+{
+    struct mem_data_chunk *next;
+    while (chunk != NULL) {
+        next = chunk->next;
+        mem_data_chunk_destroy(chunk, type);
+        chunk = next;
+    }
+}
+
+void mem_region_init(struct mem_region *region)
+{
+    region->start = NULL;
+    region->length = 0;
+    region->offset = 0;
+
+    region->data_head = NULL;
+    region->data_index = NULL;
+    region->num_data_chunks = 0;
+    region->prev_accessed_chunk = NULL;
+
+    region->labels = NULL;
+    region->num_labels = 0;
+
+    region->path = NULL;
+    region->fd = -1;
+    region->type = MEM_REGION_TYPE_EMPTY;
+
+    region->next = NULL;
+}
+
+static void mem_region_add_label(struct mem_region *region,
+        void *label, size_t reserve)
+{
+    size_t i;
+
+    if (region->labels == NULL)
+        region->labels = malloc(sizeof(void *)*reserve);
+
+    for (i = 0; i < region->num_labels; ++i) {
+        if (region->labels[i] > label) {
+            memmove(&region->labels[i+1], &region->labels[i],
+                    sizeof(void*) * (region->num_labels - i));
+            break;
+        }
+    }
+
+    region->labels[i] = label;
+    ++region->num_labels;
+}
+
+static int mem_region_add_data_chunk(struct mem_region *region,
+        struct mem_data_chunk *chunk)
+{
+    size_t i;
+    struct mem_data_chunk **cur = &region->data_head;
+    void *chunk_ceil;
+
+    chunk_ceil = (char *)chunk->start + chunk->length;
+    region->type = MEM_REGION_TYPE_MALLOC;
+
+    for (i = 0; i < region->num_data_chunks; ++i) {
+        if (in(chunk->start, (*cur)->start, (*cur)->length) ||
+            in(chunk_ceil, (*cur)->start, (*cur)->length))
+        {
+            fprintf(stderr, "error: overlapping chunks: existing: %p-%p "
+                            "new: %p-%p\n",
+                            (*cur)->start,
+                            (*cur)->start + (*cur)->length,
+                            chunk->start,
+                            chunk_ceil);
+            return -1;
+        }
+        if ((*cur)->start > chunk->start)
+            break;
+        cur = &(*cur)->next;
+    }
+
+    chunk->next = *cur;
+    *cur = chunk;
+    ++region->num_data_chunks;
+    return 0;
+}
+
+static struct mem_data_chunk *mem_region_alloc_chunk(struct mem_region *region,
+        void *start, void *end, size_t align)
+{
+    struct mem_data_chunk *chunk;
+    int rc;
+
+    chunk = malloc(sizeof(struct mem_data_chunk));
+    mem_data_chunk_init(chunk);
+
+    chunk->start = start;
+    chunk->length = (size_t)end - (size_t)start;
+    rc = posix_memalign((void **)&chunk->data, align, chunk->length);
+    if (rc < 0) {
+        perror("posix_memalign");
+        return NULL;
+    }
+
+    mem_region_add_data_chunk(region, chunk);
+    return chunk;
+}
+
+const char *str_mem_region_type(int type)
+{
+    switch (type)
+    {
+    case MEM_REGION_TYPE_EMPTY:
+        return "empty";
+    case MEM_REGION_TYPE_MALLOC:
+        return "malloc";
+    case MEM_REGION_TYPE_MMAP:
+        return "mmap";
+    case MEM_REGION_TYPE_VDSO:
+        return "vdso";
+    case MEM_REGION_TYPE_VSYSCALL:
+        return "vsyscall";
+    case MEM_REGION_TYPE_DELETED:
+        return "deleted";
+    default:
+        break;
+    }
+    return "unknown";
+}
+
+static void mem_region_print(const struct mem_region *region)
+{
+    fprintf(stderr,
+            "region addr: %zx-%zx len: %zx off: %zx num_chunks: %zd "
+            "path='%s' fd=%d type=%s\n",
+            (size_t)region->start,
+            (size_t)region->start+region->length,
+            region->length,
+            region->offset,
+            region->num_data_chunks,
+            region->path,
+            region->fd,
+            str_mem_region_type(region->type));
+}
+
+static void mem_region_create_data_chunk_index(struct mem_region *region)
+{
+    int i;
+    struct mem_data_chunk *cur;
+
+    if (!region->num_data_chunks)
+        return;
+
+    region->data_index = malloc(sizeof(
+                struct mem_data_chunk*) * region->num_data_chunks);
+
+    cur = region->data_head;
+    for (i = 0; cur != NULL; cur = cur->next) {
+        region->data_index[i++] = cur;
+
+        if (i > (int)region->num_data_chunks) {
+            fprintf(stderr, "region %p: num_data_chunks=%zd but cur != NULL\n",
+                    region, region->num_data_chunks);
+            mem_region_print(region);
+            break;
+        }
+    }
+}
+
+static char *addr_increment_clamped(char *start, char *end, size_t increment)
+{
+    assert(end >= start);
+    return ((size_t)(end - start) <= increment) ?
+            end : start + increment;
+}
+
+static int mem_region_build_label_cover(struct mem_region *region,
+        size_t generic_chunk_size, struct mem_data_chunk **chunks, size_t align)
+{
+    size_t i, n = 0;
+
+    if (region->num_labels == 0)
+        return 0;
+
+    for (i = 0; i < region->num_labels; ++i) {
+        char *cur_start, *cur_end, *region_end;
+        struct mem_data_chunk *new_chunk;
+
+        region_end = (char *)region->start + region->length;
+        cur_start = region->labels[i];
+        cur_end = addr_increment_clamped(cur_start, region_end, generic_chunk_size);
+
+        for (++i; i < region->num_labels; ++i) {
+            if ((size_t)region->labels[i] <= (size_t)cur_end) {
+                cur_end = addr_increment_clamped((char *)region->labels[i],
+                                                 region_end, generic_chunk_size);
+                if (cur_end == region_end)
+                    break;
+            }
+        }
+
+        new_chunk = mem_region_alloc_chunk(region, cur_start, cur_end, align);
+        chunks[n] = new_chunk;
+        ++n;
+    }
+
+    mem_region_create_data_chunk_index(region);
+
+    return n;
+}
+
+static int mem_region_map_file(struct mem_region *region)
+{
+    void *data;
+    struct stat stat_buf;
+    size_t length = region->length;
+
+    if (region->path == NULL || *region->path == '\0') {
+        fprintf(stderr, "trying to map file for region %p-%p "
+                "with empty path\n",
+                region->start, region->start + region->length);
+        return -1;
+    }
+
+    region->fd = open(region->path, O_RDONLY);
+    if (region->fd < 0) {
+        perror(region->path);
+        return -1;
+    }
+
+    if (fstat(region->fd, &stat_buf) < 0) {
+        int err = errno;
+        fprintf(stderr, "failed to stat file %s: %s\n", region->path, strerror(err));
+        return -1;
+    }
+
+    if (region->offset > (size_t)stat_buf.st_size) {
+        return -1;
+    }
+
+    // Accessing beyond the length of the file, even though we can map a
+    // region larger than the size of the file, will cause a SIGBUS, so
+    // truncate the length of the map to fit within the file.
+    if (region->length > stat_buf.st_size - region->offset) {
+        length = stat_buf.st_size - region->offset;
+    }
+
+    data = mmap(NULL, length, PROT_READ, MAP_SHARED, region->fd,
+                region->offset);
+
+    if (data == MAP_FAILED) {
+        int err = errno;
+        fprintf(stderr, "failed to mmap file %s (length 0x%zx, read, offset "
+                "0x%zx): %s\n", region->path, region->length, region->offset,
+                strerror(err));
+        return -1;
+    }
+
+    region->data_head = malloc(sizeof(struct mem_data_chunk));
+    mem_data_chunk_init(region->data_head);
+    region->data_head->start = region->start;
+    region->data_head->data = data;
+    region->data_head->length = length;
+
+    region->data_index = malloc(sizeof(struct mem_data_chunk**));
+    *region->data_index = region->data_head;
+    ++region->num_data_chunks;
+
+    region->prev_accessed_chunk = region->data_head;
+
+    return 0;
+}
+
+static int mem_region_init_vdso(struct mem_region *region)
+{
+    region->data_head = malloc(sizeof(struct mem_data_chunk));
+    mem_data_chunk_init(region->data_head);
+    region->data_head->start = region->start;
+    region->data_head->length = region->length;
+
+    if ((region->data_head->data = (char *)get_vdso()) == NULL)
+        return -1;
+
+    region->data_index = malloc(sizeof(struct mem_data_chunk**));
+    *region->data_index = region->data_head;
+    ++region->num_data_chunks;
+
+    region->prev_accessed_chunk = region->data_head;
+
+    return 0;
+}
+
+static int mem_region_init_vsyscall(struct mem_region *region)
+{
+    region->data_head = malloc(sizeof(struct mem_data_chunk));
+    mem_data_chunk_init(region->data_head);
+    region->data_head->start = region->start;
+    region->data_head->data = (char *)VSYSCALL_START;
+    region->data_head->length = region->length;
+
+    region->data_index = malloc(sizeof(struct mem_data_chunk**));
+    *region->data_index = region->data_head;
+    ++region->num_data_chunks;
+
+    region->prev_accessed_chunk = region->data_head;
+
+    return 0;
+}
+
+static int addr_data_chunk_compar(const void *key, const void *member)
+{
+    const struct mem_data_chunk* const *chunk = member;
+    if (key < (*chunk)->start)
+        return -1;
+    if (in(key, (*chunk)->start, (*chunk)->length))
+        return 0;
+    return 1;
+}
+
+struct mem_data_chunk *mem_region_find_data_chunk(
+        struct mem_region *region, void *addr)
+{
+    struct mem_data_chunk **chunk_ptr, *chunk;
+
+    chunk = region->prev_accessed_chunk;
+    if (chunk != NULL && !addr_data_chunk_compar(addr, &chunk))
+        return chunk;
+
+    if (region->data_index == NULL) {
+        if (region->num_data_chunks) {
+            fprintf(stderr,
+                    "error: region %p-%p is not indexed but "
+                    "attempting to read word\n",
+                    region->start,
+                    region->start + region->length);
+        }
+        return NULL;
+    }
+
+    chunk_ptr = (struct mem_data_chunk **)bsearch(addr,
+            region->data_index,
+            region->num_data_chunks,
+            sizeof(struct mem_data_chunk*),
+            addr_data_chunk_compar);
+
+    if (chunk_ptr == NULL)
+        return NULL;
+
+    chunk = *chunk_ptr;
+    region->prev_accessed_chunk = chunk;
+    return chunk;
+}
+
+static int mem_region_read_word(struct mem_region *region,
+        void *addr, uintptr_t *value)
+{
+    struct mem_data_chunk *chunk;
+
+    switch (region->type) {
+    case MEM_REGION_TYPE_EMPTY:
+        fprintf(stderr,
+                "error: trying to read word from empty region %p-%p\n",
+                region->start,
+                region->start + region->length);
+        return -1;
+
+    case MEM_REGION_TYPE_DELETED:
+        if (!opt_ignore_deleted)
+            return -2;
+
+    case MEM_REGION_TYPE_MMAP:
+        if (region->fd < 0 && mem_region_map_file(region) < 0)
+            return -1;
+        break;
+
+    case MEM_REGION_TYPE_VDSO:
+        if (region->data_head == NULL && mem_region_init_vdso(region) < 0)
+            return -1;
+        break;
+
+    case MEM_REGION_TYPE_VSYSCALL:
+        if (region->data_head == NULL && mem_region_init_vsyscall(region) < 0)
+            return -1;
+        break;
+
+    default:
+        break;
+    }
+
+    if (value == NULL)
+        return 0;
+
+    chunk = mem_region_find_data_chunk(region, addr);
+
+    if (chunk == NULL) {
+        size_t i;
+
+        if (!opt_verbose)
+            return -1;
+
+        fprintf(stderr,
+                "no chunk of memory containing %p at region %p-%p\n",
+                addr, region->start, region->start + region->length);
+        mem_region_print(region);
+
+        for (i = 0; i < region->num_data_chunks; ++i) {
+            struct mem_data_chunk *chunk = region->data_index[i];
+            fprintf(stderr, "chunk %zd: start %p length 0x%zx data %p\n",
+                    i, chunk->start, chunk->length, chunk->data);
+        }
+        return -1;
+    }
+
+    return mem_data_chunk_read_word(chunk,
+            addr,
+            value);
+}
+
+static void mem_region_destroy(struct mem_region *region)
+{
+    if (region->data_head != NULL)
+        mem_data_chunk_list_destroy(region->data_head, region->type);
+    free(region->data_index);
+    if (region->fd >= 0)
+        close(region->fd);
+    free(region->labels);
+    free(region->path);
+    free(region);
+}
+
+static void mem_region_list_destroy(struct mem_region *region)
+{
+    struct mem_region *next;
+    while (region != NULL) {
+        next = region->next;
+        mem_region_destroy(region);
+        region = next;
+    }
+}
+
+void mem_map_init(struct mem_map *map)
+{
+    map->list_head = NULL;
+    map->list_index = NULL;
+    map->num_regions = 0;
+    map->prev_accessed_region = NULL;
+}
+
+int mem_map_add_region(struct mem_map *map, struct mem_region *region)
+{
+    size_t i;
+    struct mem_region **cur = &map->list_head;
+    struct mem_region *prev = map->prev_accessed_region;
+    void *region_ceil;
+
+    region_ceil = (char *)region->start + region->length;
+
+    if (prev != NULL && prev->next == NULL) {
+        if ((char *)region->start >= ((char *)prev->start + prev->length)) {
+            prev->next = region;
+            ++map->num_regions;
+            map->prev_accessed_region = region;
+            return 0;
+        }
+    }
+
+    for (i = 0; i < map->num_regions; ++i) {
+        if (in(region->start, (*cur)->start, (*cur)->length) ||
+            in(region_ceil, (*cur)->start, (*cur)->length))
+        {
+            fprintf(stderr, "error: overlapping regions: existing: %p-%p "
+                            "new: %p-%p\n",
+                            (*cur)->start,
+                            (*cur)->start+(*cur)->length,
+                            region->start,
+                            region_ceil);
+            return -1;
+        }
+        if ((*cur)->start > region->start)
+            break;
+        cur = &(*cur)->next;
+    }
+
+    region->next = *cur;
+    *cur = region;
+    ++map->num_regions;
+    map->prev_accessed_region = region;
+    return 0;
+}
+
+void mem_map_create_region_index(struct mem_map *map)
+{
+    int i;
+    struct mem_region *cur;
+
+    if (!map->num_regions)
+        return;
+
+    map->list_index = malloc(sizeof(struct mem_region*) * map->num_regions);
+    cur = map->list_head;
+    for (i = 0; cur != NULL; cur = cur->next) {
+        map->list_index[i++] = cur;
+    }
+}
+
+static int addr_region_compar(const void *key, const void *member)
+{
+    const struct mem_region* const *region = member;
+
+    if (key < (*region)->start)
+        return -1;
+    if (in(key, (*region)->start, (*region)->length))
+        return 0;
+    return 1;
+}
+
+static struct mem_region *mem_map_find_region(struct mem_map *map, void *addr)
+{
+    struct mem_region **region_ptr, *region;
+
+    region = map->prev_accessed_region;
+    if (region != NULL && !addr_region_compar(addr, &region))
+        return region;
+
+    if (map->list_index == NULL) {
+        if (map->num_regions) {
+            fprintf(stderr,
+                    "error: map is not indexed but attempting to find region\n");
+        }
+        return NULL;
+    }
+
+    region_ptr = (struct mem_region **)bsearch(addr,
+            map->list_index,
+            map->num_regions,
+            sizeof(struct mem_region*),
+            addr_region_compar);
+
+    if (region_ptr == NULL) {
+        fprintf(stderr,
+                "cannot find region of memory containing %p\n",
+                addr);
+        region = NULL;
+    } else {
+        region = *region_ptr;
+        map->prev_accessed_region = region;
+    }
+
+    return region;
+}
+
+struct mem_region *mem_map_get_file_region(struct mem_map *map, void *addr)
+{
+    struct mem_region *region;
+
+    if ((region = mem_map_find_region(map, addr)) == NULL) {
+        fprintf(stderr, "cannot get file region\n");
+        return NULL;
+    }
+
+    if (region->type != MEM_REGION_TYPE_MMAP &&
+            region->type != MEM_REGION_TYPE_DELETED &&
+            region->type != MEM_REGION_TYPE_VDSO &&
+            region->type != MEM_REGION_TYPE_VSYSCALL) {
+        fprintf(stderr, "get file region: unexpected region type %s\n",
+                str_mem_region_type(region->type));
+        mem_region_print(region);
+        return NULL;
+    }
+
+    if (region->fd < 0 && mem_region_read_word(region, addr, NULL) == -1)
+        return NULL;
+
+    return region;
+}
+
+int mem_map_add_label(struct mem_map *map, void *label, size_t reserve)
+{
+    struct mem_region *region;
+
+    region = mem_map_find_region(map, label);
+    if (region == NULL)
+        return -1;
+
+    mem_region_add_label(region, label, reserve);
+    return 0;
+}
+
+int mem_map_build_label_cover(struct mem_map *map, size_t generic_chunk_size,
+        struct mem_data_chunk **chunks, size_t align)
+{
+    struct mem_region *cur;
+    int n = 0;
+
+    cur = map->list_head;
+    while (cur != NULL) {
+        n += mem_region_build_label_cover(
+                cur, generic_chunk_size, chunks + n, align);
+        cur = cur->next;
+    }
+
+    return n;
+}
+
+int mem_map_read_word(struct mem_map *map, void *addr, uintptr_t *value)
+{
+    struct mem_region *region;
+
+    region = mem_map_find_region(map, addr);
+    if (region == NULL)
+        return -1;
+
+    return mem_region_read_word(region,
+            addr,
+            value);
+}
+
+void mem_map_destroy(struct mem_map *map)
+{
+    if (map->list_head != NULL)
+        mem_region_list_destroy(map->list_head);
+    free(map->list_index);
+    free(map);
+}
+
+void mem_map_print(const struct mem_map *map)
+{
+    struct mem_region *region;
+
+    fprintf(stderr, "mem map with %zd regions\n", map->num_regions);
+    region = map->list_head;
+    for (; region != NULL; region = region->next)
+        mem_region_print(region);
+}
diff --git a/src/crash-stack/mem_map.h b/src/crash-stack/mem_map.h
new file mode 100644 (file)
index 0000000..309671e
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * tbstack -- fast stack trace utility
+ *
+ * Copyright (c) 2014, Tbricks AB
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TBRICKS
+ * AB BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THISS OFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef __8a2a3b50_986f_11e3_aa30_efef7030cdbc
+#define __8a2a3b50_986f_11e3_aa30_efef7030cdbc
+
+#include <stdint.h>
+#include <string.h>
+
+#define MEM_REGION_TYPE_EMPTY    0
+#define MEM_REGION_TYPE_MALLOC   1
+#define MEM_REGION_TYPE_MMAP     2
+#define MEM_REGION_TYPE_VDSO     3
+#define MEM_REGION_TYPE_VSYSCALL 4
+#define MEM_REGION_TYPE_DELETED  5
+
+struct mem_data_chunk
+{
+    /* start in process' address space */
+    void *start;
+    /* allocated memory */
+    char *data;
+    /* data size */
+    size_t length;
+    /* next list element */
+    struct mem_data_chunk *next;
+};
+
+struct mem_region
+{
+    /* start in process' address space */
+    void *start;
+    /* memory length */
+    size_t length;
+    /* file offset */
+    size_t offset;
+
+    /* list of copied data chunks */
+    struct mem_data_chunk *data_head;
+    /* sorted index for binary search */
+    struct mem_data_chunk **data_index;
+    /* number of data chunks in index */
+    size_t num_data_chunks;
+    /* cached result of previous lookup */
+    struct mem_data_chunk *prev_accessed_chunk;
+
+    /* points to build label cover and copy needed memory contents */
+    void **labels;
+    /* number of points (normally 1) */
+    size_t num_labels;
+
+    /* path of mmapped file */
+    char *path;
+    /* file descriptor */
+    int fd;
+    /* type of region */
+    int type;
+
+    /* next list element */
+    struct mem_region *next;
+};
+
+struct mem_map
+{
+    /* list of regions */
+    struct mem_region *list_head;
+    /* sorted index for binary search */
+    struct mem_region **list_index;
+    /* number of regions in index */
+    size_t num_regions;
+    /* cached result of previous lookup */
+    struct mem_region *prev_accessed_region;
+};
+
+/*
+ * mem region
+ */
+
+const char *str_mem_region_type(int type);
+
+void mem_region_init(struct mem_region *region);
+
+/*
+ * mem map
+ */
+void mem_map_init(struct mem_map *map);
+
+int mem_map_add_region(struct mem_map *map, struct mem_region *region);
+
+void mem_map_create_region_index(struct mem_map *map);
+
+int mem_map_add_label(struct mem_map *map, void *label, size_t reserve);
+
+int mem_map_build_label_cover(struct mem_map *map, size_t generic_chunk_size,
+        struct mem_data_chunk **chunks, size_t align);
+
+struct mem_region *mem_map_get_file_region(struct mem_map *map, void *addr);
+
+struct mem_data_chunk *mem_region_find_data_chunk(
+        struct mem_region *region, void *addr);
+
+int mem_map_read_word(struct mem_map *map, void *addr, uintptr_t *value);
+
+void mem_map_destroy(struct mem_map *map);
+
+void mem_map_print(const struct mem_map *map);
+
+#endif
+
diff --git a/src/crash-stack/proc.c b/src/crash-stack/proc.c
new file mode 100644 (file)
index 0000000..929929f
--- /dev/null
@@ -0,0 +1,643 @@
+/*
+ * tbstack -- fast stack trace utility
+ *
+ * Copyright (c) 2014, Tbricks AB
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TBRICKS
+ * AB BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THISS OFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#include "mem_map.h"
+#include "proc.h"
+
+#include <ctype.h>
+#include <dirent.h>
+#include <elf.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <linux/limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ptrace.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#ifndef SYS_process_vm_readv
+#if defined(__i386)
+#define SYS_process_vm_readv 365
+#elif defined(__x86_64)
+#define SYS_process_vm_readv 310
+#else
+#error SYS_process_vm_readv is undefined
+#endif
+#endif
+
+#define SLEEP_WAIT 500
+
+int attached_pid = 0;
+
+/* timeout on waiting for process to stop (us) */
+extern int stop_timeout;
+static int sleep_time = 0;
+
+/* for summary */
+int sleep_count = 0;
+size_t total_length = 0;
+
+extern struct timeval freeze_time;
+extern struct timeval unfreeze_time;
+
+extern int opt_proc_mem;
+extern int opt_use_waitpid_timeout;
+extern int opt_verbose;
+
+int proc_state(int pid)
+{
+    FILE *f;
+    char buf[128];
+    char c;
+    int res = -1;
+
+    sprintf(buf, "/proc/%d/status", pid);
+    if ((f = fopen(buf, "r")) == NULL) {
+        fprintf(stderr, "cannot open %s: %s\n", buf, strerror(errno));
+        return -1;
+    }
+
+    while (fgets(buf, sizeof(buf), f)) {
+        if (sscanf(buf, "State:\t%c", &c) == 1) {
+            res = c;
+            break;
+        }
+    }
+
+    fclose(f);
+    return res;
+}
+
+static int proc_stopped(int pid)
+{
+    int c = proc_state(pid);
+    if (c == -1)
+        return -1;
+
+    return (c == 't' || c == 'T');
+}
+
+struct mem_map *create_maps(int pid)
+{
+    FILE *f;
+    char *buf = NULL, *str = NULL;
+    size_t total_read, capacity;
+
+    size_t addr_start, addr_end, offset, len;
+    char r, w, x, p;
+    int dev_major, dev_minor, inode;
+    char path[PATH_MAX];
+
+    struct mem_map *map = NULL;
+    struct mem_region *region;
+
+    capacity = 0x100000;
+    buf = calloc(1, capacity);
+
+    sprintf(buf, "/proc/%d/maps", pid);
+    if ((f = fopen(buf, "r")) == NULL) {
+        fprintf(stderr, "cannot open %s: %s\n", buf, strerror(errno));
+        return NULL;
+    }
+
+    map = malloc(sizeof(struct mem_map));
+    mem_map_init(map);
+
+    memset(buf, 0, capacity);
+    total_read = 0;
+    while (!feof(f)) {
+        fread(&buf[total_read], capacity - total_read - 1, 1, f);
+        if (errno) {
+            perror("maps");
+            mem_map_destroy(map);
+            map = NULL;
+            goto create_maps_end;
+        }
+
+        total_read = strlen(buf);
+        if ((total_read + 1) == capacity) {
+            capacity *= 2;
+            buf = realloc(buf, capacity);
+            memset(&buf[total_read], 0, capacity - total_read);
+        } else {
+            buf[total_read] = '\0';
+        }
+    }
+
+    str = &buf[0];
+    while (*str) {
+        int scan;
+        char *next;
+
+        next = strchr(str, '\n');
+        if (next != NULL)
+            *next = '\0';
+
+        scan = sscanf(str, "%zx-%zx %c%c%c%c %zx %x:%x %d %[^\t\n]",
+                &addr_start, &addr_end,
+                &r, &w, &x, &p,
+                &offset,
+                &dev_major, &dev_minor,
+                &inode,
+                path);
+
+        if (scan < 10) {
+            fprintf(stderr, "warning: unable to parse maps "
+                    "entry '%s' (read %d)\n", str, scan);
+            break;
+        }
+
+        region = malloc(sizeof(struct mem_region));
+        mem_region_init(region);
+
+        region->start = (void *)addr_start;
+        region->length = addr_end - addr_start;
+        region->offset = offset;
+        if (scan > 10 && path[0] != '\0') {
+            if (!strcmp(path, "[vdso]")) {
+                region->type = MEM_REGION_TYPE_VDSO;
+            } else if (!strcmp(path, "[vsyscall]")) {
+                region->type = MEM_REGION_TYPE_VSYSCALL;
+            } else if ((len = strlen(path)) > 10 &&
+                    !strcmp(path + len - 10, " (deleted)")) {
+                *(path + len - 10) = '\0';
+                region->path = strdup(path);
+                region->type = MEM_REGION_TYPE_DELETED;
+            } else {
+                region->path = strdup(path);
+                region->type = MEM_REGION_TYPE_MMAP;
+            }
+        }
+
+        if (mem_map_add_region(map, region) != 0) {
+            mem_map_destroy(map);
+            map = NULL;
+            break;
+        }
+
+        if (next != NULL)
+            str = next + 1;
+    }
+
+    if (map != NULL)
+        mem_map_create_region_index(map);
+
+create_maps_end:
+    fclose(f);
+    free(buf);
+    return map;
+}
+
+int print_proc_maps(int pid)
+{
+    char cmd[32];
+    sprintf(cmd, "cat /proc/%d/maps 1>&2", pid);
+    return system(cmd);
+}
+
+/*
+ * filter for scandir(). choose only thread identifiers
+ */
+static int dir_select(const struct dirent *entry)
+{
+    const char *c = entry->d_name;
+    while (*c)
+        if (!isdigit(*c++))
+            return 0;
+    return 1;
+}
+
+int get_threads(int pid, int **tids)
+{
+    char buf[32];
+    struct dirent **namelist;
+    int cur, i, n;
+
+    snprintf(buf, sizeof(buf), "/proc/%d/task", pid);
+
+    n = scandir(buf, &namelist, dir_select, NULL);
+    if (n < 0) {
+        perror(buf);
+        return -1;
+    } else {
+        *tids = malloc(sizeof(int)*n);
+        i = 0;
+        while (i < n) {
+            cur = atoi(namelist[i]->d_name);
+            (*tids)[i] = cur;
+            free(namelist[i++]);
+        }
+        free(namelist);
+    }
+
+    return n;
+}
+
+char *get_thread_states(const int *tids, int n)
+{
+    int i;
+    char *res = calloc(1, n);
+
+    for (i = 0; i < n; ++i) {
+        int state = proc_state(tids[i]);
+        if (state < 0) {
+            fprintf(stderr, "warning: could not get state of thread %d\n",
+                    tids[i]);
+            res[i] = '?';
+            continue;
+        }
+        res[i] = state;
+    }
+
+    return res;
+}
+
+int adjust_threads(int *tids, int nr_tids, int *user_tids,
+        int *index, int nr_user)
+{
+    int i, j, n = 0;
+    for (i = 0; i < nr_user; ++i) {
+        int found = 0;
+        for (j = 0; j < nr_tids; ++j) {
+            if (tids[j] == user_tids[i]) {
+                found = 1;
+                break;
+            }
+        }
+        if (!found) {
+            if (n || (user_tids[i] > nr_tids) || (user_tids[i] <= 0)) {
+                fprintf(stderr, "unexpected thread %d\n", user_tids[i]);
+                return -1;
+            }
+        } else {
+            ++n;
+            index[i] = j + 1;
+        }
+    }
+    if (!n) {
+        for (i = 0; i < nr_user; ++i) {
+            index[i] = user_tids[i];
+            user_tids[i] = tids[user_tids[i]-1];
+        }
+    }
+    return 0;
+}
+
+int filter_threads(int tids[], int index[], char states[], int nr_tids,
+        const char *user_states)
+{
+    int i, j = 0;
+    int nr_prev = nr_tids;
+    for (i = 0; i < nr_prev; ++i) {
+        if (strchr(user_states, states[i]) != NULL) {
+            if (i != j) {
+                tids[j] = tids[i];
+                states[j] = states[i];
+                if (index != NULL)
+                    index[j] = index[i];
+            }
+            ++j;
+        } else {
+            --nr_tids;
+        }
+    }
+    return nr_tids;
+}
+
+int attach_process(int pid)
+{
+    int status = 0;
+
+    gettimeofday(&freeze_time, NULL);
+
+    attached_pid = pid;
+    if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) {
+        perror("attach");
+        detach_process(pid);
+        return -1;
+    }
+    if (!proc_stopped(pid)) {
+        struct itimerval tm;
+
+        if (opt_use_waitpid_timeout) {
+            /* setup alarm to avoid long waiting on waitpid */
+            tm.it_interval.tv_sec = 0;
+            tm.it_interval.tv_usec = 0;
+            tm.it_value.tv_sec = 1;
+            tm.it_value.tv_usec = stop_timeout % 1000000;
+            setitimer(ITIMER_REAL, &tm, NULL);
+        }
+
+        if (waitpid(pid, &status, WUNTRACED) < 0) {
+            if (errno == EINTR) {
+                fprintf(stderr, "timeout on waitpid\n");
+                detach_process(pid);
+                return -1;
+            }
+            fprintf(stderr, "waitpid %d: %s\n", pid, strerror(errno));
+            detach_process(pid);
+            return -1;
+        }
+
+        if (opt_use_waitpid_timeout) {
+            tm.it_value.tv_sec = 0;
+            tm.it_value.tv_usec = 0;
+            setitimer(ITIMER_REAL, &tm, NULL);
+        }
+
+        if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP)
+            fprintf(stderr, "warning: waitpid(%d) WIFSTOPPED=%d WSTOPSIG=%d\n",
+                    pid, WIFSTOPPED(status), WSTOPSIG(status));
+    }
+    if (kill(pid, SIGSTOP) < 0) {
+        perror("send SIGSTOP");
+        return -1;
+    }
+    return 0;
+}
+
+int attach_thread(int tid)
+{
+    if (ptrace(PTRACE_ATTACH, tid, NULL, NULL) < 0) {
+        perror("PTRACE_ATTACH");
+        return -1;
+    }
+    if (wait_thread(tid) < 0)
+        return -1;
+    return 0;
+}
+
+int detach_process(int pid)
+{
+    int rc = 0;
+    if (ptrace(PTRACE_DETACH, pid, NULL, NULL) < 0) {
+        perror("detach");
+        rc = -1;
+    }
+    if (kill(pid, SIGCONT) < 0) {
+        perror("send SIGCONT");
+        rc = -1;
+    }
+
+    attached_pid = 0;
+    gettimeofday(&unfreeze_time, NULL);
+    return rc;
+}
+
+int detach_thread(int tid)
+{
+    long rc = ptrace(PTRACE_DETACH, tid, NULL, NULL);
+    if (rc < 0) {
+        perror("PTRACE_DETACH");
+        return -1;
+    }
+    return 0;
+}
+
+int wait_thread(int tid)
+{
+    int rc;
+    while (!(rc = proc_stopped(tid))) {
+        if (stop_timeout && sleep_time > stop_timeout) {
+            fprintf(stderr, "timeout waiting for thread %d to stop", tid);
+            return -1;
+        }
+        usleep(SLEEP_WAIT);
+        sleep_time += SLEEP_WAIT;
+        sleep_count++;
+    }
+    return (rc == -1 ? -1 : 0);
+}
+
+/*
+ * copy memory contents using process_vm_readv(). reduces number
+ * of system calls comparing to /proc/pid/mem
+ *
+ * return values:
+ *      0  success
+ *     -1  fail
+ * ENOSYS  process_vm_readv() is not supported
+ */
+static int copy_memory_process_vm_readv(int pid,
+        struct mem_data_chunk **frames, int n_frames)
+{
+    struct iovec *local_iov, *remote_iov;
+    ssize_t *frame_bytes;
+    int i, rc = -1;
+    ssize_t bytes_total = 0;
+    int seg_count = 0;
+
+    local_iov = malloc(sizeof(struct iovec)*n_frames);
+    remote_iov = malloc(sizeof(struct iovec)*n_frames);
+    frame_bytes = malloc(sizeof(ssize_t)*n_frames);
+
+    for (i = 0; i < n_frames; ++i) {
+        local_iov[i].iov_base = frames[i]->data;
+        local_iov[i].iov_len = frames[i]->length;
+        remote_iov[i].iov_base = frames[i]->start;
+        remote_iov[i].iov_len = frames[i]->length;
+
+        bytes_total += frames[i]->length;
+        frame_bytes[i] = bytes_total;
+    }
+
+    bytes_total = 0;
+    while (1) {
+        ssize_t bytes_read;
+        int frames_to_read = n_frames - seg_count;
+        if (frames_to_read > IOV_MAX)
+            frames_to_read = IOV_MAX;
+
+        bytes_read = syscall(SYS_process_vm_readv,
+                pid,
+                local_iov + seg_count,
+                frames_to_read,
+                remote_iov + seg_count,
+                frames_to_read,
+                0ULL);
+
+        if (bytes_read < 0) {
+            if (errno == ENOSYS)
+                rc = ENOSYS;
+            else
+                perror("process_vm_readv");
+
+            goto process_vm_readv_end;
+        }
+
+        bytes_total += bytes_read;
+        total_length = bytes_total;
+        for (seg_count = n_frames-1; seg_count >= 0; --seg_count) {
+            if (frame_bytes[seg_count] == bytes_total)
+                break;
+        }
+
+        if (seg_count < 0) {
+            fprintf(stderr, "unknown number of bytes returned by "
+                    "process_vm_readv: bytes_read=%zd "
+                    "bytes_total=%zd seg_count=%d\n",
+                    bytes_read, bytes_total, seg_count);
+            goto process_vm_readv_end;
+        }
+
+        if (seg_count == (n_frames-1))
+            break;
+
+        ++seg_count;
+    }
+
+    rc = 0;
+
+process_vm_readv_end:
+    free(local_iov);
+    free(remote_iov);
+    free(frame_bytes);
+    return rc;
+}
+
+/*
+ * read the file /proc/<pid>/mem
+ */
+static int copy_memory_proc_mem(int pid, struct mem_data_chunk **frames,
+        int n_frames)
+{
+    int i = 0;
+    char fname[32];
+    int fd;
+    int rc = -1;
+
+    sprintf(fname, "/proc/%d/mem", pid);
+    if ((fd = open(fname, O_RDONLY)) == -1) {
+        fprintf(stderr, "cannot open %s\n", fname);
+        perror(fname);
+        return -1;
+    }
+
+    for (i = 0; i < n_frames; ++i) {
+        off_t from = (off_t)(uintptr_t)frames[i]->start;
+        char *to = frames[i]->data;
+        size_t count = frames[i]->length;
+
+        while (count > 0) {
+            ssize_t rd = pread(fd, to, count, from);
+
+            if (rd == -1) {
+                fprintf(stderr, "pread() at %s:0x%jx (#%d) failed: %s [%d]\n",
+                        fname, from, i, strerror(errno), errno);
+                goto proc_mem_end;
+            }
+
+            from += rd;
+            to += rd;
+            count -= rd;
+        }
+
+        total_length += frames[i]->length;
+    }
+
+    rc = 0;
+
+proc_mem_end:
+    close(fd);
+    return rc;
+}
+
+int copy_memory(int pid, struct mem_data_chunk **frames, int n_frames)
+{
+    if (!opt_proc_mem) {
+        int rc = copy_memory_process_vm_readv(pid, frames, n_frames);
+        if (rc == ENOSYS) {
+            if (opt_verbose) {
+                fprintf(stderr, "process_vm_readv is not supported, falling "
+                                "back to /proc/pid/mem\n");
+            }
+        } else {
+            return rc;
+        }
+    }
+
+    return copy_memory_proc_mem(pid, frames, n_frames);
+}
+
+void *get_vdso()
+{
+    static const char *auxv = "/proc/self/auxv";
+    FILE *f;
+    long entry[2];
+
+    f = fopen(auxv, "r");
+    if (f == NULL) {
+        perror(auxv);
+        return NULL;
+    }
+
+    while (!feof(f)) {
+        if (fread(entry, sizeof(entry), 1, f) != 1)
+            goto get_vdso_fail;
+
+        if (entry[0] == AT_SYSINFO_EHDR) {
+            fclose(f);
+            return (void *)entry[1];
+        }
+    }
+
+get_vdso_fail:
+    perror(auxv);
+    fclose(f);
+    return NULL;
+}
+
+void quit_handler(int signum)
+{
+    /*
+     * We can't call PTRACE_DETACH here because we are in a signal handler.
+     * Additionally ptrace will automatically detach when this process exits at
+     * the end of this function. We do however always need to send the SIGCONT
+     * if we have ptrace attached because when the ptrace automatically
+     * detaches it will leave the process in a stopped state even if we had not
+     * yet sent SIGSTOP to it.
+     */
+    if (attached_pid)
+        kill(attached_pid, SIGCONT);
+    if (signum == SIGSEGV) {
+        static volatile int *n = NULL;
+        *n = 1969;
+    }
+    _exit(1);
+}
diff --git a/src/crash-stack/proc.h b/src/crash-stack/proc.h
new file mode 100644 (file)
index 0000000..454d62a
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * tbstack -- fast stack trace utility
+ *
+ * Copyright (c) 2014, Tbricks AB
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TBRICKS
+ * AB BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THISS OFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef __0383eba0_9883_11e3_82d0_c1c5dc4afb95
+#define __0383eba0_9883_11e3_82d0_c1c5dc4afb95
+
+struct mem_data_chunk;
+struct mem_map;
+
+/*
+ * returns process state (R, S, D, T, ...) or -1 on error
+ */
+int proc_state(int pid);
+
+/*
+ * parse /proc/<pid>/maps file and create mem_map structure
+ */
+struct mem_map *create_maps(int pid);
+
+/*
+ * simple routine to print process maps for
+ * debugging or advanced error reporting
+ */
+int print_proc_maps(int pid);
+
+/*
+ * get thread identifiers of the process
+ */
+int get_threads(int pid, int **tids);
+
+/*
+ * returns a pointer to dynamically allocated array of characters representing
+ * thread states as found in /proc/<pid>/status
+ */
+char *get_thread_states(const int *tids, int n);
+
+/*
+ * translate thread numbers to system lwp ids
+ */
+int adjust_threads(int *tids, int nr_tids, int *user_tids,
+        int *index, int nr_user);
+
+/*
+ * filter threads by state. returns new number of threads
+ */
+int filter_threads(int tids[], int index[], char states[], int nr_tids,
+        const char *user_states);
+
+/*
+ * attach to the process, wait until it's stopped,
+ * send SIGSTOP to make all threads frozen
+ */
+int attach_process(int pid);
+
+/*
+ * attach to process' thread
+ */
+int attach_thread(int tid);
+
+/*
+ * detach from process, send SIGCONT
+ */
+int detach_process(int pid);
+
+/*
+ * detach from process' thread
+ */
+int detach_thread(int tid);
+
+/*
+ * wait for thread to stop. we cannot use waitpid() here because non-leader
+ * group members don't become children of tracer
+ */
+int wait_thread(int tid);
+
+/*
+ * copy process' memory contents
+ */
+int copy_memory(int pid, struct mem_data_chunk **frames, int n_frames);
+
+/*
+ * resolve VDSO mapping address
+ */
+void *get_vdso(void);
+
+/*
+ * detach from process and send SIGCONT when interrupt/termination occurs
+ */
+void quit_handler(int signum);
+
+#endif
+
diff --git a/src/crash-stack/unwind.c b/src/crash-stack/unwind.c
new file mode 100644 (file)
index 0000000..7f2e54d
--- /dev/null
@@ -0,0 +1,876 @@
+/*
+ * tbstack -- fast stack trace utility
+ *
+ * Copyright (c) 2014, Tbricks AB
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TBRICKS
+ * AB BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THISS OFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#include "mem_map.h"
+#include "snapshot.h"
+#include "unwind.h"
+
+#include <dwarf.h>
+#include <gelf.h>
+#include <libelf.h>
+#include <libgen.h>
+#include <libunwind.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/user.h>
+
+/*
+ * some libelf implementations do not provide ELF_C_READ_MMAP
+ */
+#if HAVE_DECL_ELF_C_READ_MMAP
+#define TBSTACK_ELF_C_READ ELF_C_READ_MMAP
+#else
+#define TBSTACK_ELF_C_READ ELF_C_READ
+#endif
+
+/*
+ * search unwind table for a procedure (used by find_proc_info)
+ */
+#define search_unwind_table UNW_OBJ(dwarf_search_unwind_table)
+
+extern int search_unwind_table(unw_addr_space_t as, unw_word_t ip,
+        unw_dyn_info_t *di, unw_proc_info_t *pip,
+        int need_unwind_info, void *arg);
+
+
+#ifdef HAVE_DWARF
+#define dwarf_find_debug_frame UNW_OBJ(dwarf_find_debug_frame)
+
+extern int dwarf_find_debug_frame(int found,
+                                unw_dyn_info_t *di_debug,
+                                unw_word_t ip,
+                                unw_word_t segbase,
+                                const char *obj_name, unw_word_t start,
+                                unw_word_t end);
+#endif
+
+
+/*
+ * get dwarf encoded value
+ */
+static ssize_t dw_get_value(char *data, unsigned char enc,
+        uint64_t cur, uint64_t *value)
+{
+    int64_t number = 0;
+    size_t size;
+
+    if (enc == DW_EH_PE_omit) {
+        *value = 0;
+        return 0;
+    }
+
+    if (enc == DW_EH_PE_absptr) {
+        *value = *(uint64_t *)data;
+        return 8;
+    }
+
+    switch (enc & 0xf)
+    {
+    case DW_EH_PE_udata2:
+        number = *(uint16_t *)data;
+        size = 2;
+        break;
+
+    case DW_EH_PE_sdata2:
+        number = *(int16_t *)data;
+        size = 2;
+        break;
+
+    case DW_EH_PE_udata4:
+        number = *(uint32_t *)data;
+        size = 4;
+        break;
+
+    case DW_EH_PE_sdata4:
+        number = *(int32_t *)data;
+        size = 4;
+        break;
+
+    case DW_EH_PE_udata8:
+        number = *(uint64_t *)data;
+        size = 8;
+        break;
+
+    case DW_EH_PE_sdata8:
+        number = *(int64_t *)data;
+        size = 8;
+        break;
+
+    default:
+        fprintf(stderr, "unsupported encoding in "
+                ".eh_frame_hdr: %d\n", (int)enc);
+        return -1;
+    }
+
+    switch (enc & 0xf0) {
+    case DW_EH_PE_absptr:
+        *value = number;
+        break;
+
+    case DW_EH_PE_pcrel:
+        *value = cur + number;
+        break;
+
+    default:
+        return -1;
+    }
+
+    return size;
+}
+
+/*
+ * parse contents of .eh_frame_hdr
+ */
+static int parse_eh_frame_hdr(char *data, size_t pos,
+        uint64_t *table_data, uint64_t *fde_count)
+{
+    char version, eh_frame_ptr_enc, fde_count_enc;
+    ssize_t size;
+    uint64_t eh_frame_ptr;
+
+    version = data[0];
+    eh_frame_ptr_enc = data[1];
+    fde_count_enc = data[2];
+    data += 4;
+    pos += 4;
+
+    if (version != 1) {
+        fprintf(stderr, "unknown .ehf_frame_hdr version %d\n", version);
+        return -1;
+    }
+
+    size = dw_get_value(data, eh_frame_ptr_enc, pos, &eh_frame_ptr);
+    if (size < 0)
+        return -1;
+    pos += size;
+    data += size;
+
+    size = dw_get_value(data, fde_count_enc, pos, fde_count);
+    if (size < 0)
+        return -1;
+    pos += size;
+    *table_data = pos;
+
+    return 0;
+}
+
+static Elf *elf_start(int fd, char *image, uint64_t size)
+{
+    Elf *elf;
+
+    if (fd > 0) {
+        if ((elf = elf_begin(fd, TBSTACK_ELF_C_READ, NULL)) == NULL)
+            fprintf(stderr, "elf_begin: %s\n", elf_errmsg(elf_errno()));
+    } else {
+        if ((elf = elf_memory(image, size)) == NULL)
+            fprintf(stderr, "elf_memory: %s\n", elf_errmsg(elf_errno()));
+    }
+
+    return elf;
+}
+
+/*
+ * find section .eh_frame_hdr in ELF binary
+ */
+static int find_eh_frame_hdr(int fd, char *image, uint64_t size,
+        uint64_t *table_data, uint64_t *segbase, uint64_t *fde_count)
+{
+    Elf *elf;
+    GElf_Ehdr ehdr;
+    Elf_Scn *scn = NULL;
+    GElf_Shdr shdr;
+    uint64_t offset = 0;
+
+    if ((elf = elf_start(fd, image, size)) == NULL)
+        return -1;
+
+    if (gelf_getehdr(elf, &ehdr) == NULL) {
+        fprintf(stderr, "elf_getehdr: %s\n", elf_errmsg(elf_errno()));
+        goto elf_section_offset_end;
+    }
+
+    while ((scn = elf_nextscn(elf, scn)) != NULL) {
+        char *str;
+
+        if (gelf_getshdr(scn, &shdr) == NULL) {
+            fprintf(stderr, "elf_getshdr: %s\n", elf_errmsg(elf_errno()));
+            break;
+        }
+
+        str = elf_strptr(elf, ehdr.e_shstrndx, shdr.sh_name);
+        if (str != NULL && !strcmp(str, ".eh_frame_hdr")) {
+            Elf_Data *data = NULL;
+
+            if ((data = elf_getdata(scn, data)) == NULL) {
+                fprintf(stderr, "elf_getdata: %s\n", elf_errmsg(elf_errno()));
+                break;
+            }
+
+            offset = *segbase = shdr.sh_offset;
+            parse_eh_frame_hdr(data->d_buf, offset, table_data, fde_count);
+            break;
+        }
+    }
+
+    if (!offset)
+        goto elf_section_offset_end;
+
+elf_section_offset_end:
+    elf_end(elf);
+    return (offset ? 0 : -1);
+}
+
+/*
+ * dynamic array of symbols
+ */
+struct symbols
+{
+    GElf_Sym *s_data;
+    size_t s_size;
+    size_t s_cap;
+};
+
+/*
+ * add a symbol to array
+ */
+static void push_symbol(struct symbols *array, const GElf_Sym *s)
+{
+    ++array->s_size;
+    if (array->s_size > array->s_cap) {
+        GElf_Sym *new_data;
+        array->s_cap <<= 1;
+        new_data = malloc(sizeof(GElf_Sym) * array->s_cap);
+        memcpy(new_data, array->s_data, sizeof(GElf_Sym) * (array->s_size-1));
+        free(array->s_data);
+        array->s_data = new_data;
+    }
+    memcpy(array->s_data + (array->s_size-1), s, sizeof(GElf_Sym));
+}
+
+/*
+ * symbol comparison function for qsort
+ */
+static int sym_compar(const void *v1, const void *v2)
+{
+    const GElf_Sym *s1 = v1;
+    const GElf_Sym *s2 = v2;
+
+    if (s1->st_value < s2->st_value)
+        return -1;
+    if (s1->st_value > s2->st_value)
+        return 1;
+    return 0;
+}
+
+/*
+ * get function name
+ *
+ * fd: open binary
+ * load: mmap address
+ * offset: file offset
+ * addr: ip value
+ * off: offset within the function
+ */
+static char *proc_name(int fd, char *image, size_t size, uint64_t load,
+        uint64_t offset, uint64_t addr, unw_word_t *off)
+{
+    Elf *elf;
+    Elf_Scn *scn = NULL;
+    char *str = NULL;
+    int rc = 0;
+    struct symbols all;
+    size_t pnum, i;
+    uint64_t vaddr = 0;
+
+    /*
+     * open ELF handle
+     */
+    if ((elf = elf_start(fd, image, size)) == NULL)
+        return NULL;
+
+    /*
+     * initialize dynamic array
+     */
+    all.s_cap = 64;
+    all.s_size = 0;
+    all.s_data = malloc(all.s_cap * sizeof(GElf_Sym));
+
+    if (elf_getphdrnum (elf, &pnum))
+        goto proc_name_end;
+
+    for (i = 0; i < pnum; ++i) {
+        GElf_Phdr phdr;
+        if (gelf_getphdr(elf, i, &phdr) == NULL)
+            goto proc_name_end;
+        if (phdr.p_type != PT_LOAD)
+            continue;
+        if (phdr.p_flags != (PF_X | PF_R))
+            continue;
+        if ((phdr.p_offset & ~(phdr.p_align - 1)) != offset)
+            continue;
+        vaddr = phdr.p_vaddr;
+        break;
+    }
+
+    /*
+     * adjust address
+     */
+    addr -= load;
+    addr += offset;
+
+    /*
+     * search symtab or dynsym section
+     */
+    while ((scn = elf_nextscn(elf, scn)) != NULL) {
+        GElf_Shdr shdr;
+
+        if (gelf_getshdr(scn, &shdr) == NULL) {
+            fprintf(stderr, "elf_nextscn: %s\n", elf_errmsg(elf_errno()));
+            goto proc_name_end;
+        }
+
+        if (shdr.sh_type == SHT_DYNSYM || shdr.sh_type == SHT_SYMTAB) {
+            Elf_Data *data = NULL;
+            int symbol_count;
+
+            if ((data = elf_getdata(scn, data)) == NULL) {
+                fprintf(stderr, "elf_getdata: %s\n", elf_errmsg(elf_errno()));
+                goto proc_name_end;
+            }
+
+            symbol_count = shdr.sh_size / shdr.sh_entsize;
+            for (i = 0; i < (size_t)symbol_count; ++i) {
+                GElf_Sym s;
+
+                if (gelf_getsym(data, i, &s) == NULL) {
+                    fprintf(stderr, "elf_getsym: %s\n",
+                            elf_errmsg(elf_errno()));
+                    rc = -1;
+                    goto proc_name_end;
+                }
+
+                if (ELF64_ST_TYPE(s.st_info) != STT_FUNC)
+                    continue;
+
+                /*
+                 * adjust symbol value
+                 */
+                s.st_value -= vaddr;
+
+                /*
+                 * exact match
+                 */
+                if (addr >= s.st_value && addr < (s.st_value + s.st_size)) {
+                    str = elf_strptr(elf, shdr.sh_link, s.st_name);
+                    if (str == NULL) {
+                        fprintf(stderr, "elf_strptr #1: %s\n",
+                                elf_errmsg(elf_errno()));
+                        rc = -1;
+                        goto proc_name_end;
+                    }
+                    str = strdup(str);
+                    *off = addr - s.st_value;
+                    goto proc_name_end;
+                }
+
+                /* store section link */
+                s.st_shndx = shdr.sh_link;
+                /*
+                 * save symbol in array
+                 */
+                push_symbol(&all, &s);
+            }
+        }
+    }
+
+    /*
+     * sometimes function symbols have zero size but contain the code.
+     * common example is _start on most systems.
+     * in this case we try to find two adjacent symbols with first
+     * one of zero size
+     */
+    if (!rc && str == NULL) {
+        qsort(all.s_data, all.s_size, sizeof(GElf_Sym), sym_compar);
+        for (i = 0; i < (all.s_size-1); ++i) {
+            const GElf_Sym *cur = all.s_data + i;
+            const GElf_Sym *next = all.s_data + i + 1;
+            if (cur->st_size == 0) {
+                if (cur->st_value <= addr && addr < next->st_value) {
+                    str = elf_strptr(elf, cur->st_shndx, cur->st_name);
+                    if (str == NULL) {
+                        fprintf(stderr, "elf_strptr #2: %s\n",
+                                elf_errmsg(elf_errno()));
+                        rc = -1;
+                        goto proc_name_end;
+                    }
+                    str = strdup(str);
+                    *off = addr - cur->st_value;
+                    goto proc_name_end;
+                }
+            }
+        }
+    }
+
+proc_name_end:
+    free(all.s_data);
+    elf_end(elf);
+    return str;
+}
+
+/*
+ * get mmapped ELF image info
+ */
+static int get_elf_image_info(struct mem_region *region,
+        char **elf_image, uint64_t *elf_length, uintptr_t ip)
+{
+    struct mem_data_chunk *chunk;
+
+    if ((chunk = mem_region_find_data_chunk(region, (void *)ip)) == NULL)
+        return -1;
+
+    if (chunk->data == NULL)
+        return -1;
+
+    *elf_image = chunk->data;
+    *elf_length = chunk->length;
+
+    return 0;
+}
+
+#ifdef HAVE_DWARF
+static int elf_is_exec(int fd, char *image, uint64_t size)
+{
+    Elf *elf;
+    GElf_Ehdr ehdr;
+    int ret = 0;
+
+    if ((elf = elf_start(fd, image, size)) == NULL)
+        return 0;
+
+    if (gelf_getehdr(elf, &ehdr) == NULL) {
+        fprintf(stderr, "elf_getehdr: %s\n", elf_errmsg(elf_errno()));
+        goto elf_is_exec_end;
+    }
+
+    ret = ehdr.e_type == ET_EXEC;
+
+elf_is_exec_end:
+    elf_end(elf);
+
+    return ret;
+}
+
+static int elf_get_link_base(int fd, char *image, uint64_t size,
+        uint64_t *link_base)
+{
+    Elf *elf;
+    GElf_Ehdr ehdr;
+    GElf_Phdr phdr;
+    int idx=0;
+    uint64_t offset = UINT64_MAX;
+
+    if ((elf = elf_start(fd, image, size)) == NULL)
+        return -1;
+
+    if (gelf_getehdr(elf, &ehdr) == NULL) {
+        fprintf(stderr, "elf_getehdr: %s\n", elf_errmsg(elf_errno()));
+        goto elf_section_offset_end;
+    }
+
+    /* Get the vaddr of the segment with 0 offset.  This is the link base of
+     * the shared object. */
+    while (gelf_getphdr(elf, idx, &phdr) && phdr.p_type != PT_NULL) {
+       if (phdr.p_type != PT_LOAD)
+           goto next;
+
+       if (phdr.p_offset)
+           goto next;
+
+       offset = phdr.p_vaddr;
+       break;
+
+next:
+       idx++;
+    }
+
+    *link_base = offset;
+    elf_end(elf);
+    return 0;
+
+elf_section_offset_end:
+    elf_end(elf);
+    return -1;
+}
+
+#endif
+
+/*
+ * find unwind info for function
+ */
+static int find_proc_info(unw_addr_space_t as, unw_word_t ip,
+        unw_proc_info_t *pip, int need_unwind_info, void *arg)
+{
+    struct snapshot *snap = arg;
+    struct mem_region *region;
+    char *elf_image = NULL;
+    uint64_t elf_length = 0;
+    unw_dyn_info_t di;
+    uint64_t table_data = 0;
+    uint64_t segbase, fde_count;
+    int rc = -UNW_EINVAL;
+
+    if (ip == 0)
+        return -UNW_ENOINFO;
+
+    if ((region = mem_map_get_file_region(snap->map, (void *)ip)) == NULL)
+        return rc;
+
+    if (region->fd < 0 && region->type != MEM_REGION_TYPE_VDSO
+            && region->type != MEM_REGION_TYPE_VSYSCALL)
+        return rc;
+
+    if (region->fd < 0 &&
+            get_elf_image_info(region, &elf_image, &elf_length, ip) < 0)
+        return rc;
+
+    memset(&di, 0, sizeof(di));
+
+    if (!find_eh_frame_hdr(region->fd, elf_image, elf_length,
+                &table_data, &segbase, &fde_count)) {
+
+        di.format = UNW_INFO_FORMAT_REMOTE_TABLE;
+        di.start_ip = (unw_word_t)region->start;
+        di.end_ip = (unw_word_t)region->start + region->length;
+        di.u.rti.segbase = (unw_word_t)(region->start - region->offset) + segbase;
+        di.u.rti.table_data = (unw_word_t)(region->start - region->offset) + table_data;
+        di.u.rti.table_len =
+            fde_count * sizeof(uint32_t) * 2 / sizeof(unw_word_t);
+
+        rc = search_unwind_table(as, ip, &di, pip, need_unwind_info, arg);
+    }
+
+    if (rc == 0)
+        return rc;
+
+#ifdef HAVE_DWARF
+    unw_word_t base = 0;
+    if (!elf_is_exec(region->fd, elf_image, elf_length)) {
+       uint64_t link_base;
+       if (elf_get_link_base(region->fd, elf_image, elf_length, &link_base))
+           return -UNW_EINVAL;
+        base = (uintptr_t)region->start - link_base;
+    }
+
+    if (dwarf_find_debug_frame(0, &di, ip, base, region->path,
+                (unw_word_t) region->start,
+                (unw_word_t) region->start + region->length))
+            return search_unwind_table(as, ip, &di, pip, need_unwind_info, arg);
+#endif
+
+    return rc;
+}
+
+/*
+ * put_unwind_info: do nothing
+ */
+static void put_unwind_info(unw_addr_space_t as,
+        unw_proc_info_t *pip, void *arg)
+{
+    (void) as;
+    (void) pip;
+    (void) arg;
+}
+
+/*
+ * not used
+ */
+static int get_dyn_info_list_addr(unw_addr_space_t as,
+        unw_word_t *dilap, void *arg)
+{
+    (void) as;
+    (void) dilap;
+    (void) arg;
+    return -UNW_ENOINFO;
+}
+
+/*
+ * read a word from memory. we use mem_map for that
+ */
+static int access_mem(unw_addr_space_t as, unw_word_t addr,
+        unw_word_t *valp, int write, void *arg)
+{
+    struct snapshot *snap = arg;
+
+    (void) as;
+
+    if (write) {
+        fprintf(stderr, "access_mem: requested write, rejecting\n");
+        return -UNW_EINVAL;
+    }
+
+    return mem_map_read_word(snap->map, (void *)(uintptr_t)addr, valp);
+}
+
+/*
+ * get register value
+ */
+static int access_reg(unw_addr_space_t as, unw_regnum_t reg,
+        unw_word_t *val, int write, void *arg)
+{
+    struct snapshot *snap = arg;
+
+    (void) as;
+
+    if (write) {
+        fprintf(stderr, "requested to write into register\n");
+        return -UNW_EINVAL;
+    }
+
+    switch (reg) {
+#if defined(UNW_TARGET_AARCH64)
+    case UNW_AARCH64_X0 ... UNW_AARCH64_X30:
+        /*
+         * Currently this enum directly maps to the index so this is a no-op.
+         * Assert just in case.
+         */
+        reg -= UNW_AARCH64_X0;
+        assert(reg>= 0 && reg <= 30);
+        *val = snap->regs[snap->cur_thr].regs[reg];
+        break;
+    case UNW_AARCH64_SP:
+        *val = snap->regs[snap->cur_thr].sp;
+        break;
+    case UNW_AARCH64_PC:
+        *val = snap->regs[snap->cur_thr].pc;
+        break;
+    case UNW_AARCH64_PSTATE:
+        *val = snap->regs[snap->cur_thr].pstate;
+        break;
+#elif defined(UNW_TARGET_ARM)
+    case UNW_ARM_R0 ... UNW_ARM_R15:
+        /*
+         * Currently this enum directly maps to the index so this is a no-op.
+         * Assert just in case.
+         */
+        reg -= UNW_ARM_R0;
+        assert(reg >= 0 && reg <= 15);
+        *val = snap->regs[snap->cur_thr].uregs[reg];
+        break;
+#elif defined(UNW_TARGET_X86)
+    case UNW_X86_EAX:
+        *val = snap->regs[snap->cur_thr].eax;
+        break;
+    case UNW_X86_EDX:
+        *val = snap->regs[snap->cur_thr].edx;
+        break;
+    case UNW_X86_ECX:
+        *val = snap->regs[snap->cur_thr].ecx;
+        break;
+    case UNW_X86_EBX:
+        *val = snap->regs[snap->cur_thr].ebx;
+        break;
+    case UNW_X86_ESI:
+        *val = snap->regs[snap->cur_thr].esi;
+        break;
+    case UNW_X86_EDI:
+        *val = snap->regs[snap->cur_thr].edi;
+        break;
+    case UNW_X86_EBP:
+        *val = snap->regs[snap->cur_thr].ebp;
+        break;
+    case UNW_X86_ESP:
+        *val = snap->regs[snap->cur_thr].esp;
+        break;
+    case UNW_X86_EIP:
+        *val = snap->regs[snap->cur_thr].eip;
+        break;
+#elif defined(UNW_TARGET_X86_64)
+    case UNW_X86_64_RAX:
+        *val = snap->regs[snap->cur_thr].rax;
+        break;
+    case UNW_X86_64_RDX:
+        *val = snap->regs[snap->cur_thr].rdx;
+        break;
+    case UNW_X86_64_RCX:
+        *val = snap->regs[snap->cur_thr].rcx;
+        break;
+    case UNW_X86_64_RBX:
+        *val = snap->regs[snap->cur_thr].rbx;
+        break;
+    case UNW_X86_64_RSI:
+        *val = snap->regs[snap->cur_thr].rsi;
+        break;
+    case UNW_X86_64_RDI:
+        *val = snap->regs[snap->cur_thr].rdi;
+        break;
+    case UNW_X86_64_RBP:
+        *val = snap->regs[snap->cur_thr].rbp;
+        break;
+    case UNW_X86_64_RSP:
+        *val = snap->regs[snap->cur_thr].rsp;
+        break;
+    case UNW_X86_64_R8:
+        *val = snap->regs[snap->cur_thr].r8;
+        break;
+    case UNW_X86_64_R9:
+        *val = snap->regs[snap->cur_thr].r9;
+        break;
+    case UNW_X86_64_R10:
+        *val = snap->regs[snap->cur_thr].r10;
+        break;
+    case UNW_X86_64_R11:
+        *val = snap->regs[snap->cur_thr].r11;
+        break;
+    case UNW_X86_64_R12:
+        *val = snap->regs[snap->cur_thr].r12;
+        break;
+    case UNW_X86_64_R13:
+        *val = snap->regs[snap->cur_thr].r13;
+        break;
+    case UNW_X86_64_R14:
+        *val = snap->regs[snap->cur_thr].r14;
+        break;
+    case UNW_X86_64_R15:
+        *val = snap->regs[snap->cur_thr].r15;
+        break;
+    case UNW_X86_64_RIP:
+        *val = snap->regs[snap->cur_thr].rip;
+        break;
+#else
+#error Need porting to this arch
+#endif
+    default:
+        return -UNW_EBADREG;
+    }
+
+    return 0;
+}
+
+/*
+ * floating point registers are not used
+ */
+static int access_fpreg(unw_addr_space_t as, unw_regnum_t regnum,
+        unw_fpreg_t *fpvalp, int write, void *arg)
+{
+    (void) as;
+    (void) regnum;
+    (void) fpvalp;
+    (void) write;
+    (void) arg;
+
+    fprintf(stderr, "access_fpreg is not supported\n");
+    return -UNW_ENOINFO;
+}
+
+/*
+ * not used
+ */
+static int resume(unw_addr_space_t as, unw_cursor_t *cp, void *arg)
+{
+    (void) as;
+    (void) cp;
+    (void) arg;
+
+    fprintf(stderr, "resume is not supported\n");
+    return -UNW_ENOINFO;
+}
+
+/*
+ * get function name callback
+ */
+static int get_proc_name(unw_addr_space_t as, unw_word_t addr, char *bufp,
+        size_t buf_len, unw_word_t *offp, void *arg)
+{
+    struct snapshot *snap = arg;
+    struct mem_region *region;
+    char *name = NULL;
+
+    (void) as;
+
+    if (addr == 0)
+        return -UNW_ENOINFO;
+
+    if ((region = mem_map_get_file_region(snap->map, (void *)addr)) == NULL)
+        return -UNW_ENOINFO;
+
+    if (region->fd < 0 && region->type == MEM_REGION_TYPE_DELETED) {
+        const char *base = basename(region->path);
+        snprintf(bufp, buf_len, "?? (%s is deleted)", base);
+        *offp = 0;
+        return 0;
+    } else if (region->type == MEM_REGION_TYPE_MMAP ||
+            region->type == MEM_REGION_TYPE_VDSO ||
+            region->type == MEM_REGION_TYPE_VSYSCALL) {
+        char *elf_image = NULL;
+        uint64_t elf_length = 0;
+
+        if (region->fd < 0 &&
+                get_elf_image_info(region, &elf_image, &elf_length, addr) < 0)
+            return -UNW_ENOINFO;
+
+        name = proc_name(region->fd, elf_image, elf_length,
+                (uint64_t)(uintptr_t)region->start, region->offset, addr, offp);
+    }
+
+    if (name == NULL) {
+        /*
+         * if name cannot be resolved, print binary file name or region type
+         */
+        if (region->type == MEM_REGION_TYPE_MMAP) {
+            const char *base = basename(region->path);
+            snprintf(bufp, buf_len, "?? (%s)", base);
+        } else {
+            snprintf(bufp, buf_len, "?? [%s]",
+                    str_mem_region_type(region->type));
+        }
+        *offp = 0;
+        return 0;
+    }
+
+    strncpy(bufp, name, buf_len);
+    free(name);
+
+    return 0;
+}
+
+/*
+ * libunwind remote callbacks
+ */
+unw_accessors_t snapshot_addr_space_accessors = {
+    .find_proc_info = find_proc_info,
+    .put_unwind_info = put_unwind_info,
+    .get_dyn_info_list_addr = get_dyn_info_list_addr,
+    .access_mem = access_mem,
+    .access_reg = access_reg,
+    .access_fpreg = access_fpreg,
+    .resume = resume,
+    .get_proc_name = get_proc_name,
+};