dotrace/dotrace_decode.py

121 lines
4.0 KiB
Python
Executable File

#! /usr/bin/env python3
""" A python script used to decode the addresses in traces generated by
the dotrace instrumentation.
Requires addr2line to be installed. The script works by collecting a
given number of address from the trace file and then calling addr2line
with such a block of addresses.
"""
import os
import subprocess
import collections
import sys
from itertools import chain
import argparse
def decode(bin, traces):
"""Decodes a list of address using addr2line.
Args:
bin: The executable used for retrieving the debug symbols.
traces: A list of lists of the form [c, a, b] where a and
b are addresses as string.
Returns:
Generates lists [c, call targed name, call site name,
call site source location]
"""
if len(traces) == 0:
return []
addrs = chain.from_iterable([x[1:] for x in traces])
cmd = ["addr2line", "-f", "-s", "-e", bin] + list(addrs)
decoded = subprocess.run(cmd, stdout=subprocess.PIPE).stdout
decoded = collections.deque(decoded.split(b'\n'))
for line in traces:
target_name = decoded.popleft()
target_line = decoded.popleft()
site_name = decoded.popleft()
site_line = decoded.popleft()
yield [line[0],target_name,site_name,site_line]
def processBlock(args, traces, out_file):
"""Decodes a block of trace lines.
Args:
args: The program arguments.
traces: A list of lists of the form [c, a, b] where a and
b are addresses as string and c is eiter 'e' or 'x'
indicating if the line is a function entry or exit.
out_file: The output file to write into. In binary mode.
"""
decoded = decode(args.binary, traces)
for decoded_line in decoded:
if decoded_line[0] == 'e':
out_file.write(b"Enter ")
out_file.write(decoded_line[1])
if not args.suppress_callsite:
out_file.write(b" from ")
out_file.write(decoded_line[2])
out_file.write(b" (")
out_file.write(decoded_line[3])
out_file.write(b")")
out_file.write(b"\n")
if decoded_line[0] == 'x' and (not args.suppress_exit):
out_file.write(b"Exit ")
out_file.write(decoded_line[1])
out_file.write(b"\n")
# Parser configuration
parser = argparse.ArgumentParser(description='Decode a trace file generated by dotrace.')
parser.add_argument('-b', '--binary', help='The binary file.',
required=True, metavar="FILE")
parser.add_argument('-t', '--trace', help="The trace file (default: '%(default)s').",
default="trace.out", metavar="FILE")
parser.add_argument('-o', '--output', help="The output file (default: '%(default)s').",
default="trace.decoded", metavar="FILE")
parser.add_argument('-e', '--suppress-exit',
help='Do not output function exits.', action='store_true')
parser.add_argument('-c', '--suppress-callsite',
help='Do not output callsite of function calls.', action='store_true')
parser.add_argument('-s', '--block-size', type=int, default=10000,
help='The number of addresses forwarded to addr2line (default: %(default)s).')
if __name__ == "__main__":
args = parser.parse_args()
# Check if addr2line is installed
DEVNULL = open(os.devnull, 'w')
try:
subprocess.call(["addr2line", "--version"],
stdout=DEVNULL, stderr=subprocess.STDOUT)
except FileNotFoundError as e:
print("Error: addr2line not installed.", file=sys.stderr)
sys.exit(1)
# Check if the files referenced by the options exist
for f in [args.binary, args.trace]:
if not os.path.exists(f):
print("Error: File {} does not exist.".format(f) ,file=sys.stderr)
sys.exit(1)
lines_to_read = args.block_size / 2
accumulator = []
i = 0
total = 1
with open(args.trace, 'r') as trace_file:
with open(args.output, 'bw') as out_file:
# read lines_to_read lines from the file, each line constains two
# addresses
for line in trace_file:
i = i + 1
accumulator.append(line.split())
if i >= lines_to_read:
print("Processing lines {}-{}.".format(total, total + i - 1))
processBlock(args, accumulator, out_file)
accumulator.clear()
total = total + i
i = 0
print("Processing lines {}-{} [final block].".format(total, total + i - 1))
processBlock(args, accumulator, out_file)