Files
failnix/scripts/menu.pl

527 lines
18 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 feature 'say';
# TODO: Less navigation if the object is selected first, then the action
# - Differentiate between local/remote object
# - List all types in the same list?
# - Select actions afterwards and apply fitting ones all in one go
# - Hide unfeasible actions
# TODO: Much can be extracted into utility functions
my $local_obsidian = '/home/christoph/Notes/Obsidian/Chriphost';
my $local_obsidian_attach = "$local_obsidian/attach";
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_scripts_dir = "$local_root/scripts";
my $local_builds_dir = "$local_root/builds";
my $local_archive_dir = "$local_root/injections";
my $local_charts_dir = "$local_root/charts";
my $local_ghidra_projects = "$local_root/ghidra/projects";
my $local_ghidra_scripts = "$local_root/ghidra/scripts";
my $local_db_conf = "$local_root/db.conf";
my $resultbrowser_port = '5000';
my $resultbrowser = 'resultbrowser.py';
my $remote_root = '/home/lab/smchurla/Documents/failnix';
my $remote_builds_dir = "$remote_root/builds";
my $db_host = "127.0.0.1";
my $db_port = "3306";
my $db_user = "smchurla";
my %handlers = (
'01. Build Experiments' => sub { do "$local_scripts_dir/build.pl"; },
'02. Deploy Experiments (Mars)' =>
sub { do "$local_scripts_dir/deploy.pl"; },
'03. Archive Experiments (Downloads from Mars)' => sub {
# Download ran experiments from mars
my @dirs = Mars::find_remote_subdirs($remote_builds_dir);
my @existing = Util::find_subdirs($local_archive_dir);
my @new_dirs;
foreach (@dirs) {
my $dir = $_ =~ s/:/-/gr;
unless ( grep { /$dir/ } @existing ) {
push @new_dirs, $_;
}
}
my @selected_dirs =
TUI::select_from_list( "Select Experiments to Download from Mars",
1, @new_dirs );
die "No experiment selected" unless @selected_dirs;
Mars::download_dir( "$remote_builds_dir/$_",
"$local_archive_dir/" . $_ =~ s/:/-/gr )
for @selected_dirs;
system( 'touch', "$local_archive_dir/" . $_ =~ s/:/-/gr . "/0.info" )
for @selected_dirs;
},
'04. Query Databases (Mars)' => sub {
# Select databases
my @db_names = Mars::db_list();
my @selected_dbs =
TUI::select_from_list( "Select Databases to Query", 1, @db_names );
die "No database selected" unless @selected_dbs;
# Select queries
my @queries =
map { s/\.pm//r } Util::find_files("$local_root/scripts/Queries");
my @selected_queries =
TUI::select_from_list( "Select Queries to Run", 1, @queries );
die "No query selected" unless @selected_queries;
# Run queries on databases
foreach my $db (@selected_dbs) {
foreach my $query (@selected_queries) {
Util::rewrite_file( $local_db_conf, "database=",
"database=$db\n" );
say "Running $query on $db...";
Util::execute_query( $db =~ s/smchurla_//r,
$query, $local_db_conf, $local_archive_dir, 0 );
}
}
},
'05. Import Experiments Into Ghidra' => sub {
my @existing = Util::find_files($local_ghidra_projects);
my $is_old = sub {
my ($name) = @_;
$name =~ s/:/-/g;
return grep { /^$name.gpr$/ } @existing;
};
# Import archived experiments into ghidra
my @dirs =
grep { !$is_old->($_) } Util::find_subdirs($local_archive_dir);
my @selected_dirs =
TUI::select_from_list( "Select Experiments to Import into Ghidra",
1, @dirs );
foreach (@selected_dirs) {
say "Creating Ghidra project for $_...";
system(
'ghidra-analyzeHeadless',
$local_ghidra_projects, $_ =~ s/:/-/gr,
'-import', "$local_archive_dir/$_/system.elf",
'-scriptPath', $local_ghidra_scripts,
'-postScript', 'DWARFLineInfoSourceMapScript',
'-postScript', 'DWARFLineInfoCommentScript',
'-postScript', 'ImportMarkersAsBookmarks',
"$local_archive_dir/$_/faults.csv"
);
}
},
'06. Import Experiments Into Obsidian' => sub {
my @experiments = Util::find_subdirs($local_archive_dir);
# Filter experiments that already have notes
my @new_experiments;
my @existing_notes = split "\n", qx{obsidian files};
foreach my $experiment (@experiments) {
push @new_experiments, $experiment
unless ( grep { /zettel\/$experiment/ } @existing_notes );
}
my @selected_experiments =
TUI::select_from_list( "Select Experiments to Import into Obsidian",
1, @new_experiments );
die "No experiment selected" unless @selected_experiments;
foreach my $experiment (@selected_experiments) {
# Create note
system(
'obsidian', 'create',
"name=$experiment", 'path=zettel',
'template=FailExperiment', 'open',
'newtab'
);
# Insert results
if ( -f "$local_archive_dir/$experiment/results.txt" ) {
open( my $fhandle, '<',
"$local_archive_dir/$experiment/results.txt" )
or return;
my $results = join "", <$fhandle>;
close($fhandle);
say "$local_archive_dir/$experiment/results.txt does not exist";
system( 'obsidian', 'append', "file=zettel/$experiment",
"content=## Results\n\n```\n$results```\n" );
}
# Insert charts
system(
'obsidian', 'append',
"file=zettel/$experiment", "content=## Charts\n\n"
);
my $attach_image = sub {
my ($name) = @_;
system( 'obsidian', 'append', "file=zettel/$experiment",
"content=![$name](file://$local_archive_dir/$experiment/$name.svg)\n"
);
};
$attach_image->("single_result");
$attach_image->("scatter");
}
},
'10. Open Experiment In Explorer' =>
sub { do "$local_scripts_dir/explore.pl" },
'11. Compare Experiment Results' => sub {
my @selected_experiments = Util::select_experiment(1);
# Read results
my %all_results;
foreach my $experiment (@selected_experiments) {
# Schema: benchmark, resulttype, faults
my $data = Text::CSV_XS::csv(
in => "$local_archive_dir/$experiment/resultsdata.csv",
headers => 'auto'
);
foreach my $row (@$data) {
$all_results{$experiment}{ $row->{benchmark} }
{ $row->{resulttype} } = $row->{faults};
}
}
my @benchs = ( 'ip', 'mem', 'regs' );
my @markers = (
'OK_MARKER', 'FAIL_MARKER',
'DETECTED_MARKER', 'TIMEOUT',
'TRAP', 'WRITE_TEXTSEGMENT',
'ACCESS_OUTERSPACE'
);
my $heading = sprintf( "%5s %20s ", "BENCH", "TYPE" );
my $subheading = sprintf( "%5s %20s ", "", "" );
foreach my $experiment (@selected_experiments) {
$heading .= sprintf( "%50s ", $experiment );
$subheading .=
sprintf( "%50s ", Util::read_experiment_info($experiment) );
}
my @entries = ( $heading, $subheading, "" );
foreach my $benchmark (@benchs) {
foreach my $marker (@markers) {
my $entry = sprintf( "%5s %20s ", $benchmark, $marker );
foreach my $experiment (@selected_experiments) {
if ( exists $all_results{$experiment}{$benchmark}{$marker} )
{
$entry .= sprintf(
"%50s ",
Util::format_number_sep(
$all_results{$experiment}{$benchmark}{$marker}
)
);
}
else {
$entry .= sprintf( "%50s ", "" );
}
}
push @entries, $entry;
}
push @entries, "";
}
TUI::select_from_list(
"Comparing " . scalar(@selected_experiments) . " Experiments",
0, @entries );
},
'12. Open Experiment in BinaryNinja' => sub {
my @selected_experiments = Util::select_experiment(1);
my @paths =
map { "$local_archive_dir/$_/system.elf" } @selected_experiments;
system( 'binaryninja', @paths );
},
'13. Open Experiment in Binsider' => sub {
my $selected_experiment = Util::select_experiment(0);
system( 'binsider',
"$local_archive_dir/$selected_experiment/system.elf" );
},
'14. Open Experiment in Ghidra' => sub {
my @projects =
map { s/\.gpr//r } Util::find_files($local_ghidra_projects);
my @selected_projects =
TUI::select_from_list( "Select Project to Open in Ghidra",
0, @projects );
die "No project selected" unless @selected_projects;
my $project = $selected_projects[0];
system(
join " ",
(
"_JAVA_AWT_WM_NONREPARENTING=1", "ghidra",
"$local_ghidra_projects/$project.gpr",
)
);
},
'15. Run Objdump on Experiment' => sub {
my $selected_experiment = Util::select_experiment(0);
system(
"objdump $local_archive_dir/$selected_experiment/system.elf -D -M intel -S | bat --color never"
);
},
'16. Run Wasm-Objdump on Experiment' => sub {
my $selected_experiment = Util::select_experiment(0);
system(
"wasm-objdump -d $local_archive_dir/$selected_experiment/wasm_module.wasm | bat --color never"
);
},
'17. Run Radare2 on Experiment' => sub {
my $selected_experiment = Util::select_experiment(0);
say "Radare help:";
say "s <address> - Seek to address";
say "pd <n> - Disassemble n instructions";
say "pdf - Disassemble current function";
say "pdf @ <function> - Disassemble function";
say "pdr - Disassemble recursively";
say "V - Switch view";
say "p - Switch print mode";
say "P - Switch layout";
system(
'radare2', '-AA',
'-c', '"-s dbg.os_main"',
'-e', 'scr.color=3',
'-e', 'scr.scrollbar=0',
'-e', 'scr.responsive=true',
'-e', 'scr.interactive=true',
'-e', 'scr.utf8=true',
'-e', 'scr.utf8.curvy=true',
'-e', 'asm.syntax=intel',
'-e', 'asm.lines=false',
'-e', 'asm.xrefs=true',
'-e', 'asm.flags=true',
'-e', 'asm.comments=true',
'-e', 'asm.functions=true',
'-e', 'asm.var=true',
'-e', 'asm.cmt.right=true',
'-e', 'asm.dwarf=true',
'-e', 'asm.pseudo=false',
'-e', 'asm.describe=false',
'-e', 'bin.relocs.apply=true',
"$local_archive_dir/$selected_experiment/system.elf",
);
},
'18. Open Database in ResultBrowser (Mars)' => sub {
my @db_names = Mars::db_list();
my @selected_dbs =
TUI::select_from_list( "Select Database for ResultBrowser",
0, @db_names );
die "No database selected" unless @selected_dbs;
my $selected_db = $selected_dbs[0];
Util::rewrite_file( $local_db_conf, "database=",
"database=$selected_db\n" );
system( $resultbrowser, '-c', $local_db_conf, '--host=0.0.0.0',
"--port=$resultbrowser_port" );
},
'19. Open Database in LazySQL (Mars)' => sub {
my $experiment =
Util::select_experiment(0) =~ s/T(\d\d)-(\d\d)-(\d\d)/T$1:$2:$3/r;
my $ssh = Mars::ssh_connect();
my $db_password = Mars::read_db_password_file();
system( 'lazysql', '-read-only',
"mariadb://$db_user:$db_password\@$db_host:$db_port/${db_user}_$experiment"
);
},
'20. Open TablePlus (Mars)' => sub {
system('tableplus');
},
'21. Run Build in GDB' => sub {
my @builds = grep { /linux/ } Util::find_subdirs($local_builds_dir);
my @selected_builds =
TUI::select_from_list( "Select Build to Run in GDB", 0, @builds );
die "No build selected" unless @selected_builds;
my $selected_build = $selected_builds[0];
my $build_dir = "$local_builds_dir/$selected_build";
my $build_name = $selected_build =~ s/.*-.*-.*:.*:.*?_(.*)-.*-.*/$1/r;
my $module_source = "$local_root/build-$build_name";
say "$build_name";
system(
'gdb',
'--tui',
'-q',
"$build_dir/system.elf",
'-ex',
"set substitute-path '$module_source' '$build_dir'",
'-ex',
"set substitute-path '/build/source/core' '$local_wamr/core'",
'-ex',
'break main',
'-ex',
'break fail_start_trace',
'-ex',
'break fail_stop_trace',
'-ex',
'break fail_marker_positive',
'-ex',
'break fail_marker_detected',
'-ex',
'break fail_marker_negative',
);
},
'30. Plot Results' => sub {
# Generate R ggplot2 charts
my @selected_experiments = Util::select_experiment(1);
my @charts = map { s/\.r//r } Util::find_files($local_charts_dir);
my @selected_charts =
TUI::select_from_list( "Select Plots to Generate", 1, @charts );
die "No plot selected" unless @selected_charts;
my @single_charts = grep { /single/ } @selected_charts;
foreach my $experiment (@selected_experiments) {
foreach my $chart (@single_charts) {
say " - Generating plot $chart for $experiment...";
system(
'Rscript',
"$local_charts_dir/$chart.r",
"$local_archive_dir/$experiment"
);
}
}
my @combined_charts = grep { /combined/ } @selected_charts;
my $print_experiments = join " ", @selected_experiments;
my @path_experiments =
map { "$local_archive_dir/$_" } @selected_experiments;
foreach my $chart (@combined_charts) {
say " - Generating plot $chart for ($print_experiments)...";
system( 'Rscript', "$local_charts_dir/$chart.r",
@path_experiments );
}
},
'95. Delete Builds' => sub {
# Delete old build files
my @builds = Util::find_subdirs($local_builds_dir);
my @selected_builds =
TUI::select_from_list( "Select Builds to Delete", 1, @builds );
die "No builds selected" unless @selected_builds;
system( 'rm', '-rf', "$local_builds_dir/$_" ) for @selected_builds;
},
'96. Delete Builds (Mars)' => sub {
# Delete ran experiments from mars
my @builds = Mars::find_remote_subdirs($remote_builds_dir);
my @selected_builds =
TUI::select_from_list( "Select Builds to Delete from Mars",
1, @builds );
die "No experiment selected" unless @selected_builds;
Mars::ssh_system( 'rm', '-rf', "$remote_builds_dir/$_" )
for @selected_builds;
},
'97. Delete Ghidra Projects' => sub {
# Delete ghidra projects
my @projects =
map { s/\.gpr//r } Util::find_files($local_ghidra_projects);
my @selected_projects =
TUI::select_from_list( "Select Ghidra Projects to Delete",
1, @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" )
for @selected_projects;
},
'98. Delete Archived Experiments' => sub {
# Delete archived experiments
my @selected_experiments = Util::select_experiment(1);
system( 'rm', '-rf', "$local_archive_dir/$_" )
for @selected_experiments;
},
'99. Drop Databases (Mars)' => sub {
# Drop databases on mars
my @db_names = Mars::db_list();
my @selected_dbs =
TUI::select_from_list( "Select Databases to Drop from Mars",
1, @db_names );
die "No database selected" unless @selected_dbs;
Mars::db_drop($_) for @selected_dbs;
},
);
while (1) {
my @submenu =
TUI::select_from_list( "FailNix Menu", 0, sort keys %handlers );
die "No action selected" unless @submenu;
say @submenu;
eval { $handlers{ $submenu[0] }(); }
}
Mars::db_disconnect();