strace/unwind.c
Thomas De Schampheleire 22deda33c6 stack trace support: fix check on symbol name presence
The output format of the stack trace is supposed to be different
depending on whether symbol names are available in the build.

However, the check only verified the validity of the pointer, not of the
string pointed to (which could be empty).

This commit fixes the check so that the original output:

mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x5e000
 > /lib/libc-2.10.1.so(_IO_file_doallocate+0x8c) [0x68a38]
 > /lib/libc-2.10.1.so(_IO_doallocbuf+0x6c) [0x78574]
 > /lib/libc-2.10.1.so(_IO_file_overflow+0x184) [0x7763c]
 > /lib/libc-2.10.1.so(_IO_file_xsputn+0x88) [0x76aac]
 > /lib/libc-2.10.1.so(_IO_puts+0xc8) [0x6b64c]
 > /bin/busybox(+0x0) [0x62c60]
 > /bin/busybox(+0x0) [0x4940]
 > /bin/busybox(+0x0) [0x499c]
 > /bin/busybox(+0x0) [0x4e08]
 > /lib/libc-2.10.1.so(__libc_init_first+0x30c) [0x1f84c]
 > /lib/libc-2.10.1.so(__libc_start_main+0xd8) [0x1f9f8]

becomes:

mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x5e000
 > /lib/libc-2.10.1.so(_IO_file_doallocate+0x8c) [0x68a38]
 > /lib/libc-2.10.1.so(_IO_doallocbuf+0x6c) [0x78574]
 > /lib/libc-2.10.1.so(_IO_file_overflow+0x184) [0x7763c]
 > /lib/libc-2.10.1.so(_IO_file_xsputn+0x88) [0x76aac]
 > /lib/libc-2.10.1.so(_IO_puts+0xc8) [0x6b64c]
 > /bin/busybox() [0x62c60]
 > /bin/busybox() [0x4940]
 > /bin/busybox() [0x499c]
 > /bin/busybox() [0x4e08]
 > /lib/libc-2.10.1.so(__libc_init_first+0x30c) [0x1f84c]
 > /lib/libc-2.10.1.so(__libc_start_main+0xd8) [0x1f9f8]

Signed-off-by: Thomas De Schampheleire <thomas.de.schampheleire@gmail.com>
Acked-by: Masatake YAMATO <yamato@redhat.com>
2014-11-11 15:48:31 +00:00

