Skip to content

Instantly share code, notes, and snippets.

@Lupus
Created October 14, 2014 18:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Lupus/791fa776132dd93b26c9 to your computer and use it in GitHub Desktop.
Save Lupus/791fa776132dd93b26c9 to your computer and use it in GitHub Desktop.
//===========================================================================
// Copyright 2005,2006 Don Hatch
// Permission is hereby granted to everyone to use this source code
// or any part of it for any purpose as long as it is good and not evil.
//
// Author: Don Hatch (hatch@plunk.org)
// Last Modified: Tue Feb 21 18:50:39 PST 2006
//===========================================================================
//
// backtracefilt.C - program for filtering the output of backtrace() on Linux,
// to produce a more readable trace
// containing source file names and line numbers.
//
//===========================================================================
//
// If you call the GNU libc function backtrace()
// and then backtrace_symbols() on the result, you'll get something
// that looks like this:
//
// ./libp2lite.so [0xf5c4b4]
// ./libp2lite.so(malloc+0x1a6) [0xf5de78]
// /usr/lib/perl5/libperl.so(Perl_safesysmalloc+0x37) [0x33a2c97]
// /usr/lib/perl5/libperl.so(Perl_gv_init+0x8b) [0x335ce8a]
// /usr/lib/perl5/libperl.so(Perl_gv_fetchpv+0x412) [0x335daeb]
// /usr/lib/perl5/libperl.so(Perl_newXS+0x3e) [0x3381c53]
// /usr/lib/perl5/libperl.so(Perl_boot_core_PerlIO+0x5d) [0x341edef]
// /usr/lib/perl5/libperl.so [0x3358d6e]
// /usr/lib/perl5/libperl.so(perl_parse+0x424) [0x335b862]
// perl(main+0xc2) [0x804927a]
// /lib/tls/libc.so.6(__libc_start_main+0xd3) [0x8b4e23]
// perl [0x8049131]
//
// This program filters such output, appending function/file/line numbers.
// It tries to get it right even though the addresses
// have been dynamically relocated and therefore most likely
// don't match what is in the executable or dso
// (which makes the addr2line program useless for this task).
//
// If you have a file of program output that contains
// a stack trace among other stuff, you can just
// pass the whole thing through backtracefilt;
// only stuff that looks like stack traces on the end of each line
// will be processed, and everything else will be passed through unaltered.
//
// You can pass the result through c++filt if you like.
// You can also c++filt first if you want, but it won't do much good--
// whenever backtracefilt successfully processes a line,
// it will produce a mangled symbol, so if you want to see demangled
// symbols, always pass the results through c++filt last.
//
// In order to process a line,
// we often need information from future lines (see explanation below)...
// when this is the case, the dependent line (and lines after it)
// will be put on a delay queue until the desired information is available.
// However, if a non-stacktrace line is encountered (or EOF is reached
// on input), the delay queue will be flushed even if the required
// information is not available (so some lines may not be able
// to be translated well).
//
// If you want to see the "Reading symbols from..." messages
// to stderr, run with -v 1.
// That only works when stderr is to a tty;
// if you REALLY want to see those messages even when output is to a file,
// then run with -v 2 or higher (but beware of collisions between
// this process and other processes that might be writing to the same
// stderr file at the same time, which can cause output to be lost!!).
//
// I'm sure this only works on Linux.
// In particular, it probably only works on the specific Linux releases
// I tried it on:
// - CentOS 4.1 (i.e. RedHat Enterprise Linux 4.1)
// - SuSE 9.1
//
// I compile it with:
// g++ -W -Wall -g -O2 backtracefilt.C -o backtracefilt -lbfd -liberty
//
//==========================================================================
//
// Here's the basic idea... consider a sample trace
// generated from backtrace() and backtrace_symbols():
//
// ./libp2lite.so [0xf5c4b4]
// ./libp2lite.so(malloc+0x1a6) [0xf5de78]
// /usr/lib/perl5/libperl.so(Perl_safesysmalloc+0x37) [0x33a2c97]
// /usr/lib/perl5/libperl.so(Perl_gv_init+0x8b) [0x335ce8a]
// /usr/lib/perl5/libperl.so(Perl_gv_fetchpv+0x412) [0x335daeb]
// /usr/lib/perl5/libperl.so(Perl_newXS+0x3e) [0x3381c53]
// /usr/lib/perl5/libperl.so(Perl_boot_core_PerlIO+0x5d) [0x341edef]
// /usr/lib/perl5/libperl.so [0x3358d6e]
// /usr/lib/perl5/libperl.so(perl_parse+0x424) [0x335b862]
// perl(main+0xc2) [0x804927a]
// /lib/tls/libc.so.6(__libc_start_main+0xd3) [0x8b4e23]
// perl [0x8049131]
//
// If we run addr2line on the first address:
// % addr2line -e ./libp2lite.so 0xf5c4b4
// ??:0
// It's clueless because 0xf5c4b4 is a dynamically relocated runtime address
// but addr2line expects a static address. :-(
//
// However, look at the next line: it says that at runtime,
// malloc+0x1a6 == 0xf5de78.
// If we compare this to the static address of malloc+0x1a6 in the file,
// this will tell us the relocation offset, which will enable us
// to go back and adjust the first address appropriately.
// We can find the static address of malloc in libp2lite.so
// either by running something like "nm libp2lite.so | grep malloc",
// or:
// % objdump -d --prefix-addresses libp2lite.so | grep "<malloc+0x1a6>"
// 00010e78 <malloc+0x1a6> add $0x10,%esp
// So the address malloc+0x1a6 got relocated from 0x00010e78
// in the file to 0xf5de78 at runtime.
//
// Therefore the amount that this text segment got shifted by
// during dynamic loading was 0xf5de78-0x00010e78 = 0xf4d000.
// Knowing that, we can now go back to the first dynamic address 0xf5c4b4
// and figure out its static address: 0xf5c4b4 - 0xf4d000 = 0xf4b4.
//
// Feeding *that* into addr2line:
// % addr2line -e ./libp2lite.so 0xf4b4
// /home/hatch/share/wrk/p2lite/p2lite.C:335
// Yippee!!!
// Similarly, we can find the dynamic relocation offsets for perl
// and libperl.so, allowing us to get info for each line of the stack trace.
//
// That's essentially what this program does,
// without all the manual labor.
//
//
// Took a lot of hints from the following programs in GNU binutils:
// - addr2line
// - objdump
// - nm
// And Owen Taylor's memprof program.
//
// See http://sources.redhat.com/binutils/docs-2.12/bfd.info/BFD-front-end.html
// for documentation of libbfd, which is used to read symbol tables and stuff.
//
// TODO:
// - verbose==1 should act like 2 if stderr is a fifo,
// not just if it's a tty-- the only dangerous case
// is when it's a file
// - flush when a certain signal is recieved? (SIGINT?)
// - maybe some command-line flags:
// - when to flush delay buffers
// - EOF only
// - EOF or happy
// - EOF or blank line
// - EOF or blank line or happy
// - EOF or blank line or any non-stacktrace line
// - EOF or blank line or any non-stacktrace line or happy
// - after every line
// - whether to block on output, or buffer instead (and how much max)
// - whether to block on input, or flush any pending input instead
//
// - conceptually up to 3 buffers; are they all separate issues?
// - input buffer
// should really slurp up as much input is available;
// no reason to output anything as long as input is
// coming in, since future input can help us
// (unless input buffer size gets ridiculous).
// (not sure this is any different from the
// delay buffer though)
// (ah, it is different...
// one way to do this would be to buffer input
// until a non-trace line is reached (or EOF);
// that allows caller to finish its sending stuff to us
// and to start reading stuff from us)
// - delay buffer
// - output buffer
// this could prevent deadlock-- if we were
// going to block on write, could write to the buffer
// instead. Select on both input and output,
// and do whichever thing is ready.
// Have to think about this a lot more.
// certainly the most robust way would be
// to select on stdin and stdout, and never
// block on one of them if the other has stuff pending...
// - justify not demangling function names always--
// it's something like, if they don't pass it through c++filt
// at the end, they risk not demangling other stuff...
// but then again, maybe that's okay... have to think about it
//
// - should maybe use bfd_scan_vma instead of strtoul? dunno
// - if I strip the main executable, it still has a dynamic
// symbol table but the bfd_find_nearest_line calls fail :-(
// even though they succeed for libc.so, for example.
// - strip off annotations from previous runs?
// - search for "lame" for a compiler thing to figure out
//
//===========================================================================
//
//
#undef NDEBUG // don't even think about it
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <inttypes.h>
#include <bfd.h>
#include <string>
#include <queue>
#include <map>
// Old versions of demangle.h use "typename" as a parameter,
// which makes new g++ bomb... so we temporarily redefine it.
// If you don't have /usr/include/demangle.h, you need
// to find it somewhere (binutils-2.15.90.0.1.1-31 on suse seems to have it,
// but binutils-2.15.92.0.2-15 on centos 4 doesn't-- weird)
#define typename type_name
#include "demangle.h"
#undef typename
#define numberof(things) (sizeof(things)/sizeof(*(things)))
#define FOR(i,n) for (int i = 0; (i) < (n); ++(i))
#define INRANGE(foo,bar,baz) (foo(bar))&&((bar)baz)
#define streq(a,b) (strcmp(a,b) == 0)
typedef unsigned long long ull;
typedef long long ll;
static inline bool isBlank(const char *s)
{
for (; *s != '\0'; ++s)
if (!isspace(*s))
return false;
return true;
}
// From objdump source, same thing in memprof source
// XXX and in addr2line source, but they use minisymbols,
// XXX which supposedly take up less space but more time to access...
// XXX should consider that, since we may get huge
// XXX oh, we ARE using minisymbols... clean this up
extern asymbol **
slurp_symtab(bfd *abfd, bool useMini,
long *nSymbols,
unsigned int *symbolSize,
bool *isDynamic)
{
*nSymbols = 0;
*symbolSize = 0;
*isDynamic = false;
asymbol **symbol_table = NULL;
if ((bfd_get_file_flags (abfd) & HAS_SYMS) == 0)
{
*nSymbols = 0;
fprintf(stderr, "No symbol table in %s\n", bfd_get_filename(abfd));
return NULL;
}
if (useMini)
{
*nSymbols = bfd_read_minisymbols(abfd, false /* not dynamic */, (void **)&symbol_table, symbolSize);
if (*nSymbols == 0)
{
//fprintf(stderr, "No static symbols, trying dynamic...\n");
*isDynamic = true;
*nSymbols = bfd_read_minisymbols(abfd, true /* dynamic */, (void **)&symbol_table, symbolSize);
}
else
{
//fprintf(stderr, "Found static symbols.\n");
}
if (*nSymbols < 0)
{
fprintf(stderr, "Error (bfd_read_minisymbols) in %s: ", bfd_get_filename(abfd));
bfd_perror(NULL);
return NULL;
}
if (false) // XXX maybe if super verbose
{
// Print out the symbols
asymbol *store = bfd_make_empty_symbol(abfd); // never freed until the bfd is closed, but whatever
if (store == NULL)
{
fprintf(stderr, "Error (bfd_make_empty_symbof) in %s: ", bfd_get_filename(abfd));
bfd_perror(NULL);
assert(false); // XXX
}
bfd_byte *from = (bfd_byte *)symbol_table;
bfd_byte *fromend = from + *nSymbols * *symbolSize;
int index = 0;
for (; from < fromend; from += *symbolSize, index++)
{
asymbol *sym = bfd_minisymbol_to_symbol(abfd, *isDynamic, from, store);
if (sym == NULL)
{
fprintf(stderr, "Error (bfd_minisymbol_to_symbol) in %s: ", bfd_get_filename(abfd));
bfd_perror(NULL);
assert(false); // XXX
}
const char *symbolName = bfd_asymbol_name(sym); // XXX hmm, also accessible as syminfo.name? weird
symbol_info syminfo;
bfd_get_symbol_info(abfd, sym, &syminfo);
assert(streq(syminfo.name, symbolName));
char symbolType = syminfo.type;
symvalue symbolValue = syminfo.value;
fprintf(stderr, " %d: %08llx %c \"%s\"\n", index, (unsigned long long)symbolValue, symbolType, symbolName);
if (true)
{
// Use what c++filt uses...
int demangle_flags = DMGL_PARAMS | DMGL_ANSI | DMGL_VERBOSE;
char *demangled = cplus_demangle(symbolName, demangle_flags);
if (demangled != NULL)
{
fprintf(stderr, " \"%s\"\n", demangled);
free(demangled);
}
else
{
fprintf(stderr, " DEMANGLING FAILED!!\n");
}
}
}
}
}
else
{
long storage_needed = bfd_get_symtab_upper_bound(abfd);
if (storage_needed < 0)
{
fprintf(stderr, "Error (bfd_get_symtab_upper_bound) slurping symbol table from %s: ", bfd_get_filename(abfd));
bfd_perror(NULL);
return NULL;
}
if (storage_needed != 0)
symbol_table = (asymbol **) malloc(storage_needed);
*nSymbols = bfd_canonicalize_symtab(abfd, symbol_table);
if (*nSymbols < 0)
{
fprintf(stderr, "Error (bfd_canonicalize_symtab) slurping symbol table from %s: ", bfd_get_filename(abfd));
bfd_perror(NULL);
return NULL;
}
// XXX hey! dag nab it, can I read the dynamic symbol table if there is no static one?? the mini one does... !! and what the hell does canonicalize mean??
}
if (*nSymbols == 0)
fprintf(stderr, "No symbols in %s\n", bfd_get_filename(abfd));
return symbol_table;
} // slurp_symtab
// Look for an extern symbol with the given name.
// XXX should sort the symbol table alphabetically on startup for lookup... but will that mess up bfd_find_nearest_line?
// XXX also, in this program we only ever need to look up stuff
// XXX from the dynamic table,
// XXX which is much smaller than the static one...
// XXX so should be using that instead... maybe.
// XXX on the other hand, this is called VERY infrequently,
// XXX only once per DSO... so there's really no point in optimizing.
static bool getExternTextSymbolValue(const char *desiredName,
bfd *abfd,
asymbol **symbolTable,
int nSymbols,
int symbolSize,
bool isMini,
bool isDynamic,
asymbol *scratchSymbol,
symvalue *value,
int /*verbose*/)
{
bfd_byte *from = (bfd_byte *)symbolTable;
bfd_byte *fromend = from + nSymbols * symbolSize;
int index = 0;
assert(isMini);
assert(symbolSize != 0);
bool desiredName_is_mangled = (strchr(desiredName, '(') != NULL);
for (; from < fromend; from += symbolSize, index++)
{
asymbol *sym = bfd_minisymbol_to_symbol(abfd, isDynamic, from, scratchSymbol);
if (sym == NULL)
{
fprintf(stderr, "Error (bfd_minisymbol_to_symbol) in %s: ", bfd_get_filename(abfd));
bfd_perror(NULL);
assert(false); // XXX
}
// XXX symbol name can be got from either bfd_asymbol_name, or bfd_get_symbol_info(&syminfo), syminfo.name-- what's the difference?
symbol_info syminfo;
bfd_get_symbol_info(abfd, sym, &syminfo);
if (syminfo.type == 'T' ||
syminfo.type == 'W')
{
char *demangledName = NULL;
if (desiredName_is_mangled)
{
// Use what c++filt uses...
int demangle_flags = DMGL_PARAMS | DMGL_ANSI | DMGL_VERBOSE;
demangledName = cplus_demangle(syminfo.name, demangle_flags);
//fprintf(stderr, " demangled \"%s\" to \"%s\"\n", syminfo.name, demangledName);
}
const char *nameToCompare = (demangledName != NULL ? demangledName : syminfo.name);
bool matched = streq(nameToCompare, desiredName);
if (demangledName != NULL)
free(demangledName);
if (matched)
{
*value = syminfo.value;
return true;
}
}
}
return false;
} // getExternTextSymbolValue
//
// Wrapper struct for bfd: contains a bfd, a symbol table
// and a relocation offset.
//
struct BfdWrapper
{
bfd *abfd;
asection *textSection;
asymbol **symbolTable;
int nSymbols;
int symbolSize;
bool isMini;
bool isDynamic;
asymbol *scratchSymbol;
int64_t relocationOffset; // deduced later
BfdWrapper(bfd *abfd,
asection *textSection,
asymbol **symbolTable,
int nSymbols,
int symbolSize,
bool isMini,
bool isDynamic,
asymbol *scratchSymbol,
int64_t relocationOffset)
: abfd(abfd),
textSection(textSection),
symbolTable(symbolTable),
nSymbols(nSymbols),
symbolSize(symbolSize),
isMini(isMini),
isDynamic(isDynamic),
scratchSymbol(scratchSymbol),
relocationOffset(relocationOffset)
{}
~BfdWrapper()
{}
static BfdWrapper open(const char *dsoName,
int verbose)
{
bfd *abfd = bfd_openr(dsoName, NULL);
if (abfd == NULL)
{
if (verbose >= 2)
{
fprintf(stderr, "Can't open file \"%s\": ", dsoName);
bfd_perror(NULL);
}
return BfdWrapper(NULL,NULL,NULL,0,0,false,false,NULL,-1); // fail
}
if (!bfd_check_format(abfd, bfd_object))
{
if (verbose >= 2)
{
fprintf(stderr, "Can't open file \"%s\": ", bfd_get_filename(abfd));
bfd_perror(NULL);
}
bfd_close(abfd);
return BfdWrapper(NULL,NULL,NULL,0,0,false,false,NULL,-1); // fail
}
if (false)
{
for (struct bfd_section *section = abfd->sections;
section != NULL;
section = section->next)
{
fprintf(stderr, " Got a section in %s: %s\n", bfd_get_filename(abfd), section->name);
}
}
asection *textSection = bfd_get_section_by_name(abfd, ".text");
if (textSection == NULL)
{
fprintf(stderr, "Can't find .text section in %s\n", bfd_get_filename(abfd));
bfd_close(abfd);
return BfdWrapper(NULL,NULL,NULL,0,0,false,false,NULL,-1); // fail
}
bool useMini = true; // XXX experiment with this-- needs to be true to find dynamic symbols if there are no static ones
long nSymbols = 0;
unsigned int symbolSize = 0;
bool isDynamic = false;
asymbol **symbolTable = slurp_symtab(abfd, useMini, &nSymbols, &symbolSize, &isDynamic);
if (verbose >= 2)
{
fprintf(stderr, "%s%s symbol table: %ld symbols, %u bytes each\n",
(abfd->flags&EXEC_P)!=0 ? "[executable] " : "",
isDynamic ? "dynamic" : "static", nSymbols, symbolSize);
}
if ((bfd_get_section_flags(abfd, textSection) & SEC_ALLOC) == 0)
{
fprintf(stderr, "SEC_ALLOC flag not set on .text section (whatever that means) in %s\n", bfd_get_filename(abfd));
free(symbolTable);
bfd_close(abfd);
return BfdWrapper(NULL,NULL,NULL,0,0,false,false,NULL,-1); // fail
}
//
// Apparently, libbfd doles these out from pools
// of 4064 bytes each, mallocing a new pool when the previous
// one is exhausted, and freeing them all when
// the bfd is closed.
// So even if the app allocates a zillion of them,
// it won't show up as a memory leak
// in tools like valgrind,
// but we don't want memory to grow without bound
// so we allocate one here and reuse it as necessary.
//
asymbol *scratchSymbol = bfd_make_empty_symbol(abfd);
if (scratchSymbol == NULL)
{
fprintf(stderr, "Error (bfd_make_empty_symbof) in %s: ", bfd_get_filename(abfd));
bfd_perror(NULL);
free(symbolTable);
bfd_close(abfd);
return BfdWrapper(NULL,NULL,NULL,0,0,false,false,NULL,-1); // fail
}
return BfdWrapper(abfd, textSection, symbolTable, nSymbols, symbolSize, useMini, isDynamic, scratchSymbol,-1);
} // static BfdWrapper BfdWrapper::open
void close()
{
if (symbolTable != NULL)
{
free(symbolTable);
symbolTable = NULL;
}
if (abfd != NULL)
{
bfd_close(abfd);
abfd = NULL;
}
// don't worry about the other stuff
} // void BfdWrapper::close
}; // struct BfdWrapper
typedef std::map<std::string, BfdWrapper> BfdWrappers;
/*
Example:
./libp2lite.so [0x2b54b4]
./libp2lite.so(malloc+0x1a6) [0x2b6e78]
/usr/lib/libbfd-2.15.92.0.2.so(bfd_malloc+0x1f) [0xa0d970]
/usr/lib/libbfd-2.15.92.0.2.so(_bfd_generic_read_minisymbols+0x52) [0xa11612]
./backtracefilt(__gxx_personality_v0+0x14d) [0x804882d]
./backtracefilt(__gxx_personality_v0+0x404) [0x8048ae4]
./backtracefilt [0x8048e19]
/lib/tls/libc.so.6(__libc_start_main+0xd3) [0x8b4e23]
./backtracefilt(__gxx_personality_v0+0x41) [0x8048721]
[0x8048e19]
Idiosyncracies:
- notice how the symbol names given by backtrace_symbols()
for the DSOs are correct, but for the main executable,
they are completely wrong. We work around this bug
by just assuming the main executable never gets relocated
(I doubt it ever does) and ignoring the symbol names
in that case.
- notice sometimes there's a symbol name in parens, sometimes not
- notice sometimes dsoName is empty
- notice sometimes it has a +offset, sometimes not
(and from looking at the source code,
it can be -offset)
We assume there are no spaces in the DSO name or the symbol
or between stuff, other than what is shown above.
*/
// Completely icky line parsing...
struct ParsedTraceLine
{
std::string originalLine;
std::string prefix;
std::string dsoName;
std::string runtimeSym;
int64_t runtimeOff;
int64_t runtimeAddr;
ParsedTraceLine(const char *line,
char *lineBuf) // scratch
: originalLine(""),
prefix(""),
dsoName(""),
runtimeSym(""),
runtimeOff(0),
runtimeAddr(0)
{
//fprintf(stderr, "ParsedTraceLine ctor from input line\n");
this->originalLine.assign(line);
//
// Perl expression is:
// / ([^ \t\n\(\):]*)(\((.+)([-+][0-9a-fx]+)?\))? \[([0-9a-fx]+)\]\s*$/
// ^dsoName ^runtimeSym
// ^runtimeOff ^runtimeAddr
// So we scan from the end.
// Oh, and by the way, if there are any parens in runtimeOff,
// they must match (unfortunately matching parens can't be expressed
// in a regular expression).
strcpy(lineBuf, line);
char *s = lineBuf + strlen(lineBuf);
while (s > lineBuf && isspace(s[-1]))
s--;
if (!(s > lineBuf && *--s == ']'))
return; // fail
char *runtimeAddrEnd = s; // not const
while (s > lineBuf && strchr("x0123456789abcdef", s[-1]) != NULL)
s--;
const char *runtimeAddrStart = s;
if (!(s > lineBuf && *--s == '['))
return; // fail
if (!(s > lineBuf && *--s == ' '))
return; // fail
char *runtimeOffEnd = s, *runtimeSymEnd = s; // not const
const char *runtimeOffStart = s, *runtimeSymStart = s;
if (s > lineBuf && s[-1] == ')')
{
s--;
runtimeSymEnd = s;
while (s > lineBuf && strchr("x0123456789abcdef", s[-1]) != NULL)
s--;
if (s > lineBuf && strchr("+-", s[-1]) != NULL)
{
s--;
runtimeOffEnd = runtimeSymEnd;
runtimeOffStart = s;
runtimeSymEnd = s;
}
int level = 0;
while (s > lineBuf && !(level == 0 && s[-1] == '('))
{
if (s[-1] == ')')
level++;
else if (s[-1] == '(')
--level;
else if (s[-1] == '-' || s[-1] == '+')
return; // fail
s--;
}
runtimeSymStart = s;
if (!(s > lineBuf && *--s == '('))
return; // fail
}
const char *dsoNameEnd = s;
while (s > lineBuf && !isspace(s[-1]))
{
if (strchr(":()", s[-1]) != NULL)
return; // fail
s--;
}
const char *dsoNameStart = s;
const char *prefixEnd = s;
const char *prefixStart = lineBuf;
*runtimeAddrEnd = '\0';
char *end;
int64_t runtimeAddr = (int64_t)strtoll(runtimeAddrStart, &end, 16); // always base 16 regardless of whether there's an 0x
if (*end != '\0')
return; // fail
*runtimeOffEnd = '\0';
int64_t runtimeOff = (int64_t)strtoll(runtimeOffStart, &end, 0); // any base
if (*end != '\0')
return; // fail
this->prefix.assign(prefixStart, prefixEnd-prefixStart);
this->dsoName.assign(dsoNameStart, dsoNameEnd-dsoNameStart);
this->runtimeSym.assign(runtimeSymStart, runtimeSymEnd-runtimeSymStart);
this->runtimeOff = runtimeOff;
this->runtimeAddr = runtimeAddr;
return; // success, since we set runtimeAddr. actually " [0]" is indistinguishable from failure, but that's okay
} // parse ctor
ParsedTraceLine(const ParsedTraceLine &that)
: originalLine(that.originalLine),
prefix(that.prefix),
dsoName(that.dsoName),
runtimeSym(that.runtimeSym),
runtimeOff(that.runtimeOff),
runtimeAddr(that.runtimeAddr)
{
//fprintf(stderr, "ParsedTraceLine copy ctor\n");
}
~ParsedTraceLine()
{
//fprintf(stderr, "ParsedTraceLine dtor\n");
}
}; // parsedTraceLine
int main(int argc, char **argv)
{
// -v 0: only stack trace (to stdout)
// -v 1: and tell when downloading symbols (to stderr),
// but only if stderr is a terminal (to prevent
// collisions with a calling process who might also
// be sending its stderr to the same file)
// -v 2: and tell when downloading symbols (to stderr)
// -v 3: and tell when something fails (to stderr),
// -v 4: and heavy debug (to stderr)
//
int verbose = 0;
if (argc == 3 && streq(argv[1], "-v"))
{
verbose = atoi(argv[2]);
}
else if (argc != 1)
{
fprintf(stderr, "Usage: %s [-v <verbosity level>]\n", argv[0]);
return 1; // fail
}
// verbosity 1 acts like 0 or 2, depending...
// XXX should allow it when it's a fifo, not just when it's a tty
if (verbose == 1)
verbose = (isatty(fileno(stderr)) ? 2 : 0);
setlinebuf(stdout);
setlinebuf(stderr);
// Table of currently opened DSOs...
BfdWrappers bfdWrappers;
//
// Filter one or more stack traces produced by
// backtrace_symbols(), from stdin.
//
// Delay processing each line until either:
// - We read a line that has a symbol name
// and that refers to the same DSO; that allows us
// to get the relocation offset right for this DSO.
// - We read a non-stacktrace line from stdin. This is considered
// to be an indicator from the caller to flush,
// regardless of whether we think we know what we're
// doing.
//
// Scratch buffers...
int linebufsize = 16*1024;
char *linebuf = (char *)malloc(linebufsize);
assert(linebuf != NULL);
char *scratchBuf = (char *)malloc(linebufsize);
assert(scratchBuf != NULL);
std::queue<ParsedTraceLine> delayQueue;
char *line;
do
{
line = fgets(linebuf, linebufsize, stdin);
bool flushing;
if (line != NULL)
{
char *end = strchr(line, '\n');
if (end == NULL)
{
fprintf(stderr, "Input line is ridiculously long\n");
return 1; // failure XXX should clean up
}
*end = '\0';
// Parse the line
ParsedTraceLine parsedTraceLine(line,
scratchBuf);
if (verbose >= 4)
{
fprintf(stderr, "Orig line = \"%s\"\n", parsedTraceLine.originalLine.c_str());
fprintf(stderr, " prefix = \"%s\"\n", parsedTraceLine.prefix.c_str());
fprintf(stderr, " dsoName = \"%s\"\n", parsedTraceLine.dsoName.c_str());
fprintf(stderr, " runtimeSym = \"%s\"\n", parsedTraceLine.runtimeSym.c_str());
fprintf(stderr, " runtimeOff = %#llx\n", parsedTraceLine.runtimeOff);
fprintf(stderr, " runtimeAddr = %#llx\n", parsedTraceLine.runtimeAddr);
}
if (parsedTraceLine.dsoName[0] != '\0')
{
BfdWrappers::iterator it = bfdWrappers.find(parsedTraceLine.dsoName);
if (it == bfdWrappers.end())
{
//
// First time this dsoName has been seen.
//
std::string pathName = parsedTraceLine.dsoName;
if (strchr(pathName.c_str(), '/') == NULL)
{
//
// No slashes means find it in $PATH.
// (I think this is only an issue for
// the main executable; DSOs always
// get slashes in 'em).
//
const char *PATH = getenv("PATH");
assert(PATH != NULL); // what lunatic doesn't have PATH set
char buf[PATH_MAX+2];
const char *dir = PATH;
while (1)
{
const char *dirend = dir;
while (*dirend != '\0' && *dirend != ':')
dirend++;
if (dirend != dir)
{
if (snprintf(buf, sizeof(buf), "%.*s/%s", (int)(dirend-dir), dir, pathName.c_str()) >= (int)sizeof(buf)-1)
{
assert(false);
}
if (verbose >= 4)
{
fprintf(stderr, " trying %s... ", buf);
fflush(stderr);
}
if (access(buf, F_OK) != -1)
{
if (verbose >= 4)
fprintf(stderr, "yup.\n");
break;
}
else
{
if (verbose >= 4)
fprintf(stderr, "nope.\n");
}
}
if (*dirend == '\0')
{
buf[0] = '\0';
break;
}
dir = dirend+1;
}
if (buf[0] != '\0')
pathName = buf;
else
{
if (verbose >= 3)
fprintf(stderr, "FOOEY: can't find %s in your $PATH\n", pathName.c_str());
// leave pathName the supposed name of the
// executable, even though we will probably
// fail below.
// XXX this isn't really right... if .
// XXX isn't in $PATH, shouldn't do this
}
}
if (verbose >= 2)
{
fprintf(stderr, "Reading symbols from %s... ", pathName.c_str());
fflush(stderr);
}
BfdWrapper bfdWrapper = BfdWrapper::open(pathName.c_str(), verbose);
if (bfdWrapper.abfd != NULL
&& (bfdWrapper.abfd->flags & EXEC_P) != 0)
{
//
// If it's an executable,
// don't even try to do the relocation thing;
// the symbols given by backtrace_symbols()
// in this case are totally bogus
// and the dynamic relocation offset
// is surely zero anyway.
//
if (verbose >= 4)
fprintf(stderr, "%s is executable; not even trying to figure out a relocation offset\n", pathName.c_str());
bfdWrapper.relocationOffset = 0;
}
// insert it, even if it failed, so we know
// not to try again
// XXX compiler doesn't like the following??
//it = bfdWrappers.insert(std::pair<std::string,BfdWrapper>(parsedTraceLine.dsoName,bfdWrapper)).second;
// XXX so do it the lame way instead
(void)bfdWrappers.insert(std::pair<std::string,BfdWrapper>(parsedTraceLine.dsoName,bfdWrapper)).second;
it = bfdWrappers.find(parsedTraceLine.dsoName);
}
else
{
if (verbose >= 4)
fprintf(stderr, "Already read symbols from %s.\n", parsedTraceLine.dsoName.c_str());
}
BfdWrapper &bfdWrapper = it->second;
if (bfdWrapper.abfd != NULL
&& bfdWrapper.relocationOffset == -1
&& parsedTraceLine.runtimeSym[0] != '\0')
{
if (verbose >= 4)
fprintf(stderr, "HEY! I now can figure out the relocation offset for someone!!!\n");
int64_t relocatedSymbolAddress = parsedTraceLine.runtimeAddr - parsedTraceLine.runtimeOff;
symvalue symValue;
if (getExternTextSymbolValue(parsedTraceLine.runtimeSym.c_str(),
bfdWrapper.abfd,
bfdWrapper.symbolTable,
bfdWrapper.nSymbols,
bfdWrapper.symbolSize,
bfdWrapper.isMini,
bfdWrapper.isDynamic,
bfdWrapper.scratchSymbol,
&symValue,
verbose))
{
int64_t unrelocatedSymbolAddress = symValue;
bfdWrapper.relocationOffset = relocatedSymbolAddress - unrelocatedSymbolAddress;
if (verbose >= 4)
fprintf(stderr, " YEAH!\n");
}
else
{
// Disable this BFD
if (verbose >= 3)
fprintf(stderr, " DAMN, failed to figure out relocation offset using symbol %s, closing %s :-(\n", parsedTraceLine.runtimeSym.c_str(), parsedTraceLine.dsoName.c_str());
bfdWrapper.close();
assert(bfdWrapper.abfd == NULL);
// So now items from this DSO won't
// get delayed any more
}
}
}
delayQueue.push(parsedTraceLine);
//flushing = isBlank(line);
flushing = (parsedTraceLine.dsoName[0] == '\0');
}
else
flushing = true; // flush on EOF
if (verbose >= 4)
fprintf(stderr, "queue size = %d\n", delayQueue.size());
while (!delayQueue.empty())
{
ParsedTraceLine &front = delayQueue.front();
if (verbose >= 4)
fprintf(stderr, "dsoName on front of queue is %s\n", front.dsoName.c_str());
if (front.dsoName[0] == '\0')
{
// It's not a stack trace line
printf("%s\n", front.originalLine.c_str());
delayQueue.pop();
continue;
}
BfdWrappers::const_iterator it = bfdWrappers.find(front.dsoName);
assert(it != bfdWrappers.end());
if (it->second.abfd == NULL)
{
// The dso open failed
printf("%s\n", front.originalLine.c_str());
delayQueue.pop();
continue;
}
if (it->second.relocationOffset == -1)
{
// Not ready to process it yet
if (verbose >= 4)
fprintf(stderr, "Oh, not ready to process it yet!\n");
if (flushing)
{
if (verbose >= 4)
fprintf(stderr, " But processing it anyway :-(\n");
// XXX probably would like to give it a shot anyway,
// XXX since many DSOs get placed where they prefer...
// XXX but the code below isn't smart enough yet, so...
if (true)
{
printf("%s\n", front.originalLine.c_str());
delayQueue.pop();
continue;
}
}
else
{
break;
}
}
//
// Process it and pop it
//
{
const BfdWrapper &bfdWrapper = it->second;
int64_t relocatedAddr = front.runtimeAddr;
int64_t relocationOffset = bfdWrapper.relocationOffset;
int64_t unrelocatedAddr = relocatedAddr;
if (relocationOffset != -1)
unrelocatedAddr -= relocationOffset;
const char *funcName = NULL;
const char *fileName = NULL;
unsigned int lineno = 0;
if (verbose >= 4)
fprintf(stderr, "Looking up %#llx...\n", unrelocatedAddr);
bfd_vma textSection_vma = bfd_get_section_vma(bfdWrapper.abfd, bfdWrapper.textSection);
// XXX in bfd.h from binutils-2.15.90.0.1.1-31, bfd_get_section_size() doesn't exist but bfd_section_size() does
//bfd_size_type textSection_size = bfd_get_section_size(bfdWrapper.textSection);
bfd_size_type textSection_size = bfd_section_size(bfdWrapper.abfd, bfdWrapper.textSection);
if (!INRANGE((int64_t)textSection_vma <=, unrelocatedAddr, <= (int64_t)(textSection_vma+textSection_size)))
{
fprintf(stderr, "UH OH, trying to look up an address that's outside of the range of the text section of %s... usually this means the executable or DSO in question has changed since the stack trace was generated\n", front.dsoName.c_str());
// XXX hmm, could do a clever time check on fstat(0)... it won't always succeed, but if it does we can compare against the time of the DSO and confirm the staleness hypothesis
fprintf(stderr, " dso = %s\n", front.dsoName.c_str());
fprintf(stderr, " textSection_vma = %#llx\n", (ull)textSection_vma);
fprintf(stderr, " textSection_size = %#llx\n", (ull)textSection_size);
fprintf(stderr, " textSection_vma+textSection_size = %#llx\n", (ull)textSection_vma+textSection_size);
fprintf(stderr, " relocationOffset = %#llx\n", (ull)relocationOffset);
fprintf(stderr, " unrelocatedAddr = %#llx\n", (ull)unrelocatedAddr);
printf("%s\n", front.originalLine.c_str());
delayQueue.pop();
continue;
}
assert(INRANGE((int64_t)textSection_vma <=, unrelocatedAddr, <= (int64_t)(textSection_vma+textSection_size)));
// XXX bfd_find_nearest_line leaks memory,
// XXX in binutils-2.15.92.0.2-13
// XXX I see some discussion about it, March 2005,
// XXX maybe there's a more recent version where that is fixed...
// XXX oh well.
bool suppressCallingFindNearestLine = false; // can manually set this to true to experiment-- the only real leak that occurs in this program is inside bfd_find_nearest_line, I believe
if (suppressCallingFindNearestLine
|| !bfd_find_nearest_line(bfdWrapper.abfd, bfdWrapper.textSection, bfdWrapper.symbolTable,
unrelocatedAddr - textSection_vma,
&fileName, &funcName, &lineno))
{
if (verbose >= 3)
fprintf(stderr, "Can't find line for address %#llx <- %#llx \n",
(unsigned long long)relocatedAddr,
(unsigned long long)unrelocatedAddr);
printf("%s\n", front.originalLine.c_str());
}
else
{
bool useAnnotation = false; // XXX should do this if verbose, or something... or another command line option
if (useAnnotation)
{
if (fileName != NULL)
printf("%s [[[ %s() %s:%d ]]]\n",
front.originalLine.c_str(),
funcName,
fileName,
lineno);
else
{
// This is still sometimes helpful...
// e.g. from perl [0x8049131]
// we get perl [0x8049131] [[[_start()]]].
// Even more useful would be to print something like
// <_start+0x21>
// (can get that manually from
// objdump -d --prefix-addresses /usr/bin/perl | grep 8049131 )
// And maybe print the disassembly too? Hmm...
// that would require calling the disassembler,
// like objdump -d does... nah, too much work.
printf("%s [[[ %s() ]]]\n",
front.originalLine.c_str(),
funcName);
}
}
else
{
if (fileName != NULL)
{
const char *fileNameNoDir = strrchr(fileName, '/');
if (fileNameNoDir != NULL)
fileNameNoDir++;
else
fileNameNoDir = fileName;
printf("%s%s %s:%d\n",
front.prefix.c_str(),
funcName,
fileNameNoDir,
lineno);
}
else
{
// XXX might we be throwing away information in the function name here? I've only seen it be the same as what's in the line already... no that's not true... remember the main executable often gets it totally wrong, and misses names of static functions.
printf("%s %s\n",
front.originalLine.c_str(),
funcName);
}
}
}
}
delayQueue.pop();
}
} while (line != NULL);
free(linebuf);
free(scratchBuf);
for (BfdWrappers::iterator it = bfdWrappers.begin();
it != bfdWrappers.end();
it++)
{
BfdWrapper &bfdWrapper = it->second;
bfdWrapper.close();
}
return 0;
} // main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment