Project

General

Profile

Download (12.7 KB) Statistics
| Branch: | Tag: | Revision:
#define _GNU_SOURCE
#include "backtrace.h"
#include <execinfo.h> // for backtrace, backtrace_symbols
#include <fcntl.h> // for open, O_CLOEXEC, O_RDONLY
#include <pthread.h> // for pthread_mutex_lock, pthread_mute...
#include <spawn.h> // for posix_spawn_file_actions_t, posi...
#include <stddef.h> // for ptrdiff_t
#include <stdio.h> // for snprintf, sscanf
#include <stdlib.h> // for size_t, NULL, abort, free, strtoull
#include <string.h> // for memset, strrchr
#include <sys/wait.h> // for waitpid
#include <unistd.h> // for close, read, pipe2, ssize_t, rea...
#include "simplest-defs.h" // for NELEM, UNUSED
#include "sparse.h" // for __force
#include "spew.h" // for spew, SPEW_DEBUG, SPEW_INFO, SPE...

/* DO NOT USE ASSERT() IN THIS FILE, TO AVOID INFINITE RECURSION */
#ifdef ASSERT
#error CANNOT_USE_ASSERT_IN_THIS_FILE
#endif

// I'm using pthread_mutex_t rather than xthread_mutex_t because I cannot
// think of a better way to do this than with a static variable. And with a
// static variable, I want to use PTHREAD_MUTEX_INITIALIZER, which there
// doesn't seem to have a xthread_mutex analogue.

static pthread_mutex_t backtrace_mutex = PTHREAD_MUTEX_INITIALIZER;

// Backtracer wants to fork an "addr2line" process to be able to translate
// code offsets into human-readable strings. Back in the dark ages, we would
// do the fork whenever we wanted a backtrace. That turned out to be too
// expensive, since fork() would copy huge page table (covering many gigabytes
// of memory), and it was silly because the exec() would just throw it away.
//
// Our next solution used a constructor/destructor. At the beginning of the
// program we would start up the backtracer. At that point in time the page
// table was small, and we didn't really care if it was expensive since
// nothing was really happening yet. However we don't really like the
// constructor/destructor stuff, since it's fragile. (And Matteo doesn't like
// it because it's a desugared version of C++ :-)
//
// This solution uses posix_spawnp() to do a fork/exec operation.
// posix_spawnp() lets you do some housekeeping (with dup2 and close
// operations) between the fork and the spawn. Because the kernel gets all
// the information about the fork/dup/close/spawn sequence at once, it can
// avoid copying the page tables. The performance of this sequence is about
// 200us to 500us.

struct backtracer {
int broken; // true if something went wrong and the backtracer
// is failing. Fall back to not printing
// backtraces, and try to keep running.
pid_t child; // The addr2line process
int pipe_to_child; // The write end of a pipe that goes the
// addr2line's stdin.
int pipe_from_child; // The read end of a pipe that comes from
// addr2line's stdout.
size_t va_base; // The offset to be subtracted from addresses to
// to ASLR.
};

static int __attribute__((warn_unused_result)) // NEGATIVE_ON_ERROR
sinit(posix_spawn_file_actions_t *file_actions)
// Effect: Initialize the file_actions. If anything goes wrong return -1.
{
int r = posix_spawn_file_actions_init(file_actions);
if (r != 0) {
spew(SPEW_DEBUG, "Cannot spawn_file_actions_init: r=%d", r);
return -1;
} else {
return 0;
}
}

static int __attribute__((warn_unused_result)) // NEGATIVE_ON_ERROR
sdup2(posix_spawn_file_actions_t *file_actions, int from, int to)
// Effect: Record a dup2() operation to be done when posix_spawn runs. If
// anything goes wrong, spew a diagnostic and return -1;
{
int r = posix_spawn_file_actions_adddup2(file_actions, from, to);
if (r != 0) {
spew(SPEW_DEBUG, "Cannot spawn_fileactions_adddup2: r=%d", r);
return -1;
} else {
return 0;
}
}

