From: <sv...@ww...> - 2004-06-27 02:57:53
|
Author: mkrose Date: 2004-06-26 19:57:47 -0700 (Sat, 26 Jun 2004) New Revision: 1068 Added: trunk/CSP/tools/trace Log: Add stack trace decoding tool for linux. Browse at: https://www.zerobar.net/viewcvs/viewcvs.cgi?view=rev&rev=1068 Added: trunk/CSP/tools/trace =================================================================== --- trunk/CSP/tools/trace 2004-06-27 01:04:57 UTC (rev 1067) +++ trunk/CSP/tools/trace 2004-06-27 02:57:47 UTC (rev 1068) @@ -0,0 +1,258 @@ +#!/usr/bin/python +# +# Copyright 2004 Mark Rose <mk...@us...> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +#!/usr/bin/python + +""" +Decode SimData stack traces under GNU/Linux. + +Reads a stack trace from stdin, and dumps a more readable form of +the stack trace to stdout. The easiest way to use this program is +to select the stack trace using the mouse, run %(prog)s, and then +paste the stack trace into the terminal window (you'll also need +to type <ctrl-d> to close stdin). + +Caveats: + +This program relies on GNU-specific tools, both to decode binary +addresses into source files and line numbers, and to demangle C++ +symbol names. When SimData supports stack trace generation under +Windows, a comparable mechanism for decoding them will be devised. + +Since the stack trace includes relative paths to shared libraries, +this program must be run from the same directory as the program +that generated the stack trace. Alternatively, you can specify the +'--dir' flag to set the base directory. Either way, the program +and all shared libaries must be available in order to properly +decode the stack trace. + +Usage: %(prog)s [options] +""" + +import sys +import os +import re +import os.path + +import bootstrap + +from CSP.base import app + + +re_trace1 = re.compile(r'\s+([^(]+)[(]([A-Z_0-9a-z]+)\+0x([0-9a-f]+)[)]\s+\[0x([0-9a-f]+)\]') +re_trace2 = re.compile(r'\s*(\S+)\s+\[0x([0-9a-f]+)\]') + + +def filt(mangled): + """Demangle a C++ symbol""" + return os.popen('c++filt %s' % mangled).readline().strip() + +def addr2line(bin, offset): + """ + Translate a object file offset into a source file name and line number. + + The object file must have been compiled with debugging symbols (and not + subsequently stripped). + """ + return os.popen('addr2line -e %s %x' % (bin, offset)).readline().strip() + +def fixpath(dir, path): + """ + Translate a path relative to the specified directory. Absolute paths are + unchanged. + """ + if not os.path.isabs(path): + path = os.path.join(dir, path) + path = os.path.normpath(path) + return path + + +class Symbols: + """ + Object file symbol table reader and cache. + """ + + class SymbolSet: + """ + An index for the symbol table of a single object file. + """ + + def __init__(self): + self._table = {} + self._index = [] + + def add(self, method, addr): + """Add a new symbol to the index.""" + self._table[method] = addr + self._index.append((addr, method)) + + def search(self, addr): + """Search for the symbol at, or immediately preceding, address.""" + index = self._index + if not index: return '' + A = 0 + B = len(index) + n = B / 2 + while B - A > 1: + at = index[n][0] + if at > addr: + B = n + else: + A = n + n = (A + B) / 2 + if n == len(index): return '' + return index[n][1] + + def get(self, symbol, default=0): + """Return the address of a symbol, or default.""" + return self._table.get(symbol, default) + + def __init__(self): + self._syms = {} + + def getsym(self, lib): + """ + Get the SymbolSet for the specified object file. SymbolSets are cached, + so subsequent calls for a given object file are cheap. + """ + syms = self._syms + sym = syms.get(lib, None) + if sym: return sym + symset = Symbols.SymbolSet() + cin, cout, cerr = os.popen3('nm -n %s' % lib) + for line in cout.readlines(): + parts = line.strip().split() + if len(parts) == 3: + addr, type, method = line.strip().split() + addr = int(addr, 16) + symset.add(method, addr) + syms[lib] = symset + return symset + + +class StackTrace: + + def __init__(self): + self._symbols = Symbols() + self._offsets = {} + + def _decode1(self, dir, line, parts): + """ + Decode stack trace lines that match re_trace1. + """ + lib = fixpath(dir, parts[0]) + if not os.path.exists(lib): + print >>sys.stderr, 'unable to find object file (%s); may need to specify --dir' % lib + sys.exit(1) + method = parts[1] + offset = int(parts[2], 16) + image = int(parts[3], 16) + real_name = filt(method) + symset = self._symbols.getsym(lib) + method_address = symset.get(method, default=0) + if method_address: + address = method_address + offset + self._offsets[lib] = address - image + point = addr2line(lib, address) + return '%s (%s)' % (real_name, point) + else: + symbol = symset.search(image) + if symbol: real_name = filt(symbol) + point = addr2line(lib, image) + if point.find('?') < 0: + return '%s (%s)' % (real_name, point) + return '%s (%s+0x%x) [0x%x]' % (os.path.basename(lib), real_name, offset, image) + + def _decode2(self, dir, line, parts): + """ + Decode stack trace lines that match re_trace2. + """ + lib, addr = parts + lib = fixpath(dir, lib) + if not os.path.exists(lib): + print >>sys.stderr, 'unable to find object file (%s); may need to specify --dir' % lib + sys.exit(1) + addr = int(addr, 16) + offset = self._offsets.get(lib, 0) + symset = self._symbols.getsym(lib) + symbol = symset.search(addr + offset) + if symbol: + point = addr2line(lib, addr) + if point.find('?') < 0: + return '%s (%s)' % (filt(symbol), point) + else: + return '%s (%s)' % (filt(symbol), os.path.basename(lib)) + return '%s [0x%x]' % (os.path.basename(lib), addr) + + def process(self, input, dir): + """ + Process input lines and print the decoded stack trace to stdout. + """ + start = 0 + trace = [] + output = [] + for line in input: + line = line.rstrip() + if not line: continue + + if line.find('CALL STACK BACKTRACE') >= 0: + start = 1 + continue + + m = re_trace1.match(line) + if m: + start = 1 + output.append(self._decode1(dir, line, m.groups())) + continue + + m = re_trace2.match(line) + if m: + start = 1 + output.append(self._decode2(dir, line, m.groups())) + continue + + if start and line.startswith(' '): + parts = line.split() + if len(parts): + parts[0] = os.path.basename(parts[0]) + output.append(' '.join(parts)) + + print '=============' + for idx, line in enumerate(output): + print '%2d. %s' % (idx+1, line) + + +def main(args): + if args: + app.usage() + return 1 + + try: + input = sys.stdin.readlines() + except KeyboardInterrupt: + return 0 + + dir = app.options.dir + stack_trace = StackTrace() + stack_trace.process(input, dir) + return 0 + + +app.addOption('-d', '--dir', default='.', help='specify the starting directory of the program that generated the stack trace') +app.start() + + Property changes on: trunk/CSP/tools/trace ___________________________________________________________________ Name: svn:executable + * |