631 lines
16 KiB
Perl
631 lines
16 KiB
Perl
#!/usr/bin/env perl
|
|
|
|
use strict;
|
|
use warnings;
|
|
use diagnostics;
|
|
|
|
use FindBin;
|
|
use lib $FindBin::Bin;
|
|
|
|
use Util;
|
|
use Mars;
|
|
use TUI;
|
|
use Text::CSV_XS;
|
|
use File::Temp qw(tempfile);
|
|
|
|
use feature 'say';
|
|
|
|
my $local_wamr = '/home/christoph/Notes/TU/MastersThesis/05 WAMR';
|
|
my $local_newlib = '/home/christoph/Notes/TU/MastersThesis/07 NewLib';
|
|
my $local_root = '/home/christoph/Notes/TU/MastersThesis/FailNix';
|
|
my $local_archive_dir = "$local_root/injections";
|
|
|
|
# Select experiment to open
|
|
my @experiments = Util::find_subdirs($local_archive_dir);
|
|
my @selected_experiments =
|
|
TUI::select_from_list( "Select Archived Experiment to Open", 0,
|
|
@experiments );
|
|
die "No experiment selected" unless @selected_experiments;
|
|
my $selected_experiment = $selected_experiments[0];
|
|
|
|
my $cui = TUI::init_cui();
|
|
|
|
# TODO: Add a TextEditor panel beside the experiment selection for notes.
|
|
# Store notes in experiment/notes/description.txt
|
|
# TODO: Add a TextEditor panel below the markers for notes.
|
|
# Store notes in experiment/notes/benchmark-type-address.txt
|
|
# TODO: Add a more exhaustive faults query that also retrieves
|
|
# what fault has been injected. Then add an info popup or
|
|
# display it in the markers panel
|
|
# TODO: Add a filter for address ranges
|
|
# TODO: Add a filter for segments
|
|
|
|
# =============================================================================
|
|
# Data handling
|
|
# =============================================================================
|
|
|
|
my @marker_values;
|
|
my %marker_labels;
|
|
my @filtered_marker_values;
|
|
|
|
my $assembly_text;
|
|
|
|
my $source_file;
|
|
my $source_line;
|
|
my $source_text;
|
|
|
|
my @benchs = ( "ip", "mem", "regs" );
|
|
my @markers = (
|
|
"OK_MARKER", "DETECTED_MARKER",
|
|
"FAIL_MARKER", "TRAP",
|
|
"TIMEOUT", "ACCESS_OUTERSPACE",
|
|
"WRITE_TEXTSEGMENT",
|
|
);
|
|
|
|
# Filter popup state
|
|
my $popup_focus = "left";
|
|
|
|
my @selected_benchs = @benchs;
|
|
my %index_of_benchs;
|
|
@index_of_benchs{@benchs} = 0 .. $#benchs;
|
|
|
|
my @selected_markers = ("FAIL_MARKER");
|
|
my %index_of_markers;
|
|
@index_of_markers{@markers} = 0 .. $#markers;
|
|
|
|
my $selected_threshold = 0;
|
|
|
|
# TODO: Display the segment the addresses belong to
|
|
sub load_faults_csv {
|
|
@marker_values = ();
|
|
%marker_labels = ();
|
|
|
|
# Schema: benchmark, resulttype, faults, fault_address
|
|
my $data = Text::CSV_XS::csv(
|
|
in => "$local_archive_dir/$selected_experiment/faults.csv",
|
|
headers => 'auto'
|
|
);
|
|
|
|
sub format_number_sep {
|
|
my ($number) = @_;
|
|
1 while $number =~ s/^(-?\d+)(\d{3})/$1.$2/;
|
|
return $number;
|
|
}
|
|
|
|
# Result:
|
|
# [ { benchmark => "ip", resulttype => "OK_MARKER",
|
|
# faults => "100", fault_address => "0x100074" },
|
|
# ... ]
|
|
@marker_values = @$data;
|
|
@filtered_marker_values = @marker_values;
|
|
%marker_labels =
|
|
map {
|
|
$_ => sprintf(
|
|
"%5s %10s %20s %15sx",
|
|
$_->{benchmark}, $_->{fault_address},
|
|
$_->{resulttype}, format_number_sep( $_->{faults} ),
|
|
);
|
|
} @marker_values;
|
|
}
|
|
|
|
sub filter_marker_values {
|
|
|
|
sub is_allowed {
|
|
my ($marker) = @_;
|
|
|
|
my $bench = $marker->{benchmark};
|
|
my $type = $marker->{resulttype};
|
|
my $count = int( $marker->{faults} );
|
|
|
|
return ( grep { /^$bench$/ } @selected_benchs
|
|
and grep { /^$type$/ } @selected_markers
|
|
and $count > $selected_threshold );
|
|
}
|
|
|
|
@filtered_marker_values = grep { is_allowed($_) } @marker_values;
|
|
}
|
|
|
|
# How much to disassemble around the address / how many lines to show
|
|
my $region_size = $cui->height() / 2 - 2;
|
|
my $asm_region_size = $region_size + 120;
|
|
|
|
my %assembly_cache;
|
|
|
|
sub align_region {
|
|
my ( $assembly_region, $objdump_address ) = @_;
|
|
|
|
my @region;
|
|
my @lines = split "\n", $assembly_region;
|
|
my $center;
|
|
for my $i ( 1 .. $#lines ) {
|
|
my $line = $lines[ $i - 1 ];
|
|
chomp $line;
|
|
|
|
# Mark the center line
|
|
if ( grep { /^\s*$objdump_address/ } $line ) {
|
|
push @region, sprintf( ">> %s", $line );
|
|
$center = $i;
|
|
}
|
|
else {
|
|
push @region, sprintf( " %s", $line );
|
|
}
|
|
}
|
|
|
|
if ( defined $center ) {
|
|
my $offset = $center - $region_size;
|
|
if ( $offset > 0 ) {
|
|
|
|
# Remove lines ($center > $region_size)
|
|
@region = @region[ $offset .. $#region ];
|
|
}
|
|
elsif ( $offset < 0 ) {
|
|
|
|
# Add lines
|
|
for ( 1 .. abs($offset) + 1 ) {
|
|
unshift @region, "";
|
|
}
|
|
}
|
|
}
|
|
|
|
return join "\n", @region;
|
|
}
|
|
|
|
sub load_assembly_elf {
|
|
my ($marker) = @_;
|
|
|
|
return unless defined $marker;
|
|
|
|
my $address = hex( $marker->{fault_address} );
|
|
|
|
if ( !exists $assembly_cache{$address} ) {
|
|
my $file = "$local_archive_dir/$selected_experiment/system.elf";
|
|
my $start = $address - $asm_region_size;
|
|
my $end = $address + $asm_region_size;
|
|
|
|
my $objdump_address = lc substr( $marker->{fault_address}, 2 );
|
|
|
|
my $assembly_region =
|
|
qx{objdump $file -D -M intel -C --start-address=$start --stop-address=$end}
|
|
=~ s/.*\n.*\n.*\n.*\n.*\n.*\n//r;
|
|
|
|
$assembly_cache{$address} =
|
|
align_region( $assembly_region, $objdump_address );
|
|
}
|
|
|
|
$assembly_text = $assembly_cache{$address};
|
|
}
|
|
|
|
my %remappings = (
|
|
"/build/source/core" => "$local_wamr/core",
|
|
"/build/newlib-.*/newlib" => "$local_newlib/newlib",
|
|
".*/module_host.c" =>
|
|
"$local_archive_dir/$selected_experiment/module_host.c",
|
|
);
|
|
|
|
sub remap_source_path {
|
|
my ($path) = @_;
|
|
|
|
foreach my $source ( keys %remappings ) {
|
|
my $target = $remappings{$source};
|
|
$path =~ s/$source/$target/;
|
|
}
|
|
|
|
return $path;
|
|
}
|
|
|
|
sub read_source_file {
|
|
my ( $sourcefile, $lineno ) = @_;
|
|
|
|
return unless ( -f $sourcefile );
|
|
|
|
open( my $fhandle, '<', $sourcefile ) or return;
|
|
|
|
my @lines = <$fhandle>;
|
|
close($fhandle);
|
|
|
|
my $start = int($lineno) - $region_size;
|
|
$start = 1 if $start < 1;
|
|
my $end = int($lineno) + $region_size;
|
|
$end = scalar(@lines) if $end > scalar(@lines);
|
|
|
|
my @region;
|
|
for my $i ( $start .. $end ) {
|
|
my $line = $lines[ $i - 1 ];
|
|
chomp $line;
|
|
|
|
# Mark the center line
|
|
if ( $i == $lineno ) {
|
|
push @region, sprintf( ">> %6d | %s", $i, $line );
|
|
}
|
|
else {
|
|
push @region, sprintf( " %6d | %s", $i, $line );
|
|
}
|
|
}
|
|
|
|
return join( "\n", @region );
|
|
}
|
|
|
|
my %source_cache;
|
|
my %source_file_cache;
|
|
my %source_line_cache;
|
|
|
|
sub load_source_c {
|
|
my ($marker) = @_;
|
|
|
|
return unless defined $marker;
|
|
|
|
my $address = $marker->{fault_address};
|
|
|
|
if ( !exists $source_cache{$address} ) {
|
|
my $elffile = "$local_archive_dir/$selected_experiment/system.elf";
|
|
my $host_file = "$local_archive_dir/$selected_experiment/module_host.c";
|
|
my $module_file =
|
|
"$local_archive_dir/$selected_experiment/wasm-module.cpp";
|
|
|
|
# Example:
|
|
# wasm_interp_call_func_bytecode
|
|
# /build/source/core/iwasm/interpreter/wasm_interp_classic.c:1685
|
|
my $location = qx{addr2line -e $elffile -f -C -i $address};
|
|
my @lines = split( "\n", $location );
|
|
if ( scalar(@lines) < 2 ) {
|
|
$source_text = "Addr2Line Error\n";
|
|
return;
|
|
}
|
|
|
|
my $line = $lines[1]; # Ignore possible further lines
|
|
my ( $sourcefile, $lineno ) = split ":", $line;
|
|
($lineno) = $lineno =~ /^(\d+)/;
|
|
my $remapped = remap_source_path($sourcefile);
|
|
|
|
my $source_region = read_source_file( $remapped, $lineno );
|
|
if ( !defined $source_region or length($source_region) == 0 ) {
|
|
$source_text = "Failed to Read Source: $location";
|
|
$source_file = "Unknown";
|
|
$source_line = "Unknown";
|
|
return;
|
|
}
|
|
|
|
$source_cache{$address} = $source_region;
|
|
$source_file_cache{$address} = $remapped;
|
|
$source_line_cache{$address} = $lineno;
|
|
}
|
|
|
|
$source_text = $source_cache{$address};
|
|
$source_file = $source_file_cache{$address};
|
|
$source_line = $source_line_cache{$address};
|
|
}
|
|
|
|
# =============================================================================
|
|
# UI Layout
|
|
# =============================================================================
|
|
|
|
my $win = $cui->add( 'explorer', 'Window' );
|
|
|
|
my $left = $win->add( 'left', 'Container' ); # , -padbottom => 1,
|
|
my $middle = $win->add( 'middle', 'Container' ); # , -padbottom => 1,
|
|
my $right = $win->add( 'right', 'Container' ); # , -padbottom => 1,
|
|
|
|
my $markers_panel;
|
|
my $assembly_panel;
|
|
my $source_panel;
|
|
|
|
my $refresh_panels = sub {
|
|
load_assembly_elf( $markers_panel->get_active_value() );
|
|
load_source_c( $markers_panel->get_active_value() );
|
|
|
|
$assembly_panel->text($assembly_text);
|
|
$source_panel->text($source_text);
|
|
|
|
$source_panel->title("Source View: $source_file");
|
|
|
|
$assembly_panel->draw();
|
|
$source_panel->draw();
|
|
};
|
|
|
|
$markers_panel = $left->add(
|
|
'markers_list',
|
|
'Listbox',
|
|
-title => "Markers",
|
|
-border => 1,
|
|
-values => \@filtered_marker_values,
|
|
-labels => \%marker_labels,
|
|
-multi => 0,
|
|
-radio => 0,
|
|
-vscrollbar => 'right',
|
|
-onselchange => $refresh_panels,
|
|
);
|
|
|
|
$assembly_panel = $middle->add(
|
|
'assembly_viewer',
|
|
'TextViewer',
|
|
-title => "Assembly View",
|
|
-border => 1,
|
|
-text => $assembly_text,
|
|
);
|
|
|
|
$source_panel = $right->add(
|
|
'source_viewer',
|
|
'TextViewer',
|
|
-title => "Source View",
|
|
-border => 1,
|
|
-text => $source_text,
|
|
);
|
|
|
|
$win->add(
|
|
'titlebar', 'Label',
|
|
-y => 0,
|
|
-text => "Experiment: $selected_experiment",
|
|
);
|
|
|
|
$win->add(
|
|
'info', 'Label',
|
|
-y => -1,
|
|
-text =>
|
|
"Space = toggle, F = filter markers, S = open source, A = open assembly, R = resize, Q = quit",
|
|
);
|
|
|
|
sub set_geometry {
|
|
my ( $widget, $x, $y, $w, $h ) = @_;
|
|
|
|
$widget->{-x} = $x;
|
|
$widget->{-y} = $y;
|
|
$widget->{-width} = $w;
|
|
$widget->{-height} = $h;
|
|
|
|
# $widget->layout();
|
|
}
|
|
|
|
sub layout_resize {
|
|
my $w = $win->width();
|
|
my $h = $win->height();
|
|
|
|
my $w1 = int( $w * 0.2 );
|
|
my $w2 = int( $w * 0.4 );
|
|
my $w3 = int( $w - $w1 - $w2 );
|
|
|
|
set_geometry( $left, 0, 1, $w1, $h - 2 );
|
|
set_geometry( $middle, $w1, 1, $w2, $h - 2 );
|
|
set_geometry( $right, $w1 + $w2, 1, $w3, $h - 2 );
|
|
|
|
$cui->layout();
|
|
}
|
|
|
|
# =============================================================================
|
|
# Filter Popup
|
|
# =============================================================================
|
|
|
|
sub markers_filter_popup {
|
|
my $w = 60;
|
|
my $h = 24;
|
|
my $h1 = $h - 7; # top listboxes height
|
|
|
|
my @bench_values = @benchs;
|
|
my %bench_labels = map { $_ => $_ } @benchs;
|
|
|
|
my @marker_values = @markers;
|
|
my %marker_labels = map { $_ => $_ } @markers;
|
|
|
|
my $popup = $cui->add(
|
|
'popup', 'Window',
|
|
-x => int( $cui->width() / 2 - $w / 2 ),
|
|
-y => int( $cui->height() / 2 - $h / 2 ),
|
|
-width => $w,
|
|
-height => $h,
|
|
-border => 1,
|
|
-title => "Filter Markers",
|
|
);
|
|
|
|
my $popup_left = $popup->add(
|
|
'popup_left', 'Container',
|
|
-x => 0,
|
|
-y => 1,
|
|
-width => $w / 2,
|
|
-height => $h1,
|
|
);
|
|
|
|
my $popup_right = $popup->add(
|
|
'popup_right', 'Container',
|
|
-x => $w / 2,
|
|
-y => 1,
|
|
-width => $w / 2,
|
|
-height => $h1,
|
|
);
|
|
|
|
my $popup_bottom = $popup->add(
|
|
'popup_bottom', 'Container',
|
|
-x => 0,
|
|
-y => $h1 + 1,
|
|
-width => $w,
|
|
-height => 3,
|
|
);
|
|
|
|
my $benchmark_filter = $popup_left->add(
|
|
'benchmark_filter',
|
|
'Listbox',
|
|
-title => "By Benchmark",
|
|
-border => 1,
|
|
-values => \@bench_values,
|
|
-labels => \%bench_labels,
|
|
-multi => 1,
|
|
-radio => 0,
|
|
);
|
|
|
|
my $marker_filter = $popup_right->add(
|
|
'marker_filter',
|
|
'Listbox',
|
|
-title => "By Type",
|
|
-border => 1,
|
|
-values => \@marker_values,
|
|
-labels => \%marker_labels,
|
|
-multi => 1,
|
|
-radio => 0,
|
|
);
|
|
|
|
my $threshold = $popup_bottom->add(
|
|
'threshold', 'TextEntry',
|
|
-title => "By Count Threshold",
|
|
-border => 1,
|
|
-text => $selected_threshold,
|
|
-regexp => '/^\d*$/',
|
|
);
|
|
|
|
$popup->add(
|
|
'popup_info', 'Label',
|
|
-y => -1,
|
|
-text => "Space = toggle, F = cycle focus, C = confirm, Q = quit",
|
|
);
|
|
|
|
$benchmark_filter->set_selection( map { $index_of_benchs{$_} }
|
|
@selected_benchs );
|
|
$marker_filter->set_selection( map { $index_of_markers{$_} }
|
|
@selected_markers );
|
|
|
|
my $focus = sub {
|
|
if ( $popup_focus eq "left" ) {
|
|
$benchmark_filter->focus();
|
|
}
|
|
elsif ( $popup_focus eq "right" ) {
|
|
$marker_filter->focus();
|
|
}
|
|
else {
|
|
$threshold->focus();
|
|
}
|
|
};
|
|
|
|
$popup->set_binding(
|
|
sub {
|
|
@selected_benchs = $benchmark_filter->get();
|
|
@selected_markers = $marker_filter->get();
|
|
$selected_threshold = int( $threshold->get() ) or 0;
|
|
|
|
filter_marker_values( @selected_benchs, @selected_markers );
|
|
|
|
$cui->delete('popup');
|
|
$cui->layout();
|
|
},
|
|
'c',
|
|
);
|
|
|
|
$popup->set_binding(
|
|
sub {
|
|
if ( $popup_focus eq "left" ) {
|
|
$popup_focus = "right";
|
|
}
|
|
elsif ( $popup_focus eq "right" ) {
|
|
$popup_focus = "bottom";
|
|
}
|
|
else {
|
|
$popup_focus = "left";
|
|
}
|
|
$focus->();
|
|
},
|
|
'f',
|
|
);
|
|
|
|
$popup->set_binding(
|
|
sub {
|
|
$cui->delete('popup');
|
|
$cui->layout();
|
|
},
|
|
'q'
|
|
);
|
|
|
|
$cui->layout();
|
|
$focus->();
|
|
}
|
|
|
|
# =============================================================================
|
|
# Bindings
|
|
# =============================================================================
|
|
|
|
$win->set_binding(
|
|
sub {
|
|
markers_filter_popup();
|
|
},
|
|
'f',
|
|
);
|
|
|
|
$win->set_binding(
|
|
sub {
|
|
layout_resize();
|
|
},
|
|
'r',
|
|
);
|
|
|
|
$win->set_binding(
|
|
sub {
|
|
system( 'neovide', '--fork', $source_file, '--', "+$source_line" );
|
|
},
|
|
's',
|
|
);
|
|
|
|
my $objdump_out;
|
|
my @objdump_lines;
|
|
my $temp_file;
|
|
|
|
$win->set_binding(
|
|
sub {
|
|
my $file = "$local_archive_dir/$selected_experiment/system.elf";
|
|
|
|
my $marker = $markers_panel->get_active_value();
|
|
my $objdump_address = lc substr( $marker->{fault_address}, 2 );
|
|
|
|
if ( !defined $objdump_out ) {
|
|
$objdump_out = qx{objdump $file -D -M intel -C --source};
|
|
$objdump_out =~ s/.*\n.*\n.*\n.*\n.*\n.*\n//;
|
|
@objdump_lines = split "\n", $objdump_out;
|
|
|
|
( my $fhandle, $temp_file ) = tempfile();
|
|
print $fhandle $objdump_out;
|
|
close($fhandle);
|
|
}
|
|
|
|
my $temp_line;
|
|
for my $i ( 1 .. scalar(@objdump_lines) ) {
|
|
my $line = $objdump_lines[ $i - 1 ];
|
|
|
|
if ( grep { /^\s*$objdump_address/ } $line ) {
|
|
$temp_line = $i;
|
|
last;
|
|
}
|
|
}
|
|
|
|
if ( defined $temp_line ) {
|
|
system( 'neovide', '--fork', $temp_file, '--', "+$temp_line" );
|
|
}
|
|
else {
|
|
system( 'neovide', '--fork', $temp_file );
|
|
}
|
|
},
|
|
'a',
|
|
);
|
|
|
|
$win->set_binding(
|
|
sub {
|
|
|
|
},
|
|
'g',
|
|
);
|
|
|
|
$win->set_binding(
|
|
sub {
|
|
$cui->mainloopExit();
|
|
},
|
|
'q',
|
|
);
|
|
|
|
# =============================================================================
|
|
# Main Loop
|
|
# =============================================================================
|
|
|
|
load_faults_csv();
|
|
filter_marker_values();
|
|
|
|
layout_resize();
|
|
$markers_panel->focus();
|
|
$refresh_panels->();
|
|
$cui->mainloop();
|
|
|
|
$cui->leave_curses();
|
|
$cui->delete('explorer');
|