static int __attribute__((warn_unused_result)) // NEGATIVE_ON_ERROR
sclose(posix_spawn_file_actions_t *file_actions, int fd)
// Effect: Record a close() operation to be cone when posix_spawn runs. If
// anything goes wrong, spew a diagnostic and return -1;
{
int r = posix_spawn_file_actions_addclose(file_actions, fd);
if (r != 0) {
spew(SPEW_DEBUG, "Cannot spawn_fileactions_addclose: r=%d", r);
return -1;
} else {
return 0;
}
}

static const struct backtracer errresult = {
.pipe_to_child=-1, .pipe_from_child=-1, .child=0, .broken=1, .va_base = 0};

static struct backtracer
mk_backtracer(void) {
// Only one backtracer may exist at a time (to prevent forkbombing if all
// threads start backtracing.) This mutex is unlocked when you destroy
// the backtracer.
pthread_mutex_lock(&backtrace_mutex);

// Plumbing
int tochildpipe[2];
int fromchildpipe[2];
{
int r = pipe2(tochildpipe, O_CLOEXEC);
if (r) {
spew(SPEW_ERROR, "pipe2 failed");
}
}
{
int r = pipe2(fromchildpipe, O_CLOEXEC);
if (r) {
spew(SPEW_ERROR, "pipe2 failed");
}
}

// Find the path to the current executable. It's a symlink in
// /proc/self/exe.

char exec_path[204];
// readlink doesn't null terminate. Fill it with zeros and tell it to
// only use sizeof(exec_path)-1 to make sure there is a null at the
// end.
memset(exec_path, 0, sizeof(exec_path));
ssize_t rc = readlink("/proc/self/exe", exec_path,
sizeof(exec_path)-1);
UNUSED(rc);
spew(SPEW_INFO, "exec path is %s", exec_path);

// Set up to do the plumbing after the spawn.
posix_spawn_file_actions_t file_actions;
if (0
|| sinit(&file_actions)
|| sdup2(&file_actions, tochildpipe[0], 0)
|| sdup2(&file_actions, fromchildpipe[1], 1)
|| sclose(&file_actions, tochildpipe[0])
|| sclose(&file_actions, tochildpipe[1])
|| sclose(&file_actions, fromchildpipe[0])
|| sclose(&file_actions, fromchildpipe[1]))
{
return errresult;
}
pid_t child;
{
// Hack around because posix_spawnp doesn't have const's in quite the right place.
char addr2line_s[] = "addr2line";
char _e_s[] = "-e";
char _f_s[] = "-f";
char * const argv[] = {addr2line_s, _e_s, exec_path, _f_s, NULL};
int r = posix_spawnp(&child, "addr2line",
&file_actions, NULL,
argv, environ);
if (r != 0) {
spew(SPEW_DEBUG, "Could not posix_spawnp r=%d", r);
return errresult;
}
}
{
int r = posix_spawn_file_actions_destroy(&file_actions);
if (r) spew(SPEW_FATAL, "Cannot destroy fail_actions r=%d", r);
}

// Plumbing back here the main program.
struct backtracer result = {.broken = 0,
.child = child,
.pipe_to_child = tochildpipe[1],
.pipe_from_child = fromchildpipe[0],
.va_base = 0};
close(tochildpipe[0]);
close(fromchildpipe[1]);

// Find the va_base
{
int fd = open("/proc/self/maps", O_RDONLY);
if (fd >= 0) {
char addr_buffer[256];
// HACK: we're going to assume that the first line in the
// file contains the mapping for our executable. Dynamic
// libraries will be mapped in different locations, but we
// don't really use any of them so our traceback should be
// mostly correct.
ssize_t bytes_read = read(fd, &addr_buffer, NELEM(addr_buffer)-1);
if (bytes_read > 0) {
addr_buffer[bytes_read] = 0;
int rval = sscanf(addr_buffer, "%zx-", &result.va_base);
if (rval != 1) {
result.va_base = 0;
}
}
}
close(fd);
}
spew(SPEW_INFO, "va_base is %zx", result.va_base);
if (result.va_base < 0x800000) {
// If ASLR is off, subtracting va_base makes offsets that addr2line
// doesn't understand. The best heuristic I've (jmp) found is that if
// va_base is "low", then ASLR is off. I haven't been able to figure
// out if there's a "low" address under which ASLR is disabled, but
// I've seen 4 MiB in many systems, so I'm going to claim any va_base
// under 8 MiB is in a non-ASLR system.
result.va_base = 0;
}

return result;
}