601 lines
14 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2013 Luca Clementi <luca.clementi@gmail.com>
*
* 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.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 THE AUTHOR 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
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "defs.h"
#include <limits.h>
#include <libunwind-ptrace.h>
#ifdef _LARGEFILE64_SOURCE
# ifdef HAVE_FOPEN64
# define fopen_for_input fopen64
# else
# define fopen_for_input fopen
# endif
#else
# define fopen_for_input fopen
#endif
#define DPRINTF(F, A, ...) if (debug_flag) fprintf(stderr, " [unwind(" A ")] " F "\n", __VA_ARGS__)
/*
* Кeep a sorted array of cache entries,
* so that we can binary search through it.
*/
struct mmap_cache_t {
/**
* example entry:
* 7fabbb09b000-7fabbb09f000 r-xp 00179000 fc:00 1180246 /lib/libc-2.11.1.so
*
* start_addr is 0x7fabbb09b000
* end_addr is 0x7fabbb09f000
* mmap_offset is 0x179000
* binary_filename is "/lib/libc-2.11.1.so"
*/
unsigned long start_addr;
unsigned long end_addr;
unsigned long mmap_offset;
char *binary_filename;
};
/*
* Type used in stacktrace walker
*/
typedef void (*call_action_fn)(void *data,
const char *binary_filename,
const char *symbol_name,
unw_word_t function_offset,
unsigned long true_offset);
typedef void (*error_action_fn)(void *data,
const char *error,
unsigned long true_offset);
/*
* Type used in stacktrace capturing
*/
struct call_t {
struct call_t* next;
char *output_line;
};
struct queue_t {
struct call_t *tail;
struct call_t *head;
};
static void queue_print(struct queue_t *queue);
static void delete_mmap_cache(struct tcb *tcp, const char *caller);
static unw_addr_space_t libunwind_as;
static unsigned int mmap_cache_generation;
void
unwind_init(void)
{
libunwind_as = unw_create_addr_space(&_UPT_accessors, 0);
if (!libunwind_as)
error_msg_and_die("failed to create address space for stack tracing");
unw_set_caching_policy(libunwind_as, UNW_CACHE_GLOBAL);
}
void
unwind_tcb_init(struct tcb *tcp)
{
tcp->libunwind_ui = _UPT_create(tcp->pid);
if (!tcp->libunwind_ui)
die_out_of_memory();
tcp->queue = malloc(sizeof(*tcp->queue));
if (!tcp->queue)
die_out_of_memory();
tcp->queue->head = NULL;
tcp->queue->tail = NULL;
}
void
unwind_tcb_fin(struct tcb *tcp)
{
queue_print(tcp->queue);
free(tcp->queue);
tcp->queue = NULL;
delete_mmap_cache(tcp, __FUNCTION__);
_UPT_destroy(tcp->libunwind_ui);
tcp->libunwind_ui = NULL;
}
/*
* caching of /proc/ID/maps for each process to speed up stack tracing
*
* The cache must be refreshed after syscalls that affect memory mappings,
* e.g. mmap, mprotect, munmap, execve.
*/
static void
build_mmap_cache(struct tcb* tcp)
{
FILE *fp;
struct mmap_cache_t *cache_head;
/* start with a small dynamically-allocated array and then expand it */
size_t cur_array_size = 10;
char filename[sizeof("/proc/4294967296/maps")];
char buffer[PATH_MAX + 80];
unw_flush_cache(libunwind_as, 0, 0);
sprintf(filename, "/proc/%u/maps", tcp->pid);
fp = fopen_for_input(filename, "r");
if (!fp) {
perror_msg("fopen: %s", filename);
return;
}
cache_head = calloc(cur_array_size, sizeof(*cache_head));
if (!cache_head)
die_out_of_memory();
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
struct mmap_cache_t *entry;
unsigned long start_addr, end_addr, mmap_offset;
char exec_bit;
char binary_path[PATH_MAX];
if (sscanf(buffer, "%lx-%lx %*c%*c%c%*c %lx %*x:%*x %*d %[^\n]",
&start_addr, &end_addr, &exec_bit,
&mmap_offset, binary_path) != 5)
continue;
/* ignore mappings that have no PROT_EXEC bit set */
if (exec_bit != 'x')
continue;
if (end_addr < start_addr) {
error_msg("%s: unrecognized file format", filename);
break;
}
/*
* sanity check to make sure that we're storing
* non-overlapping regions in ascending order
*/
if (tcp->mmap_cache_size > 0) {
entry = &cache_head[tcp->mmap_cache_size - 1];
if (entry->start_addr == start_addr &&
entry->end_addr == end_addr) {
/* duplicate entry, e.g. [vsyscall] */
continue;
}
if (start_addr <= entry->start_addr ||
start_addr < entry->end_addr) {
error_msg("%s: overlapping memory region",
filename);
continue;
}
}
if (tcp->mmap_cache_size >= cur_array_size) {
cur_array_size *= 2;
cache_head = realloc(cache_head,
cur_array_size * sizeof(*cache_head));
if (!cache_head)
die_out_of_memory();
}
entry = &cache_head[tcp->mmap_cache_size];
entry->start_addr = start_addr;
entry->end_addr = end_addr;
entry->mmap_offset = mmap_offset;
entry->binary_filename = strdup(binary_path);
if (!entry->binary_filename)
die_out_of_memory();
tcp->mmap_cache_size++;
}
fclose(fp);
tcp->mmap_cache = cache_head;
tcp->mmap_cache_generation = mmap_cache_generation;
DPRINTF("tgen=%u, ggen=%u, tcp=%p, cache=%p",
"cache-build",
tcp->mmap_cache_generation,
mmap_cache_generation,
tcp, tcp->mmap_cache);
}
/* deleting the cache */
static void
delete_mmap_cache(struct tcb *tcp, const char *caller)
{
unsigned int i;
DPRINTF("tgen=%u, ggen=%u, tcp=%p, cache=%p, caller=%s",
"cache-delete",
tcp->mmap_cache_generation,
mmap_cache_generation,
tcp, tcp->mmap_cache, caller);
for (i = 0; i < tcp->mmap_cache_size; i++) {
free(tcp->mmap_cache[i].binary_filename);
tcp->mmap_cache[i].binary_filename = NULL;
}
free(tcp->mmap_cache);
tcp->mmap_cache = NULL;
tcp->mmap_cache_size = 0;
}
static bool
rebuild_cache_if_invalid(struct tcb *tcp, const char *caller)
{
if ((tcp->mmap_cache_generation != mmap_cache_generation)
&& tcp->mmap_cache)
delete_mmap_cache(tcp, caller);
if (!tcp->mmap_cache)
build_mmap_cache(tcp);
if (!tcp->mmap_cache || !tcp->mmap_cache_size)
return false;
else
return true;
}
void
unwind_cache_invalidate(struct tcb* tcp)
{
#if SUPPORTED_PERSONALITIES > 1
if (tcp->currpers != DEFAULT_PERSONALITY) {
/* disable strack trace */
return;
}
#endif
mmap_cache_generation++;
DPRINTF("tgen=%u, ggen=%u, tcp=%p, cache=%p", "increment",
tcp->mmap_cache_generation,
mmap_cache_generation,
tcp,
tcp->mmap_cache);
}
static void
get_symbol_name(unw_cursor_t *cursor, char **name,
size_t *size, unw_word_t *offset)
{
for (;;) {
int rc = unw_get_proc_name(cursor, *name, *size, offset);
if (rc == 0)
break;
if (rc != -UNW_ENOMEM) {
**name = '\0';
*offset = 0;
break;
}
*size *= 2;
*name = realloc(*name, *size);
if (!*name)
die_out_of_memory();
}
}
static int
print_stack_frame(struct tcb *tcp,
call_action_fn call_action,
error_action_fn error_action,
void *data,
unw_cursor_t *cursor,
char **symbol_name,
size_t *symbol_name_size)
{
unw_word_t ip;
int lower = 0;
int upper = (int) tcp->mmap_cache_size - 1;
if (unw_get_reg(cursor, UNW_REG_IP, &ip) < 0) {
perror_msg("Can't walk the stack of process %d", tcp->pid);
return -1;
}
while (lower <= upper) {
struct mmap_cache_t *cur_mmap_cache;
int mid = (upper + lower) / 2;
cur_mmap_cache = &tcp->mmap_cache[mid];
if (ip >= cur_mmap_cache->start_addr &&
ip < cur_mmap_cache->end_addr) {
unsigned long true_offset;
unw_word_t function_offset;
get_symbol_name(cursor, symbol_name, symbol_name_size,
&function_offset);
true_offset = ip - cur_mmap_cache->start_addr +
cur_mmap_cache->mmap_offset;
call_action(data,
cur_mmap_cache->binary_filename,
*symbol_name,
function_offset,
true_offset);
return 0;
}
else if (ip < cur_mmap_cache->start_addr)
upper = mid - 1;
else
lower = mid + 1;
}
/*
* there is a bug in libunwind >= 1.0
* after a set_tid_address syscall
* unw_get_reg returns IP == 0
*/
if(ip)
error_action(data, "unexpected_backtracing_error", ip);
return -1;
}
/*
* walking the stack
*/
static void
stacktrace_walk(struct tcb *tcp,
call_action_fn call_action,
error_action_fn error_action,
void *data)
{
char *symbol_name;
size_t symbol_name_size = 40;
unw_cursor_t cursor;
int stack_depth;
if (!tcp->mmap_cache)
error_msg_and_die("bug: mmap_cache is NULL");
if (tcp->mmap_cache_size == 0)
error_msg_and_die("bug: mmap_cache is empty");
symbol_name = malloc(symbol_name_size);
if (!symbol_name)
die_out_of_memory();
if (unw_init_remote(&cursor, libunwind_as, tcp->libunwind_ui) < 0)
perror_msg_and_die("Can't initiate libunwind");
for (stack_depth = 0; stack_depth < 256; ++stack_depth) {
if (print_stack_frame(tcp, call_action, error_action, data,
&cursor, &symbol_name, &symbol_name_size) < 0)
break;
if (unw_step(&cursor) <= 0)
break;
}
if (stack_depth >= 256)
error_action(data, "too many stack frames", 0);
free(symbol_name);
}
/*
* printing an entry in stack to stream or buffer
*/
/*
* we want to keep the format used by backtrace_symbols from the glibc
*
* ./a.out() [0x40063d]
* ./a.out() [0x4006bb]
* ./a.out() [0x4006c6]
* /lib64/libc.so.6(__libc_start_main+0xed) [0x7fa2f8a5976d]
* ./a.out() [0x400569]
*/
#define STACK_ENTRY_SYMBOL_FMT \
" > %s(%s+0x%lx) [0x%lx]\n", \
binary_filename, \
symbol_name, \
(unsigned long) function_offset, \
true_offset
#define STACK_ENTRY_NOSYMBOL_FMT \
" > %s() [0x%lx]\n", \
binary_filename, true_offset
#define STACK_ENTRY_BUG_FMT \
" > BUG IN %s\n"
#define STACK_ENTRY_ERROR_WITH_OFFSET_FMT \
" > %s [0x%lx]\n", error, true_offset
#define STACK_ENTRY_ERROR_FMT \
" > %s\n", error
static void
print_call_cb(void *dummy,
const char *binary_filename,
const char *symbol_name,
unw_word_t function_offset,
unsigned long true_offset)
{
if (symbol_name && (symbol_name[0] != '\0'))
tprintf(STACK_ENTRY_SYMBOL_FMT);
else if (binary_filename)
tprintf(STACK_ENTRY_NOSYMBOL_FMT);
else
tprintf(STACK_ENTRY_BUG_FMT, __FUNCTION__);
line_ended();
}
static void
print_error_cb(void *dummy,
const char *error,
unsigned long true_offset)
{
if (true_offset)
tprintf(STACK_ENTRY_ERROR_WITH_OFFSET_FMT);
else
tprintf(STACK_ENTRY_ERROR_FMT);
line_ended();
}
static char *
sprint_call_or_error(const char *binary_filename,
const char *symbol_name,
unw_word_t function_offset,
unsigned long true_offset,
const char *error)
{
char *output_line = NULL;
int n;
if (symbol_name)
n = asprintf(&output_line, STACK_ENTRY_SYMBOL_FMT);
else if (binary_filename)
n = asprintf(&output_line, STACK_ENTRY_NOSYMBOL_FMT);
else if (error)
n = true_offset
? asprintf(&output_line, STACK_ENTRY_ERROR_WITH_OFFSET_FMT)
: asprintf(&output_line, STACK_ENTRY_ERROR_FMT);
else
n = asprintf(&output_line, STACK_ENTRY_BUG_FMT, __FUNCTION__);
if (n < 0)
error_msg_and_die("error in asprintf");
return output_line;
}
/*
* queue manipulators
*/
static void
queue_put(struct queue_t *queue,
const char *binary_filename,
const char *symbol_name,
unw_word_t function_offset,
unsigned long true_offset,
const char *error)
{
struct call_t *call;
call = malloc(sizeof(*call));
if (!call)
die_out_of_memory();
call->output_line = sprint_call_or_error(binary_filename,
symbol_name,
function_offset,
true_offset,
error);
call->next = NULL;
if (!queue->head) {
queue->head = call;
queue->tail = call;
} else {
queue->tail->next = call;
queue->tail = call;
}
}
static void
queue_put_call(void *queue,
const char *binary_filename,
const char *symbol_name,
unw_word_t function_offset,
unsigned long true_offset)
{
queue_put(queue,
binary_filename,
symbol_name,
function_offset,
true_offset,
NULL);
}
static void
queue_put_error(void *queue,
const char *error,
unsigned long ip)
{
queue_put(queue, NULL, NULL, 0, ip, error);
}
static void
queue_print(struct queue_t *queue)
{
struct call_t *call, *tmp;
queue->tail = NULL;
call = queue->head;
queue->head = NULL;
while (call) {
tmp = call;
call = call->next;
tprints(tmp->output_line);
line_ended();
free(tmp->output_line);
tmp->output_line = NULL;
tmp->next = NULL;
free(tmp);
}
}
/*
* printing stack
*/
void
unwind_print_stacktrace(struct tcb* tcp)
{
#if SUPPORTED_PERSONALITIES > 1
if (tcp->currpers != DEFAULT_PERSONALITY) {
/* disable strack trace */
return;
}
#endif
if (tcp->queue->head) {
DPRINTF("tcp=%p, queue=%p", "queueprint", tcp, tcp->queue->head);
queue_print(tcp->queue);
}
else if (rebuild_cache_if_invalid(tcp, __FUNCTION__)) {
DPRINTF("tcp=%p, queue=%p", "stackprint", tcp, tcp->queue->head);
stacktrace_walk(tcp, print_call_cb, print_error_cb, NULL);
}
}
/*
* capturing stack
*/
void
unwind_capture_stacktrace(struct tcb *tcp)
{
#if SUPPORTED_PERSONALITIES > 1
if (tcp->currpers != DEFAULT_PERSONALITY) {
/* disable strack trace */
return;
}
#endif
if (tcp->queue->head)
error_msg_and_die("bug: unprinted entries in queue");
if (rebuild_cache_if_invalid(tcp, __FUNCTION__)) {
stacktrace_walk(tcp, queue_put_call, queue_put_error,
tcp->queue);
DPRINTF("tcp=%p, queue=%p", "captured", tcp, tcp->queue->head);
}
}