add result explorer

This commit is contained in:
2026-04-20 20:59:36 +02:00
parent aacc895800
commit 7b31507403
7 changed files with 633 additions and 12 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -13,6 +13,7 @@ sub init_cui {
if ( !defined $_cui ) { if ( !defined $_cui ) {
$_cui = new Curses::UI( $_cui = new Curses::UI(
-color_support => 1, -color_support => 1,
-mouse_support => 1,
# -clear_on_exit => 1, # -clear_on_exit => 1,
); );
@ -34,7 +35,6 @@ sub select_from_list {
my @selection; my @selection;
my $cui = init_cui(); my $cui = init_cui();
my $win = $cui->add( 'root', 'Window', ); my $win = $cui->add( 'root', 'Window', );
my $listbox = $win->add( my $listbox = $win->add(
@ -53,21 +53,21 @@ sub select_from_list {
$win->add( $win->add(
'info', 'Label', 'info', 'Label',
-y => -1, -y => -1,
-text => "Space/Enter = toggle, c = confirm, q = quit", -text => "Space/Enter = toggle, C = confirm, Q = quit",
); );
$listbox->clear_binding('loose-focus'); $listbox->clear_binding('loose-focus');
$listbox->set_binding( $listbox->set_binding(
sub { sub {
my @picked = $listbox->get; my @picked = $listbox->get();
if ( $multiselect && grep { $_ eq '__ALL__' } @picked ) { if ( $multiselect && grep { $_ eq '__ALL__' } @picked ) {
@selection = @items; @selection = @items;
} }
else { else {
@selection = @picked; @selection = @picked;
} }
$cui->mainloopExit; $cui->mainloopExit();
}, },
'c', 'c',
); );
@ -75,15 +75,15 @@ sub select_from_list {
$listbox->set_binding( $listbox->set_binding(
sub { sub {
@selection = (); @selection = ();
$cui->mainloopExit; $cui->mainloopExit();
}, },
'q', 'q',
); );
$listbox->focus; $listbox->focus();
$cui->mainloop; $cui->mainloop();
$cui->leave_curses; $cui->leave_curses();
$cui->delete('root'); $cui->delete('root');
return @selection; return @selection;

610
scripts/explore.pl Normal file
View File

@ -0,0 +1,610 @@
#!/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 Experiments to Delete",
0, @experiments );
die "No experiment selected" unless @selected_experiments;
my $selected_experiment = $selected_experiments[0];
my $cui = TUI::init_cui();
# =============================================================================
# Data handling
# =============================================================================
my @marker_values;
my %marker_labels;
my @filtered_marker_values;
my $assembly_text = "Test";
my $source_file;
my $source_line;
my $source_text = "Test\nTest";
my @benchs = ( "ip", "mem", "regs" );
my @markers = (
"OK_MARKER", "DETECTED_MARKER", "FAIL_MARKER", "TRAP",
"TIMEOUT", "ACCESS_OUTERSPACE"
);
# Filter popup state
my $popup_focus = "left";
my @selected_benchs = @benchs;
my %index_of_benchs;
@index_of_benchs{@benchs} = 0 .. $#benchs;
my @selected_markers = @markers;
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 %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) = @_;
my $address = hex( $marker->{fault_address} );
if ( !exists $assembly_cache{$address} ) {
my $file = "$local_archive_dir/$selected_experiment/system.elf";
my $start = $address - $region_size - 120;
my $end = $address + $region_size + 120;
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) = @_;
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+)/;
$sourcefile = remap_source_path($sourcefile);
my $source_region = read_source_file( $sourcefile, $lineno );
if ( !defined $source_region or length($source_region) == 0 ) {
$source_text = "Failed to Read Source: $sourcefile:$lineno";
return;
}
$source_cache{$address} = $source_region;
$source_file_cache{$address} = $sourcefile;
$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( 'root', '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, O = open source, P = 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" );
},
'o',
);
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 );
}
},
'p',
);
$win->set_binding(
sub {
},
'g',
);
$win->set_binding(
sub {
$cui->mainloopExit();
},
'q',
);
# =============================================================================
# Main Loop
# =============================================================================
load_faults_csv();
layout_resize();
$markers_panel->focus();
$refresh_panels->();
$cui->mainloop();
$cui->leave_curses();
$cui->delete('root');

View File

@ -129,7 +129,7 @@ my %handlers = (
} }
}, },
'10. Open Experiment in Ghidra' => sub { '07. Open Experiment in Ghidra' => sub {
my @projects = my @projects =
map { s/\.gpr//r } Util::find_files($local_ghidra_projects); map { s/\.gpr//r } Util::find_files($local_ghidra_projects);
@ -147,7 +147,7 @@ my %handlers = (
); );
}, },
'11. Open Experiment In Explorer' => '08. Open Experiment In Explorer' =>
sub { do "$local_scripts_dir/explore.pl" }, sub { do "$local_scripts_dir/explore.pl" },
'95. Delete Builds (Local)' => sub { '95. Delete Builds (Local)' => sub {
@ -178,7 +178,7 @@ my %handlers = (
map { s/\.gpr//r } Util::find_files($local_ghidra_projects); map { s/\.gpr//r } Util::find_files($local_ghidra_projects);
my @selected_projects = my @selected_projects =
TUI::select_from_list( "Select Projects to Delete", 1, @projects ); TUI::select_from_list( "Select Projects to Delete", 1, @projects );
die "No projects selected" unless @selected_projects; die "No project selected" unless @selected_projects;
system( 'rm', '-rf', "$local_ghidra_projects/$_.gpr" ) system( 'rm', '-rf', "$local_ghidra_projects/$_.gpr" )
for @selected_projects; for @selected_projects;
system( 'rm', '-rf', "$local_ghidra_projects/$_.rep" ) system( 'rm', '-rf', "$local_ghidra_projects/$_.rep" )
@ -192,7 +192,7 @@ my %handlers = (
my @selected_experiments = my @selected_experiments =
TUI::select_from_list( "Select Archived Experiments to Delete", TUI::select_from_list( "Select Archived Experiments to Delete",
1, @experiments ); 1, @experiments );
die "No experiments selected" unless @selected_experiments; die "No experiment selected" unless @selected_experiments;
system( 'rm', '-rf', "$local_archive_dir/$_" ) system( 'rm', '-rf', "$local_archive_dir/$_" )
for @selected_experiments; for @selected_experiments;
}, },

View File

@ -242,6 +242,8 @@ build-c-host module target="fail":
exit 1 exit 1
fi fi
cp targets/c-host/c_host.c {{ BUILD_DIR }}-{{ module }}/module_host.c
[private] [private]
build-system-startup-fail module: build-system-startup-fail module:
{{ CROSS_CC }} targets/startup.s {{ CROSS_CFLAGS }} -c -o {{ BUILD_DIR }}-{{ module }}/startup.o {{ CROSS_CC }} targets/startup.s {{ CROSS_CFLAGS }} -c -o {{ BUILD_DIR }}-{{ module }}/startup.o