From 7b31507403c660eac1ddfc45b0acf5a128399819 Mon Sep 17 00:00:00 2001 From: Christoph Urlacher Date: Mon, 20 Apr 2026 20:59:36 +0200 Subject: [PATCH] add result explorer --- .../module_host.c | 3 + .../module_host.c | 3 + .../module_host.c | 3 + scripts/TUI.pm | 16 +- scripts/explore.pl | 610 ++++++++++++++++++ scripts/menu.pl | 8 +- scripts/wasm.just | 2 + 7 files changed, 633 insertions(+), 12 deletions(-) create mode 100644 injections/2026-04-18T17:48:31_sum0_base-fail-c/module_host.c create mode 100644 injections/2026-04-18T17:48:31_sum1_repl_naive_late-fail-c/module_host.c create mode 100644 injections/2026-04-18T17:48:31_sum2_repl_cored_late-fail-c/module_host.c create mode 100644 scripts/explore.pl diff --git a/injections/2026-04-18T17:48:31_sum0_base-fail-c/module_host.c b/injections/2026-04-18T17:48:31_sum0_base-fail-c/module_host.c new file mode 100644 index 0000000..1747e93 --- /dev/null +++ b/injections/2026-04-18T17:48:31_sum0_base-fail-c/module_host.c @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc7325c8b81907db9b06f4ebf82caca946ed9395321e1195878899fc501bf679 +size 428 diff --git a/injections/2026-04-18T17:48:31_sum1_repl_naive_late-fail-c/module_host.c b/injections/2026-04-18T17:48:31_sum1_repl_naive_late-fail-c/module_host.c new file mode 100644 index 0000000..1747e93 --- /dev/null +++ b/injections/2026-04-18T17:48:31_sum1_repl_naive_late-fail-c/module_host.c @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc7325c8b81907db9b06f4ebf82caca946ed9395321e1195878899fc501bf679 +size 428 diff --git a/injections/2026-04-18T17:48:31_sum2_repl_cored_late-fail-c/module_host.c b/injections/2026-04-18T17:48:31_sum2_repl_cored_late-fail-c/module_host.c new file mode 100644 index 0000000..1747e93 --- /dev/null +++ b/injections/2026-04-18T17:48:31_sum2_repl_cored_late-fail-c/module_host.c @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc7325c8b81907db9b06f4ebf82caca946ed9395321e1195878899fc501bf679 +size 428 diff --git a/scripts/TUI.pm b/scripts/TUI.pm index 51afd02..805a5de 100644 --- a/scripts/TUI.pm +++ b/scripts/TUI.pm @@ -13,6 +13,7 @@ sub init_cui { if ( !defined $_cui ) { $_cui = new Curses::UI( -color_support => 1, + -mouse_support => 1, # -clear_on_exit => 1, ); @@ -34,7 +35,6 @@ sub select_from_list { my @selection; my $cui = init_cui(); - my $win = $cui->add( 'root', 'Window', ); my $listbox = $win->add( @@ -53,21 +53,21 @@ sub select_from_list { $win->add( 'info', 'Label', -y => -1, - -text => "Space/Enter = toggle, c = confirm, q = quit", + -text => "Space/Enter = toggle, C = confirm, Q = quit", ); $listbox->clear_binding('loose-focus'); $listbox->set_binding( sub { - my @picked = $listbox->get; + my @picked = $listbox->get(); if ( $multiselect && grep { $_ eq '__ALL__' } @picked ) { @selection = @items; } else { @selection = @picked; } - $cui->mainloopExit; + $cui->mainloopExit(); }, 'c', ); @@ -75,15 +75,15 @@ sub select_from_list { $listbox->set_binding( sub { @selection = (); - $cui->mainloopExit; + $cui->mainloopExit(); }, 'q', ); - $listbox->focus; - $cui->mainloop; + $listbox->focus(); + $cui->mainloop(); - $cui->leave_curses; + $cui->leave_curses(); $cui->delete('root'); return @selection; diff --git a/scripts/explore.pl b/scripts/explore.pl new file mode 100644 index 0000000..b976c9a --- /dev/null +++ b/scripts/explore.pl @@ -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'); diff --git a/scripts/menu.pl b/scripts/menu.pl index fc7d142..45dff39 100644 --- a/scripts/menu.pl +++ b/scripts/menu.pl @@ -129,7 +129,7 @@ my %handlers = ( } }, - '10. Open Experiment in Ghidra' => sub { + '07. Open Experiment in Ghidra' => sub { my @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" }, '95. Delete Builds (Local)' => sub { @@ -178,7 +178,7 @@ my %handlers = ( map { s/\.gpr//r } Util::find_files($local_ghidra_projects); my @selected_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" ) for @selected_projects; system( 'rm', '-rf', "$local_ghidra_projects/$_.rep" ) @@ -192,7 +192,7 @@ my %handlers = ( my @selected_experiments = TUI::select_from_list( "Select Archived Experiments to Delete", 1, @experiments ); - die "No experiments selected" unless @selected_experiments; + die "No experiment selected" unless @selected_experiments; system( 'rm', '-rf', "$local_archive_dir/$_" ) for @selected_experiments; }, diff --git a/scripts/wasm.just b/scripts/wasm.just index c0a5166..84daffe 100644 --- a/scripts/wasm.just +++ b/scripts/wasm.just @@ -242,6 +242,8 @@ build-c-host module target="fail": exit 1 fi + cp targets/c-host/c_host.c {{ BUILD_DIR }}-{{ module }}/module_host.c + [private] build-system-startup-fail module: {{ CROSS_CC }} targets/startup.s {{ CROSS_CFLAGS }} -c -o {{ BUILD_DIR }}-{{ module }}/startup.o