Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
#!/usr/bin/env python3
# Copyright 2021 ETH Zurich and University of Bologna.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
# This script parses the traces generated by Snitch and creates an annotated
# trace that includes code sources
# Example output:
# ; snrt_hartid (team.c:14)
# ; in snrt_cluster_core_idx (team.c:47)
# ; in main (event_unit.c:21)
# ; asm("csrr %0, mhartid" : "=r"(hartid));
# 80000048 x13=0000000a # csrr a3, mhartid
#
# If the -d/--diff option is specified, it instead outputs a (fictitious) diff
# file which allows to visualize the trace-source correlation side-by-side
# instead of interleaved.
# For neater visualization, feed the diff file into a diff visualization tool e.g.:
# kompare -o <diff_file>
import sys
import os
import re
from functools import lru_cache
import argparse
from termcolor import colored
# Argument parsing
parser = argparse.ArgumentParser('annotate', allow_abbrev=True)
parser.add_argument(
'elf',
metavar='<elf>',
help='The binary executed to generate the annotation',
)
parser.add_argument(
'trace',
metavar='<trace>',
help='The trace file to annotate')
parser.add_argument(
'-o',
'--output',
metavar='<annotated>',
nargs='?',
default='annotated.s',
help='Output annotated trace')
parser.add_argument(
'--addr2line',
metavar='<path>',
nargs='?',
default='llvm-addr2line',
help='`addr2line` binary to use for parsing')
parser.add_argument(
'-d',
'--diff',
action='store_true',
default=False,
help='When true outputs a diff file instead of the annotated trace')
parser.add_argument(
'-s',
'--start',
metavar='<line>',
nargs='?',
type=int,
default=0,
help='First line to parse, zero-based and inclusive')
parser.add_argument(
'-e',
'--end',
metavar='<line>',
nargs='?',
type=int,
default=-1,
help='Last line to parse, zero-based and inclusive')
parser.add_argument(
'--keep-time',
action='store_true',
help='Preserve simulation time in trace')
parser.add_argument(
'-q',
'--quiet',
action='store_true',
help='Quiet output')
args = parser.parse_args()
elf = args.elf
trace = args.trace
output = args.output
diff = args.diff
addr2line = args.addr2line
quiet = args.quiet
keep_time = args.keep_time
if not quiet:
print('elf:', elf, file=sys.stderr)
print('trace:', trace, file=sys.stderr)
print('output:', output, file=sys.stderr)
print('diff:', diff, file=sys.stderr)
print('addr2line:', addr2line, file=sys.stderr)
print('keep_time:', keep_time, file=sys.stderr)
of = open(output, 'w')
if not quiet:
print(f' annotating: {output} ', end='')
# buffer source files
src_files = {}
trace_start_col = -1
@lru_cache(maxsize=1024)
def adr2line(addr):
cmd = f'{addr2line} -e {elf} -f -i {addr:x}'
return os.popen(cmd).read().split('\n')
# helper functions to parse addr2line output
def a2l_file_path(a2l_file_str):
return a2l_file_str.split(':')[0]
def a2l_file_name(a2l_file_str):
return a2l_file_str.split('/')[-1].split(':')[0]
def a2l_file_line(a2l_file_str):
return int(a2l_file_str.split(':')[-1].split(' ')[0])
def format_a2l_funcname(a2l_func_name):
if a2l_func_name == '??':
return 'unknown function'
return a2l_func_name
# helper functions to assemble diff output
def format_call(level, call):
funcname = format_a2l_funcname(call[0])
if level == 0:
return f'{funcname} ({call[1]})\n'
else:
indentation = ' ' * (level - 1)
return f'{indentation}{call[4]}: inlined call to {funcname} ({call[1]})\n'
def assemble_call_stack(funs, file_paths, file_lines):
call_stack = list(zip(funs, # func name
file_paths, # func path
file_lines, # func line
[*(file_paths[1:]), '??'], # caller path
[*(file_lines[1:]), 0])) # call line
call_stack.reverse()
return call_stack
def matching_call_stack_levels(cstack1, cstack2):
matching_levels = 0
for i, call in enumerate(cstack1):
# Compare each call: i.e. called function, caller file and call line
if i < len(cstack2) and \
call[0] == cstack2[i][0] and \
call[1] == cstack2[i][1] and \
call[3] == cstack2[i][3] and \
call[4] == cstack2[i][4]:
matching_levels += 1
else:
return matching_levels
return matching_levels
def matching_source_line(cstack1, cstack2):
# Two trace lines match the same source line evaluation
# if the corresponding source line is the same *and* also
# the call stack which led that source line
try:
matched_src_line = cstack1[-1][2] == cstack2[-1][2]
except IndexError:
matched_src_line = False
matched_call_stack = matching_call_stack_levels(cstack1, cstack2) == len(next_call_stack)
return matched_src_line and matched_call_stack
def dump_hunk(hunk_tstart, hunk_sstart, hunk_trace, hunk_source):
hunk_tlen = len(hunk_trace.splitlines())
hunk_slen = len(hunk_source.splitlines())
hunk_header = f'@@ -{hunk_tstart},{hunk_tlen} +{hunk_sstart},{hunk_slen} @@\n'
of.write(f'{hunk_header}{hunk_trace}{hunk_source}')
# core functionality
with open(trace, 'r') as f:
# get modified timestamp of trace to compare with source files
trace_timestamp = os.path.getmtime(trace)
last = ''
if diff:
of.write('--- trace\n+++ source\n')
call_stack = []
hunk_trace = ''
hunk_source = ''
hunk_tstart = 1
hunk_sstart = 1
# Filter lines before args.start
trace_lines = f.readlines()[args.start:]
# Filter lines after args.end (requires special care to make the bound inclusive)
end = args.end + 1
if end == 0:
end = len(trace_lines)
trace_lines = trace_lines[:end]
tot_lines = len(trace_lines)
last_prog = 0
for lino, line in enumerate(trace_lines):
# Split trace line in columns
cols = re.split(r" +", line.strip())
# Get simulation time from first column
time = cols[0]
# RTL traces might not contain a PC on each line
try:
# Get address from PC column
addr_str = cols[3]
addr = int(addr_str, base=16)
# Find index of first character in PC
if trace_start_col < 0:
trace_start_col = line.find(addr_str)
except (ValueError, IndexError):
if keep_time:
filtered_line = f'{time:>12} {line[trace_start_col:]}'
else:
filtered_line = f'{line[trace_start_col:]}'
if diff:
hunk_trace += f'-{filtered_line}'
else:
of.write(f' {filtered_line}')
continue
# Filter trace line for printing
if keep_time:
filtered_line = f'{time:>12} {line[trace_start_col:]}'
else:
filtered_line = f'{line[trace_start_col:]}'
addr_hex = f'{addr:x}'
ret = adr2line(addr)
funs = ret[::2]
file_paths = [a2l_file_path(x) for x in ret[1::2]]
file_names = [a2l_file_name(x) for x in ret[1::2]]
file_lines = [a2l_file_line(x) for x in ret[1::2]]
# Assemble annotation string
if len(funs):
annot = f'#; {funs[0]} ({file_names[0]}:{file_lines[0]})'
for fun, file_name, file_line in zip(funs[1:], file_names[1:], file_lines[1:]):
annot = f'{annot}\n#; in {fun} ({file_name}:{file_line})'
# Get source of last file and print the line
src_fname = file_paths[0]
if src_fname not in src_files.keys():
try:
# Issue warning if source was modified after trace
src_timestamp = os.path.getmtime(src_fname)
if src_timestamp >= trace_timestamp:
print(colored('Warning:', 'yellow'), f'{src_fname} has been edited since the trace was generated')
with open(src_fname, 'r') as src_f:
src_files[src_fname] = [x.strip() for x in src_f.readlines()]
except OSError:
src_files[src_fname] = None
if src_files[src_fname] is not None:
src_line = src_files[src_fname][file_lines[0]-1]
annot = f'{annot}\n#; {src_line}'
# Print diff
if diff:
# Compare current and previous call stacks
next_call_stack = assemble_call_stack(funs, file_paths, file_lines)
matching_cstack_levels = matching_call_stack_levels(next_call_stack, call_stack)
matching_src_line = matching_source_line(next_call_stack, call_stack)
# If this instruction does not map to the same evaluation of the source line
# of the last instruction, we finalize and dump the previous hunk
if hunk_trace and not matching_src_line:
dump_hunk(hunk_tstart, hunk_sstart, hunk_trace, hunk_source)
# Initialize next hunk
hunk_tstart += len(hunk_trace.splitlines())
hunk_sstart += len(hunk_source.splitlines())
hunk_trace = ''
hunk_source = ''
# Update state for next iteration
call_stack = next_call_stack
# Assemble source part of hunk
if len(funs) and src_files[src_fname]:
for i, call in enumerate(call_stack):
if i >= matching_cstack_levels:
hunk_source += f'+{format_call(i, call)}'
if not matching_src_line:
indentation = ' ' * (len(call_stack) - 1)
hunk_source += f'+{indentation}{file_lines[0]}: {src_line}\n'
# Assemble trace part of hunk
hunk_trace += f'-{filtered_line}'
# Default: print trace interleaved with source annotations
else:
if len(annot) and annot != last:
of.write(annot+'\n')
of.write(f' {filtered_line}')
last = annot
# very simple progress
if not quiet:
prog = int(100.0 / tot_lines * lino)
if prog > last_prog:
last_prog = prog
sys.stdout.write(f'\b\b\b\b{prog:3d}%')
sys.stdout.flush()
# Dump last hunk
if diff:
dump_hunk(hunk_tstart, hunk_sstart, hunk_trace, hunk_source)
if not quiet:
print(' done')
print(adr2line.cache_info())
a480b9028This file is pinned to the selected release. Download the original to open it in your design tool.