--- /dev/null
+/*
+ * 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(®ion->labels[i+1], ®ion->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 = ®ion->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, ®ion))
+ 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);
+}
--- /dev/null
+/*
+ * 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
+
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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
+
--- /dev/null
+/*
+ * 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,
+};