static void
backtracer_destroy(struct backtracer *bt) {
close(bt->pipe_to_child);
close(bt->pipe_from_child);
spew(SPEW_INFO, "waitpid(%d)", bt->child);
int wstatus;
waitpid(bt->child, &wstatus, 0);
spew(SPEW_INFO, "waitpid(%d) done wstatus=%d", bt->child, wstatus);
*bt = errresult;
pthread_mutex_unlock(&backtrace_mutex);
}

static void read_tail_of_line(int fd, char *line, size_t linelen)
// Effect: Read a line of data from fd into line. If the line is too long to
// fit, then put the last characters in so that if we read
// "longfilename/fun.c:25" and linelen is 11 we end up with "e/fun.c:25"
// rather than "longfilena".
// line will always end NUL-terminated.
// Requires: linelen>0. This function will fail unpredictably if linelen==0.
{
for (size_t i = 0; i+1 < linelen; i++) {
char c;
ssize_t rr = read(fd, &c, 1);
if (0 == rr || '\n' == c) {
line[i] = 0;
return;
} else {
line[i] = c;
}
}
while (1) {
char c;
ssize_t rr = read(fd, &c, 1);
if (0 == rr || '\n' == c) {
line[linelen-1] = 0;
return;
} else {
line[linelen-1] = c;
// Shift the string left by 1.o
for (size_t j = 0; j+1 < linelen; j++) {
line[j] = line[j+1];
}
}
}
}

static void spew_backtrace_symbol(struct backtracer *bt,
const void *addr,
const char *from_backtrace_symbol,
size_t spew_level)
{
void *addr_from_base = (void*)((__force size_t)addr
- bt->va_base);
char str_of_ptr[20];
char fun_line[300] = "";
char file_line[300] = "";
if (!bt->broken) {
int len = snprintf(str_of_ptr, sizeof(str_of_ptr),
"%p\n", addr_from_base);
ssize_t wr = write(bt->pipe_to_child, str_of_ptr,
(__force size_t)(ptrdiff_t)len);
int did_write = wr == len;
if (!did_write) {
bt->broken = 1;
} else {
read_tail_of_line(bt->pipe_from_child, fun_line, sizeof(fun_line));
read_tail_of_line(bt->pipe_from_child, file_line, sizeof(file_line));
}
}
if (!from_backtrace_symbol) {
from_backtrace_symbol = "(unknown offset)";
}
const char* function = &fun_line[0];
if (!fun_line[0]) {
function = "(unknown function)";
}
size_t line_number = 0;
const char* filename = &file_line[0];
if (*filename) {
// Find line number by looking for last : in file_line.
char* line_number_start = strrchr(file_line, ':');
if (line_number_start != NULL) {
// Parse the line number
line_number = strtoull(line_number_start + 1, NULL, 10);
if (line_number > 0) {
// Parsed, remove the line number and : from file_line.
*line_number_start = 0;
}
}
} else {
// No filename, substitute backtrace symbol for filename.
filename = from_backtrace_symbol;
from_backtrace_symbol = "";
}
spew_for_backtrace(
spew_level, filename, line_number, function, from_backtrace_symbol);
}

void spew_stored_backtrace(void *const*ptrs, int nptrs, size_t spew_level)
{
char **strings = backtrace_symbols(ptrs, nptrs);

struct backtracer bt = mk_backtracer();

for (int j = 0; j < nptrs; j++) {
spew_backtrace_symbol(&bt,
ptrs[(__force ptrdiff_t)j],
strings ? strings[(__force ptrdiff_t)j] : NULL,
spew_level);
}

backtracer_destroy(&bt);

free(strings);
}

void spew_backtrace(size_t spew_level)
{
if (spew_level < spew_get_level()) return;
void *buffer[100];
int nptrs = backtrace(buffer, (__force int)NELEM(buffer));

/* cannot assert in this file */
if (nptrs >= 0 && nptrs <= (__force int)NELEM(buffer)) {
spew_stored_backtrace(buffer, nptrs, spew_level);
} else {
abort(); // can't happen
}
}
(3-3/14)