1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/addr2line.c
knu 1c0323adbc Fix build when gcc is used and the platform's libc lacks alloca().
* include/ruby/ruby.h (alloca), eval_intern.h (alloca), gc.c
  (alloca): Make alloca() globally available by moving the
  ultimate ifdef's to ruby/ruby.h.  Gcc hides its builtin alloca()
  when compiling with -ansi, and linking thus fails on platforms
  that lack their own alloca() implementation in libc, which
  include OpenBSD and some ports of NetBSD.  We use alloca()
  everywhere including from within third party C extentions, so
  alloca() must be made globally available. [Bug #7307]

* addr2line.c (alloca): Replace the alloca() part with the
  ultimate ifdef's. [Bug #7307]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@37571 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
2012-11-09 03:34:08 +00:00

640 lines
15 KiB
C

/**********************************************************************
addr2line.c -
$Author$
Copyright (C) 2010 Shinichiro Hamaji
**********************************************************************/
#include "ruby/config.h"
#include "addr2line.h"
#include <stdio.h>
#include <errno.h>
#ifdef USE_ELF
#ifdef __OpenBSD__
#include <elf_abi.h>
#else
#include <elf.h>
#endif
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
/* Make alloca work the best possible way. */
#ifdef __GNUC__
# ifndef atarist
# ifndef alloca
# define alloca __builtin_alloca
# endif
# endif /* atarist */
#else
# ifdef HAVE_ALLOCA_H
# include <alloca.h>
# else
# ifdef _AIX
#pragma alloca
# else
# ifndef alloca /* predefined by HP cc +Olibcalls */
void *alloca();
# endif
# endif /* AIX */
# endif /* HAVE_ALLOCA_H */
#endif /* __GNUC__ */
#ifdef HAVE_DL_ITERATE_PHDR
# ifndef _GNU_SOURCE
# define _GNU_SOURCE
# endif
# include <link.h>
#endif
#define DW_LNS_copy 0x01
#define DW_LNS_advance_pc 0x02
#define DW_LNS_advance_line 0x03
#define DW_LNS_set_file 0x04
#define DW_LNS_set_column 0x05
#define DW_LNS_negate_stmt 0x06
#define DW_LNS_set_basic_block 0x07
#define DW_LNS_const_add_pc 0x08
#define DW_LNS_fixed_advance_pc 0x09
#define DW_LNS_set_prologue_end 0x0a /* DWARF3 */
#define DW_LNS_set_epilogue_begin 0x0b /* DWARF3 */
#define DW_LNS_set_isa 0x0c /* DWARF3 */
/* Line number extended opcode name. */
#define DW_LNE_end_sequence 0x01
#define DW_LNE_set_address 0x02
#define DW_LNE_define_file 0x03
#define DW_LNE_set_discriminator 0x04 /* DWARF4 */
#ifndef ElfW
# if SIZEOF_VOIDP == 8
# define ElfW(x) Elf64##_##x
# else
# define ElfW(x) Elf32##_##x
# endif
#endif
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif
typedef struct {
const char *dirname;
const char *filename;
int line;
int fd;
void *mapped;
size_t mapped_size;
unsigned long base_addr;
} line_info_t;
/* Avoid consuming stack as this module may be used from signal handler */
static char binary_filename[PATH_MAX];
static unsigned long
uleb128(char **p)
{
unsigned long r = 0;
int s = 0;
for (;;) {
unsigned char b = *(unsigned char *)(*p)++;
if (b < 0x80) {
r += (unsigned long)b << s;
break;
}
r += (b & 0x7f) << s;
s += 7;
}
return r;
}
static long
sleb128(char **p)
{
long r = 0;
int s = 0;
for (;;) {
unsigned char b = *(unsigned char *)(*p)++;
if (b < 0x80) {
if (b & 0x40) {
r -= (0x80 - b) << s;
}
else {
r += (b & 0x3f) << s;
}
break;
}
r += (b & 0x7f) << s;
s += 7;
}
return r;
}
static const char *
get_nth_dirname(unsigned long dir, char *p)
{
if (!dir--) {
return "";
}
while (dir--) {
while (*p) p++;
p++;
if (!*p) {
fprintf(stderr, "Unexpected directory number %lu in %s\n",
dir, binary_filename);
return "";
}
}
return p;
}
static void
fill_filename(int file, char *include_directories, char *filenames,
line_info_t *line)
{
int i;
char *p = filenames;
char *filename;
unsigned long dir;
for (i = 1; i <= file; i++) {
filename = p;
if (!*p) {
/* Need to output binary file name? */
fprintf(stderr, "Unexpected file number %d in %s\n",
file, binary_filename);
return;
}
while (*p) p++;
p++;
dir = uleb128(&p);
/* last modified. */
uleb128(&p);
/* size of the file. */
uleb128(&p);
if (i == file) {
line->filename = filename;
line->dirname = get_nth_dirname(dir, include_directories);
}
}
}
static int
get_path_from_symbol(const char *symbol, const char **p, size_t *len)
{
if (symbol[0] == '0') {
/* libexecinfo */
*p = strchr(symbol, '/');
if (*p == NULL) return 0;
*len = strlen(*p);
}
else {
/* glibc */
const char *q;
*p = symbol;
q = strchr(symbol, '(');
if (q == NULL) return 0;
*len = q - symbol;
}
return 1;
}
static void
fill_line(int num_traces, void **traces,
unsigned long addr, int file, int line,
char *include_directories, char *filenames, line_info_t *lines)
{
int i;
for (i = 0; i < num_traces; i++) {
unsigned long a = (unsigned long)traces[i] - lines[i].base_addr;
/* We assume one line code doesn't result >100 bytes of native code.
We may want more reliable way eventually... */
if (addr < a && a < addr + 100) {
fill_filename(file, include_directories, filenames, &lines[i]);
lines[i].line = line;
}
}
}
static void
parse_debug_line_cu(int num_traces, void **traces,
char **debug_line, line_info_t *lines)
{
char *p, *cu_end, *cu_start, *include_directories, *filenames;
unsigned long unit_length;
int default_is_stmt, line_base;
unsigned int header_length, minimum_instruction_length, line_range,
opcode_base;
/* unsigned char *standard_opcode_lengths; */
/* The registers. */
unsigned long addr = 0;
unsigned int file = 1;
unsigned int line = 1;
/* unsigned int column = 0; */
int is_stmt;
/* int basic_block = 0; */
/* int end_sequence = 0; */
/* int prologue_end = 0; */
/* int epilogue_begin = 0; */
/* unsigned int isa = 0; */
p = *debug_line;
unit_length = *(unsigned int *)p;
p += sizeof(unsigned int);
if (unit_length == 0xffffffff) {
unit_length = *(unsigned long *)p;
p += sizeof(unsigned long);
}
cu_end = p + unit_length;
/*dwarf_version = *(unsigned short *)p;*/
p += 2;
header_length = *(unsigned int *)p;
p += sizeof(unsigned int);
cu_start = p + header_length;
minimum_instruction_length = *(unsigned char *)p;
p++;
is_stmt = default_is_stmt = *(unsigned char *)p;
p++;
line_base = *(char *)p;
p++;
line_range = *(unsigned char *)p;
p++;
opcode_base = *(unsigned char *)p;
p++;
/* standard_opcode_lengths = (unsigned char *)p - 1; */
p += opcode_base - 1;
include_directories = p;
/* skip include directories */
while (*p) {
while (*p) p++;
p++;
}
p++;
filenames = p;
p = cu_start;
#define FILL_LINE() \
do { \
fill_line(num_traces, traces, addr, file, line, \
include_directories, filenames, lines); \
/*basic_block = prologue_end = epilogue_begin = 0;*/ \
} while (0)
while (p < cu_end) {
unsigned long a;
unsigned char op = *p++;
switch (op) {
case DW_LNS_copy:
FILL_LINE();
break;
case DW_LNS_advance_pc:
a = uleb128(&p);
addr += a;
break;
case DW_LNS_advance_line: {
long a = sleb128(&p);
line += a;
break;
}
case DW_LNS_set_file:
file = (unsigned int)uleb128(&p);
break;
case DW_LNS_set_column:
/*column = (unsigned int)*/(void)uleb128(&p);
break;
case DW_LNS_negate_stmt:
is_stmt = !is_stmt;
break;
case DW_LNS_set_basic_block:
/*basic_block = 1; */
break;
case DW_LNS_const_add_pc:
a = ((255 - opcode_base) / line_range) *
minimum_instruction_length;
addr += a;
break;
case DW_LNS_fixed_advance_pc:
a = *(unsigned char *)p++;
addr += a;
break;
case DW_LNS_set_prologue_end:
/* prologue_end = 1; */
break;
case DW_LNS_set_epilogue_begin:
/* epilogue_begin = 1; */
break;
case DW_LNS_set_isa:
/* isa = (unsigned int)*/(void)uleb128(&p);
break;
case 0:
a = *(unsigned char *)p++;
op = *p++;
switch (op) {
case DW_LNE_end_sequence:
/* end_sequence = 1; */
FILL_LINE();
addr = 0;
file = 1;
line = 1;
/* column = 0; */
is_stmt = default_is_stmt;
/* end_sequence = 0; */
/* isa = 0; */
break;
case DW_LNE_set_address:
addr = *(unsigned long *)p;
p += sizeof(unsigned long);
break;
case DW_LNE_define_file:
fprintf(stderr, "Unsupported operation in %s\n",
binary_filename);
break;
case DW_LNE_set_discriminator:
/* TODO:currently ignore */
uleb128(&p);
break;
default:
fprintf(stderr, "Unknown extended opcode: %d in %s\n",
op, binary_filename);
}
break;
default: {
unsigned long addr_incr;
unsigned long line_incr;
a = op - opcode_base;
addr_incr = (a / line_range) * minimum_instruction_length;
line_incr = line_base + (a % line_range);
addr += (unsigned int)addr_incr;
line += (unsigned int)line_incr;
FILL_LINE();
}
}
}
*debug_line = p;
}
static void
parse_debug_line(int num_traces, void **traces,
char *debug_line, unsigned long size, line_info_t *lines)
{
char *debug_line_end = debug_line + size;
while (debug_line < debug_line_end) {
parse_debug_line_cu(num_traces, traces, &debug_line, lines);
}
if (debug_line != debug_line_end) {
fprintf(stderr, "Unexpected size of .debug_line in %s\n",
binary_filename);
}
}
/* read file and fill lines */
static void
fill_lines(int num_traces, void **traces, char **syms, int check_debuglink,
line_info_t *current_line, line_info_t *lines);
static void
follow_debuglink(char *debuglink, int num_traces, void **traces, char **syms,
line_info_t *current_line, line_info_t *lines)
{
/* Ideally we should check 4 paths to follow gnu_debuglink,
but we handle only one case for now as this format is used
by some linux distributions. See GDB's info for detail. */
static const char global_debug_dir[] = "/usr/lib/debug";
char *p, *subdir;
p = strrchr(binary_filename, '/');
if (!p) {
return;
}
p[1] = '\0';
subdir = (char *)alloca(strlen(binary_filename) + 1);
strcpy(subdir, binary_filename);
strcpy(binary_filename, global_debug_dir);
strncat(binary_filename, subdir,
PATH_MAX - strlen(binary_filename) - 1);
strncat(binary_filename, debuglink,
PATH_MAX - strlen(binary_filename) - 1);
munmap(current_line->mapped, current_line->mapped_size);
close(current_line->fd);
fill_lines(num_traces, traces, syms, 0, current_line, lines);
}
/* read file and fill lines */
static void
fill_lines(int num_traces, void **traces, char **syms, int check_debuglink,
line_info_t *current_line, line_info_t *lines)
{
int i;
char *shstr;
char *section_name;
ElfW(Ehdr) *ehdr;
ElfW(Shdr) *shdr, *shstr_shdr;
ElfW(Shdr) *debug_line_shdr = NULL, *gnu_debuglink_shdr = NULL;
int fd;
off_t filesize;
char *file;
fd = open(binary_filename, O_RDONLY);
if (fd < 0) {
return;
}
filesize = lseek(fd, 0, SEEK_END);
if (filesize < 0) {
int e = errno;
close(fd);
fprintf(stderr, "lseek: %s\n", strerror(e));
return;
}
#if SIZEOF_OFF_T > SIZEOF_SIZE_T
if (filesize > (off_t)SIZE_MAX) {
close(fd);
fprintf(stderr, "Too large file %s\n", binary_filename);
return;
}
#endif
lseek(fd, 0, SEEK_SET);
/* async-signal unsafe */
file = (char *)mmap(NULL, (size_t)filesize, PROT_READ, MAP_SHARED, fd, 0);
if (file == MAP_FAILED) {
int e = errno;
close(fd);
fprintf(stderr, "mmap: %s\n", strerror(e));
return;
}
current_line->fd = fd;
current_line->mapped = file;
current_line->mapped_size = (size_t)filesize;
for (i = 0; i < num_traces; i++) {
const char *path;
size_t len;
if (get_path_from_symbol(syms[i], &path, &len) &&
!strncmp(path, binary_filename, len)) {
lines[i].line = -1;
}
}
ehdr = (ElfW(Ehdr) *)file;
shdr = (ElfW(Shdr) *)(file + ehdr->e_shoff);
shstr_shdr = shdr + ehdr->e_shstrndx;
shstr = file + shstr_shdr->sh_offset;
for (i = 0; i < ehdr->e_shnum; i++) {
section_name = shstr + shdr[i].sh_name;
if (!strcmp(section_name, ".debug_line")) {
debug_line_shdr = shdr + i;
break;
} else if (!strcmp(section_name, ".gnu_debuglink")) {
gnu_debuglink_shdr = shdr + i;
}
}
if (!debug_line_shdr) {
/* This file doesn't have .debug_line section,
let's check .gnu_debuglink section instead. */
if (gnu_debuglink_shdr && check_debuglink) {
follow_debuglink(file + gnu_debuglink_shdr->sh_offset,
num_traces, traces, syms,
current_line, lines);
}
return;
}
parse_debug_line(num_traces, traces,
file + debug_line_shdr->sh_offset,
debug_line_shdr->sh_size,
lines);
}
#ifdef HAVE_DL_ITERATE_PHDR
typedef struct {
int num_traces;
char **syms;
line_info_t *lines;
} fill_base_addr_state_t;
static int
fill_base_addr(struct dl_phdr_info *info, size_t size, void *data)
{
int i;
fill_base_addr_state_t *st = (fill_base_addr_state_t *)data;
for (i = 0; i < st->num_traces; i++) {
const char *path;
size_t len;
size_t name_len = strlen(info->dlpi_name);
if (get_path_from_symbol(st->syms[i], &path, &len) &&
(len == name_len || (len > name_len && path[len-name_len-1] == '/')) &&
!strncmp(path+len-name_len, info->dlpi_name, name_len)) {
st->lines[i].base_addr = info->dlpi_addr;
}
}
return 0;
}
#endif /* HAVE_DL_ITERATE_PHDR */
void
rb_dump_backtrace_with_lines(int num_traces, void **trace, char **syms)
{
int i;
/* async-signal unsafe */
line_info_t *lines = (line_info_t *)calloc(num_traces,
sizeof(line_info_t));
/* Note that line info of shared objects might not be shown
if we don't have dl_iterate_phdr */
#ifdef HAVE_DL_ITERATE_PHDR
fill_base_addr_state_t fill_base_addr_state;
fill_base_addr_state.num_traces = num_traces;
fill_base_addr_state.syms = syms;
fill_base_addr_state.lines = lines;
/* maybe async-signal unsafe */
dl_iterate_phdr(fill_base_addr, &fill_base_addr_state);
#endif /* HAVE_DL_ITERATE_PHDR */
for (i = 0; i < num_traces; i++) {
const char *path;
size_t len;
if (lines[i].line) {
continue;
}
if (!get_path_from_symbol(syms[i], &path, &len)) {
continue;
}
strncpy(binary_filename, path, len);
binary_filename[len] = '\0';
fill_lines(num_traces, trace, syms, 1, &lines[i], lines);
}
/* fprintf may not be async-signal safe */
for (i = 0; i < num_traces; i++) {
line_info_t *line = &lines[i];
if (line->line > 0) {
fprintf(stderr, "%s ", syms[i]);
if (line->filename) {
if (line->dirname && line->dirname[0]) {
fprintf(stderr, "%s/", line->dirname);
}
fprintf(stderr, "%s", line->filename);
} else {
fprintf(stderr, "???");
}
fprintf(stderr, ":%d\n", line->line);
} else {
fprintf(stderr, "%s\n", syms[i]);
}
}
for (i = 0; i < num_traces; i++) {
line_info_t *line = &lines[i];
if (line->fd) {
munmap(line->mapped, line->mapped_size);
close(line->fd);
}
}
free(lines);
}
#else /* defined(USE_ELF) */
#error not supported
#endif