Compare commits

...

123 Commits

Author SHA1 Message Date
5ff6f6d0d8 remove obsolete startup.s modifications 2026-04-22 15:41:15 +02:00
6c7351b3d1 add result comparison menu action 2026-04-22 15:40:57 +02:00
e36cfdc07d abort assembly/source loading if no marker is selected in explorer 2026-04-22 10:22:57 +02:00
7067c015bc exclude existing experiments when archiving 2026-04-21 23:21:14 +02:00
94ec38fa84 cleanup old injections/projects 2026-04-21 22:37:18 +02:00
42817bab52 decrease loop iterations in sum sample 2026-04-21 22:35:33 +02:00
aec774c633 link mmap_space + aot array into .text 2026-04-21 22:34:36 +02:00
41ffcaeb08 add obsidian experiment note creation menu action 2026-04-21 22:32:17 +02:00
41e079b977 add resultbrowser menu action 2026-04-21 18:39:47 +02:00
371b323980 add ssh keepalive to perl ssh connection 2026-04-21 18:39:27 +02:00
d1d6e4de34 undo last change 2026-04-21 16:34:10 +02:00
a7e2c14529 don't redirect fail client output to /dev/null 2026-04-21 16:21:29 +02:00
7bbd30a6f8 add ACCESS_OUTERSPACE markertype to explorer filters 2026-04-21 16:18:27 +02:00
8ea5aed355 fix database creation in deploy.pl 2026-04-21 14:48:47 +02:00
264795bf71 don't link mmap region into .text but aot array itself 2026-04-21 14:46:31 +02:00
ea56079c09 replace : with - on experiment archival 2026-04-21 14:11:57 +02:00
f45ce702d2 add abandoned ghidra path remapping script 2026-04-21 14:11:24 +02:00
545b581659 color points based on resulttype in scatterplot 2026-04-21 14:10:37 +02:00
353f971408 remove faulty injections 2026-04-21 14:07:58 +02:00
77d0cd7814 remove faulty ghidra projects 2026-04-21 14:07:18 +02:00
ed0a3b67de filter markers by default in explorer 2026-04-21 12:21:15 +02:00
51fb2ef4d4 only deploy "fail" targets, only gdb "linux" targets, only import new results into ghidra 2026-04-21 01:06:57 +02:00
0dbcaf68cd update wasm aot recipe for linux (only use --xip for fail/baremetal) 2026-04-21 00:39:00 +02:00
19048ed4bf add run in gdb menu action 2026-04-20 23:52:06 +02:00
495f74ade7 ignore db.conf 2026-04-20 21:06:14 +02:00
6125a3b9fc add barebones results bar chart 2026-04-20 21:01:07 +02:00
7b31507403 add result explorer 2026-04-20 20:59:36 +02:00
aacc895800 update flake to use direnv 2026-04-20 11:41:08 +02:00
116aa5f303 update ghidra projects 2026-04-20 00:21:29 +02:00
2d9837219c update injections 2026-04-20 00:21:18 +02:00
fabf7745ee update ghidra marker import script (headless, import all types from all benchs) 2026-04-20 00:17:46 +02:00
5b316bbd64 add curses entry point for scripts (archival, ghidra import, plots, queries, cleanup) 2026-04-20 00:16:27 +02:00
9c7933e912 add query for detected markers 2026-04-19 13:21:56 +02:00
d925b19135 add queries for other fault types 2026-04-19 13:19:55 +02:00
31d2b422cb move update_db_config to util 2026-04-19 13:05:32 +02:00
10bffc0fbc run all available queries automatically 2026-04-19 12:55:33 +02:00
f6989084ff fix query typos 2026-04-19 12:46:36 +02:00
d345745962 use qualified names for module functions 2026-04-19 12:45:46 +02:00
f729cfbcd2 add current script path to @INC 2026-04-19 12:42:52 +02:00
8a381d71cf add objdump just recipe 2026-04-19 12:39:14 +02:00
c092295520 move queries to modules 2026-04-19 12:39:00 +02:00
7efa945977 move some functions to Util module 2026-04-19 12:38:47 +02:00
40d40bc57b update targets 2026-04-18 19:46:45 +02:00
5dd763fd8a pass substitution as function 2026-04-18 18:03:05 +02:00
4d714a4442 substitute tabs with , for csv output 2026-04-18 17:49:04 +02:00
540596c33d fix injection error handling 2026-04-18 17:42:14 +02:00
cf319c5a72 wait for fail server port 2026-04-18 12:10:01 +02:00
e7cfc59096 add fork logs to inject 2026-04-18 11:52:52 +02:00
d9e0d8d70d send injection results as ntfy attachements 2026-04-18 11:50:06 +02:00
b141ba1e38 invoke runner without screen + add log file 2026-04-18 11:36:47 +02:00
959c9a8bee run with all available cpus 2026-04-18 11:36:36 +02:00
86e3814fb2 loop dropdb + support multiple selection 2026-04-18 11:36:12 +02:00
a287df261c fix runner external commands 2026-04-18 10:27:08 +02:00
eebb596773 update sub import/inject 2026-04-17 22:45:10 +02:00
d45d367d89 update sub trace 2026-04-17 22:39:38 +02:00
038660c969 fix print to filehandle bug 2026-04-17 22:33:56 +02:00
ca405f953b add success check to file writing 2026-04-17 22:27:36 +02:00
c3ada55395 add newline to db.conf updater 2026-04-17 22:21:48 +02:00
52e766e1c2 fix duplicated filehandle bug 2026-04-17 22:18:02 +02:00
a10f04c83e fix screen old session termination bug + db.conf rewriting bug 2026-04-17 22:13:31 +02:00
0d168bf759 add wip buildscripts for automated experiment execution 2026-04-17 21:58:29 +02:00
94f3fc7611 update injections + projects (aot readonly test) 2026-04-17 15:17:53 +02:00
5538e19c82 move justfiles to scripts/ 2026-04-17 15:17:30 +02:00
980025e5a1 disable --catch-write-textsegment after moving mmap memory region into .text 2026-04-17 00:59:04 +02:00
18c3f13a71 move wamr mmap memory region into .text segment for wamr aot mode 2026-04-17 00:25:22 +02:00
3563a810a1 enable --catch-write-textsegment and --catch-outerspace 2026-04-16 22:36:48 +02:00
329014aada unify host programs across targets 2026-04-16 22:36:36 +02:00
e73ab0a788 update build recipe: copy wasm-module source to build directory for archival 2026-04-16 12:47:06 +02:00
bd7f09251e update injections + projects (cored aot/interp) 2026-04-16 12:46:42 +02:00
187ad521cb update injections + projects (cored c) 2026-04-13 23:04:05 +02:00
550ce0b079 do fail interactions inside wasm module + unify host modules + fix cored module 2026-04-13 23:03:54 +02:00
fe6c2f5b99 update injections + projects (cored interp) 2026-04-08 18:32:44 +02:00
89db76e555 update injections + projects (cored c/aot) 2026-04-07 22:13:13 +02:00
28a3c37d41 update hosts for cored target
WIP: currently the hosts are not generic and don't support different
targets
2026-04-07 22:12:57 +02:00
39d2e1c51f switch fail markers to function symbols 2026-04-01 21:17:01 +02:00
353dfae39c add interp ghidra project with -O0 2026-03-19 18:59:25 +01:00
e3b97ba865 add interp injection with -O0 2026-03-19 18:59:09 +01:00
fc1d3feadb update mars faildir 2026-03-19 09:02:53 +01:00
63231f8b48 update mars faildir 2026-03-19 01:17:43 +01:00
07830e1976 re-add file (lfs issue) 2026-03-19 01:14:08 +01:00
f170ded33b remove file 2026-03-19 01:10:39 +01:00
23f1d55feb rename file 2026-03-19 01:08:44 +01:00
a10dc97616 update injections + projects 2026-03-19 01:01:01 +01:00
d797e9f71c compile with -O0 and -ggdb3 2026-03-19 01:00:54 +01:00
ea6a009c0d add aot ghidra project 2026-03-18 23:13:34 +01:00
bc2eefface add c-only ghidra project 2026-03-18 23:13:27 +01:00
a0ea3a3bbf update .gitignore 2026-03-18 23:13:21 +01:00
92ac5ec7d8 move ghidra script location 2026-03-18 23:12:47 +01:00
07ac6f6fc3 update .gitattributes 2026-03-18 23:12:18 +01:00
7b47a70448 add aot injection 2026-03-18 23:11:00 +01:00
96082f33aa add c-only injection 2026-03-18 23:10:56 +01:00
945c1fe0bc update recipes 2026-03-18 23:10:41 +01:00
5efff7c84e add rough fail db table overview 2026-03-18 23:10:38 +01:00
83ae53768c update .gitattributes 2026-03-18 23:10:33 +01:00
c8fb5d537d add gitattributes 2026-03-18 20:06:36 +01:00
a6a335aaf4 move fail binaries 2026-03-18 19:54:44 +01:00
5d4d84de39 add ghidra script to import markers as bookmarks 2026-03-17 23:52:33 +01:00
8afdee2fd2 add recipe to download markers from mars 2026-03-17 23:52:22 +01:00
0d871c4e56 add recipe to export markers to csv 2026-03-17 23:09:12 +01:00
5e0b71a818 update radare2 config 2026-03-17 20:43:39 +01:00
af70aebcff update radare2 config 2026-03-17 20:42:26 +01:00
cacd2d8883 update radare2 config 2026-03-17 20:41:54 +01:00
35dec73236 update radare2 config 2026-03-17 20:40:57 +01:00
e23a3d5033 disable importing --sources because of libdwarf error on mars
FAIL_IMPORT requires dwarf_init from libdwarf, but provided version only
defines dwarf_init_b?
2026-03-13 00:37:28 +01:00
b500d56c8e make radare recipe accept an address 2026-03-13 00:31:23 +01:00
8a0193408f remove obsolete recipe 2026-03-13 00:24:57 +01:00
129ba0e0b6 add a c-only target (no WASM) 2026-03-13 00:22:03 +01:00
0f847d7d2d split compilation into wasm.just + add targets for interpreted wasm 2026-03-12 21:13:06 +01:00
28d1db3b79 launch fail on non-default port on mars 2026-03-12 15:36:09 +01:00
c1eb861bfb fix just path in mars.just 2026-03-12 15:16:57 +01:00
4b8a4ad0f1 split justfile into nixos.just + mars.just 2026-03-12 15:15:15 +01:00
a79219d39e add just static binaries 2026-03-12 14:57:48 +01:00
593b88c3fd don't stop server in all-in-one 2026-03-12 14:52:22 +01:00
b1a8fe0c53 add all in one recipe 2026-03-12 14:26:36 +01:00
99608cc645 use shebang recipes for conditional dependencies instead of inline templating 2026-03-12 14:06:45 +01:00
50c6e9adea add linux-posix + linux-baremetal recipes 2026-03-12 13:45:33 +01:00
66eb0b3814 replace compose-file with direct docker command + add dbeaver recipe 2026-03-12 10:16:09 +01:00
078fdca44b fix incompatible libdwarf version 2026-03-12 10:15:30 +01:00
744af52f76 slightly restructure flake 2026-03-12 09:27:07 +01:00
43414edd0a add injection targets 2026-03-12 01:42:34 +01:00
679aeb24d4 add build + fail recipes 2026-03-12 01:42:25 +01:00
facf04df7f add wasi/wamr dependencies 2026-03-12 01:41:43 +01:00
86baf67fac add binaries 2026-03-11 20:22:30 +01:00
77 changed files with 15623 additions and 45 deletions

1
.envrc Normal file
View File

@ -0,0 +1 @@
use flake

7
.gitattributes vendored
View File

@ -5,3 +5,10 @@ fail/bin/import-trace filter=lfs diff=lfs merge=lfs -text
fail/bin/prune-trace filter=lfs diff=lfs merge=lfs -text
fail/share/** filter=lfs diff=lfs merge=lfs -text
just-bin/just filter=lfs diff=lfs merge=lfs -text
injections/** filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
ghidra/**/*.db filter=lfs diff=lfs merge=lfs -text
ghidra/**/*.gbf filter=lfs diff=lfs merge=lfs -text
ghidra/**/*.prp filter=lfs diff=lfs merge=lfs -text
ghidra/**/*.bak filter=lfs diff=lfs merge=lfs -text
ghidra/**/*.dat filter=lfs diff=lfs merge=lfs -text

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
/build-*
/builds
/ghidra/projects/**/*.lock*
/mars-db.conf
/.direnv
/db.conf
/fail/bin/resultbrowser/app/__pycache__

23
charts/single_result.r Normal file
View File

@ -0,0 +1,23 @@
library(ggplot2)
# Usage: Rscript single_result.r exp_abspath
args <- commandArgs(trailingOnly = TRUE)
experiment <- args[1]
datafile <- paste(experiment, "/resultsdata.csv", sep = "")
if (!file.exists(datafile)) {
print(paste("Input file", datafile, "is missing"))
stop()
}
data <- readr::read_csv(datafile)
tibble::glimpse(data)
plot <- ggplot(data, aes(x = benchmark, y = faults, fill = resulttype)) +
geom_col(position = "dodge") +
scale_y_log10() +
labs(x = "Benchmark", y = "Faults", fill = "Result Type") +
theme_minimal()
ggsave(paste(experiment, "/single_result.svg", sep = ""), plot = plot)

26
charts/single_scatter.r Normal file
View File

@ -0,0 +1,26 @@
library(ggplot2)
# Usage: Rscript single_scatter.r exp_abspath
# TODO: Allow filtering resulttypes (or at least exclude OK_MARKER)
args <- commandArgs(trailingOnly = TRUE)
experiment <- args[1]
datafile <- paste(experiment, "/faults.csv", sep = "")
if (!file.exists(datafile)) {
print(paste("Input file", datafile, "is missing"))
stop()
}
data <- readr::read_csv(datafile)
data$fault_address <- strtoi(data$fault_address)
tibble::glimpse(data)
plot <- ggplot(data, aes(x = fault_address, y = faults)) +
geom_point(aes(color = resulttype)) +
scale_y_log10() +
labs(x = "Address", y = "Faults", color = "Type") +
theme_minimal()
ggsave(paste(experiment, "/scatter.svg", sep = ""), plot = plot)

BIN
fail-db.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,147 @@
#!/usr/bin/python3
import os, sys
from optparse import OptionParser
from subprocess import *
from tempfile import mkstemp, mkdtemp
import shutil
from distutils.spawn import find_executable
import os.path
def parseArgs():
parser = OptionParser()
parser.add_option("-e", "--elf-file", dest="elf_file",
help="elf file to be executed", metavar="ELF")
parser.add_option("-i", "--iso-file", dest="iso_file",
help="iso file to be executed", metavar="ISO")
parser.add_option("-f", "--fail-client", dest="fail_client",
help="fail-client to be executed", metavar="ISO")
parser.add_option("-m", "--memory", dest="memory", default="16",
help="memory for the bochs VM", metavar="SIZE")
parser.add_option("-b", "--bios", dest="bios", default="/fs/proj/i4ezs/tools/fail-ws21/buildartifacts/BIOS-bochs-latest",
help="bios image for bochs", metavar="BIOS")
parser.add_option("-V", "--vgabios", dest="vgabios", default="/fs/proj/i4ezs/tools/fail-ws21/buildartifacts/vgabios.bin",
help="vgabios image for bochs", metavar="VGABIOS")
parser.add_option("-F", "--freq", dest="freq", default="5",
help="frquency in MHZ", metavar="MHZ")
parser.add_option("-1", "--once",
action="store_false", dest="forever", default=True,
help="fail-client to be executed")
parser.add_option("-j", "--jobs",
dest="jobs", default="1",
help="parallel execution")
(options, args) = parser.parse_args()
if not (options.elf_file and options.iso_file and options.fail_client):
parser.error("elf, iso and fail-client are required")
return options, args
def execute(options, args, bochsrc, statedir):
failcmd = options.fail_client
command = "FAIL_ELF_PATH=%s FAIL_STATEDIR=%s %s -q -f %s %s" % \
(options.elf_file, statedir, failcmd, bochsrc, " ".join(args))
print("executing: " + command)
p = Popen(command, shell=True, stdout=PIPE, stderr=STDOUT)
reconnect = 0
while p.poll() is None:
line = p.stdout.readline()
if line is None:
break
if "Connection refused" in line.decode("utf-8", "ignore"):
reconnect += 1
print(line),
if reconnect > 10:
return 1
p.wait()
if reconnect > 0:
return 123
return p.returncode
def main(options, args):
bochsrc_args = {
"memory": options.memory,
"bios": options.bios,
"vgabios": options.vgabios,
"iso": options.iso_file,
"ips": int(options.freq) * 1000000,
}
bochsrc_text = """
config_interface: textconfig
display_library: nogui
romimage: file="{bios}"
cpu: count=1, ips={ips}, reset_on_triple_fault=1, ignore_bad_msrs=1, msrs="msrs.def"
cpuid: mmx=1, sep=1, sse=sse4_2, xapic=1, aes=1, movbe=1, xsave=1, cpuid_limit_winnt=0
memory: guest={memory}, host={memory}
vgaromimage: file="{vgabios}"
vga: extension=vbe
ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
ata1: enabled=0, ioaddr1=0x170, ioaddr2=0x370, irq=15
ata2: enabled=0, ioaddr1=0x1e8, ioaddr2=0x3e0, irq=11
ata3: enabled=0, ioaddr1=0x168, ioaddr2=0x360, irq=9
ata0-slave: type=cdrom, path="{iso}", status=inserted
port_e9_hack: enabled=1
com1: enabled=1, mode=file, dev=serial.out
boot: cdrom
clock: sync=none, time0=946681200
floppy_bootsig_check: disabled=0
panic: action=fatal
error: action=fatal
info: action=ignore
debug: action=ignore
pass: action=ignore
debugger_log: -
parport1: enabled=0
vga_update_interval: 300000
keyboard_serial_delay: 250
keyboard_paste_delay: 100000
private_colormap: enabled=0
i440fxsupport: enabled=0, slot1=pcivga
""".format(**bochsrc_args)
bochsrc = mkstemp()
fd = os.fdopen(bochsrc[0], "w")
fd.write(bochsrc_text)
fd.close()
bochsrc = bochsrc[1]
statedir = mkdtemp()
if options.forever:
while True:
res = execute(options, args, bochsrc, statedir)
if res != 0:
break
ret = 0
else:
ret = execute(options, args, bochsrc, statedir)
os.unlink(bochsrc)
shutil.rmtree(statedir)
sys.exit(ret)
if __name__ == "__main__":
(options, args) = parseArgs()
import threading
i = 1
# n-1 jobs in threads
while i < int(options.jobs):
fred = threading.Thread(target = main, args = (options, args))
fred.start()
print("starting: "+str(i))
i = i + 1
main(options, args)

BIN
fail/bin/fail-x86-tracing (Stored with Git LFS) Executable file

Binary file not shown.

BIN
fail/bin/generic-experiment-client (Stored with Git LFS) Executable file

Binary file not shown.

BIN
fail/bin/generic-experiment-server (Stored with Git LFS) Executable file

Binary file not shown.

BIN
fail/bin/import-trace (Stored with Git LFS) Executable file

Binary file not shown.

BIN
fail/bin/prune-trace (Stored with Git LFS) Executable file

Binary file not shown.

1
fail/bin/resultbrowser.py Symbolic link
View File

@ -0,0 +1 @@
./resultbrowser/run.py

View File

@ -0,0 +1,19 @@
FAIL* Result Browser
Requirements:
* Python
* Flask (sudo pip install Flask)
* MySQLDB (sudo aptitude install python-mysqldb)
* YAML (sudo aptitude install python-yaml)
Based on Flask web microframework (Werkzeug, Jinja 2)
and old school MySQL bindings.
Connects to a FAIL* result database given by a mysql config file.
Usage:
./run.py
Defaults to mysql config file ~/.my.cnf, or
./run.py -c <path to sqlconfig.cnf>
YAML based configuration for table and variant details.

View File

@ -0,0 +1,4 @@
from flask import Flask
app = Flask(__name__)
from app import views

View File

@ -0,0 +1,143 @@
from pprint import pprint
from . import details
from . import model
def scrub(table_name):
return ''.join( chr for chr in table_name if chr.isalnum() or chr == '_' )
class Resulttype:
def __init__(self, name, count):
self.name = name
self.count = count
def getName(self):
return self.name
def getCount(self):
return self.count
class Variant:
def __init__(self, id, name, table, benchmark, detail):
self.id = id
self.dbname = name
self.parenttable = table # TableDetails
self.details = detail # VariantDetails
self.benchmark = benchmark # BenchmarkDetails
self.results = {}
self.totalresults = 0
def getMapper(self):
mapper = self.benchmark.getMapper()
if not mapper: #try benchmark mapper
mapper = self.details.getMapper()
if not mapper: # of not there, try parent tables mapper
mapper = self.parenttable.getMapper()
if not mapper: # no mapper found at all, try default mapper
mapper = model.detaildealer.getDefaultMapper()
return mapper
def addResulttype(self, name, count):
mapper = self.getMapper()
label = mapper.getLabel(name)
oldcount = self.results.setdefault(label, 0)
self.results[label] = oldcount + count
self.totalresults += count
def getResultLabels(self):
return self.results.keys()
def getDBName(self):
return str(self.name)
def getId(self):
return self.id
def getResults(self):
return self.results
def getTableDetails(self):
return self.parenttable
def getBenchmarkDetails(self):
return self.benchmark
def getDetails(self):
return self.details
def getTotals(self):
return self.totalresults
def __str__(self):
ret = "Variant: " + self.getDetails().getTitle() + " - " + self.getBenchmarkDetails().getTitle() +" (id: " + str( self.id )+ ")" + " "
ret += "Total Results: " + str( self.totalresults ) + "\n"
for v in self.results:
ret += "\t" + v.name + ": " + str( v.count ) + "\n"
return ret
__repr__ = __str__
'''A ResultTable contains n Variants'''
class ResultTable:
def __init__(self, name, cfg):
self.name = scrub(name)
self.details = cfg.getTable(name)
self.variants = {}
def addVariant(self, var):
if var.getId() in self.variants:
return
self.variants[var.getId()] = var # Add if not existing yet
def getVariant(self, id):
if id in self.variants:
return self.variants[id]
return None
def getVariantById(self, varid):
for k,v in self.variants.items():
if int(v.getId()) == int(varid):
return v
return None
def getDetails(self):
return self.details
def getVariants(self):
return self.variants
def __str__(self):
ret = "Result: " + self.getDetails().getTitle() + "\n"
for k,v in self.variants.items():
ret += "\t" + str(v) + "\n"
return ret
__repr__ = __str__
'''Overview has n ResultTables'''
class Overview:
def __init__(self):
self.tables = {}
def add(self, table):
self.tables[table.getDetails().getDBName()] = table
def getTables(self):
return self.tables
def getTable(self, dbname):
return self.tables.get(dbname, None)
def getVariantById(self, variant_id):
for key,table in self.tables.items():
variant = table.getVariantById(variant_id)
if variant:
return variant
print("Variant not found.")
return None
def length(self):
return len(self.tables)

View File

@ -0,0 +1,216 @@
class BasicDetails(object):
def __init__(self,name):
self.dbname = name
self.title = name
self.details = ''
self.mapper = None
def getDBName(self):
return self.dbname
def getDetails(self):
return self.details
def setDetails(self,det):
self.details = det
def getTitle(self):
return self.title
def addMapper(self, mapper):
self.mapper = mapper
def getMapper(self):
return self.mapper
def extractDetails(self, dictionary):
self.details = dictionary.pop(('details'), '')
self.title = dictionary.pop(('title'), self.dbname)
custommapping = dictionary.pop(('mapping'), None)
if custommapping:
self.mapper = ResulttypeMapper()
self.mapper.add(custommapping)
else:
self.mapper = None
def __repr__(self):
return self.getTitle() + ": " + self.getDetails()
__str__ = __repr__
class BenchmarkDetails(BasicDetails):
def __init__(self, dbname):
BasicDetails.__init__(self, dbname)
def __repr__(self):
return "Benchmark: " + BasicDetails.__repr__(self)
__str__ = __repr__
class VariantDetails(BasicDetails):
def __init__(self, dbname):
BasicDetails.__init__(self, dbname)
self.benchmarks = {}
def addBenchmark(self, bm):
self.benchmarks[bm.getDBName()] = bm
def getBenchmark(self, dbbm):
return self.benchmarks.get(dbbm, BenchmarkDetails(dbbm))
def __repr__(self):
ret = "Variant: " + BasicDetails.__repr__(self)
for v in self.benchmarks.values():
ret += "\n\t\t" + str(v)
return ret
__str__ = __repr__
class TableDetails(BasicDetails):
def __init__(self, tbl):
BasicDetails.__init__(self, tbl)
self.variants = {}
def addVariant(self, var):
self.variants[var.getDBName()] = var
def getVariant(self, varname):
return self.variants.get(varname, VariantDetails(varname))
def __repr__(self):
ret = "Table: " + BasicDetails.__repr__(self) + "(" + self.getDBName() + ")"
for v in self.variants.values():
ret += "\n\t" + str(v)
return ret
__str__ = __repr__
from pprint import pprint
class ResulttypeMapper(object):
def __init__(self):
self.mappings = {}
def add(self, mapping):
for label, dbnamelist in mapping.items():
self.mappings[label] = dbnamelist
def getLabel(self, dbname):
for label, dbnamelist in self.mappings.items():
if dbname in dbnamelist:
return label
return dbname
def getLabelList(self):
return self.mappings.keys()
def getDBNames(self, label):
return self.mappings.get(label, None)
def __repr__(self):
ret = "Resulttype Mapper:"
for label,dbnames in self.mappings.items():
ret += "\n\t" + label
for db in dbnames:
ret += "\n\t\t" + db
return ret
__str__ = __repr__
import yaml
class DetailDealer:
def __init__(self, configfile=None):
self.tables = {}
self.defaultMapper = ResulttypeMapper()
if not configfile:
return
self.reload(configfile)
if not self.tables:
print("DetailDealer: no details found for " + configfile)
def reload(self, configfile):
self.tables = {}
self.defaultMapper = ResulttypeMapper()
if not configfile:
return # no details.
f = open(configfile)
# use safe_load instead load
cfg = yaml.safe_load(f)
f.close()
# Read out default mapping, if existent
self.extractDefaults(cfg)
tables = cfg.pop('tables', None)
if tables:
for tablename,details in tables.items():
tab = TableDetails(tablename)
# pop: return and remove when key present, else return 'notfound'
tab.extractDetails(details)
variants = details.pop('variants')
for variantname, vdetails in variants.items():
var = VariantDetails(variantname)
var.extractDetails(vdetails)
benchmarks = vdetails.pop('benchmarks')
for benchmark, bdetails in benchmarks.items():
bm = BenchmarkDetails(benchmark)
bm.extractDetails(bdetails)
var.addBenchmark(bm)
tab.addVariant(var)
self.tables[tab.getDBName()] = (tab)
def extractDefaults(self, cfg):
defs = cfg.pop('defaults', None)
if defs:
defmap = defs.pop('mapping', None)
if defmap:
self.defaultMapper.add(defmap)
def getDefaultMapper(self):
return self.defaultMapper
def getTable(self, tablename):
tab = self.tables.get(tablename, None)
if tab:
return tab
return TableDetails(tablename)
def getVariant(self, tablename, variantname):
tab = self.getTable(tablename)
if tab:
return tab.getVariant(variantname)
return VariantDetails(variantname) # Default
def getBenchmark(self, table, variant, bechmark):
tab = self.getTable(table)
if tab:
var = tab.getVariant(variant)
if var:
return var.getBenchmark(bechmark)
return BenchmarkDetails(benchmark) # Default
def __repr__(self):
ret = str(self.defaultMapper) + '\n'
for tabledetails in self.tables.values():
ret += str(tabledetails) + '\n'
return ret
if __name__ == "__main__":
dd = DetailDealer('./test.yml')
pprint(dd)

View File

@ -0,0 +1,211 @@
#!/usr/bin/env python
import MySQLdb
import MySQLdb.cursors
import yaml
import sys
import os.path
from pprint import pprint
from . import data
from . import details
"""Get command line options"""
from optparse import OptionParser
parser = OptionParser()
parser.add_option("-c", "--conf", type="string", help="MySQL config file", dest="config", default= os.path.join(os.path.expanduser("~"),".my.cnf"))
parser.add_option("-s", "--host", type="string", help="Webserver hostname", dest="host", default="localhost")
parser.add_option("-d", "--details", type="string", help="Detailed information (YAML configuration file)", dest="details", default=None)
parser.add_option("-p", "--port", type="string", help="Webserver port", dest="port", default="5000")
opts, args = parser.parse_args()
"""Check if configuration files exist"""
def checkConfigFile(msg, fname):
if not os.path.isfile(fname):
sys.exit("Error: '" + fname + "' not found")
else:
print(msg, "->", fname)
# Check sql config
sqlconfig = opts.config
checkConfigFile("MySQL config", sqlconfig)
# Check details file
if opts.details:
checkConfigFile("Details", opts.details)
# Instantiate global detail dealer, will be initialized in reloadOverview
detaildealer = details.DetailDealer()
"""Remove all characters from string except alphanuermics and _"""
def scrub(table_name):
return ''.join( chr for chr in table_name if chr.isalnum() or chr == '_' )
"""Global mysql handles"""
db = None
cur = None
def loadSession(dbconf):
global db
if db:
db.close()
db = MySQLdb.connect(read_default_file=dbconf, cursorclass=MySQLdb.cursors.DictCursor)
return db.cursor()
def closeSession():
if cur: cur.close()
global db
db.close()
db = None
'''Populate variant results for overview data'''
def getVariants(cur, table):
restbl = table.getDetails().getDBName()
cur.execute("""SELECT sum((t.time2 - t.time1 + 1) * width) AS total, resulttype,variant, v.id as variant_id, benchmark, details FROM variant v JOIN trace t ON v.id = t.variant_id JOIN fspgroup g ON g.variant_id = t.variant_id AND g.instr2 = t.instr2 AND g.data_address = t.data_address JOIN %s r ON r.pilot_id = g.pilot_id JOIN fsppilot p ON r.pilot_id = p.id GROUP BY v.id, resulttype, details""" % (restbl)) # % is used here, as a tablename must not be quoted
res = cur.fetchall()
rdic = {}
# Build dict with variant id as key
for r in res:
# if variant entry already exists:
variant = table.getVariant(int(r['variant_id']))
if not variant: # if variant did not exist yet, create it:
variant_details = detaildealer.getVariant(restbl, r['variant'])
benchmark_details = detaildealer.getBenchmark(restbl, r['variant'], r['benchmark'])
table_details = detaildealer.getTable(restbl)
variant = data.Variant(int(r['variant_id']), r['variant'], table_details, benchmark_details, variant_details)
variant.addResulttype(r['resulttype'], r['total'])
table.addVariant(variant)
'''Get overview data for index page'''
def reloadOverview():
overview = data.Overview()
detaildealer.reload(opts.details)
cur = loadSession(sqlconfig)
cur.execute("show tables like 'result_%'")
result_tables = cur.fetchall()
results = {}
for rdic in result_tables:
# r is the tablename, -> result_FOOBAR
for key, tablename in rdic.items():
table = data.ResultTable(tablename,detaildealer)
getVariants(cur, table)
overview.add(table)
# Check if objdump table exists
cur.execute("SHOW TABLES like 'objdump'")
objdump_exists = (len(cur.fetchall()) == 1)
closeSession()
return overview, objdump_exists
"""Load overview data at server startup"""
print("Loading overview data from database. This may take a while ...")
overview_data, objdump_exists = reloadOverview()
print("done.")
## Get overview data for views.index()
def getOverview():
return overview_data
def objdumpExists():
return objdump_exists
"""Get Results for one variant id"""
def getVariantResult(table, variantid):
cur = loadSession(sqlconfig)
restbl = scrub(table)
stmt = "SELECT resulttype, count(*) as total from %s r join fsppilot on r.pilot_id=fsppilot.id join variant on fsppilot.variant_id=variant.id" % (restbl)
where = " WHERE variant.id = %s group by resulttype ORDER BY resulttype "
stmt = stmt + where
cur.execute(stmt, variantid)
res = cur.fetchall()
closeSession()
return res
'''Show objdump together with according injection result types.'''
def getCode(result_table, variant_id, resultlabel=None):
result_table = scrub(result_table)
filt = ''
if not variant_id or not result_table:
return None
variant = overview_data.getVariantById(variant_id)
mapper = variant.getMapper()
if resultlabel:
dbnames = mapper.getDBNames(resultlabel)
if dbnames:
filt = " and ( "
for dbn in dbnames[:-1]:
filt += "resulttype = '" + dbn + "' OR "
filt += "resulttype = '" + dbnames[-1] +"' ) "
else:
filt = " and resulttype = '" + resultlabel + "' "
# I especially like this one:
select = "SELECT instr_address, opcode, disassemble, comment, sum(t.time2 - t.time1 + 1) as totals, GROUP_CONCAT(DISTINCT resulttype SEPARATOR ', ') as results FROM variant v "
join = " JOIN trace t ON v.id = t.variant_id JOIN fspgroup g ON g.variant_id = t.variant_id AND g.instr2 = t.instr2 AND g.data_address = t.data_address JOIN %s r ON r.pilot_id = g.pilot_id JOIN fsppilot p ON r.pilot_id = p.id JOIN objdump ON objdump.variant_id = v.id AND objdump.instr_address = injection_instr_absolute " %(scrub(result_table))
where = "WHERE v.id = %s "
group = "GROUP BY injection_instr_absolute ORDER BY totals DESC "
cur = loadSession(sqlconfig)
stmt = select + join + where + filt + group
cur.execute(stmt, (variant_id))
dump = cur.fetchall()
closeSession()
resulttypes = variant.getResultLabels()
return dump, resulttypes
def getCodeExcerpt(variant_id, instr_addr):
code = {}
limit = 8
cur = loadSession(sqlconfig)
cur.execute( """(SELECT instr_address, opcode, disassemble, comment FROM objdump \
WHERE instr_address < %s AND variant_id = %s \
ORDER BY instr_address DESC LIMIT %s) \
ORDER BY instr_address ASC""" , (instr_addr, variant_id, limit))
below = cur.fetchall()
code['below'] = below
cur.execute("""SELECT instr_address, opcode, disassemble, comment FROM objdump \
WHERE instr_address >= %s AND variant_id = %s \
ORDER BY instr_address ASC LIMIT %s""", (instr_addr, variant_id, limit+1))
upper = cur.fetchall()
code['upper'] = upper
closeSession()
return code
def getResultsbyInstruction(result_table, variant_id, instr_addr, resultlabel=None):
restypefilter = None
if resultlabel:
variant = overview_data.getVariantById(variant_id)
mapper = variant.getMapper()
if resultlabel:
dbnames = mapper.getDBNames(resultlabel)
if dbnames:
restypefilter = " and ( "
for dbn in dbnames[:-1]:
restypefilter += "resulttype = '" + dbn + "' OR "
restypefilter += "resulttype = '" + dbnames[-1] +"' ) "
select = "SELECT bitoffset as 'Bit Offset', hex(injection_instr_absolute) as 'Instruction Address', hex(original_value) as 'Original Value', hex(data_address) as 'Data Address', resulttype as 'Result Type', details as 'Details' from %s " % scrub(result_table)
join = "JOIN fsppilot ON pilot_id = fsppilot.id "
where = "WHERE variant_id = %s and injection_instr_absolute = %s "
order = "ORDER BY data_address, bitoffset"
cur = loadSession(sqlconfig)
if not restypefilter:
stmt = select + join + where + order
cur.execute(stmt, (variant_id, instr_addr))
else:
stmt = select + join + where + restypefilter + order
cur.execute(stmt, (variant_id, instr_addr))
res = cur.fetchall()
closeSession()
return res
def showDBstatus():
res = "TODO"
return res

View File

@ -0,0 +1,5 @@
dl.horizontal {font-size:12px; width:850px;}
dl.horizontal dt {float:left; width:300px; clear:both; margin:0 0 5px 0; padding:3px;}
dl.horizontal dd {float:left; width:500px; border:1px solid #aaaaaa; margin:0 0 5px 0; padding:2px; -moz-box-shadow: 1px 1px 3px #aaaaaa;}
dl.horizontal dd span {background:#91b4e6; display:block; color:black; text-indent:4px;}

View File

@ -0,0 +1,214 @@
body {
margin: 0;
padding: 0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #444;
}
a:visited {
color : #444;
}
a:link {
color : #444;
text-decoration: none;
}
/*
* Create dark grey header with a white logo
*/
header {
background-color: #2B2B2B;
height: 30px;
width: 100%;
opacity: .9;
margin-bottom: 10px;
}
header h1.logo {
margin: 0;
font-size: 1.5em;
color: #fff;
text-transform: uppercase;
float: left;
}
header h1.logo:hover {
color: #fff;
text-decoration: none;
}
/*
* Center the body content
*/
.container {
width: 95%;
margin: 0 auto;
}
div.jumbo {
padding: 10px 0 30px 0;
background-color: #eeeeee;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
}
h2 {
font-size: 1.2em;
margin-top: 40px;
text-align: center;
letter-spacing: -1px;
}
h3 {
font-size: 1.0em;
font-weight: 100;
margin-top: 30px;
text-align: center;
/*letter-spacing: -1px;*/
color: #999;
}
#footer {
text-align: center;
color: #444;
font-size:10pt;
}
/*
* Table styles
*/
.codetable, .resulttable, .overviewtable{
font-family: monospace;
margin: auto;
}
.overviewtable {
border: 1px gray solid;
margin: auto;
}
/*
.overviewtable tr:nth-child(4n), .overviewtable tr:nth-child(4n-1) {
background: #fff;
}
.overviewtable tr:nth-child(4n-2), .overviewtable tr:nth-child(4n-3) {
background: #e8ffb3;
}
*/
.resulttable, .codetable, .overviewtable{
margin: auto;
border: solid #ccc 1px;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
border-radius: 6px;
-webkit-box-shadow: 0 1px 1px #ccc;
-moz-box-shadow: 0 1px 1px #ccc;
box-shadow: 0 1px 1px #ccc;
}
.codetable tr:hover, .resulttable tr:hover {
background: #fbf8e9;
/* -o-transition: all 0.1s ease-in-out;
-webkit-transition: all 0.1s ease-in-out;
-moz-transition: all 0.1s ease-in-out;
-ms-transition: all 0.1s ease-in-out;
transition: all 0.1s ease-in-out;
*/
}
.resulttable td, .resulttable th, .codetable td, .codetable th {
border-left: none;
border-top: none;
padding: 2px;
text-align: left;
display: table-cell;
}
.codetable td a {
text-decoration: none;
display: block;
padding: 0px;
height: 100%;
}
.resulttable th, .codetable th, .overviewtable th {
background-color: #dce9f9;
background-image: -webkit-gradient(linear, left top, left bottom, from(#efe), to(#91b4e6));
background-image: -webkit-linear-gradient(top, #efe, #91b4e6);
background-image: -moz-linear-gradient(top, #efe, #91b4e6);
background-image: -ms-linear-gradient(top, #efe, #91b4e6);
background-image: -o-linear-gradient(top, #efe, #91b4e6);
background-image: linear-gradient(top, #efe, #91b4e6);
-webkit-box-shadow: 0 1px 0 rgba(255,255,255,.8) inset;
-moz-box-shadow:0 1px 0 rgba(255,255,255,.8) inset;
box-shadow: 0 1px 0 rgba(255,255,255,.8) inset;
border-top: none;
text-shadow: 0 1px 0 rgba(255,255,255,.5);
}
.resulttable td:first-child, .resulttable th:first-child, .codetable td:first-child, .codetable th:first-child {
border-left: none;
}
.resulttable th:first-child, .codetable th:first-child {
-moz-border-radius: 6px 0 0 0;
-webkit-border-radius: 6px 0 0 0;
border-radius: 6px 0 0 0;
}
.resulttable th:last-child, .codetable th:last-child {
-moz-border-radius: 0 6px 0 0;
-webkit-border-radius: 0 6px 0 0;
border-radius: 0 6px 0 0;
}
.resulttable th:only-child, .codetable th:only-child{
-moz-border-radius: 6px 6px 0 0;
-webkit-border-radius: 6px 6px 0 0;
border-radius: 6px 6px 0 0;
}
.resulttable tr:last-child td:first-child, .codetable tr:last-child td:first-child {
-moz-border-radius: 0 0 0 6px;
-webkit-border-radius: 0 0 0 6px;
border-radius: 0 0 0 6px;
}
.resulttable tr:last-child td:last-child, .codetable tr:last-child td:last-child {
-moz-border-radius: 0 0 6px 0;
-webkit-border-radius: 0 0 6px 0;
border-radius: 0 0 6px 0;
}
.resulttypemenu {
text-align: center;
font-size: 12px
}
/*
* Display navigation links inline
*/
.menu {
float: right;
margin-top: 8px;
}
.menu li {
display: inline;
}
.menu li + li {
margin-left: 35px;
}
.menu li a {
color: #999;
text-decoration: none;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -0,0 +1,11 @@
{% extends "layout.html" %}
{% block content %}
{% if status %}
<h2>About</h2>
{{ status }}
{% else %}
<h2> Sorry, no status information.</h2>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,50 @@
{% extends "layout.html" %}
{% block content %}
<h1>Variant Results</h1>
{% if resulttypes %}
<table>
<tr><td>Result Table</td><td><b>{{ variant_details.getTableDetails().getTitle() }}</b><td></tr>
<tr><td>Variant </td><td><b>{{ variant_details.getDetails().getTitle() }}</b></td></tr>
<tr><td>Details </td><td><b>{{ variant_details.getDetails().getDetails() }}</b></td></tr>
<tr><td>Benchmark </td><td><b>{{ variant_details.getBenchmarkDetails().getTitle() }}</b></td></tr>
<tr><td>Details </td><td><b>{{ variant_details.getBenchmarkDetails().getDetails() }}</b></td></tr>
<tr><td>Result Count </td><td><b>{{ results|length }}</b></td></tr>
</table>
<hr>
<p class='resulttypemenu'>| <a href="{{ url_for('code', variant_id=request.args.get('variant_id'), table=request.args.get('table')) }}">All Results</a> |
{% for restype in resulttypes %}
<a href="{{ url_for('code', variant_id=request.args.get('variant_id'), table=request.args.get('table'), resulttype=restype )}}">{{ restype}}</a> |
{% endfor %}
</p>
<hr>
<table class="codetable">
<tr>
<th>Address</th>
<th>Opcode</th>
<th>Disassembly</th>
<th>Comment</th>
<th>Results</th>
<th># Results</th>
</tr>
{% for d in results %}
{% set link = url_for('instr_details', table=request.args.get('table'), variant_id=request.args.get('variant_id'), benchmark=request.args.get('benchmark'), variant=request.args.get('variant'), resulttype=request.args.get('resulttype'), instr_address=d['instr_address'] ) %}
<tr>
<td><a href="{{ link }}">{{ "0x%x"|format(d['instr_address']) }}</a></td>
<td><a href="{{ link }}">{{ d['opcode'] }}</a></td>
<td><a href="{{ link }}">{{ d['disassemble'] }}</a></td>
<td><a href="{{ link }}">{{ d['comment'] }}</a></td>
<td><a href="{{ link }}">{{ d['results'] }}</a></td>
<td><a href="{{ link }}">{{ d['totals'] }}</a></td>
</tr>
{% endfor %}
</table>
{% else %}
<h1> Sorry, no dump found.</h1>
{% endif %}
{%endblock %}

View File

@ -0,0 +1,55 @@
{% extends "layout.html" %}
{% block content %}
{%if overview %}
{% for tablekey, resulttable in overview.getTables().items() %}
<h2>Result table: {{ resulttable.getDetails().getTitle() }}</h2>
-> <a href="{{ url_for('index', reload=1) }}">Reload data</a> <-
<dl>
<dt>Details:</td>
<dd>
{{ resulttable.getDetails().getDetails() }}
<dd>
</dl>
{% if not objdump_there %}
No objdump found
{% endif %}
<table class="overviewtable" cellspacing="0px">
{% for varid, variant in resulttable.getVariants().items() %}
<tr><th>
{% set variant_title=variant.getDetails().getTitle() ~ " - " ~ variant.getBenchmarkDetails().getTitle() ~ " id: " ~ variant.getId() %}
{% if objdump_there %}
<a href="{{ url_for('code',table=resulttable.getDetails().getDBName(), variant_id=variant.getId() ) }}">{{ variant_title }}</a>
{% else %}
{{ variant_title }}
{% endif %}
(Total: {{ variant.getTotals() }})</a>
</th></tr>
<tr><td>
{% if variant.getDetails().getDetails() %}
Variant Details: {{ variant.getDetails().getDetails() }}
{% endif %}
{% if variant.getBenchmarkDetails().getDetails() %}
<br> Benchmark Details: {{ variant.getBenchmarkDetails().getDetails() }}</td></tr>
{% endif %}
<tr><td>
<dl class="horizontal">
{% for reslabel,count in variant.getResults().items() %}
<dt>
{% if objdump_there %}
<a href="{{ url_for('code', table=resulttable.getDetails().getDBName(), variant_id=variant.getId(), resulttype=reslabel ) }}">{{ reslabel }}</a>
{% else %}
{{ reslabel }}
{% endif %}
</dt>
<dd><span style="width:{{count * 100 / variant.getTotals() }}%;">{{count}}</span></dd>
{% endfor %}
</dl></td></tr>
{% endfor %}
</table>
{% endfor %}
{% else %}
<h2> Sorry, no results found.</h2>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,65 @@
{% extends "layout.html" %}
{% block content %}
{% if code %}
<h1>Result by Instruction</h1>
<table>
<tr><td>Result Table</td><td><b>{{ variant_details.getTableDetails().getTitle() }}</b><td></tr>
<tr><td>Variant </td><td><b>{{ variant_details.getDetails().getTitle() }}</b></td></tr>
<tr><td>Benchmark </td><td><b>{{ variant_details.getBenchmarkDetails().getTitle() }}</b></td></tr>
<tr><td>Details </td><td><b>{{ variant_details.getDetails().getDetails() }}</b></td></tr>
<tr><td>Instruction Address </td><td><b>{{ "0x%x (Dec: %d)"|format(request.args.get('instr_address')|int, request.args.get('instr_address')|int) }}</b></td></tr>
<tr><td>Total Results</td><td><b>{{ result|length }}</b></td></tr>
</table>
<hr>
<h2>Code</h2>
<table class="codetable">
<tr>
<th>Address</th>
<th>Opcode</th>
<th>Disassembly</th>
<th>Comment</th>
</tr>
{% for d in code['below'] %}
<tr>
<td>{{ "0x%x"|format(d['instr_address']) }}</td>
<td>{{ d['opcode'] }}</td>
<td>{{ d['disassemble'] }}</td>
<td>{{ d['comment'] }}</td>
</tr>
{% endfor %}
<tr style="font-weight: bold">
<td>{{ "0x%x"|format(code['upper'][0]['instr_address']) }}</td>
<td>{{ code['upper'][0]['opcode'] }}</td>
<td>{{ code['upper'][0]['disassemble'] }}</td>
<td>{{ code['upper'][0]['comment'] }}</td>
</tr>
{% for d in code['upper'][1:] %}
<tr>
<td>{{ "0x%x"|format(d['instr_address']) }}</td>
<td>{{ d['opcode'] }}</td>
<td>{{ d['disassemble'] }}</td>
<td>{{ d['comment'] }}</td>
</tr>
{% endfor %}</table>
<hr>
<h2>Results ({{ result|length }})</h2>
<table class="resulttable">
<tr>
{% for key, value in result[0].items() -%}
<th>{{ key }}</th>
{% endfor -%}
</tr>
{% for r in result -%}
<tr>
{% for k,v in r.items() -%}
<td>{{ v }}</td>
{% endfor -%}
</tr>
{% endfor -%}
</tr>
</table>
{% else %}
<h2> Sorry, no details found.</h2>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<title>FAIL* Results</title>
<strong><link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}"></strong>
<strong><link rel="stylesheet" href="{{ url_for('static', filename='css/barchart.css') }}"></strong>
</head>
<body>
<header>
<div class="container">
<h1 class="logo">FAIL*</h1>
<strong><nav>
<ul class="menu">
<li><a href="{{ url_for('index') }}">Home</a></li>
<li><a href="{{ url_for('about') }}">About</a></li>
</ul>
</nav></strong>
</div>
</header>
<div class="container">
{% block content %}
{% endblock %}
</div>
<hr>
<div id="footer">
&copy; FAIL* - Fault Injection Leveraged
</div>
</body>
</html>

View File

@ -0,0 +1,42 @@
from flask import render_template,request
from app import app
# import model
# import data
from . import model
from . import data
@app.route('/')
@app.route('/index')
def index():
reload_overview = request.args.get('reload', False)
if reload_overview:
print("Reloading overview...")
model.reloadOverview()
return render_template("index.html", overview=model.getOverview(), objdump_there = model.objdumpExists())
@app.route('/code')
def code():
variant_id = request.args.get('variant_id', None)
resulttype = request.args.get('resulttype', None)
table = request.args.get('table', None)
res,restypes = model.getCode(table, variant_id, resulttype)
var_dets = model.getOverview().getVariantById(variant_id)
return render_template("code.html", results=res, resulttypes=restypes, variant_details=var_dets )
@app.route('/instr_details')
def instr_details():
table = request.args.get('table', None)
variant_id = request.args.get('variant_id', None)
instr_addr = request.args.get('instr_address', None)
resulttype = request.args.get('resulttype', None)
codeexcerpt = model.getCodeExcerpt(variant_id, instr_addr)
var_dets = model.getOverview().getVariantById(variant_id)
results = model.getResultsbyInstruction(table, variant_id, instr_addr, resulttype)
return render_template("instr_details.html", code=codeexcerpt, result=results, variant_details=var_dets)
@app.route('/about')
def about():
stat = model.showDBstatus()
return render_template("about.html", status=stat)

View File

@ -0,0 +1,69 @@
# YAML-based: http://yaml.org/
# Online parser for testing: http://yaml-online-parser.appspot.com/
# Some notes:
# YAML is case-sensitive and structured by indention!
#
# The 'defaults' section describes an *optional* default result type mapping for all tables.
# The 'tables' section describes result tables in more detail.
# A table consists of variants, each variant of benchmarks.
# Each of these configuration items
# title: Table title
# details: Some textual description
# mapping: A distinct mapping, if not set, the parent item's mapping is used
defaults:
mapping:
Everything OK:
- OK
- OK_DETECTED_ERROR
- OK_WRONG_CONTROL_FLOW
Outside Data Section:
- ERR_OUTSIDE_DATA
Hardware Trap:
- ERR_OUTSIDE_TEXT
- ERR_TRAP
Silent Data Corruption:
- ERR_WRONG_RESULT
tables:
result_CoredVoterProtoMsg:
title: CoRed Voter Experiment Results
variants:
x86_cored_voter:
title: x86 CoRed Voter Experiment
details: Some interesting details about the experiment.
benchmarks:
ean-random-4:
title: Random 4 bit injections
details: |
The details can also written this way.
The pipe insert the newlines. Cool, isn't it?
ean-random-5:
title: Random 5 bit injections
details: Details about 5 bit random injection benchmark.
mapping:
Alright:
- OK
- OK_DETECTED_ERROR
- OK_WRONG_CONTROL_FLOW
Not Alright:
- ERR_OUTSIDE_DATA
- ERR_OUTSIDE_TEXT
- ERR_TRAP
- ERR_WRONG_RESULT
Timeout:
- ERR_TIMEOUT
# Another variant within result_CoredVoterProtoMsg
x86_cored_voter2:
title: variant title
details: variant details
benchmarks:
ean-random-2:
title: benchmarktitle
details: some benchmark details
ean-random-3:
title: benchmark random 3
details: some benchmark 3 details

5
fail/bin/resultbrowser/run.py Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env python
from app import app
from app import model
app.run(debug=False, port=int(model.opts.port), host=model.opts.host)

BIN
fail/share/BIOS-bochs-latest (Stored with Git LFS) Executable file

Binary file not shown.

BIN
fail/share/vgabios.bin (Stored with Git LFS) Executable file

Binary file not shown.

389
flake.nix
View File

@ -22,7 +22,19 @@ rec {
overlays = [];
};
boost174_pkgs =
i386_pkgs = import nixpkgs {
inherit system;
config.allowUnfree = true;
overlays = [];
# Build crosscompiler
crossSystem = {
config = "i386-elf";
libc = "newlib";
};
};
boost_pkgs =
import (builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/824421b1796332ad1bcb35bc7855da832c43305f.tar.gz";
sha256 = "sha256:1w6cjnakz1yi66rs8c6nmhymsr7bj82vs2hz200ipi1sfiq8dy4y";
@ -30,6 +42,14 @@ rec {
inherit system;
};
libdwarf_pkgs =
import (builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/f597e7e9fcf37d8ed14a12835ede0a7d362314bd.tar.gz";
sha256 = "sha256:1l79hh7jh7m8yc5mvc8dbg6s8rf30bgm994kf07xriqbzwfn158r";
}) {
inherit system;
};
inherit (pkgs) lib stdenv;
# =========================================================================================
@ -75,27 +95,49 @@ rec {
buildDebug = mkBuildScript "Debug";
buildRelease = mkBuildScript "Release";
# Use this to specify commands that should be ran after entering fish shell
initProjectShell = pkgs.writers.writeFish "init-shell.fish" ''
echo "Entering \"${description}\" environment..."
# Determine the project root, used e.g. in cmake scripts
set -g -x FLAKE_PROJECT_ROOT (git rev-parse --show-toplevel)
# Rust Bevy:
# abbr -a build-release-windows "CARGO_FEATURE_PURE=1 cargo xwin build --release --target x86_64-pc-windows-msvc"
# Add project-local fish abbrs here
abbrs = {
fail = "perl ./scripts/menu.pl";
# C/C++:
# abbr -a cmake-debug "${cmakeDebug}"
# abbr -a cmake-release "${cmakeRelease}"
# abbr -a build-debug "${buildDebug}"
# abbr -a build-release "${buildRelease}"
# cmake-debug = "${cmakeDebug}";
# cmake-release = "${cmakeRelease}";
# build-debug = "${buildDebug}";
# build-release = "${buildRelease}";
};
eraseAbbr = name: value: ''abbr --erase ${name} 2>/dev/null'';
createAbbr = name: value: ''abbr -a ${name} "${value}"'';
# This will be sourced by the global fish config if INIT_PROJECT_SHELL gets unset
unloadProjectShell = pkgs.writers.writeFish "unload-shell.fish" ''
echo "Unloading \"${description}\" environment..."
${builtins.concatStringsSep "\n" (lib.mapAttrsToList eraseAbbr abbrs)}
'';
# This will be sourced by the global fish config if INIT_PROJECT_SHELL gets set
initProjectShell = pkgs.writers.writeFish "init-shell.fish" ''
# Unload just in case, to not have redefinition errors
source ${unloadProjectShell}
echo "Sourcing \"${description}\" environment..."
${builtins.concatStringsSep "\n" (lib.mapAttrsToList createAbbr abbrs)}
'';
in
builtins.concatStringsSep "\n" [
# Launch into pure fish shell
''
exec "$(type -p fish)" -C "source ${initProjectShell} && abbr -a menu '${pkgs.bat}/bin/bat "${initProjectShell}"'"
# Can't do the "exec" with nix-direnv
# - The "exec fish" would call direnv again => Infinite loop
# - The shellHook is Bash/POSIX, so fish syntax doesn't work
# exec "$(type -p fish)" -C "source ${initProjectShell} && abbr -a menu '${pkgs.bat}/bin/bat "${initProjectShell}"'"
# Determine the project root, used e.g. in cmake scripts
export FLAKE_PROJECT_ROOT="$(git rev-parse --show-toplevel)"
export INIT_PROJECT_SHELL="${initProjectShell}"
export UNLOAD_PROJECT_SHELL="${unloadProjectShell}"
''
];
@ -111,14 +153,21 @@ rec {
pyyaml
]);
boost174 = boost174_pkgs.boost174;
# perl = pkgs.perl.withPackages (p:
# with p; [
# # Those are already installed system-wide
# # PLS
# # PerlTidy
#
# NetOpenSSH
# ]);
libpcl = stdenv.mkDerivation rec {
pname = "libpcl1";
version = "1.12-2";
src = pkgs.fetchurl {
url = "http://launchpadlibrarian.net/521269537/libpcl1_1.12-2_amd64.deb";
url = "http://launchpadlibrarian.net/521269537/${pname}_${version}_amd64.deb";
hash = "sha256-GL3mjPAccAtRMAJPnDMCHiDf6xNvGi4oUWylOIqBjP0=";
};
@ -131,9 +180,6 @@ rec {
installPhase = ''
runHook preInstall
ls -al
# dpkg-deb -x ${pname}_${version}_amd64.deb libpcl
mkdir -p $out/lib
cp -rv usr/lib/x86_64-linux-gnu/* $out/lib/
@ -141,6 +187,209 @@ rec {
'';
};
wasi-sdk = stdenv.mkDerivation rec {
pname = "wasi-sdk";
version = "29";
src = let
baseurl = "https://github.com/WebAssembly/wasi-sdk/releases/download";
in
builtins.fetchTarball {
url = "${baseurl}/${pname}-${version}/${pname}-${version}.0-x86_64-linux.tar.gz";
sha256 = "sha256:16afis71iqfvwiny4dz0lk9f7wbary0wa67ybwyhywr8g57ss6hq";
};
nativeBuildInputs = with pkgs; [
autoPatchelfHook
];
buildInputs = with pkgs; [
libgcc.lib
];
dontBuild = true;
installPhase = ''
runHook preInstall
mkdir -p $out
cp -rv ./* $out/
runHook postInstall
'';
};
iwasm = stdenv.mkDerivation rec {
pname = "iwasm";
version = "2.4.4";
src = let
baseurl = "https://github.com/bytecodealliance/wasm-micro-runtime/releases/download";
in
builtins.fetchTarball {
url = "${baseurl}/WAMR-${version}/${pname}-${version}-x86_64-ubuntu-22.04.tar.gz";
sha256 = "sha256:05irihz3yf7hpc0a59qz9i62imhrsni9xy9nxwsn6b8s92c1yzrp";
};
nativeBuildInputs = with pkgs; [
autoPatchelfHook
];
buildInputs = with pkgs; [
libz
zstd
libgcc.lib
];
dontBuild = true;
installPhase = ''
runHook preInstall
mkdir -p $out/bin
cp -rv ./* $out/bin/
runHook postInstall
'';
};
wamrc = stdenv.mkDerivation rec {
pname = "wamrc";
version = "2.4.4";
src = let
baseurl = "https://github.com/bytecodealliance/wasm-micro-runtime/releases/download";
in
builtins.fetchTarball {
url = "${baseurl}/WAMR-${version}/${pname}-${version}-x86_64-ubuntu-22.04.tar.gz";
sha256 = "sha256:0264arh03gc35z0zdvw07qdvqgfvsxr3qgl1aszghwicmdmh4sqj";
};
nativeBuildInputs = with pkgs; [
autoPatchelfHook
];
buildInputs = with pkgs; [
libz
zstd
libgcc.lib
];
dontBuild = true;
installPhase = ''
runHook preInstall
mkdir -p $out/bin
cp -rv ./* $out/bin/
runHook postInstall
'';
};
wamr = stdenv.mkDerivation {
pname = "wamr";
version = "2.4.4";
src = pkgs.fetchFromGitea {
domain = "gitea.local.chriphost.de";
owner = "christoph";
repo = "wamr";
rev = "fd69a4e76ec0d384bd79f514772b7dfa240fc0d7";
hash = "sha256-rlCx4isI0k6rC9E0hWIA9LeinqiACug7zxj9z/e4SBQ=";
};
dontBuild = true;
installPhase = ''
runHook preInstall
mkdir -p $out
cp -rv ./* $out/
runHook postInstall
'';
};
mkLibiwasm = {
buildenv,
platform,
buildtype,
cflags,
extraCmakeFlags ? [],
}:
buildenv.mkDerivation {
pname = "libiwasm";
version = "2.4.4";
src = pkgs.fetchFromGitea {
domain = "gitea.local.chriphost.de";
owner = "christoph";
repo = "wamr";
rev = "cda2009deb85511089b04b0ac736ad4da2d07e58";
hash = "sha256-CN6xTiwzF4Jbrpf21TF5c/C03Xb3urwkibRuIXjoU/w=";
};
nativeBuildInputs = with pkgs; [cmake];
dontStrip = true;
cmakeBuildType = buildtype;
cmakeFlags =
extraCmakeFlags
++ [
"-DCMAKE_VERBOSE_MAKEFILE=ON"
"-DCMAKE_COLOR_DIAGNOSTICS=ON"
"-DWAMR_BUILD_PLATFORM=${platform}"
"-DWAMR_BUILD_TARGET=X86_32"
"-DWAMR_BUILD_AOT=1"
"-DWAMR_BUILD_WAMR_COMPILER=0"
"-DWAMR_BUILD_INTERP=1"
"-DWAMR_BUILD_FAST_INTERP=0"
"-DWAMR_BUILD_JIT=0"
"-DWAMR_BUILD_FAST_JIT=0"
"-DWAMR_BUILD_LIBC_BUILTIN=1"
"-DWAMR_BUILD_LIBC_WASI=0"
"-DWAMR_BUILD_SIMD=0"
];
# Since GCC 15, implicit declarations are an error. Disable this.
NIX_CFLAGS_COMPILE = "-Wno-error=implicit-function-declaration " + cflags;
};
libiwasm-baremetal-debug = mkLibiwasm {
buildenv = i386_pkgs.stdenv;
platform = "baremetal";
buildtype = "Debug";
cflags = "-O0 -ggdb3";
extraCmakeFlags = [
"-DCMAKE_SYSTEM_NAME=Generic"
"-DCMAKE_SYSTEM_PROCESSOR=i386"
"-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY"
];
};
libiwasm-baremetal-release = mkLibiwasm {
buildenv = i386_pkgs.stdenv;
platform = "baremetal";
buildtype = "MinSizeRel";
cflags = "-O2 -ggdb3 -DNDEBUG";
extraCmakeFlags = [
"-DCMAKE_SYSTEM_NAME=Generic"
"-DCMAKE_SYSTEM_PROCESSOR=i386"
"-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY"
];
};
libiwasm-linux-debug = mkLibiwasm {
buildenv = pkgs.multiStdenv;
platform = "linux";
buildtype = "Debug";
cflags = "-O0 -ggdb3";
};
libiwasm-linux-release = mkLibiwasm {
buildenv = pkgs.multiStdenv;
platform = "linux";
buildtype = "MinSizeRel";
cflags = "-O2 -ggdb3 -DNDEBUG";
};
# ===========================================================================================
# Specify dependencies
# https://nixos.org/manual/nixpkgs/stable/#ssec-stdenv-dependencies-overview
@ -149,56 +398,90 @@ rec {
# Add dependencies to nativeBuildInputs if they are executed during the build:
# - Those which are needed on $PATH during the build, for example cmake and pkg-config
# - Setup hooks, for example makeWrapper
# - Setup hooks, for example makeWrapper/autoPatchelfHook
# - Interpreters needed by patchShebangs for build scripts (with the --build flag), which can be the case for e.g. perl
nativeBuildInputs = with pkgs; [
autoPatchelfHook
just
gdb
xxd
wabt
grub2
xorriso
mariadb.client
dbeaver-bin
iwasm
wamrc
fail-bin
# perl
# Don't install to not pollute our PATH. Just export as environment variable.
# wasi-sdk
# libiwasm_debug
# libiwasm_release
];
# Add dependencies to buildInputs if they will end up copied or linked into the final output or otherwise used at runtime:
# - Libraries used by compilers, for example zlib
# - Interpreters needed by patchShebangs for scripts which are installed, which can be the case for e.g. perl
buildInputs = with pkgs; [
python # For resultbrowser
buildInputs = with pkgs; [];
alsa-lib # libasound.so.2
boost174 # libboost_coroutine.so.1.74.0, libboost_regex.so.1.74.0, libboost_thread.so.1.74.0
capstone_4 # libcapstone.so.4
libdwarf # libdwarf.so.1
libelf # libelf.so.1
mariadb-connector-c # libmariadb.so.3
libpcl # libpcl.so.1
protobuf # libprotobuf.so.32
SDL # libSDL-1.2.so.0
libx11 # libX11.so.6
libxrandr # libXrandr.so.2
libz # libz.so.1
];
# ===========================================================================================
# Define buildable + installable packages
# ===========================================================================================
package = stdenv.mkDerivation rec {
inherit nativeBuildInputs buildInputs;
fail-bin = stdenv.mkDerivation {
pname = "fail";
version = "1.0.0";
src = ./.;
nativeBuildInputs = with pkgs; [
autoPatchelfHook
];
buildInputs = with pkgs; [
# FAIL runtime dependencies
python # bochs-experiment-runner.py, resultbrowser.py
alsa-lib # libasound.so.2
boost_pkgs.boost174 # libboost_coroutine.so.1.74.0, libboost_regex.so.1.74.0, libboost_thread.so.1.74.0
capstone_4 # libcapstone.so.4
libdwarf_pkgs.libdwarf # libdwarf.so.1
elfutils # libelf.so.1
mariadb # libmariadb.so.3
libpcl # libpcl.so.1
protobuf_21 # libprotobuf.so.32
SDL # libSDL-1.2.so.0
libx11 # libX11.so.6
libxrandr # libXrandr.so.2
libz # libz.so.1
];
installPhase = ''
runHook preInstall
mkdir -p $out
cp -rv ./bin $out/bin
cp -rv ./share $out/share
cp -rv ./fail/bin $out/bin
cp -rv ./fail/share $out/share
runHook postInstall
'';
};
in rec {
in {
# Provide package for "nix build"
packages = {
default = package;
default = fail-bin;
fail = fail-bin;
wasi-sdk = wasi-sdk;
iwasm = iwasm;
wamrc = wamrc;
};
apps = {
default = flake-utils.lib.mkApp {drv = fail-bin;};
fail = fail-bin;
wasi-sdk = wasi-sdk;
iwasm = iwasm;
wamrc = wamrc;
};
apps.default = flake-utils.lib.mkApp {drv = package;};
devShells = {
# Provide default environment for "nix develop".
@ -212,7 +495,23 @@ rec {
# =========================================================================================
# Dynamic libraries from buildinputs:
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs;
# LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs;
JUST_WORKING_DIRECTORY = "/home/christoph/Notes/TU/MastersThesis/FailNix";
JUST_JUSTFILE = "/home/christoph/Notes/TU/MastersThesis/FailNix/scripts/nixos.just";
# Those are read by the justfile
FAIL_SHARE = "${fail-bin}/share";
WASI_ROOT = wasi-sdk;
WAMR_ROOT = wamr;
LIBIWASM_DEBUG = "${libiwasm-baremetal-debug}/lib";
LIBIWASM_RELEASE = "${libiwasm-baremetal-release}/lib";
LIBIWASM_LINUX_DEBUG = "${libiwasm-linux-debug}/lib";
LIBIWASM_LINUX_RELEASE = "${libiwasm-linux-release}/lib";
CROSS_CC = "${i386_pkgs.stdenv.cc}/bin/i386-elf-gcc";
CROSS_CXX = "${i386_pkgs.stdenv.cc}/bin/i386-elf-g++";
LINUX_CC = "${pkgs.multiStdenv.cc}/bin/gcc";
LINUX_CXX = "${pkgs.multiStdenv.cc}/bin/g++";
};
};
});

BIN
ggplot2_a.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
ggplot2_b.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,115 @@
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.BookmarkManager;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
public class ImportMarkersAsBookmarks extends GhidraScript {
@Override
public void run() throws Exception {
// Can't do this in headless mode
// File input = askFile("Select CSV file", "Import");
String[] args = getScriptArgs();
if (args.length < 1) {
throw new IllegalArgumentException(
"Missing CSV file path.\nUsage: ImportMarkersAsBookmarks <markers.csv>");
}
File input = new File(args[0]);
if (!input.isFile()) {
throw new IllegalArgumentException(
"CSV file does not exist or is not a regular file: " + input.getAbsolutePath());
}
println("Importing bookmarks from: " + input.getAbsolutePath());
// https://ghidra.re/ghidra_docs/api/ghidra/program/model/listing/BookmarkManager.html
BookmarkManager bm = currentProgram.getBookmarkManager();
int imported = 0;
int skipped = 0;
int lineNo = 0;
// CSV columns: (benchmark, resulttype, faults, fault_address)
// Example: (ip , OK_MARKER , 4311 , 0x10001B )
try (BufferedReader br = new BufferedReader(new FileReader(input))) {
String line;
while ((line = br.readLine()) != null) {
lineNo++;
line = line.trim();
if (line.isEmpty() || line.startsWith("#") || line.startsWith("benchmark")) {
continue;
}
// Make sure to always use "," delimiter and never quotations
String[] parts = line.split(",");
if (parts.length != 4) {
skipped++;
printerr("Line " + lineNo + ": malformed");
continue;
}
String benchText = parts[0].trim();
String typeText = parts[1].trim();
String countText = parts[2].trim();
String addrText = parts[3].trim();
if (benchText.isEmpty()
|| typeText.isEmpty()
|| countText.isEmpty()
|| addrText.isEmpty()) {
skipped++;
printerr("Line " + lineNo + ": malformed");
continue;
}
Address addr = parseAddress(addrText);
if (addr == null) {
skipped++;
printerr("Line " + lineNo + ": invalid address: " + addrText);
continue;
}
println(
"Adding bookmark at "
+ addr
+ " with type "
+ benchText
+ " with category "
+ benchText // ip, mem or regs
+ " - "
+ typeText
+ " description "
+ countText
+ "x");
// TYPE CATEGORY DESCRIPTION
bm.setBookmark(addr, benchText, benchText + " - " + typeText, countText + "x");
imported++;
}
}
println("Imported " + imported + " bookmarks.");
if (skipped > 0) {
println("Skipped " + skipped + " lines.");
}
}
public Address parseAddress(String text) {
text = text.trim();
if (text.startsWith("0x") || text.startsWith("0X")) {
text = text.substring(2);
}
try {
return toAddr(text);
} catch (Exception e) {
return null;
}
}
}

View File

@ -0,0 +1,36 @@
import ghidra.program.database.sourcemap.UserDataPathTransformer;
import ghidra.program.model.sourcemap.SourcePathTransformer;
// Remap:
// /build/source/core (libiwasm)
// /home/christoph/Notes/TU/MastersThesis/FailNix/build-sum2_repl_cored_late/module_host.c
public class RemapSourcePaths extends GhidraScript {
@Override
protected void run() throws Exception {
String[] args = getScriptArgs();
if (args.length < 2) {
throw new IllegalArgumentException(
"Missing file paths.\nUsage: RemapSourcePaths <libiwasm> <host>");
}
// https://ghidra.re/ghidra_docs/api/ghidra/program/model/sourcemap/SourcePathTransformer.html
SourcePathTransformer tx = UserDataPathTransformer.getPathTransformer(currentProgram);
String oldIwasmDir = "/build/source/core";
String newIwasmDir = args[0];
tx.addDirectoryTransform(oldIwasmDir, newIwasmDir);
println("Added transform:");
println(" " + oldIwasmDir + " -> " + newIwasmDir);
// TODO: What to do here? Need to know the build dir name...
String failNix = "/home/christoph/Notes/TU/MastersThesis/FailNix";
String oldHostDir = failNix + "/build-.../module_host.c";
String newHostDir = args[1];
tx.addDirectoryTransform(oldHostDir, newHostDir);
println("Added transform:");
println(" " + oldHostDir + " -> " + newHostDir);
}
}

1430
just-bin/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

108
just-bin/Cargo.toml Normal file
View File

@ -0,0 +1,108 @@
[package]
name = "just"
version = "1.46.0"
authors = ["Casey Rodarmor <casey@rodarmor.com>"]
autotests = false
categories = ["command-line-utilities", "development-tools"]
description = "🤖 Just a command runner"
edition = "2021"
exclude = ["/book", "/icon.png", "/screenshot.png", "/www"]
homepage = "https://github.com/casey/just"
keywords = ["command-line", "task", "runner", "development", "utility"]
license = "CC0-1.0"
readme = "crates-io-readme.md"
repository = "https://github.com/casey/just"
rust-version = "1.82.0"
[workspace]
members = [".", "crates/*"]
[dependencies]
ansi_term = "0.12.0"
blake3 = { version = "1.5.0", features = ["rayon", "mmap"] }
camino = "1.0.4"
chrono = "0.4.38"
clap = { version = "4.0.0", features = ["derive", "env", "wrap_help"] }
clap_mangen = "0.2.20"
derive-where = "1.2.7"
dirs = "6.0.0"
dotenvy = "0.15"
edit-distance = "2.0.0"
heck = "0.5.0"
is_executable = "1.0.4"
lexiclean = "0.0.1"
libc = "0.2.0"
num_cpus = "1.15.0"
percent-encoding = "2.3.1"
rand = "0.9.0"
regex = "1.10.4"
rustversion = "1.0.18"
semver = "1.0.20"
serde = { version = "1.0.130", features = ["derive", "rc"] }
serde_json = "1.0.68"
sha2 = "0.10"
shellexpand = "3.1.0"
similar = { version = "2.1.0", features = ["unicode"] }
snafu = "0.8.0"
strum = { version = "0.27.1", features = ["derive"] }
target = "2.0.0"
tempfile = "3.0.0"
typed-arena = "2.0.1"
unicode-width = "0.2.0"
uuid = { version = "1.0.0", features = ["v4"] }
[target.'cfg(unix)'.dependencies]
nix = { version = "0.30.1", features = ["signal", "user", "fs"] }
[target.'cfg(windows)'.dependencies]
ctrlc = { version = "3.1.1", features = ["termination"] }
[dev-dependencies]
clap_complete = "=4.5.48"
executable-path = "1.0.0"
pretty_assertions = "1.0.0"
temptree = "0.2.0"
which = "8.0.0"
[lints.rust]
mismatched_lifetime_syntaxes = "allow"
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] }
[lints.clippy]
all = { level = "deny", priority = -1 }
arbitrary-source-item-ordering = "deny"
enum_glob_use = "allow"
ignore_without_reason = "allow"
needless_pass_by_value = "allow"
pedantic = { level = "deny", priority = -1 }
similar_names = "allow"
struct_excessive_bools = "allow"
struct_field_names = "allow"
too_many_arguments = "allow"
too_many_lines = "allow"
type_complexity = "allow"
undocumented_unsafe_blocks = "deny"
unnecessary_wraps = "allow"
wildcard_imports = "allow"
[lib]
doctest = false
[[bin]]
path = "src/main.rs"
name = "just"
test = false
# The public documentation is minimal and doesn't change between
# platforms, so we only build them for linux on docs.rs to save
# their build machines some cycles.
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[profile.release]
lto = true
codegen-units = 1
[[test]]
name = "integration"
path = "tests/lib.rs"

152
just-bin/GRAMMAR.md Normal file
View File

@ -0,0 +1,152 @@
justfile grammar
================
Justfiles are processed by a mildly context-sensitive tokenizer
and a recursive descent parser. The grammar is LL(k), for an
unknown but hopefully reasonable value of k.
tokens
------
```
BACKTICK = `[^`]*`
INDENTED_BACKTICK = ```[^(```)]*```
COMMENT = #([^!].*)?$
DEDENT = emitted when indentation decreases
EOF = emitted at the end of the file
INDENT = emitted when indentation increases
LINE = emitted before a recipe line
NAME = [a-zA-Z_][a-zA-Z0-9_-]*
NEWLINE = \n|\r\n
RAW_STRING = '[^']*'
INDENTED_RAW_STRING = '''[^(''')]*'''
STRING = "[^"]*" # also processes \n \r \t \" \\ escapes
INDENTED_STRING = """[^(""")]*""" # also processes \n \r \t \" \\ escapes
LINE_PREFIX = @-|-@|@|-
TEXT = recipe text, only matches in a recipe body
```
grammar syntax
--------------
```
| alternation
() grouping
_? option (0 or 1 times)
_* repetition (0 or more times)
_+ repetition (1 or more times)
```
grammar
-------
```
justfile : item* EOF
item : alias
| assignment
| eol
| export
| import
| module
| recipe
| set
eol : NEWLINE
| COMMENT NEWLINE
alias : 'alias' NAME ':=' target eol
target : NAME ('::' NAME)*
assignment : NAME ':=' expression eol
export : 'export' assignment
set : 'set' setting eol
setting : 'allow-duplicate-recipes' boolean?
| 'allow-duplicate-variables' boolean?
| 'dotenv-filename' ':=' string
| 'dotenv-load' boolean?
| 'dotenv-path' ':=' string
| 'dotenv-required' boolean?
| 'export' boolean?
| 'fallback' boolean?
| 'ignore-comments' boolean?
| 'positional-arguments' boolean?
| 'script-interpreter' ':=' string_list
| 'quiet' boolean?
| 'shell' ':=' string_list
| 'tempdir' ':=' string
| 'unstable' boolean?
| 'windows-powershell' boolean?
| 'windows-shell' ':=' string_list
| 'working-directory' ':=' string
boolean : ':=' ('true' | 'false')
string_list : '[' string (',' string)* ','? ']'
import : 'import' '?'? string? eol
module : 'mod' '?'? NAME string? eol
expression : disjunct || expression
| disjunct
disjunct : conjunct && disjunct
| conjunct
conjunct : 'if' condition '{' expression '}' 'else' '{' expression '}'
| 'assert' '(' condition ',' expression ')'
| '/' expression
| value '/' expression
| value '+' expression
| value
condition : expression '==' expression
| expression '!=' expression
| expression '=~' expression
value : NAME '(' sequence? ')'
| BACKTICK
| INDENTED_BACKTICK
| NAME
| string
| '(' expression ')'
string : 'x'? STRING
| 'x'? INDENTED_STRING
| 'x'? RAW_STRING
| 'x'? INDENTED_RAW_STRING
sequence : expression ',' sequence
| expression ','?
recipe : attributes* '@'? NAME parameter* variadic? ':' dependencies eol body?
attributes : '[' attribute (',' attribute)* ']' eol
attribute : NAME
| NAME ':' string
| NAME '(' string (',' string)* ')'
parameter : '$'? NAME
| '$'? NAME '=' value
variadic : '*' parameter
| '+' parameter
dependencies : dependency* ('&&' dependency+)?
dependency : target
| '(' target expression* ')'
body : INDENT line+ DEDENT
line : LINE LINE_PREFIX? (TEXT | interpolation)+ NEWLINE
| NEWLINE
interpolation : '{{' expression '}}'
```

121
just-bin/LICENSE Normal file
View File

@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

4882
just-bin/README.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,189 @@
_just() {
local i cur prev words cword opts cmd
COMPREPLY=()
# Modules use "::" as the separator, which is considered a wordbreak character in bash.
# The _get_comp_words_by_ref function is a hack to allow for exceptions to this rule without
# modifying the global COMP_WORDBREAKS environment variable.
if type _get_comp_words_by_ref &>/dev/null; then
_get_comp_words_by_ref -n : cur prev words cword
else
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
words=$COMP_WORDS
cword=$COMP_CWORD
fi
cmd=""
opts=""
for i in ${words[@]}
do
case "${cmd},${i}" in
",$1")
cmd="just"
;;
*)
;;
esac
done
case "${cmd}" in
just)
opts="-E -n -g -f -q -u -v -d -c -e -l -s -h -V --alias-style --ceiling --check --chooser --clear-shell-args --color --command-color --cygpath --dotenv-filename --dotenv-path --dry-run --dump-format --explain --global-justfile --highlight --justfile --list-heading --list-prefix --list-submodules --no-aliases --no-deps --no-dotenv --no-highlight --one --quiet --allow-missing --set --shell --shell-arg --shell-command --tempdir --timestamp --timestamp-format --unsorted --unstable --verbose --working-directory --yes --changelog --choose --command --completions --dump --edit --evaluate --fmt --groups --init --list --man --request --show --summary --usage --variables --help --version [ARGUMENTS]..."
if [[ ${cur} == -* ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
else
local recipes=$(just --summary 2> /dev/null)
if echo "${cur}" | \grep -qF '/'; then
local path_prefix=$(echo "${cur}" | sed 's/[/][^/]*$/\//')
local recipes=$(just --summary 2> /dev/null -- "${path_prefix}")
local recipes=$(printf "${path_prefix}%s\t" $recipes)
fi
if [[ $? -eq 0 ]]; then
COMPREPLY=( $(compgen -W "${recipes}" -- "${cur}") )
if type __ltrim_colon_completions &>/dev/null; then
__ltrim_colon_completions "$cur"
fi
return 0
fi
fi
case "${prev}" in
--alias-style)
COMPREPLY=($(compgen -W "left right separate" -- "${cur}"))
return 0
;;
--ceiling)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--chooser)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--color)
COMPREPLY=($(compgen -W "always auto never" -- "${cur}"))
return 0
;;
--command-color)
COMPREPLY=($(compgen -W "black blue cyan green purple red yellow" -- "${cur}"))
return 0
;;
--cygpath)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--dotenv-filename)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--dotenv-path)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-E)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--dump-format)
COMPREPLY=($(compgen -W "json just" -- "${cur}"))
return 0
;;
--justfile)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-f)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--list-heading)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--list-prefix)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--set)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--shell)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--shell-arg)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--tempdir)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--timestamp-format)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--working-directory)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-d)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--command)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-c)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--completions)
COMPREPLY=($(compgen -W "bash elvish fish nushell powershell zsh" -- "${cur}"))
return 0
;;
--list)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-l)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--request)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--show)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-s)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--usage)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
esac
}
if [[ "${BASH_VERSINFO[0]}" -eq 4 && "${BASH_VERSINFO[1]}" -ge 4 || "${BASH_VERSINFO[0]}" -gt 4 ]]; then
complete -F _just -o nosort -o bashdefault -o default just
else
complete -F _just -o bashdefault -o default just
fi

View File

@ -0,0 +1,94 @@
use builtin;
use str;
set edit:completion:arg-completer[just] = {|@words|
fn spaces {|n|
builtin:repeat $n ' ' | str:join ''
}
fn cand {|text desc|
edit:complex-candidate $text &display=$text' '(spaces (- 14 (wcswidth $text)))$desc
}
var command = 'just'
for word $words[1..-1] {
if (str:has-prefix $word '-') {
break
}
set command = $command';'$word
}
var completions = [
&'just'= {
cand --alias-style 'Set list command alias display style'
cand --ceiling 'Do not ascend above <CEILING> directory when searching for a justfile.'
cand --chooser 'Override binary invoked by `--choose`'
cand --color 'Print colorful output'
cand --command-color 'Echo recipe lines in <COMMAND-COLOR>'
cand --cygpath 'Use binary at <CYGPATH> to convert between unix and Windows paths.'
cand --dotenv-filename 'Search for environment file named <DOTENV-FILENAME> instead of `.env`'
cand -E 'Load <DOTENV-PATH> as environment file instead of searching for one'
cand --dotenv-path 'Load <DOTENV-PATH> as environment file instead of searching for one'
cand --dump-format 'Dump justfile as <FORMAT>'
cand -f 'Use <JUSTFILE> as justfile'
cand --justfile 'Use <JUSTFILE> as justfile'
cand --list-heading 'Print <TEXT> before list'
cand --list-prefix 'Print <TEXT> before each list item'
cand --set 'Override <VARIABLE> with <VALUE>'
cand --shell 'Invoke <SHELL> to run recipes'
cand --shell-arg 'Invoke shell with <SHELL-ARG> as an argument'
cand --tempdir 'Save temporary files to <TEMPDIR>.'
cand --timestamp-format 'Timestamp format string'
cand -d 'Use <WORKING-DIRECTORY> as working directory. --justfile must also be set'
cand --working-directory 'Use <WORKING-DIRECTORY> as working directory. --justfile must also be set'
cand -c 'Run an arbitrary command with the working directory, `.env`, overrides, and exports set'
cand --command 'Run an arbitrary command with the working directory, `.env`, overrides, and exports set'
cand --completions 'Print shell completion script for <SHELL>'
cand -l 'List available recipes in <MODULE> or root if omitted'
cand --list 'List available recipes in <MODULE> or root if omitted'
cand --request 'Execute <REQUEST>. For internal testing purposes only. May be changed or removed at any time.'
cand -s 'Show recipe at <PATH>'
cand --show 'Show recipe at <PATH>'
cand --usage 'Print recipe usage information'
cand --check 'Run `--fmt` in ''check'' mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required.'
cand --clear-shell-args 'Clear shell arguments'
cand -n 'Print what just would do without doing it'
cand --dry-run 'Print what just would do without doing it'
cand --explain 'Print recipe doc comment before running it'
cand -g 'Use global justfile'
cand --global-justfile 'Use global justfile'
cand --highlight 'Highlight echoed recipe lines in bold'
cand --list-submodules 'List recipes in submodules'
cand --no-aliases 'Don''t show aliases in list'
cand --no-deps 'Don''t run recipe dependencies'
cand --no-dotenv 'Don''t load `.env` file'
cand --no-highlight 'Don''t highlight echoed recipe lines in bold'
cand --one 'Forbid multiple recipes from being invoked on the command line'
cand -q 'Suppress all output'
cand --quiet 'Suppress all output'
cand --allow-missing 'Ignore missing recipe and module errors'
cand --shell-command 'Invoke <COMMAND> with the shell used to run recipe lines and backticks'
cand --timestamp 'Print recipe command timestamps'
cand -u 'Return list and summary entries in source order'
cand --unsorted 'Return list and summary entries in source order'
cand --unstable 'Enable unstable features'
cand -v 'Use verbose output'
cand --verbose 'Use verbose output'
cand --yes 'Automatically confirm all recipes.'
cand --changelog 'Print changelog'
cand --choose 'Select one or more recipes to run using a binary chooser. If `--chooser` is not passed the chooser defaults to the value of $JUST_CHOOSER, falling back to `fzf`'
cand --dump 'Print justfile'
cand -e 'Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`'
cand --edit 'Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`'
cand --evaluate 'Evaluate and print all variables. If a variable name is given as an argument, only print that variable''s value.'
cand --fmt 'Format and overwrite justfile'
cand --groups 'List recipe groups'
cand --init 'Initialize new justfile in project root'
cand --man 'Print man page'
cand --summary 'List names of available recipes'
cand --variables 'List names of variables'
cand -h 'Print help'
cand --help 'Print help'
cand -V 'Print version'
cand --version 'Print version'
}
]
$completions[$command]
}

View File

@ -0,0 +1,87 @@
function __fish_just_complete_recipes
if string match -rq '(-f|--justfile)\s*=?(?<justfile>[^\s]+)' -- (string split -- ' -- ' (commandline -pc))[1]
set -fx JUST_JUSTFILE "$justfile"
end
printf "%s\n" (string split " " (just --summary))
end
# don't suggest files right off
complete -c just -n "__fish_is_first_arg" --no-files
# complete recipes
complete -c just -a '(__fish_just_complete_recipes)'
# autogenerated completions
complete -c just -l alias-style -d 'Set list command alias display style' -r -f -a "left\t''
right\t''
separate\t''"
complete -c just -l ceiling -d 'Do not ascend above <CEILING> directory when searching for a justfile.' -r -F
complete -c just -l chooser -d 'Override binary invoked by `--choose`' -r
complete -c just -l color -d 'Print colorful output' -r -f -a "always\t''
auto\t''
never\t''"
complete -c just -l command-color -d 'Echo recipe lines in <COMMAND-COLOR>' -r -f -a "black\t''
blue\t''
cyan\t''
green\t''
purple\t''
red\t''
yellow\t''"
complete -c just -l cygpath -d 'Use binary at <CYGPATH> to convert between unix and Windows paths.' -r -F
complete -c just -l dotenv-filename -d 'Search for environment file named <DOTENV-FILENAME> instead of `.env`' -r
complete -c just -s E -l dotenv-path -d 'Load <DOTENV-PATH> as environment file instead of searching for one' -r -F
complete -c just -l dump-format -d 'Dump justfile as <FORMAT>' -r -f -a "json\t''
just\t''"
complete -c just -s f -l justfile -d 'Use <JUSTFILE> as justfile' -r -F
complete -c just -l list-heading -d 'Print <TEXT> before list' -r
complete -c just -l list-prefix -d 'Print <TEXT> before each list item' -r
complete -c just -l set -d 'Override <VARIABLE> with <VALUE>' -r
complete -c just -l shell -d 'Invoke <SHELL> to run recipes' -r
complete -c just -l shell-arg -d 'Invoke shell with <SHELL-ARG> as an argument' -r
complete -c just -l tempdir -d 'Save temporary files to <TEMPDIR>.' -r -F
complete -c just -l timestamp-format -d 'Timestamp format string' -r
complete -c just -s d -l working-directory -d 'Use <WORKING-DIRECTORY> as working directory. --justfile must also be set' -r -F
complete -c just -s c -l command -d 'Run an arbitrary command with the working directory, `.env`, overrides, and exports set' -r
complete -c just -l completions -d 'Print shell completion script for <SHELL>' -r -f -a "bash\t''
elvish\t''
fish\t''
nushell\t''
powershell\t''
zsh\t''"
complete -c just -s l -l list -d 'List available recipes in <MODULE> or root if omitted' -r
complete -c just -l request -d 'Execute <REQUEST>. For internal testing purposes only. May be changed or removed at any time.' -r
complete -c just -s s -l show -d 'Show recipe at <PATH>' -r
complete -c just -l usage -d 'Print recipe usage information' -r
complete -c just -l check -d 'Run `--fmt` in \'check\' mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required.'
complete -c just -l clear-shell-args -d 'Clear shell arguments'
complete -c just -s n -l dry-run -d 'Print what just would do without doing it'
complete -c just -l explain -d 'Print recipe doc comment before running it'
complete -c just -s g -l global-justfile -d 'Use global justfile'
complete -c just -l highlight -d 'Highlight echoed recipe lines in bold'
complete -c just -l list-submodules -d 'List recipes in submodules'
complete -c just -l no-aliases -d 'Don\'t show aliases in list'
complete -c just -l no-deps -d 'Don\'t run recipe dependencies'
complete -c just -l no-dotenv -d 'Don\'t load `.env` file'
complete -c just -l no-highlight -d 'Don\'t highlight echoed recipe lines in bold'
complete -c just -l one -d 'Forbid multiple recipes from being invoked on the command line'
complete -c just -s q -l quiet -d 'Suppress all output'
complete -c just -l allow-missing -d 'Ignore missing recipe and module errors'
complete -c just -l shell-command -d 'Invoke <COMMAND> with the shell used to run recipe lines and backticks'
complete -c just -l timestamp -d 'Print recipe command timestamps'
complete -c just -s u -l unsorted -d 'Return list and summary entries in source order'
complete -c just -l unstable -d 'Enable unstable features'
complete -c just -s v -l verbose -d 'Use verbose output'
complete -c just -l yes -d 'Automatically confirm all recipes.'
complete -c just -l changelog -d 'Print changelog'
complete -c just -l choose -d 'Select one or more recipes to run using a binary chooser. If `--chooser` is not passed the chooser defaults to the value of $JUST_CHOOSER, falling back to `fzf`'
complete -c just -l dump -d 'Print justfile'
complete -c just -s e -l edit -d 'Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`'
complete -c just -l evaluate -d 'Evaluate and print all variables. If a variable name is given as an argument, only print that variable\'s value.'
complete -c just -l fmt -d 'Format and overwrite justfile'
complete -c just -l groups -d 'List recipe groups'
complete -c just -l init -d 'Initialize new justfile in project root'
complete -c just -l man -d 'Print man page'
complete -c just -l summary -d 'List names of available recipes'
complete -c just -l variables -d 'List names of variables'
complete -c just -s h -l help -d 'Print help'
complete -c just -s V -l version -d 'Print version'

View File

@ -0,0 +1,8 @@
def "nu-complete just" [] {
(^just --dump --unstable --dump-format json | from json).recipes | transpose recipe data | flatten | where {|row| $row.private == false } | select recipe doc parameters | rename value description
}
# Just: A Command Runner
export extern "just" [
...recipe: string@"nu-complete just", # Recipe(s) to run, may be with argument(s)
]

View File

@ -0,0 +1,120 @@
using namespace System.Management.Automation
using namespace System.Management.Automation.Language
Register-ArgumentCompleter -Native -CommandName 'just' -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
$commandElements = $commandAst.CommandElements
$command = @(
'just'
for ($i = 1; $i -lt $commandElements.Count; $i++) {
$element = $commandElements[$i]
if ($element -isnot [StringConstantExpressionAst] -or
$element.StringConstantType -ne [StringConstantType]::BareWord -or
$element.Value.StartsWith('-') -or
$element.Value -eq $wordToComplete) {
break
}
$element.Value
}) -join ';'
$completions = @(switch ($command) {
'just' {
[CompletionResult]::new('--alias-style', '--alias-style', [CompletionResultType]::ParameterName, 'Set list command alias display style')
[CompletionResult]::new('--ceiling', '--ceiling', [CompletionResultType]::ParameterName, 'Do not ascend above <CEILING> directory when searching for a justfile.')
[CompletionResult]::new('--chooser', '--chooser', [CompletionResultType]::ParameterName, 'Override binary invoked by `--choose`')
[CompletionResult]::new('--color', '--color', [CompletionResultType]::ParameterName, 'Print colorful output')
[CompletionResult]::new('--command-color', '--command-color', [CompletionResultType]::ParameterName, 'Echo recipe lines in <COMMAND-COLOR>')
[CompletionResult]::new('--cygpath', '--cygpath', [CompletionResultType]::ParameterName, 'Use binary at <CYGPATH> to convert between unix and Windows paths.')
[CompletionResult]::new('--dotenv-filename', '--dotenv-filename', [CompletionResultType]::ParameterName, 'Search for environment file named <DOTENV-FILENAME> instead of `.env`')
[CompletionResult]::new('-E', '-E ', [CompletionResultType]::ParameterName, 'Load <DOTENV-PATH> as environment file instead of searching for one')
[CompletionResult]::new('--dotenv-path', '--dotenv-path', [CompletionResultType]::ParameterName, 'Load <DOTENV-PATH> as environment file instead of searching for one')
[CompletionResult]::new('--dump-format', '--dump-format', [CompletionResultType]::ParameterName, 'Dump justfile as <FORMAT>')
[CompletionResult]::new('-f', '-f', [CompletionResultType]::ParameterName, 'Use <JUSTFILE> as justfile')
[CompletionResult]::new('--justfile', '--justfile', [CompletionResultType]::ParameterName, 'Use <JUSTFILE> as justfile')
[CompletionResult]::new('--list-heading', '--list-heading', [CompletionResultType]::ParameterName, 'Print <TEXT> before list')
[CompletionResult]::new('--list-prefix', '--list-prefix', [CompletionResultType]::ParameterName, 'Print <TEXT> before each list item')
[CompletionResult]::new('--set', '--set', [CompletionResultType]::ParameterName, 'Override <VARIABLE> with <VALUE>')
[CompletionResult]::new('--shell', '--shell', [CompletionResultType]::ParameterName, 'Invoke <SHELL> to run recipes')
[CompletionResult]::new('--shell-arg', '--shell-arg', [CompletionResultType]::ParameterName, 'Invoke shell with <SHELL-ARG> as an argument')
[CompletionResult]::new('--tempdir', '--tempdir', [CompletionResultType]::ParameterName, 'Save temporary files to <TEMPDIR>.')
[CompletionResult]::new('--timestamp-format', '--timestamp-format', [CompletionResultType]::ParameterName, 'Timestamp format string')
[CompletionResult]::new('-d', '-d', [CompletionResultType]::ParameterName, 'Use <WORKING-DIRECTORY> as working directory. --justfile must also be set')
[CompletionResult]::new('--working-directory', '--working-directory', [CompletionResultType]::ParameterName, 'Use <WORKING-DIRECTORY> as working directory. --justfile must also be set')
[CompletionResult]::new('-c', '-c', [CompletionResultType]::ParameterName, 'Run an arbitrary command with the working directory, `.env`, overrides, and exports set')
[CompletionResult]::new('--command', '--command', [CompletionResultType]::ParameterName, 'Run an arbitrary command with the working directory, `.env`, overrides, and exports set')
[CompletionResult]::new('--completions', '--completions', [CompletionResultType]::ParameterName, 'Print shell completion script for <SHELL>')
[CompletionResult]::new('-l', '-l', [CompletionResultType]::ParameterName, 'List available recipes in <MODULE> or root if omitted')
[CompletionResult]::new('--list', '--list', [CompletionResultType]::ParameterName, 'List available recipes in <MODULE> or root if omitted')
[CompletionResult]::new('--request', '--request', [CompletionResultType]::ParameterName, 'Execute <REQUEST>. For internal testing purposes only. May be changed or removed at any time.')
[CompletionResult]::new('-s', '-s', [CompletionResultType]::ParameterName, 'Show recipe at <PATH>')
[CompletionResult]::new('--show', '--show', [CompletionResultType]::ParameterName, 'Show recipe at <PATH>')
[CompletionResult]::new('--usage', '--usage', [CompletionResultType]::ParameterName, 'Print recipe usage information')
[CompletionResult]::new('--check', '--check', [CompletionResultType]::ParameterName, 'Run `--fmt` in ''check'' mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required.')
[CompletionResult]::new('--clear-shell-args', '--clear-shell-args', [CompletionResultType]::ParameterName, 'Clear shell arguments')
[CompletionResult]::new('-n', '-n', [CompletionResultType]::ParameterName, 'Print what just would do without doing it')
[CompletionResult]::new('--dry-run', '--dry-run', [CompletionResultType]::ParameterName, 'Print what just would do without doing it')
[CompletionResult]::new('--explain', '--explain', [CompletionResultType]::ParameterName, 'Print recipe doc comment before running it')
[CompletionResult]::new('-g', '-g', [CompletionResultType]::ParameterName, 'Use global justfile')
[CompletionResult]::new('--global-justfile', '--global-justfile', [CompletionResultType]::ParameterName, 'Use global justfile')
[CompletionResult]::new('--highlight', '--highlight', [CompletionResultType]::ParameterName, 'Highlight echoed recipe lines in bold')
[CompletionResult]::new('--list-submodules', '--list-submodules', [CompletionResultType]::ParameterName, 'List recipes in submodules')
[CompletionResult]::new('--no-aliases', '--no-aliases', [CompletionResultType]::ParameterName, 'Don''t show aliases in list')
[CompletionResult]::new('--no-deps', '--no-deps', [CompletionResultType]::ParameterName, 'Don''t run recipe dependencies')
[CompletionResult]::new('--no-dotenv', '--no-dotenv', [CompletionResultType]::ParameterName, 'Don''t load `.env` file')
[CompletionResult]::new('--no-highlight', '--no-highlight', [CompletionResultType]::ParameterName, 'Don''t highlight echoed recipe lines in bold')
[CompletionResult]::new('--one', '--one', [CompletionResultType]::ParameterName, 'Forbid multiple recipes from being invoked on the command line')
[CompletionResult]::new('-q', '-q', [CompletionResultType]::ParameterName, 'Suppress all output')
[CompletionResult]::new('--quiet', '--quiet', [CompletionResultType]::ParameterName, 'Suppress all output')
[CompletionResult]::new('--allow-missing', '--allow-missing', [CompletionResultType]::ParameterName, 'Ignore missing recipe and module errors')
[CompletionResult]::new('--shell-command', '--shell-command', [CompletionResultType]::ParameterName, 'Invoke <COMMAND> with the shell used to run recipe lines and backticks')
[CompletionResult]::new('--timestamp', '--timestamp', [CompletionResultType]::ParameterName, 'Print recipe command timestamps')
[CompletionResult]::new('-u', '-u', [CompletionResultType]::ParameterName, 'Return list and summary entries in source order')
[CompletionResult]::new('--unsorted', '--unsorted', [CompletionResultType]::ParameterName, 'Return list and summary entries in source order')
[CompletionResult]::new('--unstable', '--unstable', [CompletionResultType]::ParameterName, 'Enable unstable features')
[CompletionResult]::new('-v', '-v', [CompletionResultType]::ParameterName, 'Use verbose output')
[CompletionResult]::new('--verbose', '--verbose', [CompletionResultType]::ParameterName, 'Use verbose output')
[CompletionResult]::new('--yes', '--yes', [CompletionResultType]::ParameterName, 'Automatically confirm all recipes.')
[CompletionResult]::new('--changelog', '--changelog', [CompletionResultType]::ParameterName, 'Print changelog')
[CompletionResult]::new('--choose', '--choose', [CompletionResultType]::ParameterName, 'Select one or more recipes to run using a binary chooser. If `--chooser` is not passed the chooser defaults to the value of $JUST_CHOOSER, falling back to `fzf`')
[CompletionResult]::new('--dump', '--dump', [CompletionResultType]::ParameterName, 'Print justfile')
[CompletionResult]::new('-e', '-e', [CompletionResultType]::ParameterName, 'Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`')
[CompletionResult]::new('--edit', '--edit', [CompletionResultType]::ParameterName, 'Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`')
[CompletionResult]::new('--evaluate', '--evaluate', [CompletionResultType]::ParameterName, 'Evaluate and print all variables. If a variable name is given as an argument, only print that variable''s value.')
[CompletionResult]::new('--fmt', '--fmt', [CompletionResultType]::ParameterName, 'Format and overwrite justfile')
[CompletionResult]::new('--groups', '--groups', [CompletionResultType]::ParameterName, 'List recipe groups')
[CompletionResult]::new('--init', '--init', [CompletionResultType]::ParameterName, 'Initialize new justfile in project root')
[CompletionResult]::new('--man', '--man', [CompletionResultType]::ParameterName, 'Print man page')
[CompletionResult]::new('--summary', '--summary', [CompletionResultType]::ParameterName, 'List names of available recipes')
[CompletionResult]::new('--variables', '--variables', [CompletionResultType]::ParameterName, 'List names of variables')
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
break
}
})
function Get-JustFileRecipes([string[]]$CommandElements) {
$justFileIndex = $commandElements.IndexOf("--justfile");
if ($justFileIndex -ne -1 -and $justFileIndex + 1 -le $commandElements.Length) {
$justFileLocation = $commandElements[$justFileIndex + 1]
}
$justArgs = @("--summary")
if (Test-Path $justFileLocation) {
$justArgs += @("--justfile", $justFileLocation)
}
$recipes = $(just @justArgs) -split ' '
return $recipes | ForEach-Object { [CompletionResult]::new($_) }
}
$elementValues = $commandElements | Select-Object -ExpandProperty Value
$recipes = Get-JustFileRecipes -CommandElements $elementValues
$completions += $recipes
$completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
Sort-Object -Property ListItemText
}

View File

@ -0,0 +1,181 @@
#compdef just
autoload -U is-at-least
_just() {
typeset -A opt_args
typeset -a _arguments_options
local ret=1
if is-at-least 5.2; then
_arguments_options=(-s -S -C)
else
_arguments_options=(-s -C)
fi
local context curcontext="$curcontext" state line
local common=(
'(--no-aliases)--alias-style=[Set list command alias display style]: :(left right separate)' \
'--ceiling=[Do not ascend above <CEILING> directory when searching for a justfile.]: :_files' \
'--chooser=[Override binary invoked by \`--choose\`]: :_default' \
'--color=[Print colorful output]: :(always auto never)' \
'--command-color=[Echo recipe lines in <COMMAND-COLOR>]: :(black blue cyan green purple red yellow)' \
'--cygpath=[Use binary at <CYGPATH> to convert between unix and Windows paths.]: :_files' \
'(-E --dotenv-path)--dotenv-filename=[Search for environment file named <DOTENV-FILENAME> instead of \`.env\`]: :_default' \
'-E+[Load <DOTENV-PATH> as environment file instead of searching for one]: :_files' \
'--dotenv-path=[Load <DOTENV-PATH> as environment file instead of searching for one]: :_files' \
'--dump-format=[Dump justfile as <FORMAT>]:FORMAT:(json just)' \
'-f+[Use <JUSTFILE> as justfile]: :_files' \
'--justfile=[Use <JUSTFILE> as justfile]: :_files' \
'--list-heading=[Print <TEXT> before list]:TEXT:_default' \
'--list-prefix=[Print <TEXT> before each list item]:TEXT:_default' \
'*--set=[Override <VARIABLE> with <VALUE>]: :(_just_variables)' \
'--shell=[Invoke <SHELL> to run recipes]: :_default' \
'*--shell-arg=[Invoke shell with <SHELL-ARG> as an argument]: :_default' \
'--tempdir=[Save temporary files to <TEMPDIR>.]: :_files' \
'--timestamp-format=[Timestamp format string]: :_default' \
'-d+[Use <WORKING-DIRECTORY> as working directory. --justfile must also be set]: :_files' \
'--working-directory=[Use <WORKING-DIRECTORY> as working directory. --justfile must also be set]: :_files' \
'*-c+[Run an arbitrary command with the working directory, \`.env\`, overrides, and exports set]: :_default' \
'*--command=[Run an arbitrary command with the working directory, \`.env\`, overrides, and exports set]: :_default' \
'--completions=[Print shell completion script for <SHELL>]:SHELL:(bash elvish fish nushell powershell zsh)' \
'()-l+[List available recipes in <MODULE> or root if omitted]' \
'()--list=[List available recipes in <MODULE> or root if omitted]' \
'--request=[Execute <REQUEST>. For internal testing purposes only. May be changed or removed at any time.]: :_default' \
'-s+[Show recipe at <PATH>]: :(_just_commands)' \
'--show=[Show recipe at <PATH>]: :(_just_commands)' \
'()--usage=[Print recipe usage information]:PATH:_default' \
'--check[Run \`--fmt\` in '\''check'\'' mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required.]' \
'--clear-shell-args[Clear shell arguments]' \
'(-q --quiet)-n[Print what just would do without doing it]' \
'(-q --quiet)--dry-run[Print what just would do without doing it]' \
'--explain[Print recipe doc comment before running it]' \
'(-f --justfile -d --working-directory)-g[Use global justfile]' \
'(-f --justfile -d --working-directory)--global-justfile[Use global justfile]' \
'--highlight[Highlight echoed recipe lines in bold]' \
'--list-submodules[List recipes in submodules]' \
'--no-aliases[Don'\''t show aliases in list]' \
'--no-deps[Don'\''t run recipe dependencies]' \
'--no-dotenv[Don'\''t load \`.env\` file]' \
'--no-highlight[Don'\''t highlight echoed recipe lines in bold]' \
'--one[Forbid multiple recipes from being invoked on the command line]' \
'(-n --dry-run)-q[Suppress all output]' \
'(-n --dry-run)--quiet[Suppress all output]' \
'--allow-missing[Ignore missing recipe and module errors]' \
'--shell-command[Invoke <COMMAND> with the shell used to run recipe lines and backticks]' \
'--timestamp[Print recipe command timestamps]' \
'-u[Return list and summary entries in source order]' \
'--unsorted[Return list and summary entries in source order]' \
'--unstable[Enable unstable features]' \
'*-v[Use verbose output]' \
'*--verbose[Use verbose output]' \
'--yes[Automatically confirm all recipes.]' \
'--changelog[Print changelog]' \
'--choose[Select one or more recipes to run using a binary chooser. If \`--chooser\` is not passed the chooser defaults to the value of \$JUST_CHOOSER, falling back to \`fzf\`]' \
'--dump[Print justfile]' \
'-e[Edit justfile with editor given by \$VISUAL or \$EDITOR, falling back to \`vim\`]' \
'--edit[Edit justfile with editor given by \$VISUAL or \$EDITOR, falling back to \`vim\`]' \
'--evaluate[Evaluate and print all variables. If a variable name is given as an argument, only print that variable'\''s value.]' \
'--fmt[Format and overwrite justfile]' \
'--groups[List recipe groups]' \
'--init[Initialize new justfile in project root]' \
'--man[Print man page]' \
'--summary[List names of available recipes]' \
'--variables[List names of variables]' \
'-h[Print help]' \
'--help[Print help]' \
'-V[Print version]' \
'--version[Print version]' \
)
_arguments "${_arguments_options[@]}" $common \
'1: :_just_commands' \
'*: :->args' \
&& ret=0
case $state in
args)
curcontext="${curcontext%:*}-${words[2]}:"
local lastarg=${words[${#words}]}
local recipe
local cmds; cmds=(
${(s: :)$(_call_program commands just --summary)}
)
# Find first recipe name
for ((i = 2; i < $#words; i++ )) do
if [[ ${cmds[(I)${words[i]}]} -gt 0 ]]; then
recipe=${words[i]}
break
fi
done
if [[ $lastarg = */* ]]; then
# Arguments contain slash would be recognised as a file
_arguments -s -S $common '*:: :_files'
elif [[ $lastarg = *=* ]]; then
# Arguments contain equal would be recognised as a variable
_message "value"
elif [[ $recipe ]]; then
# Show usage message
_message "`just --show $recipe`"
# Or complete with other commands
#_arguments -s -S $common '*:: :_just_commands'
else
_arguments -s -S $common '*:: :_just_commands'
fi
;;
esac
return ret
}
(( $+functions[_just_commands] )) ||
_just_commands() {
[[ $PREFIX = -* ]] && return 1
integer ret=1
local variables; variables=(
${(s: :)$(_call_program commands just --variables)}
)
local commands; commands=(
${${${(M)"${(f)$(_call_program commands just --list)}":# *}/ ##/}/ ##/:Args: }
)
if compset -P '*='; then
case "${${words[-1]%=*}#*=}" in
*) _message 'value' && ret=0 ;;
esac
else
_describe -t variables 'variables' variables -qS "=" && ret=0
_describe -t commands 'just commands' commands "$@"
fi
}
if [ "$funcstack[1]" = "_just" ]; then
(( $+functions[_just_variables] )) ||
_just_variables() {
[[ $PREFIX = -* ]] && return 1
integer ret=1
local variables; variables=(
${(s: :)$(_call_program commands just --variables)}
)
if compset -P '*='; then
case "${${words[-1]%=*}#*=}" in
*) _message 'value' && ret=0 ;;
esac
else
_describe -t variables 'variables' variables && ret=0
fi
return ret
}
_just "$@"
else
compdef _just just
fi

BIN
just-bin/just (Stored with Git LFS) Executable file

Binary file not shown.

294
just-bin/just.1 Normal file
View File

@ -0,0 +1,294 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH just 1 "just 1.46.0"
.SH NAME
just \- 🤖 Just a command runner \- https://github.com/casey/just
.SH SYNOPSIS
\fBjust\fR [\fB\-\-alias\-style\fR] [\fB\-\-ceiling\fR] [\fB\-\-check\fR] [\fB\-\-chooser\fR] [\fB\-\-clear\-shell\-args\fR] [\fB\-\-color\fR] [\fB\-\-command\-color\fR] [\fB\-\-cygpath\fR] [\fB\-\-dotenv\-filename\fR] [\fB\-E\fR|\fB\-\-dotenv\-path\fR] [\fB\-n\fR|\fB\-\-dry\-run\fR] [\fB\-\-dump\-format\fR] [\fB\-\-explain\fR] [\fB\-g\fR|\fB\-\-global\-justfile\fR] [\fB\-\-highlight\fR] [\fB\-f\fR|\fB\-\-justfile\fR] [\fB\-\-list\-heading\fR] [\fB\-\-list\-prefix\fR] [\fB\-\-list\-submodules\fR] [\fB\-\-no\-aliases\fR] [\fB\-\-no\-deps\fR] [\fB\-\-no\-dotenv\fR] [\fB\-\-no\-highlight\fR] [\fB\-\-one\fR] [\fB\-q\fR|\fB\-\-quiet\fR] [\fB\-\-allow\-missing\fR] [\fB\-\-set\fR] [\fB\-\-shell\fR] [\fB\-\-shell\-arg\fR] [\fB\-\-shell\-command\fR] [\fB\-\-tempdir\fR] [\fB\-\-timestamp\fR] [\fB\-\-timestamp\-format\fR] [\fB\-u\fR|\fB\-\-unsorted\fR] [\fB\-\-unstable\fR] [\fB\-v\fR|\fB\-\-verbose\fR]... [\fB\-d\fR|\fB\-\-working\-directory\fR] [\fB\-\-yes\fR] [\fB\-\-changelog\fR] [\fB\-\-choose\fR] [\fB\-c\fR|\fB\-\-command\fR] [\fB\-\-completions\fR] [\fB\-\-dump\fR] [\fB\-e\fR|\fB\-\-edit\fR] [\fB\-\-evaluate\fR] [\fB\-\-fmt\fR] [\fB\-\-groups\fR] [\fB\-\-init\fR] [\fB\-l\fR|\fB\-\-list\fR] [\fB\-\-man\fR] [\fB\-s\fR|\fB\-\-show\fR] [\fB\-\-summary\fR] [\fB\-\-usage\fR] [\fB\-\-variables\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fIARGUMENTS\fR]
.SH DESCRIPTION
🤖 Just a command runner \- https://github.com/casey/just
.SH OPTIONS
.TP
\fB\-\-alias\-style\fR [default: right]
Set list command alias display style
.br
.br
[\fIpossible values: \fRleft, right, separate]
.RS
May also be specified with the \fBJUST_ALIAS_STYLE\fR environment variable.
.RE
.TP
\fB\-\-ceiling\fR
Do not ascend above <CEILING> directory when searching for a justfile.
.RS
May also be specified with the \fBJUST_CEILING\fR environment variable.
.RE
.TP
\fB\-\-check\fR
Run `\-\-fmt` in \*(Aqcheck\*(Aq mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required.
.TP
\fB\-\-chooser\fR
Override binary invoked by `\-\-choose`
.RS
May also be specified with the \fBJUST_CHOOSER\fR environment variable.
.RE
.TP
\fB\-\-clear\-shell\-args\fR
Clear shell arguments
.TP
\fB\-\-color\fR [default: auto]
Print colorful output
.br
.br
[\fIpossible values: \fRalways, auto, never]
.RS
May also be specified with the \fBJUST_COLOR\fR environment variable.
.RE
.TP
\fB\-\-command\-color\fR
Echo recipe lines in <COMMAND\-COLOR>
.br
.br
[\fIpossible values: \fRblack, blue, cyan, green, purple, red, yellow]
.RS
May also be specified with the \fBJUST_COMMAND_COLOR\fR environment variable.
.RE
.TP
\fB\-\-cygpath\fR [default: cygpath]
Use binary at <CYGPATH> to convert between unix and Windows paths.
.RS
May also be specified with the \fBJUST_CYGPATH\fR environment variable.
.RE
.TP
\fB\-\-dotenv\-filename\fR
Search for environment file named <DOTENV\-FILENAME> instead of `.env`
.TP
\fB\-E\fR, \fB\-\-dotenv\-path\fR
Load <DOTENV\-PATH> as environment file instead of searching for one
.TP
\fB\-n\fR, \fB\-\-dry\-run\fR
Print what just would do without doing it
.RS
May also be specified with the \fBJUST_DRY_RUN\fR environment variable.
.RE
.TP
\fB\-\-dump\-format\fR \fI<FORMAT>\fR [default: just]
Dump justfile as <FORMAT>
.br
.br
[\fIpossible values: \fRjson, just]
.RS
May also be specified with the \fBJUST_DUMP_FORMAT\fR environment variable.
.RE
.TP
\fB\-\-explain\fR
Print recipe doc comment before running it
.RS
May also be specified with the \fBJUST_EXPLAIN\fR environment variable.
.RE
.TP
\fB\-g\fR, \fB\-\-global\-justfile\fR
Use global justfile
.TP
\fB\-\-highlight\fR
Highlight echoed recipe lines in bold
.RS
May also be specified with the \fBJUST_HIGHLIGHT\fR environment variable.
.RE
.TP
\fB\-f\fR, \fB\-\-justfile\fR
Use <JUSTFILE> as justfile
.RS
May also be specified with the \fBJUST_JUSTFILE\fR environment variable.
.RE
.TP
\fB\-\-list\-heading\fR \fI<TEXT>\fR [default: Available recipes:
]
Print <TEXT> before list
.RS
May also be specified with the \fBJUST_LIST_HEADING\fR environment variable.
.RE
.TP
\fB\-\-list\-prefix\fR \fI<TEXT>\fR [default: ]
Print <TEXT> before each list item
.RS
May also be specified with the \fBJUST_LIST_PREFIX\fR environment variable.
.RE
.TP
\fB\-\-list\-submodules\fR
List recipes in submodules
.RS
May also be specified with the \fBJUST_LIST_SUBMODULES\fR environment variable.
.RE
.TP
\fB\-\-no\-aliases\fR
Don\*(Aqt show aliases in list
.RS
May also be specified with the \fBJUST_NO_ALIASES\fR environment variable.
.RE
.TP
\fB\-\-no\-deps\fR
Don\*(Aqt run recipe dependencies
.RS
May also be specified with the \fBJUST_NO_DEPS\fR environment variable.
.RE
.TP
\fB\-\-no\-dotenv\fR
Don\*(Aqt load `.env` file
.RS
May also be specified with the \fBJUST_NO_DOTENV\fR environment variable.
.RE
.TP
\fB\-\-no\-highlight\fR
Don\*(Aqt highlight echoed recipe lines in bold
.RS
May also be specified with the \fBJUST_NO_HIGHLIGHT\fR environment variable.
.RE
.TP
\fB\-\-one\fR
Forbid multiple recipes from being invoked on the command line
.RS
May also be specified with the \fBJUST_ONE\fR environment variable.
.RE
.TP
\fB\-q\fR, \fB\-\-quiet\fR
Suppress all output
.RS
May also be specified with the \fBJUST_QUIET\fR environment variable.
.RE
.TP
\fB\-\-allow\-missing\fR
Ignore missing recipe and module errors
.RS
May also be specified with the \fBJUST_ALLOW_MISSING\fR environment variable.
.RE
.TP
\fB\-\-set\fR \fI<VARIABLE>\fR\fI \fR\fI<VALUE>\fR
Override <VARIABLE> with <VALUE>
.TP
\fB\-\-shell\fR
Invoke <SHELL> to run recipes
.TP
\fB\-\-shell\-arg\fR
Invoke shell with <SHELL\-ARG> as an argument
.TP
\fB\-\-shell\-command\fR
Invoke <COMMAND> with the shell used to run recipe lines and backticks
.TP
\fB\-\-tempdir\fR
Save temporary files to <TEMPDIR>.
.RS
May also be specified with the \fBJUST_TEMPDIR\fR environment variable.
.RE
.TP
\fB\-\-timestamp\fR
Print recipe command timestamps
.RS
May also be specified with the \fBJUST_TIMESTAMP\fR environment variable.
.RE
.TP
\fB\-\-timestamp\-format\fR [default: %H:%M:%S]
Timestamp format string
.RS
May also be specified with the \fBJUST_TIMESTAMP_FORMAT\fR environment variable.
.RE
.TP
\fB\-u\fR, \fB\-\-unsorted\fR
Return list and summary entries in source order
.RS
May also be specified with the \fBJUST_UNSORTED\fR environment variable.
.RE
.TP
\fB\-\-unstable\fR
Enable unstable features
.RS
May also be specified with the \fBJUST_UNSTABLE\fR environment variable.
.RE
.TP
\fB\-v\fR, \fB\-\-verbose\fR
Use verbose output
.RS
May also be specified with the \fBJUST_VERBOSE\fR environment variable.
.RE
.TP
\fB\-d\fR, \fB\-\-working\-directory\fR
Use <WORKING\-DIRECTORY> as working directory. \-\-justfile must also be set
.RS
May also be specified with the \fBJUST_WORKING_DIRECTORY\fR environment variable.
.RE
.TP
\fB\-\-yes\fR
Automatically confirm all recipes.
.RS
May also be specified with the \fBJUST_YES\fR environment variable.
.RE
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help
.TP
\fB\-V\fR, \fB\-\-version\fR
Print version
.TP
[\fIARGUMENTS\fR]
Overrides and recipe(s) to run, defaulting to the first recipe in the justfile
.SH COMMANDS
.TP
\fB\-\-changelog\fR
Print changelog
.TP
\fB\-\-choose\fR
Select one or more recipes to run using a binary chooser. If `\-\-chooser` is not passed the chooser defaults to the value of $JUST_CHOOSER, falling back to `fzf`
.TP
\fB\-c\fR, \fB\-\-command\fR
Run an arbitrary command with the working directory, `.env`, overrides, and exports set
.TP
\fB\-\-completions\fR \fI<SHELL>\fR
Print shell completion script for <SHELL>
.br
.br
[\fIpossible values: \fRbash, elvish, fish, nushell, powershell, zsh]
.TP
\fB\-\-dump\fR
Print justfile
.TP
\fB\-e\fR, \fB\-\-edit\fR
Edit justfile with editor given by $VISUAL or $EDITOR, falling back to `vim`
.TP
\fB\-\-evaluate\fR
Evaluate and print all variables. If a variable name is given as an argument, only print that variable\*(Aqs value.
.TP
\fB\-\-fmt\fR
Format and overwrite justfile
.TP
\fB\-\-groups\fR
List recipe groups
.TP
\fB\-\-init\fR
Initialize new justfile in project root
.TP
\fB\-l\fR, \fB\-\-list\fR [\fI<MODULE>...\fR]
List available recipes in <MODULE> or root if omitted
.TP
\fB\-\-man\fR
Print man page
.TP
\fB\-s\fR, \fB\-\-show\fR \fI<PATH>...\fR
Show recipe at <PATH>
.TP
\fB\-\-summary\fR
List names of available recipes
.TP
\fB\-\-usage\fR \fI<PATH>...\fR
Print recipe usage information
.TP
\fB\-\-variables\fR
List names of variables
.SH VERSION
v1.46.0
.SH AUTHORS
Casey Rodarmor <casey@rodarmor.com>

181
scripts/Mars.pm Normal file
View File

@ -0,0 +1,181 @@
package Mars;
use strict;
use warnings;
use diagnostics;
use DBI;
use Net::OpenSSH;
use feature 'say';
my $local_root = '/home/christoph/Notes/TU/MastersThesis/FailNix';
my $_remote = undef;
my $remote_host = 'mars'; # smchurla@mars.cs.tu-dortmund.de
# The mars db is bound to local port 3306 over SSH.
# - This requires using the configured 'mars'
# remote_host instead of smchurla@mars
# or setting up another tunnel here
my $_db = undef;
my $db_host = "127.0.0.1";
my $db_port = "3306";
my $db_user = "smchurla";
my $_db_password = undef;
sub ssh_connect {
if ( !defined $_remote ) {
# Initialize SSH connection
# - This connection also sets up the database tunnel
$_remote = Net::OpenSSH->new(
$remote_host,
timeout => 30,
master_opts => [
-o => 'BatchMode=yes',
-o => 'StrictHostKeyChecking=accept-new',
-o => 'ServerAliveInterval=60',
],
);
$_remote->error and die 'SSH connection failed: ' . $_remote->error;
say 'Connected to mars.cs.tu-dortmund.de';
}
return $_remote;
}
sub ssh_system {
my (@cmd) = @_;
my $ssh = ssh_connect();
$ssh->system(@cmd);
$ssh->error and die "Remote command failed (@cmd): " . $ssh->error;
}
sub ssh_capture {
my (@cmd) = @_;
my $ssh = ssh_connect();
my $out = $ssh->capture(@cmd);
$ssh->error and die "Remote command failed (@cmd): " . $ssh->error;
return $out;
}
sub upload_dir {
my ( $source_dir, $target_dir ) = @_;
my $ssh = ssh_connect();
say " - Uploading $source_dir to $target_dir...";
$ssh->scp_put(
{
recursive => 1,
copy_attrs => 1
},
$source_dir,
$target_dir,
) or die "Failed to upload $source_dir: " . $ssh->error;
}
sub download_dir {
my ( $source_dir, $target_dir ) = @_;
my $ssh = ssh_connect();
say " - Downloading $source_dir to $target_dir...";
$ssh->scp_get(
{
recursive => 1,
copy_attrs => 1
},
$source_dir,
$target_dir,
) or die "Failed to download $source_dir: " . $ssh->error;
}
sub find_remote_subdirs {
my ($dir) = @_;
my $out = ssh_capture(
'find', $dir, '-mindepth', '1',
'-maxdepth', '1', '-type', 'd',
'-printf', '%f' . "\n",
);
my @subdirs = sort grep { length } split /\n/, $out;
return @subdirs;
}
sub read_db_password_file {
if ( !defined $_db_password ) {
open( my $fhandle, '<', "$local_root/mars-db.conf" )
or die "Failed to read mars-db.conf: $!";
chomp( $_db_password = <$fhandle> );
close($fhandle);
}
return $_db_password;
}
sub db_connect {
# Opens tunnel for db_port
my $ssh = ssh_connect();
if ( !defined $_db ) {
$_db = DBI->connect( "DBI:MariaDB:host=$db_host;port=$db_port",
$db_user, read_db_password_file() )
or die 'Failed to connect to database: ' . $DBI::errstr;
say 'Connected to database';
}
return $_db;
}
sub db_disconnect {
if ( defined $_db ) {
$_db->disconnect or warn $_db->errstr;
}
}
sub db_prefix {
return $db_user;
}
sub db_list {
my $db = db_connect();
my @db_names =
sort
map { s/DBI:MariaDB://r }
grep { !/information_schema|smchurla_ll/ } $db->data_sources();
return @db_names;
}
sub db_do {
my (@cmd) = @_;
my $db = db_connect();
$db->do(@cmd) or die "Database command failed (@cmd): " . $db->errstr;
}
sub db_create {
my ($db_name) = @_;
say " - Creating database $db_name...";
db_do("create database `$db_name`");
}
sub db_drop {
my ($db_name) = @_;
say " - Dropping database $db_name...";
db_do("drop database `$db_name`");
}
1;

29
scripts/Queries/Faults.pm Normal file
View File

@ -0,0 +1,29 @@
package Queries::Faults;
use strict;
use warnings;
use diagnostics;
sub query {
my ($experiment) = @_;
return "SELECT
benchmark, resulttype, SUM(t.time2 - t.time1 + 1) AS faults,
CONCAT('0x', HEX(p.injection_instr_absolute)) AS fault_address
FROM variant v
JOIN trace t ON v.id = t.variant_id
JOIN fspgroup g ON g.variant_id = t.variant_id AND g.instr2 = t.instr2 AND g.data_address = t.data_address
JOIN result_GenericExperimentMessage r ON r.pilot_id = g.pilot_id
JOIN fsppilot p ON r.pilot_id = p.id
WHERE v.variant = '$experiment'
GROUP BY benchmark, resulttype, p.injection_instr_absolute
ORDER BY benchmark, resulttype, SUM(t.time2 - t.time1 + 1) DESC;"
}
sub args { return "--batch --raw"; }
sub filename { return "faults.csv"; }
sub postprocess { $_[0] =~ s/\t/,/g; }
1;

View File

@ -0,0 +1,28 @@
package Queries::Results;
use strict;
use warnings;
use diagnostics;
sub query {
my ($experiment) = @_;
return "SELECT
benchmark, resulttype, sum(t.time2 - t.time1 + 1) AS faults
FROM variant v
JOIN trace t ON v.id = t.variant_id
JOIN fspgroup g ON g.variant_id = t.variant_id AND g.instr2 = t.instr2 AND g.data_address = t.data_address
JOIN result_GenericExperimentMessage r ON r.pilot_id = g.pilot_id
JOIN fsppilot p ON r.pilot_id = p.id
WHERE v.variant = '$experiment'
GROUP BY v.id, resulttype
ORDER BY variant, benchmark, resulttype;";
}
sub args { return "-t"; }
sub filename { return "results.txt"; }
sub postprocess { }
1;

View File

@ -0,0 +1,28 @@
package Queries::ResultsData;
use strict;
use warnings;
use diagnostics;
sub query {
my ($experiment) = @_;
return "SELECT
benchmark, resulttype, sum(t.time2 - t.time1 + 1) AS faults
FROM variant v
JOIN trace t ON v.id = t.variant_id
JOIN fspgroup g ON g.variant_id = t.variant_id AND g.instr2 = t.instr2 AND g.data_address = t.data_address
JOIN result_GenericExperimentMessage r ON r.pilot_id = g.pilot_id
JOIN fsppilot p ON r.pilot_id = p.id
WHERE v.variant = '$experiment'
GROUP BY v.id, resulttype
ORDER BY variant, benchmark, resulttype;";
}
sub args { return "--batch --raw"; }
sub filename { return "resultsdata.csv"; }
sub postprocess { $_[0] =~ s/\t/,/g; }
1;

92
scripts/TUI.pm Normal file
View File

@ -0,0 +1,92 @@
package TUI;
use strict;
use warnings;
use diagnostics;
use Curses::UI;
# Singleton
my $_cui;
sub init_cui {
if ( !defined $_cui ) {
$_cui = new Curses::UI(
-color_support => 1,
-mouse_support => 1,
# -clear_on_exit => 1,
);
}
return $_cui;
}
sub select_from_list {
my ( $title, $multiselect, @items ) = @_;
die "No items to choose from" unless @items;
my @values = $multiselect ? ( '__ALL__', @items ) : @items;
my %labels =
$multiselect
? ( '__ALL__' => '[ALL]', map { $_ => $_ } @items, )
: map { $_ => $_ } @items;
my @selection;
my $cui = init_cui();
my $win = $cui->add( 'root', 'Window', );
my $listbox = $win->add(
'item_list',
'Listbox',
-title => $title,
-border => 1,
-values => \@values,
-labels => \%labels,
-multi => $multiselect == 1,
-radio => $multiselect == 0,
-padbottom => 1,
);
$win->add(
'info', 'Label',
-y => -1,
-text => "Space/Enter = toggle, C = confirm, Q = quit",
);
$listbox->clear_binding('loose-focus');
$listbox->set_binding(
sub {
my @picked = $listbox->get();
if ( $multiselect && grep { $_ eq '__ALL__' } @picked ) {
@selection = @items;
}
else {
@selection = @picked;
}
$cui->mainloopExit();
},
'c',
);
$listbox->set_binding(
sub {
@selection = ();
$cui->mainloopExit();
},
'q',
);
$listbox->focus();
$cui->mainloop();
$cui->leave_curses();
$cui->delete('root');
return @selection;
}
1;

142
scripts/Util.pm Normal file
View File

@ -0,0 +1,142 @@
package Util;
use strict;
use warnings;
use diagnostics;
use DateTime;
use feature 'say';
my $ntfy_url = 'https://ntfy.vps.chriphost.de';
my $ntfy_token = 'tk_rx8fd6hojuz4ekcb72j7juugkbmga'; # May be public
my $ntfy_topic = 'fail-alerts';
sub notify {
my ($msg) = @_;
system( 'curl', '-H', "Authorization: Bearer $ntfy_token",
'-d', $msg, "$ntfy_url/$ntfy_topic" );
sleep(1);
}
sub notify_file {
my ($file) = @_;
system(
'curl', '-H', "Authorization: Bearer $ntfy_token",
'-T', $file, '-H', "Filename: $file",
"$ntfy_url/$ntfy_topic"
);
sleep(1);
}
sub shell_quote {
my ($string) = @_;
$string =~ s/'/'"'"'/g;
return "'$string'";
}
sub date_now {
my $dt = DateTime->now( time_zone => 'local' );
my $date = $dt->iso8601;
return $date;
}
sub rewrite_file {
my ( $file, $matches, $replacement ) = @_;
open( my $readhandle, '<', $file ) or die "failed to open $file: $!";
my @lines;
my $found = 0;
while ( my $line = <$readhandle> ) {
if ( index( $line, $matches ) != -1 ) {
$line = $replacement;
$found = 1;
}
push @lines, $line;
}
close($readhandle) or die "failed to close $file: $!";
die "no line containing $matches found in $file" unless $found;
open( my $writehandle, '>', $file ) or die "failed to open $file: $!";
print $writehandle @lines or die "failed to write $file: $!";
close($writehandle) or die "failed to close $file: $!";
say "Updated $file with $replacement";
}
sub cpu_count {
open( my $handle, '/proc/cpuinfo' ) or die "Can't open cpuinfo: $!\n";
my $count = scalar( map /^processor/, <$handle> );
close $handle;
return $count;
}
sub find_files {
my ($dir) = @_;
opendir( my $dhandle, $dir ) or die "opendir($dir): $!";
my @files = sort grep { -f "$dir/$_" } readdir($dhandle);
closedir($dhandle);
return @files;
}
sub find_subdirs {
my ($dir) = @_;
opendir( my $dhandle, $dir ) or die "opendir($dir): $!";
my @subdirs =
sort grep { $_ ne '.' && $_ ne '..' && -d "$dir/$_" } readdir($dhandle);
closedir($dhandle);
return @subdirs;
}
sub execute_query {
my ( $experiment, $queryname, $db_conf, $builds_dir, $do_notify_file ) = @_;
my $module = "Queries::$queryname";
my $file = "$module.pm";
$file =~ s/::/\//g;
require $file;
my $query = $module->can('query') or die "$module can't query()";
my $args = $module->can('args') or die "$module can't args()";
my $filename = $module->can('filename') or die "$module can't filanem()";
my $postprocess = $module->can('postprocess')
or die "$module can't postprocess()";
my $querystring = $query->($experiment);
my $argsstring = $args->();
my $filenamestring = $filename->();
# TODO: Pass the values instead of rewriting db.conf.
# Can also use DBI's database handle directly.
my $result =
qx{mariadb --defaults-file=$db_conf $argsstring -e "$querystring"};
die "Query failed: $?" if $? != 0;
$postprocess->($result);
system( 'mkdir', '-p', "$builds_dir/$experiment" );
open( my $results_handle, '>', "$builds_dir/$experiment/$filenamestring" )
or die "failed to open file: $!";
print $results_handle $result;
close($results_handle) or die "failed to close file: $!";
if ( defined $do_notify_file and $do_notify_file == 1 ) {
notify_file("$builds_dir/$experiment/$filenamestring");
}
return $result;
}
1;

65
scripts/build.pl Executable file
View File

@ -0,0 +1,65 @@
#!/usr/bin/env perl
use strict;
use warnings;
use diagnostics;
use FindBin;
use lib $FindBin::Bin;
use Util;
use TUI;
use feature 'say';
my $date = Util::date_now;
my $local_root = '/home/christoph/Notes/TU/MastersThesis/FailNix';
my $local_builds_dir = "$local_root/builds";
my $local_experiments_dir = "$local_root/targets/wasm-module";
my $justbin = "$local_root/just-bin/just";
my $justfile = "$local_root/scripts/nixos.just";
my @targets = ( "fail", "linux", "linux-baremetal" );
my @modes = ( "c", "aot", "interp" );
sub just {
say "Running: just @_...";
system("$justbin -d $local_root -f $justfile @_")
and die "Build failed";
}
# Find and select experiments
my @experiments = map { s/\.cpp//r } Util::find_files($local_experiments_dir);
my @selected_experiments =
TUI::select_from_list( "Select Experiments to Build", 1, @experiments );
die "No experiment selected" unless @selected_experiments;
# Select targets
my @selected_targets =
TUI::select_from_list( "Select Targets Platforms", 1, @targets );
die "No target selected" unless @selected_targets;
# Select modes
my @selected_modes =
TUI::select_from_list( "Select Execution Modes", 1, @modes );
die "No mode selected" unless @selected_modes;
# Build everything
# TODO: linux-baremetal target is broken
system( "mkdir", "-p", "$local_builds_dir" );
foreach my $experiment (@selected_experiments) {
foreach my $target (@selected_targets) {
foreach my $mode (@selected_modes) {
just( "build", $experiment, $target, $mode );
system(
join " ",
(
"mv",
"$local_root/build-$experiment",
"$local_builds_dir/${date}_$experiment-$target-$mode",
)
);
}
}
}

49
scripts/deploy.pl Executable file
View File

@ -0,0 +1,49 @@
#!/usr/bin/env perl
use strict;
use warnings;
use diagnostics;
use FindBin;
use lib $FindBin::Bin;
use Util;
use Mars;
use TUI;
use feature 'say';
my $local_root = '/home/christoph/Notes/TU/MastersThesis/FailNix';
my $local_builds_dir = "$local_root/builds";
my $remote_root = '/home/lab/smchurla/Documents/failnix';
my $remote_builds_dir = "$remote_root/builds";
my $remote_runner = "$remote_root/scripts/runner.pl";
my $remote_log = "$remote_root/runner.log";
# Upload new experiments
my @experiments = grep { /fail/ } Util::find_subdirs($local_builds_dir);
my @selected_experiments =
TUI::select_from_list( "Select Experiments to Run", 1, @experiments );
die "No experiment selected" unless @selected_experiments;
Mars::ssh_system( 'mkdir', '-p', $remote_builds_dir );
Mars::upload_dir( "$local_builds_dir/$_", "$remote_builds_dir/$_" )
for @selected_experiments;
# Create dbs for new experiments
say 'Existing databases:';
say " - $_" for Mars::db_list();
say 'Creating databases...';
Mars::db_create( Mars::db_prefix . "_$_" ) for @selected_experiments;
# Launch remote runner
Mars::ssh_system(
join " ",
(
"nohup sh -c",
Util::shell_quote("cd $remote_root && perl $remote_runner"),
">" . Util::shell_quote($remote_log),
"2>&1 < /dev/null &"
)
);
say "Started remote runner for ", scalar(@experiments), " experiments";

630
scripts/explore.pl Normal file
View File

@ -0,0 +1,630 @@
#!/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');

134
scripts/fail.just Normal file
View File

@ -0,0 +1,134 @@
[doc("Trace a golden run using FAIL*")]
[group("4: fail")]
trace module:
{{ BOCHS_RUNNER }} \
-V {{ FAIL_SHARE }}/vgabios.bin \
-b {{ FAIL_SHARE }}/BIOS-bochs-latest \
-1 \
-f {{ FAIL_TRACE }} \
-e {{ BUILD_DIR }}-{{ module }}/system.elf \
-i {{ BUILD_DIR }}-{{ module }}/system.iso \
-- \
-Wf,--start-symbol=fail_start_trace \
-Wf,--save-symbol=fail_start_trace \
-Wf,--end-symbol=fail_stop_trace \
-Wf,--state-file={{ BUILD_DIR }}-{{ module }}/state \
-Wf,--trace-file={{ BUILD_DIR }}-{{ module }}/trace.pb \
-Wf,--elf-file={{ BUILD_DIR }}-{{ module }}/system.elf
@echo "Next step: \"just import {{ module }}\""
# [doc("Dump a FAIL* golden run trace")]
# [group("fail")]
# dump module:
# {{ FAIL_DUMP }} {{ BUILD_DIR }}-{{ module }}/trace.pb
[doc("Import a FAIL* golden run trace")]
[group("4: fail")]
import module:
{{ FAIL_IMPORT }} --database-option-file ./db.conf -t {{ BUILD_DIR }}-{{ module }}/trace.pb \
-i MemoryImporter \
-e {{ BUILD_DIR }}-{{ module }}/system.elf -v {{ module }} -b mem
{{ FAIL_IMPORT }} --database-option-file ./db.conf -t {{ BUILD_DIR }}-{{ module }}/trace.pb \
-i RegisterImporter \
-e {{ BUILD_DIR }}-{{ module }}/system.elf -v {{ module }} -b regs --flags
{{ FAIL_IMPORT }} --database-option-file ./db.conf -t {{ BUILD_DIR }}-{{ module }}/trace.pb \
-i RegisterImporter \
-e {{ BUILD_DIR }}-{{ module }}/system.elf -v {{ module }} -b ip --no-gp --ip
{{ FAIL_IMPORT }} --database-option-file ./db.conf -t {{ BUILD_DIR }}-{{ module }}/trace.pb \
-i ElfImporter --objdump objdump \
-e {{ BUILD_DIR }}-{{ module }}/system.elf -v {{ module }} -b ip
{{ FAIL_IMPORT }} --database-option-file ./db.conf -t {{ BUILD_DIR }}-{{ module }}/trace.pb \
-i ElfImporter --objdump objdump \
-e {{ BUILD_DIR }}-{{ module }}/system.elf -v {{ module }} -b mem
{{ FAIL_IMPORT }} --database-option-file ./db.conf -t {{ BUILD_DIR }}-{{ module }}/trace.pb \
-i ElfImporter --objdump objdump \
-e {{ BUILD_DIR }}-{{ module }}/system.elf -v {{ module }} -b regs
# {{ FAIL_IMPORT }} --database-option-file ./db.conf -t {{ BUILD_DIR }}-{{ module }}/trace.pb \
# -i ElfImporter --objdump objdump \
# -e {{ BUILD_DIR }}-{{ module }}/system.elf -v {{ module }} -b ip --sources
# {{ FAIL_IMPORT }} --database-option-file ./db.conf -t {{ BUILD_DIR }}-{{ module }}/trace.pb \
# -i ElfImporter --objdump objdump \
# -e {{ BUILD_DIR }}-{{ module }}/system.elf -v {{ module }} -b mem --sources
# {{ FAIL_IMPORT }} --database-option-file ./db.conf -t {{ BUILD_DIR }}-{{ module }}/trace.pb \
# -i ElfImporter --objdump objdump \
# -e {{ BUILD_DIR }}-{{ module }}/system.elf -v {{ module }} -b regs --sources
{{ FAIL_PRUNE }} --database-option-file ./db.conf -v {{ module }} -b %% --overwrite
@echo "Next step: \"just server {{ module }}\""
[doc("Start the FAIL* campaign server")]
[group("4: fail")]
server module:
{{ FAIL_SERVER }} \
--port {{ FAIL_SERVER_PORT }} \
--database-option-file ./db.conf \
-v {{ module }} \
-b % \
--inject-single-bit \
--inject-registers \
&
@echo "Next step: \"just client {{ module }}\""
[doc("Stop the FAIL* campaign server")]
[group("4: fail")]
stop-server:
pkill -f {{ FAIL_SERVER }}
[doc("Start a FAIL* campaign client")]
[group("4: fail")]
client module:
# -Wf,--catch-write-textsegment
# -Wf,--catch-outerspace
{{ BOCHS_RUNNER }} \
-V {{ FAIL_SHARE }}/vgabios.bin \
-b {{ FAIL_SHARE }}/BIOS-bochs-latest \
-f {{ FAIL_INJECT }} \
-e {{ BUILD_DIR }}-{{ module }}/system.elf \
-i {{ BUILD_DIR }}-{{ module }}/system.iso \
-j {{ num_cpus() }} \
-- \
-Wf,--server-port={{ FAIL_SERVER_PORT }} \
-Wf,--state-dir={{ BUILD_DIR }}-{{ module }}/state \
-Wf,--trap \
-Wf,--catch-outerspace \
-Wf,--timeout=500000 \
-Wf,--ok-marker=fail_marker_positive \
-Wf,--fail-marker=fail_marker_negative \
-Wf,--detected-marker=fail_marker_detected \
> /dev/null
@echo "Next step: \"just result {{ module }}\" or \"just resultbrowser\""
[doc("Query FAIL* marker statistics from the database")]
[group("4: fail")]
result module:
@echo "select variant, benchmark, resulttype, sum(t.time2 - t.time1 + 1) as faults \
FROM variant v \
JOIN trace t ON v.id = t.variant_id \
JOIN fspgroup g ON g.variant_id = t.variant_id AND g.instr2 = t.instr2 AND g.data_address = t.data_address \
JOIN result_GenericExperimentMessage r ON r.pilot_id = g.pilot_id \
JOIN fsppilot p ON r.pilot_id = p.id \
WHERE v.variant = \"{{ module }}\" \
GROUP BY v.id, resulttype \
ORDER BY variant, benchmark, resulttype;" | mariadb --defaults-file=./db.conf -t
[doc("Dump FAIL* markers to CSV")]
[group("4: fail")]
result-csv module:
@echo "SELECT \
CONCAT(\"0x\", HEX(p.injection_instr_absolute)) AS fault_address, \
SUM(t.time2 - t.time1 + 1) AS total_fail_markers \
FROM trace t \
JOIN variant v ON v.id = t.variant_id \
JOIN fspgroup g ON g.variant_id = t.variant_id AND g.instr2 = t.instr2 AND g.data_address = t.data_address \
JOIN result_GenericExperimentMessage r ON r.pilot_id = g.pilot_id \
JOIN fsppilot p ON p.id = r.pilot_id \
WHERE v.variant = \"{{ module }}\" AND r.resulttype = \"FAIL_MARKER\" \
GROUP BY p.injection_instr_absolute \
ORDER BY SUM(t.time2 - t.time1 + 1) DESC;" | mariadb --defaults-file=./db.conf --batch --raw | sed 's/\t/,/g'
[doc("Start the FAIL* resultbrowser")]
[group("4: fail")]
resultbrowser:
{{ RESULT_BROWSER }} -c ./db.conf --host=0.0.0.0 --port={{ RESULTBROWSER_PORT }}

35
scripts/mars.just Normal file
View File

@ -0,0 +1,35 @@
import "fail.just"
BUILD_DIR := "build"
# FAIL* variables
FAIL_SERVER_PORT := "22941"
RESULTBROWSER_PORT := "22941"
FAIL_BIN := "fail/bin"
FAIL_SHARE := "fail/share"
BOCHS_RUNNER := f"{{FAIL_BIN}}/bochs-experiment-runner.py"
FAIL_TRACE := f"{{FAIL_BIN}}/fail-x86-tracing"
FAIL_DUMP := f"{{FAIL_BIN}}/dump-trace"
FAIL_IMPORT := f"{{FAIL_BIN}}/import-trace"
FAIL_PRUNE := f"{{FAIL_BIN}}/prune-trace"
FAIL_SERVER := f"{{FAIL_BIN}}/generic-experiment-server"
FAIL_INJECT := f"{{FAIL_BIN}}/generic-experiment-client"
RESULT_BROWSER := f"{{FAIL_BIN}}/resultbrowser.py"
# =================================================================================================================== #
# Helper recipes
# =================================================================================================================== #
[default]
[private]
list:
@./just --list --unsorted
# Create a database:
# - mysql -u smchurla -p
# - CREATE DATABASE database_name;
# - SHOW DATABASES;
procs:
ps -u smchurla

480
scripts/menu.pl Normal file
View File

@ -0,0 +1,480 @@
#!/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 %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;
},
'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
my $attach_image = sub {
my ($name) = @_;
return unless ( -f "$local_archive_dir/$experiment/$name.svg" );
system(
'cp', '-i',
"$local_archive_dir/$experiment/$name.svg",
"$local_obsidian_attach/${experiment}_$name.svg"
);
system( 'obsidian', 'append', "file=zettel/$experiment",
"content=![[${experiment}_$name.svg]]\n" );
};
system(
'obsidian', 'append',
"file=zettel/$experiment", "content=## Charts\n\n"
);
$attach_image->("single_result");
$attach_image->("scatter");
}
},
'10. Open Experiment In Explorer' =>
sub { do "$local_scripts_dir/explore.pl" },
'11. 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",
)
);
},
'12. Open Experiment in Binsider' => sub {
my @experiments = Util::find_subdirs($local_archive_dir);
my @selected_experiments =
TUI::select_from_list( "Select Experiments to Open in Binsider",
0, @experiments );
die "No experiment selected" unless @selected_experiments;
my $selected_experiment = $selected_experiments[0];
system( 'binsider',
"$local_archive_dir/$selected_experiment/system.elf" );
},
'13. Open Experiment in ResultBrowser' => 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" );
},
'14. Compare Experiment Results' => sub {
# TODO: To util function (select_experiments,
# could also include the file-existance filter)
my @experiments = Util::find_subdirs($local_archive_dir);
# Only include experiments with resultsdata.csv
my @viable_experiments;
foreach my $experiment (@experiments) {
push @viable_experiments, $experiment
if ( -f "$local_archive_dir/$experiment/resultsdata.csv" );
}
# Select experiments
my @selected_experiments =
TUI::select_from_list( "Select Experiments to Compare",
1, @viable_experiments );
die "No experiments selected" unless @selected_experiments;
# 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};
}
}
# TODO: To util function
sub format_number_sep {
my ($number) = @_;
1 while $number =~ s/^(-?\d+)(\d{3})/$1.$2/;
return $number;
}
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" );
foreach my $experiment (@selected_experiments) {
$heading .= sprintf( "%50s ", $experiment );
}
my @entries = ( $heading, "" );
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 ",
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 );
},
'15. Plot Results' => sub {
# Generate R ggplot2 charts
my @experiments = Util::find_subdirs($local_archive_dir);
my @selected_experiments =
TUI::select_from_list( "Select Experiments to Plot", 1,
@experiments );
die "No experiment selected" unless @selected_experiments;
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 );
}
},
'16. 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 experiment 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',
);
},
'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 @experiments = Util::find_subdirs($local_archive_dir);
my @selected_experiments =
TUI::select_from_list( "Select Archived Experiments to Delete",
1, @experiments );
die "No experiment selected" unless @selected_experiments;
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();

244
scripts/nixos.just Normal file
View File

@ -0,0 +1,244 @@
import "wasm.just"
import "fail.just"
BUILD_DIR := "build"
# Load environment variables set by "nix develop"-shell
FAIL_SHARE := env("FAIL_SHARE")
WASI_ROOT := env("WASI_ROOT")
WAMR_ROOT := env("WAMR_ROOT")
LIBIWASM_DEBUG := env("LIBIWASM_DEBUG")
LIBIWASM_RELEASE := env("LIBIWASM_RELEASE")
LIBIWASM_LINUX_DEBUG := env("LIBIWASM_LINUX_DEBUG")
LIBIWASM_LINUX_RELEASE := env("LIBIWASM_LINUX_RELEASE")
CROSS_CC := env("CROSS_CC")
LINUX_CC := env("LINUX_CC")
# FAIL* variables
FAIL_SERVER_PORT := "1111"
RESULTBROWSER_PORT := "5000"
BOCHS_RUNNER := "bochs-experiment-runner.py"
FAIL_TRACE := "fail-x86-tracing"
FAIL_DUMP := "dump-trace"
FAIL_IMPORT := "import-trace"
FAIL_PRUNE := "prune-trace"
FAIL_SERVER := "generic-experiment-server"
FAIL_INJECT := "generic-experiment-client"
RESULT_BROWSER := "resultbrowser.py"
# =================================================================================================================== #
# Helper recipes
# =================================================================================================================== #
[default]
[private]
list:
@just --list
[private]
create-build-dir module:
mkdir -p {{ BUILD_DIR }}-{{ module }}
[doc("Delete the build directory")]
clean module:
rm -rf {{ BUILD_DIR }}-{{ module }}
# =================================================================================================================== #
# MySQL recipes
# =================================================================================================================== #
[doc("Start MySQL container to receive FAIL* trace/campaign results")]
[group("3: fail db")]
start-db:
docker run -d \
--name fail-db \
-e MYSQL_ROOT_PASSWORD=fail \
-e MYSQL_USER=fail \
-e MYSQL_PASSWORD=fail \
-e MYSQL_DATABASE=fail \
-p 3306:3306 \
mysql
[doc("Connect to MySQL database using DBeaver")]
[group("3: fail db")]
connect-db:
dbeaver -con "name=fail|driver=mysql|host=localhost|port=3306|database=fail|user=fail|password=fail"
[doc("Stop MySQL container")]
[group("3: fail db")]
stop-db:
docker stop fail-db
[doc("Remove MySQL container")]
[group("3: fail db")]
remove-db:
docker container rm fail-db
# =================================================================================================================== #
# Debugging recipes
# =================================================================================================================== #
[doc("Launch gdb")]
[group("debug")]
gdb module:
gdb --tui {{ BUILD_DIR }}-{{ module }}/system.elf
# [doc("Launch radare2 at address and disassemble")]
# [group("debug")]
# r2 module addr="dbg.os_main":
# # -e asm.section=true
# # -e asm.bytes=true
# radare2 -AA \
# -c "f TARGET @ {{ addr }}; s {{ addr }}; pd-- 30" \
# -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 \
# {{ BUILD_DIR }}-{{ module }}/system.elf
[doc("Disassemble with objdump at address")]
[group("debug")]
dump dir addr="0x100000" saddr="0x100100":
objdump {{ dir }}/system.elf \
--disassemble-all \
--disassembler-options=intel \
--disassembler-color=terminal \
--source \
--demangle \
--section=.text \
--start-address={{ addr }} \
--stop-address={{ saddr }} \
--prefix={{ dir }} \
--prefix-strip=7
[doc("Launch radare2 at address (interactive)")]
[group("debug")]
r2i module addr="dbg.os_main":
# -e asm.section=true
# -e asm.bytes=true
radare2 -AA \
-c "s {{ addr }}" \
-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 \
{{ BUILD_DIR }}-{{ module }}/system.elf
# =================================================================================================================== #
# Just do it
# =================================================================================================================== #
[arg("mode", pattern="c|aot|interp", help="Which WASM mode to use")]
[arg("target", pattern="fail|linux|linux-baremetal", help="Which platform to compile for")]
[doc("Perform all steps for a fail/linux/linux-bm build with aot/interp WASM")]
[group("5: just do it")]
build module="__help" target="fail" mode="aot":
#!/usr/bin/env sh
if [ "{{ module }}" = "__help" ]; then
just --usage build
exit 0
fi
just clean {{ module }}
just create-build-dir {{ module }}
just copy-c-module {{ module }}
if [ "{{ mode }}" = "aot" ]; then
just build-wasm-module {{ module }}
just build-wasm-aot {{ module }} {{ target }}
just build-wasm-aot-array {{ module }}
just prepare-aot-host {{ module }}
just build-wasm-host {{ module }} {{ target }}
just build-system-startup {{ module }} {{ target }}
just build-system-syscalls {{ module }} {{ target }}
just link-system {{ module }} {{ target }}
elif [ "{{ mode }}" = "interp" ]; then
just build-wasm-module {{ module }}
just build-wasm-interp-array {{ module }}
just prepare-interp-host {{ module }}
just build-wasm-host {{ module }} {{ target }}
just build-system-startup {{ module }} {{ target }}
just build-system-syscalls {{ module }} {{ target }}
just link-system {{ module }} {{ target }}
elif [ "{{ mode }}" = "c" ]; then
just build-c-module {{ module }} {{ target }}
just build-c-host {{ module }} {{ target }}
just build-system-startup {{ module }} {{ target }}
just link-c-system {{ module }} {{ target }}
else
echo "unknown mode: {{ mode }}" >&2
exit 1
fi
just build-iso {{ module }}
[doc("Run binary")]
[group("5: just do it")]
run module:
@echo "Running {{ module }}:"
@{{ BUILD_DIR }}-{{ module }}/system.elf
[arg("mode", pattern="c|aot|interp", help="Which WASM mode to use")]
[arg("target", pattern="fail|linux|linux-baremetal", help="Which platform to compile for")]
[doc("Perform all steps for a fail/linux/linux-bm build with aot/interp WASM")]
[group("5: just do it")]
build-run module="__help" target="fail" mode="aot": (build module target mode) (run module)
[doc("Send binaries to mars")]
[group("5: just do it")]
upload module:
scp -r {{ BUILD_DIR }}-{{ module }} mars:~/Documents/failnix/{{ BUILD_DIR }}-{{ module }}
[doc("Send markers to local")]
[group("5: just do it")]
download-markers:
scp mars:~/Documents/failnix/markers.csv ./markers.csv
[doc("Perform all steps for a FAIL* campaign")]
[group("5: just do it")]
inject module:
just start-db
@echo "Waiting for database..."
sleep 20
just trace {{ module }}
just import {{ module }}
just server {{ module }}
just client {{ module }}
just result {{ module }}
[doc("Copy build directory to injections/ with timestamp")]
[group("5: just do it")]
archive module suffix:
cp -rv {{ BUILD_DIR }}-{{ module }} ./injections/`date +%Y-%m-%d_%H-%M`_{{ module }}_"{{ suffix }}"

249
scripts/runner.pl Normal file
View File

@ -0,0 +1,249 @@
#!/usr/bin/env perl
use strict;
use warnings;
use diagnostics;
use FindBin;
use lib $FindBin::Bin;
use Util;
use feature 'say';
my $remote_root = '/home/lab/smchurla/Documents/failnix';
my $remote_db_conf = "$remote_root/db.conf";
my $remote_builds_dir = "$remote_root/builds";
my $remote_runner = "$remote_root/scripts/runner.pl";
my $db_user = "smchurla";
my $db_prefix = "$db_user";
my $fail_server_port = '22941';
my $resultbrowser_port = '22941';
my $fail_bin = "$remote_root/fail/bin";
my $fail_share = "$remote_root/fail/share";
my $bochs_runner = "$fail_bin/bochs-experiment-runner.py";
my $fail_trace = "$fail_bin/fail-x86-tracing";
my $fail_dump = "$fail_bin/dump-trace";
my $fail_import = "$fail_bin/import-trace";
my $fail_prune = "$fail_bin/prune-trace";
my $fail_server = "$fail_bin/generic-experiment-server";
my $fail_inject = "$fail_bin/generic-experiment-client";
my $result_browser = "$fail_bin/resultbrowser.py";
sub trace {
my ($experiment) = @_;
Util::notify("Tracing $experiment...");
system(
join " ",
(
"$bochs_runner",
"-V $fail_share/vgabios.bin",
"-b $fail_share/BIOS-bochs-latest",
"-1",
"-f $fail_trace",
"-e $remote_builds_dir/$experiment/system.elf",
"-i $remote_builds_dir/$experiment/system.iso",
"--",
"-Wf,--start-symbol=fail_start_trace",
"-Wf,--save-symbol=fail_start_trace",
"-Wf,--end-symbol=fail_stop_trace",
"-Wf,--state-file=$remote_builds_dir/$experiment/state",
"-Wf,--trace-file=$remote_builds_dir/$experiment/trace.pb",
"-Wf,--elf-file=$remote_builds_dir/$experiment/system.elf"
)
);
}
sub import_trace {
my ($experiment) = @_;
Util::notify("Importing $experiment trace...");
system(
join " ",
(
"$fail_import",
"--database-option-file $remote_db_conf",
"-t $remote_builds_dir/$experiment/trace.pb",
"-i MemoryImporter",
"-e $remote_builds_dir/$experiment/system.elf",
"-v $experiment",
"-b mem"
)
);
system(
join " ",
(
"$fail_import",
"--database-option-file $remote_db_conf",
"-t $remote_builds_dir/$experiment/trace.pb",
"-i RegisterImporter",
"-e $remote_builds_dir/$experiment/system.elf",
"-v $experiment",
"-b regs",
"--flags"
)
);
system(
join " ",
(
"$fail_import",
"--database-option-file $remote_db_conf",
"-t $remote_builds_dir/$experiment/trace.pb",
"-i RegisterImporter",
"-e $remote_builds_dir/$experiment/system.elf",
"-v $experiment",
"-b ip",
"--no-gp",
"--ip"
)
);
system(
join " ",
(
"$fail_import",
"--database-option-file $remote_db_conf",
"-t $remote_builds_dir/$experiment/trace.pb",
"-i ElfImporter",
"--objdump objdump",
"-e $remote_builds_dir/$experiment/system.elf",
"-v $experiment",
"-b ip"
)
);
system(
join " ",
(
"$fail_import",
"--database-option-file $remote_db_conf",
"-t $remote_builds_dir/$experiment/trace.pb",
"-i ElfImporter",
"--objdump objdump",
"-e $remote_builds_dir/$experiment/system.elf",
"-v $experiment",
"-b mem"
)
);
system(
join " ",
(
"$fail_import",
"--database-option-file $remote_db_conf",
"-t $remote_builds_dir/$experiment/trace.pb",
"-i ElfImporter",
"--objdump objdump",
"-e $remote_builds_dir/$experiment/system.elf",
"-v $experiment",
"-b regs"
)
);
system(
join " ",
(
"$fail_prune",
"--database-option-file $remote_db_conf",
"-v $experiment",
"-b %%", "--overwrite"
)
);
}
sub inject {
my ($experiment) = @_;
my $count = Util::cpu_count();
Util::notify("Injecting $experiment using $count cores...");
say "Forking...";
my $pid = fork();
die "fork failed: $!" unless defined $pid;
if ( $pid == 0 ) {
# child -> server
say "Running server in child process...";
exec(
join " ",
(
"$fail_server",
"--port $fail_server_port",
"--database-option-file $remote_db_conf",
"-v $experiment",
"-b %",
"--inject-single-bit",
"--inject-registers"
)
) == 0 or die "exec server failed: $!";
}
# parent -> client
say "Waiting for server...";
sleep(10);
say "Running client with $count cores in parent process";
system(
join " ",
(
"nice $bochs_runner",
"-V $fail_share/vgabios.bin",
"-b $fail_share/BIOS-bochs-latest",
"-f $fail_inject",
"-e $remote_builds_dir/$experiment/system.elf",
"-i $remote_builds_dir/$experiment/system.iso",
"-j $count",
"--",
"-Wf,--server-port=$fail_server_port",
"-Wf,--state-dir=$remote_builds_dir/$experiment/state",
"-Wf,--trap",
"-Wf,--catch-outerspace",
"-Wf,--catch-write-textsegment",
"-Wf,--timeout=500000",
"-Wf,--ok-marker=fail_marker_positive",
"-Wf,--fail-marker=fail_marker_negative",
"-Wf,--detected-marker=fail_marker_detected",
">/dev/null"
)
) == 0 or die "client failed: $?";
say "Killing server with pid $pid...";
kill 'TERM', $pid;
waitpid( $pid, 0 );
}
sub results {
my ($experiment) = @_;
my @queries = Util::find_files("$remote_root/scripts/Queries");
foreach (@queries) {
my $query = $_;
$query =~ s/\.pm//g;
# Util::notify("Running query $query for $experiment...");
Util::execute_query( $experiment, $query,
$remote_db_conf, $remote_builds_dir, 1 );
}
}
# Run experiments
my @experiments = Util::find_subdirs($remote_builds_dir);
for my $experiment (@experiments) {
Util::rewrite_file( $remote_db_conf, "database=",
"database=${db_prefix}_$experiment\n" );
trace($experiment);
import_trace($experiment);
inject($experiment);
results($experiment);
}

385
scripts/wasm.just Normal file
View File

@ -0,0 +1,385 @@
# =================================================================================================================== #
# Build WASM module recipes
# =================================================================================================================== #
WASI_CC := f"{{WASI_ROOT}}/bin/clang"
WASI_CFLAGS := "\
--target=wasm32 \
--sysroot={{WASI_ROOT}}/share/wasi-sysroot \
-z stack-size=4096 \
-O0 \
-nostdlib \
-Wl,--no-entry \
-Wl,--export=wasm_module \
-Wl,--no-gc-sections \
-Wl,--initial-memory=65536 \
-Wl,--export=__heap_base \
-Wl,--export=__data_end \
-Wl,--allow-undefined \
"
CROSS_CFLAGS_NOWASM := "\
-O0 \
-m32 \
-ffunction-sections \
-fdata-sections \
-ffreestanding \
-fpermissive \
-ggdb3 \
"
CROSS_LDFLAGS_NOWASM := "\
-Wl,--build-id=none \
-static \
-nostdlib \
-m32 \
-lc \
-lgcc \
-lm \
"
LINUX_CFLAGS_NOWASM := "\
-O0 \
-m32 \
-ffunction-sections \
-fdata-sections \
-fpermissive \
-ggdb3 \
"
LINUX_LDFLAGS_NOWASM := "\
-Wl,--build-id=none \
-m32 \
-lm \
"
WAMRC := "wamrc"
CROSS_WAMRCFLAGS := "\
--target=i386 \
--cpu=generic \
--opt-level=0 \
--enable-indirect-mode \
--disable-llvm-intrinsics \
"
LINUX_WAMRCFLAGS := "\
--target=i386 \
--cpu=generic \
--opt-level=0 \
"
XXD := "xxd"
[doc("C -> WASM: Compile a C function to a WASM module using WASI-SDK")]
[group("1: build module")]
build-wasm-module module:
{{ WASI_CC }} {{ WASI_CFLAGS }} targets/wasm-module/{{ module }}.cpp -o {{ BUILD_DIR }}-{{ module }}/wasm_module.wasm
[private]
build-wasm-aot-linux module:
{{ WAMRC }} {{ LINUX_WAMRCFLAGS }} -o {{ BUILD_DIR }}-{{ module }}/wasm_module.aot {{ BUILD_DIR }}-{{ module }}/wasm_module.wasm
[private]
build-wasm-aot-cross module:
{{ WAMRC }} {{ CROSS_WAMRCFLAGS }} -o {{ BUILD_DIR }}-{{ module }}/wasm_module.aot {{ BUILD_DIR }}-{{ module }}/wasm_module.wasm
[doc("WASM -> AOT: Compile a WASM module ahead-of-time using WAMR")]
[group("1: build module")]
build-wasm-aot module target="fail":
#!/usr/bin/env sh
if [ "{{ target }}" = "fail" ]; then
just build-wasm-aot-cross "{{ module }}"
elif [ "{{ target }}" = "linux" ]; then
just build-wasm-aot-linux "{{ module }}"
elif [ "{{ target }}" = "linux-baremetal" ]; then
just build-wasm-aot-cross "{{ module }}"
else
echo "unknown target: {{ target }}" >&2
exit 1
fi
[doc("AOT -> C-Array: Dump a WASM module compiled ahead-of-time to a binary array")]
[group("1: build module")]
build-wasm-aot-array module:
{{ XXD }} -i {{ BUILD_DIR }}-{{ module }}/wasm_module.aot > {{ BUILD_DIR }}-{{ module }}/wasm_aot_array.c
# Add __attribute__((section...)) to this array, so it is located into the .text.wamr_aot segment...
sed -i '1s/^/__attribute__((section(".text.wamr_aot"), aligned(4096)))\n/' {{ BUILD_DIR }}-{{ module }}/wasm_aot_array.c
[doc("WASM -> C-Array: Dump a WASM module to a binary array")]
[group("1: build module")]
build-wasm-interp-array module:
{{ XXD }} -i {{ BUILD_DIR }}-{{ module }}/wasm_module.wasm > {{ BUILD_DIR }}-{{ module }}/wasm_interp_array.c
[private]
build-c-module-fail module:
{{ CROSS_CC }} {{ CROSS_CFLAGS_NOWASM }} \
-c targets/wasm-module/{{ module }}.cpp \
-o {{ BUILD_DIR }}-{{ module }}/c_module.o
[private]
build-c-module-linux module:
{{ LINUX_CC }} {{ LINUX_CFLAGS_NOWASM }} \
-c targets/wasm-module/{{ module }}.cpp \
-o {{ BUILD_DIR }}-{{ module }}/c_module.o
[doc("C -> Object: Compile a C function (no WASM)")]
[group("1: build module")]
build-c-module module target="fail":
#!/usr/bin/env sh
if [ "{{ target }}" = "fail" ]; then
just build-c-module-fail "{{ module }}"
elif [ "{{ target }}" = "linux" ]; then
just build-c-module-linux "{{ module }}"
else
echo "unknown target: {{ target }}" >&2
exit 1
fi
[private]
copy-c-module module:
cp targets/wasm-module/{{ module }}.cpp {{ BUILD_DIR }}-{{ module }}/wasm-module.cpp
# =================================================================================================================== #
# Host program recipes
# =================================================================================================================== #
# FAIL*
CROSS_CFLAGS := f"-I./targets/wasm-host {{CROSS_CFLAGS_NOWASM}}"
CROSS_LDFLAGS := f"-L{{LIBIWASM_DEBUG}} -liwasm {{CROSS_LDFLAGS_NOWASM}}"
CROSS_INCLUDES := f"\
-I{{WAMR_ROOT}}/core/iwasm/include \
-I{{WAMR_ROOT}}/core/shared/utils \
-I{{WAMR_ROOT}}/core/shared/platform/baremetal \
"
# LINUX-POSIX
LINUX_CFLAGS := f"-I./targets/wasm-host {{LINUX_CFLAGS_NOWASM}}"
LINUX_LDFLAGS := f"-Wl,-rpath,{{LIBIWASM_LINUX_DEBUG}} -L{{LIBIWASM_LINUX_DEBUG}} -liwasm {{LINUX_LDFLAGS_NOWASM}}"
LINUX_INCLUDES := f"\
-I{{WAMR_ROOT}}/core/iwasm/include \
-I{{WAMR_ROOT}}/core/shared/utils \
-I{{WAMR_ROOT}}/core/shared/platform/linux \
"
# LINUX-Baremetal
LINUX_BAREMETAL_CFLAGS := "\
-I./targets/wasm-host \
-O0 \
-m32 \
-ffunction-sections \
-fdata-sections \
-ffreestanding \
-ggdb3 \
"
LINUX_BAREMETAL_LDFLAGS := f"\
-Wl,--build-id=none \
-static \
-nostdlib \
-m32 \
-L{{LIBIWASM_DEBUG}} \
-liwasm \
-lc \
-lgcc \
-lm \
--entry main \
"
LINUX_BAREMETAL_INCLUDES := f"\
-I{{WAMR_ROOT}}/core/iwasm/include \
-I{{WAMR_ROOT}}/core/shared/utils \
-I{{WAMR_ROOT}}/core/shared/platform/baremetal \
"
[doc("Insert the AOT array into the host program")]
[group("2: build host")]
prepare-aot-host module:
cp targets/wasm-host/wasm_host.c {{ BUILD_DIR }}-{{ module }}/module_host.c
sed -i \
-e "s/__WASM_ARRAY_FILE__/wasm_aot_array.c/g" \
-e "s/__WASM_ARRAY__/build_{{ module }}_wasm_module_aot/g" \
-e "s/__WASM_ARRAY_LEN__/build_{{ module }}_wasm_module_aot_len/g" \
{{ BUILD_DIR }}-{{ module }}/module_host.c
[doc("Insert the WASM array into the host program")]
[group("2: build host")]
prepare-interp-host module:
cp targets/wasm-host/wasm_host.c {{ BUILD_DIR }}-{{ module }}/module_host.c
sed -i \
-e "s/__WASM_ARRAY_FILE__/wasm_interp_array.c/g" \
-e "s/__WASM_ARRAY__/build_{{ module }}_wasm_module_wasm/g" \
-e "s/__WASM_ARRAY_LEN__/build_{{ module }}_wasm_module_wasm_len/g" \
{{ BUILD_DIR }}-{{ module }}/module_host.c
[private]
build-wasm-host-fail module:
{{ CROSS_CC }} {{ CROSS_CFLAGS }} {{ CROSS_INCLUDES }} \
-DTARGET_FAIL \
-c {{ BUILD_DIR }}-{{ module }}/module_host.c \
-o {{ BUILD_DIR }}-{{ module }}/system.o
[private]
build-wasm-host-linux module:
{{ LINUX_CC }} {{ LINUX_CFLAGS }} {{ LINUX_INCLUDES }} \
-DTARGET_LINUX \
-c {{ BUILD_DIR }}-{{ module }}/module_host.c \
-o {{ BUILD_DIR }}-{{ module }}/system.o
[private]
build-wasm-host-linux-baremetal module:
{{ CROSS_CC }} {{ LINUX_BAREMETAL_CFLAGS }} {{ LINUX_BAREMETAL_INCLUDES }} \
-DTARGET_LINUX_BAREMETAL \
-c {{ BUILD_DIR }}-{{ module }}/module_host.c \
-o {{ BUILD_DIR }}-{{ module }}/system.o
[doc("Compile C-Host: The host uses WAMR to load the WASM/AOT module")]
[group("2: build host")]
build-wasm-host module target="fail":
#!/usr/bin/env sh
if [ "{{ target }}" = "fail" ]; then
just build-wasm-host-fail "{{ module }}"
elif [ "{{ target }}" = "linux" ]; then
just build-wasm-host-linux "{{ module }}"
elif [ "{{ target }}" = "linux-baremetal" ]; then
just build-wasm-host-linux-baremetal "{{ module }}"
else
echo "unknown target: {{ target }}" >&2
exit 1
fi
[private]
build-c-host-fail module:
{{ CROSS_CC }} {{ CROSS_CFLAGS_NOWASM }} \
-DTARGET_FAIL \
-c targets/c-host/c_host.c \
-o {{ BUILD_DIR }}-{{ module }}/c_host.o
[private]
build-c-host-linux module:
{{ LINUX_CC }} {{ LINUX_CFLAGS_NOWASM }} \
-DTARGET_LINUX \
-c targets/c-host/c_host.c \
-o {{ BUILD_DIR }}-{{ module }}/c_host.o
[doc("Insert the C function into the host program (no WASM)")]
[group("2: build host")]
build-c-host module target="fail":
#!/usr/bin/env sh
if [ "{{ target }}" = "fail" ]; then
just build-c-host-fail "{{ module }}"
elif [ "{{ target }}" = "linux" ]; then
just build-c-host-linux "{{ module }}"
else
echo "unknown target: {{ target }}" >&2
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
[doc("Compile bootloader")]
[group("2: build host")]
build-system-startup module target="fail":
#!/usr/bin/env sh
if [ "{{ target }}" = "fail" ]; then
just build-system-startup-fail "{{ module }}"
else
echo "{{ target }} doesn't need bootloader"
fi
[private]
build-system-syscalls-fail module:
{{ CROSS_CC }} targets/syscalls.c {{ CROSS_CFLAGS }} -c -o {{ BUILD_DIR }}-{{ module }}/syscalls.o
[private]
build-system-syscalls-linux-baremetal module:
{{ CROSS_CC }} targets/syscalls.c {{ LINUX_BAREMETAL_CFLAGS }} -c -o {{ BUILD_DIR }}-{{ module }}/syscalls.o
[doc("Compile newlib syscall stubs")]
[group("2: build host")]
build-system-syscalls module target="fail":
#!/usr/bin/env sh
if [ "{{ target }}" = "fail" ]; then
just build-system-syscalls-fail "{{ module }}"
elif [ "{{ target }}" = "linux-baremetal" ]; then
just build-system-syscalls-linux-baremetal "{{ module }}"
else
echo "{{ target }} doesn't require syscall stubs"
fi
[private]
link-system-fail module:
{{ CROSS_CC }} \
-Wl,-T targets/linker.ld \
{{ BUILD_DIR }}-{{ module }}/system.o \
{{ BUILD_DIR }}-{{ module }}/startup.o \
{{ BUILD_DIR }}-{{ module }}/syscalls.o \
{{ CROSS_LDFLAGS }} \
-o {{ BUILD_DIR }}-{{ module }}/system.elf
[private]
link-system-linux module:
{{ LINUX_CC }} \
{{ BUILD_DIR }}-{{ module }}/system.o \
{{ LINUX_LDFLAGS }} \
-o {{ BUILD_DIR }}-{{ module }}/system.elf
[private]
link-system-linux-baremetal module:
{{ CROSS_CC }} \
{{ BUILD_DIR }}-{{ module }}/system.o \
{{ BUILD_DIR }}-{{ module }}/syscalls.o \
{{ LINUX_BAREMETAL_LDFLAGS }} \
-o {{ BUILD_DIR }}-{{ module }}/system.elf
[doc("Link C-Host, syscall stubs and bootloader")]
[group("2: build host")]
link-system module target="fail":
#!/usr/bin/env sh
if [ "{{ target }}" = "fail" ]; then
just link-system-fail "{{ module }}"
elif [ "{{ target }}" = "linux" ]; then
just link-system-linux "{{ module }}"
elif [ "{{ target }}" = "linux-baremetal" ]; then
just link-system-linux-baremetal "{{ module }}"
else
echo "unknown target: {{ target }}" >&2
exit 1
fi
[private]
link-c-system-fail module:
{{ CROSS_CC }} \
-Wl,-T targets/linker.ld \
{{ BUILD_DIR }}-{{ module }}/c_host.o \
{{ BUILD_DIR }}-{{ module }}/startup.o \
{{ BUILD_DIR }}-{{ module }}/c_module.o \
{{ CROSS_LDFLAGS_NOWASM }} \
-o {{ BUILD_DIR }}-{{ module }}/system.elf
[private]
link-c-system-linux module:
{{ LINUX_CC }} \
{{ BUILD_DIR }}-{{ module }}/c_host.o \
{{ BUILD_DIR }}-{{ module }}/c_module.o \
{{ LINUX_LDFLAGS_NOWASM }} \
-o {{ BUILD_DIR }}-{{ module }}/system.elf
[doc("Link C-Host, C-function and bootloader")]
[group("2: build host")]
link-c-system module target="fail":
#!/usr/bin/env sh
if [ "{{ target }}" = "fail" ]; then
just link-c-system-fail "{{ module }}"
elif [ "{{ target }}" = "linux" ]; then
just link-c-system-linux "{{ module }}"
else
echo "unknown target: {{ target }}" >&2
exit 1
fi
[doc("Create bootdisk")]
[group("2: build host")]
build-iso module:
mkdir -p {{ BUILD_DIR }}-{{ module }}/grub/boot/grub
cp targets/grub.cfg {{ BUILD_DIR }}-{{ module }}/grub/boot/grub/
cp {{ BUILD_DIR }}-{{ module }}/system.elf {{ BUILD_DIR }}-{{ module }}/grub/boot/
grub-mkrescue -o {{ BUILD_DIR }}-{{ module }}/system.iso {{ BUILD_DIR }}-{{ module }}/grub

20
targets/c-host/c_host.c Normal file
View File

@ -0,0 +1,20 @@
#include "../lib.h"
#ifdef TARGET_LINUX
#include <stdio.h>
#endif
void fail_start_trace(void) {}
void fail_stop_trace(void) {}
void fail_marker_positive(void) {}
void fail_marker_negative(void) {}
void fail_marker_detected(void) {}
void print(const char *msg) { PRINT("[C] %s", msg); }
int wasm_module(void);
MAIN {
int retval = wasm_module();
PRINT_SUCCESS("wasm_module returned %d.\n", retval);
RET(retval);
}

7
targets/grub.cfg Normal file
View File

@ -0,0 +1,7 @@
set timeout=0
set default=0
menuentry "CoRedOS" {
multiboot /boot/system.elf
boot
}

74
targets/lib.h Normal file
View File

@ -0,0 +1,74 @@
#ifndef _include_fail_h
#define _include_fail_h
#include <stdint.h>
#define INLINE __attribute__((always_inline)) inline
#define NOINLINE __attribute__((noinline))
#define EXPORT(fnct) __attribute__((export_name(fnct)))
#define IMPORT(fnct) __attribute__((import_module("env"), import_name(fnct)))
#if !defined(TARGET_FAIL) && !defined(TARGET_LINUX_BAREMETAL) && \
!defined(TARGET_LINUX)
// Set to linux while editing to prevent lsp errors
#define TARGET_LINUX
#endif
#ifdef TARGET_FAIL
#define MAIN void os_main(void)
#define PRINT(fmt, ...)
#define PRINT_ERROR(fmt, ...)
#define PRINT_SUCCESS(fmt, ...)
#define HOST_PRINT(msg)
#define RET(val) return
#endif
#ifdef TARGET_LINUX_BAREMETAL
#define MAIN int main(int argc, char *argv[])
#define PRINT(fmt, ...)
#define PRINT_ERROR(fmt, ...)
#define PRINT_SUCCESS(fmt, ...)
#define HOST_PRINT(msg)
#define RET(val) return
#endif
#ifdef TARGET_LINUX
#define MAIN int main(int argc, char *argv[])
#define PRINT(fmt, ...) fprintf(stdout, fmt, ##__VA_ARGS__)
#define PRINT_ERROR(fmt, ...) \
fprintf(stderr, "[Error] "); \
fprintf(stderr, fmt, ##__VA_ARGS__)
#define PRINT_SUCCESS(fmt, ...) \
fprintf(stdout, "[Success] "); \
fprintf(stdout, fmt, ##__VA_ARGS__)
#define HOST_PRINT(msg) print(msg)
#define RET(val) return val;
#endif
typedef uint16_t enc_t;
typedef uint8_t plain_t;
typedef int8_t sign_t;
#define check(vc, A, B) (((vc - B) % A) == 0)
#define encode(v, A, B) ((((plain_t)v) * ((sign_t)A)) + ((sign_t)B))
#define decode(vc, A, B) ((vc - B) / A)
#define equals(vc1, vc2, B1, B2) ((vc1 - vc2) == (B1 - B2))
#ifdef __cplusplus
extern "C" {
#endif
// Those functions are defined in the host program
void NOINLINE fail_start_trace(void); // Mark start of injection
void NOINLINE fail_stop_trace(void); // Mark end of injection
void NOINLINE fail_marker_positive(void); // Everything ok
void NOINLINE fail_marker_detected(void); // Everything ok
void NOINLINE fail_marker_negative(void); // Invalid code
void NOINLINE print(const char *msg);
#ifdef __cplusplus
}
#endif
#endif

83
targets/linker.ld Normal file
View File

@ -0,0 +1,83 @@
/* Kernel entry function */
ENTRY(_start)
OUTPUT_FORMAT(elf32-i386)
SECTIONS {
/DISCARD/ : {
*(".text.inlined*")
*(.comment)
*(.eh_frame)
*(.note.gnu.build-id)
}
/* Set kernel start address */
. = 0x100000;
/* Code and readonly data */
.text : {
/* fill gaps with int3 opcode to detect invalid jumps */
FILL(0xcc)
/* multiboot header */
multiboot_header = .;
KEEP (*(".rodata.multiboot"))
/* /\* fixed address for IRQ handlers *\/ */
/* . = 0x1000; */
/* /\* start of interrupt handlers *\/ */
/* _stext_irqs = .; */
/* /\* IRQ Handlers *\/ */
/* KEEP (*(".text.irqhandlers*")) /\* ASM *\/ */
/* KEEP (*(".text.irq_handler*")) /\* C *\/ */
/* *(".text.isrs*") /\* C *\/ */
/* *(".text.isr_*") /\* C *\/ */
/* KEEP (*(".text.OSEKOS_ISR*")) */
/* KEEP (*(".text.idt")) /\* ASM *\/ */
/* /\* sysenter handler *\/ */
/* KEEP (*(".text.sysenter_syscall")) */
/* _etext_irqs = .; */
/* . += 16; /\* padding after data, workaround for import-trace *\/ */
KEEP (*(".text.startup"))
*(".text*")
*(".rodata*")
}
/* Data and Stacks */
/* NOTE: When including the WAMR mmap region inside .text, it has to be large */
/* . = 0x200000; */
. = ALIGN(4096);
.data : {
KEEP (*(".startup_stack"))
KEEP (*(".kernel_stack"))
*(".data*")
}
/* Uninitialized data */
.bss : {
_sbss = .;
*(.bss*)
*(COMMON)
_ebss = .;
}
/* Align and mark end of all sections — heap starts here */
. = ALIGN(4096);
_end = .;
/* Memory-mapped I/O APIC */
_sioapic = 0xFEC00000;
ioapic = 0xFEC00000;
_eioapic = 0xFEC00FFF;
/* Memory-mapped Local APIC */
_slapic = 0xFEE00000;
lapic = 0xFEE00000;
_elapic = 0xFEE00FFF;
}

58
targets/startup.s Normal file
View File

@ -0,0 +1,58 @@
## Bare bone boot.s from wiki.osdev.org
# multiboot header
.section .rodata.multiboot
.align 4
# magic number
.long 0x1BADB002
# flags: align, meminfo
.long 0x3
# checksum: -(magic+flags)
.long -(0x1BADB002 + 0x3)
# the initial kernel stack
.section .kernel_stack
.global os_stack
.size os_stack, 4096
#.Lstack_bottom:
os_stack:
.byte 0
.skip 65565 # 64 KiB
# .skip 16384 # 16 KiB
# .skip 4094 # 4 KiB
.byte 0
.Lstack_top:
# The linker script specifies _start as the entry point to the kernel and the
# bootloader will jump to this position once the kernel has been loaded. It
# doesn't make sense to return from this function as the bootloader is gone.
.section .text.startup
.global _start
.type _start, @function
_start:
# Welcome to kernel mode!
# To set up a stack, we simply set the esp register to point to the top of
# our stack (as it grows downwards).
movl $.Lstack_top, %esp
# We are now ready to actually execute C code. (see ./startup.cc)
call os_main
# In case the function returns, we'll want to put the computer into an
# infinite loop. To do that, we use the clear interrupt ('cli') instruction
# to disable interrupts, the halt instruction ('hlt') to stop the CPU until
# the next interrupt arrives, and jumping to the halt instruction if it ever
# continues execution, just to be safe. We will create a local label rather
# than real symbol and jump to there endlessly.
cli
hlt
.Lhang:
jmp .Lhang
# Set the size of the _start symbol to the current location '.' minus its start.
# This is useful when debugging or when you implement call tracing.
.size _start, . - _start

35
targets/syscalls.c Normal file
View File

@ -0,0 +1,35 @@
#include <errno.h>
#include <sys/stat.h>
extern char _end; /* provided by linker script */
static char *heap_ptr = &_end;
void *sbrk(int incr) {
char *prev = heap_ptr;
heap_ptr += incr;
return prev;
}
int write(int fd, const char *buf, int len) { return len; }
int read(int fd, char *buf, int len) { return 0; }
int close(int fd) { return -1; }
int fstat(int fd, struct stat *st) {
st->st_mode = S_IFCHR;
return 0;
}
int isatty(int fd) { return 1; }
int lseek(int fd, int offset, int whence) { return 0; }
void _exit(int status) {
while (1)
;
}
void exit(int status) {
while (1)
;
}
int kill(int pid, int sig) {
errno = EINVAL;
return -1;
}
int getpid(void) { return 1; }

View File

@ -0,0 +1,139 @@
#include "../lib.h"
#include "../wasm_export.h"
#include "bh_platform.h"
#include "__WASM_ARRAY_FILE__"
#ifdef TARGET_LINUX
#include <stdio.h>
#include <string.h>
#endif
// FAIL* instrumentation symbols
void fail_start_trace(void) {}
void fail_stop_trace(void) {}
void fail_marker_positive(void) {}
void fail_marker_negative(void) {}
void fail_marker_detected(void) {}
// Those functions will be called from WASM
void host_fail_start_trace(wasm_exec_env_t exec_env) { fail_start_trace(); }
void host_fail_stop_trace(wasm_exec_env_t exec_env) { fail_stop_trace(); }
void host_fail_marker_positive(wasm_exec_env_t exec_env) {
fail_marker_positive();
}
void host_fail_marker_negative(wasm_exec_env_t exec_env) {
fail_marker_negative();
}
void host_fail_marker_detected(wasm_exec_env_t exec_env) {
fail_marker_detected();
}
void host_print(wasm_exec_env_t exec_env, const char *msg) {
PRINT("[WASM] %s", msg);
}
#define STACK_SIZE (4 * 1024)
#define HEAP_SIZE STACK_SIZE
#define RUNTIME_POOL_SIZE 4 * STACK_SIZE
MAIN {
char error_buf[128];
// Step 1: Initialize WAMR Runtime
static RuntimeInitArgs init_args;
memset(&init_args, 0, sizeof(RuntimeInitArgs));
static char global_heap_buf[RUNTIME_POOL_SIZE];
init_args.mem_alloc_type = Alloc_With_Pool;
init_args.mem_alloc_option.pool.heap_buf = global_heap_buf;
init_args.mem_alloc_option.pool.heap_size = sizeof(global_heap_buf);
init_args.max_thread_num = 1;
if (!wasm_runtime_full_init(&init_args)) {
PRINT_ERROR("wasm_runtime_full_init failed.\n");
goto error_cleanup;
}
// Step 2: Export Native Symbols
static NativeSymbol native_symbols[] = {
{"fail_start_trace", (void *)host_fail_start_trace, "()", NULL},
{"fail_stop_trace", (void *)host_fail_stop_trace, "()", NULL},
{"fail_marker_positive", (void *)host_fail_marker_positive, "()", NULL},
{"fail_marker_negative", (void *)host_fail_marker_negative, "()", NULL},
{"fail_marker_detected", (void *)host_fail_marker_detected, "()", NULL},
{"print", (void *)host_print, "(*)", NULL},
};
int count = sizeof(native_symbols) / sizeof(NativeSymbol);
if (!wasm_runtime_register_natives("env", native_symbols, count)) {
PRINT_ERROR("wasm_runtime_register_natives failed.\n");
goto error_cleanup;
}
// Step 3: Parse and Validate Module
wasm_module_t module = wasm_runtime_load(__WASM_ARRAY__, __WASM_ARRAY_LEN__,
error_buf, sizeof(error_buf));
if (!module) {
PRINT_ERROR("wasm_runtime_load failed with \"%s\".\n", error_buf);
goto error_cleanup;
}
// Step 4: Instantiate Module
wasm_module_inst_t module_inst = wasm_runtime_instantiate(
module, STACK_SIZE, HEAP_SIZE, error_buf, sizeof(error_buf));
if (!module_inst) {
PRINT_ERROR("wasm_runtime_instantiate failed with \"%s\".\n", error_buf);
goto error_cleanup;
}
// Step 5: Create Execution Environment
wasm_exec_env_t exec_env =
wasm_runtime_create_exec_env(module_inst, STACK_SIZE);
if (!exec_env) {
PRINT_ERROR("wasm_runtime_create_exec_env failed.\n");
goto error_cleanup;
}
// Step 6: Find and Call Exported Function
wasm_function_inst_t func =
wasm_runtime_lookup_function(module_inst, "wasm_module");
if (!func) {
PRINT_ERROR("wasm_runtime_lookup_function failed.\n");
goto error_cleanup;
}
// In case wasm_module accepts arguments, set them here
uint32_t args[1];
if (!wasm_runtime_call_wasm(exec_env, func, 0, args)) {
const char *exception = wasm_runtime_get_exception(module_inst);
PRINT_ERROR("wasm_runtime_call_wasm failed with \"%s\".\n",
exception ? exception : "unknown");
goto error_cleanup;
}
PRINT_SUCCESS("wasm function execution finished.\n");
// In case wasm_module returns a value we can do sth with it
uint32_t retval = args[0];
success_cleanup:
wasm_runtime_destroy_exec_env(exec_env);
wasm_runtime_deinstantiate(module_inst);
wasm_runtime_unload(module);
wasm_runtime_destroy();
RET(0);
error_cleanup:
if (exec_env) {
wasm_runtime_destroy_exec_env(exec_env);
}
if (module_inst) {
wasm_runtime_deinstantiate(module_inst);
}
if (module) {
wasm_runtime_unload(module);
}
wasm_runtime_destroy();
RET(1);
}

View File

@ -0,0 +1,36 @@
#include "../lib.h"
#define REPLICA_COUNT 1
static plain_t sum_out[REPLICA_COUNT];
#define X sum_out[0]
template <const unsigned int N> static INLINE void sum(void) {
int sum = 0;
for (int i = 0; i < 5; ++i) {
sum += 1;
}
sum_out[N] = sum;
}
extern "C" EXPORT("wasm_module") int wasm_module(void) {
X = 0;
fail_start_trace();
sum<0>();
fail_stop_trace();
if (X == 5) {
HOST_PRINT("result correct.\n");
fail_marker_positive();
return 0;
} else {
HOST_PRINT("result incorrect.\n");
fail_marker_negative();
return 1;
}
}

View File

@ -0,0 +1,58 @@
#include "../lib.h"
#define REPLICA_COUNT 3
static plain_t vote_res;
static plain_t sum_out[REPLICA_COUNT];
#define XC sum_out[0]
#define YC sum_out[1]
#define ZC sum_out[2]
// The prints here can't happen because they're disabled under test ¯\_(ツ)_/¯
static void naive_vote(void) {
if (XC == YC || XC == ZC) {
vote_res = XC;
} else if (YC == ZC) {
vote_res = YC;
} else {
HOST_PRINT("all replicas differ.\n");
fail_marker_detected();
}
}
template <const unsigned int N> static INLINE void sum(void) {
int sum = 0;
for (int i = 0; i < 5; ++i) {
sum += 1;
}
sum_out[N] = sum;
}
extern "C" EXPORT("wasm_module") int wasm_module(void) {
XC = 0;
YC = 0;
ZC = 0;
sum<0>();
sum<1>();
sum<2>();
fail_start_trace();
naive_vote();
fail_stop_trace();
if (vote_res == 5) {
HOST_PRINT("vote success.\n");
fail_marker_positive();
return 0;
} else {
HOST_PRINT("undetected error.\n");
fail_marker_negative();
return 1;
}
}

View File

@ -0,0 +1,117 @@
#include "../lib.h"
#define THE_A 110
// CONSTANT Replica results signatures
#define SIG_X 32
#define SIG_Y 23
#define SIG_Z 67
#define SIG_MAX SIG_Z
// CONSTANT Voter Result Signatures
#define SIG_s_XYZ ((SIG_X - SIG_Y) + (SIG_X - SIG_Z))
#define SIG_s_XY (SIG_X - SIG_Y)
#define SIG_s_YZ (SIG_Y - SIG_Z)
#define SIG_s_XZ (SIG_X - SIG_Z)
#define REPLICA_COUNT 3
static enc_t cored_res;
static enc_t sum_out[REPLICA_COUNT];
#define XC sum_out[0]
#define YC sum_out[1]
#define ZC sum_out[2]
// The prints here can't happen because they're disabled under test ¯\_(ツ)_/¯
static INLINE enc_t apply(enc_t vc, sign_t bdyn) {
if (bdyn > SIG_MAX) {
HOST_PRINT("signature overflow.\n");
fail_marker_detected();
}
return vc + bdyn;
}
static sign_t cored_vote(void) {
if (equals(XC, YC, SIG_X, SIG_Y)) {
if (equals(XC, ZC, SIG_X, SIG_Z)) {
cored_res = apply(XC, (XC - YC) + (XC - ZC));
return SIG_s_XYZ;
} else {
cored_res = apply(XC, (XC - YC));
return SIG_s_XY;
}
} else if (equals(YC, ZC, SIG_Y, SIG_Z)) {
cored_res = apply(YC, (YC - ZC));
return SIG_s_YZ;
} else if (equals(XC, ZC, SIG_X, SIG_Z)) {
cored_res = apply(XC, (XC - ZC));
return SIG_s_XZ;
} else {
HOST_PRINT("all replicas differ.\n");
fail_marker_detected();
return 0;
}
}
template <const unsigned int N, const sign_t S> static INLINE void sum(void) {
int sum = 0;
for (int i = 0; i < 5; ++i) {
sum += 1;
}
sum_out[N] = encode(sum, THE_A, S);
}
extern "C" EXPORT("wasm_module") int wasm_module(void) {
XC = 0;
YC = 0;
ZC = 0;
sum<0, SIG_X>();
sum<1, SIG_Y>();
sum<2, SIG_Z>();
fail_start_trace();
sign_t static_sig = cored_vote();
fail_stop_trace();
sign_t vote_result_sig;
switch (static_sig) {
case SIG_s_XYZ:
case SIG_s_XY:
case SIG_s_XZ:
vote_result_sig = SIG_X;
break;
case SIG_s_YZ:
vote_result_sig = SIG_Y;
break;
default:
HOST_PRINT("unknown static_sig.\n");
break;
}
// Inversely apply constant program flow signature.
cored_res -= static_sig;
/* Validate Vote result */
if (!check(cored_res, THE_A, vote_result_sig)) {
HOST_PRINT("voted result invalid.\n");
fail_marker_detected();
return 2;
}
plain_t res = decode(cored_res, THE_A, vote_result_sig);
if (res == 5) {
HOST_PRINT("cored success.\n");
fail_marker_positive();
return 0;
} else {
HOST_PRINT("undetected error.\n");
fail_marker_negative();
return 1;
}
}

View File

@ -0,0 +1,58 @@
#include "../lib.h"
#define REPLICA_COUNT 3
static plain_t vote_res;
static plain_t sum_out[REPLICA_COUNT];
#define XC sum_out[0]
#define YC sum_out[1]
#define ZC sum_out[2]
// The prints here can't happen because they're disabled under test ¯\_(ツ)_/¯
static void naive_vote(void) {
if (XC == YC || XC == ZC) {
vote_res = XC;
} else if (YC == ZC) {
vote_res = YC;
} else {
HOST_PRINT("all replicas differ.\n");
fail_marker_detected();
}
}
template <const unsigned int N> static INLINE void sum(void) {
int sum = 0;
for (int i = 0; i < 5; ++i) {
sum += 1;
}
sum_out[N] = sum;
}
extern "C" EXPORT("wasm_module") int wasm_module(void) {
XC = 0;
YC = 0;
ZC = 0;
fail_start_trace();
sum<0>();
sum<1>();
sum<2>();
naive_vote();
fail_stop_trace();
if (vote_res == 5) {
HOST_PRINT("vote success.\n");
fail_marker_positive();
return 0;
} else {
HOST_PRINT("undetected error.\n");
fail_marker_negative();
return 1;
}
}

View File

@ -0,0 +1,117 @@
#include "../lib.h"
#define THE_A 110
// CONSTANT Replica results signatures
#define SIG_X 32
#define SIG_Y 23
#define SIG_Z 67
#define SIG_MAX SIG_Z
// CONSTANT Voter Result Signatures
#define SIG_s_XYZ ((SIG_X - SIG_Y) + (SIG_X - SIG_Z))
#define SIG_s_XY (SIG_X - SIG_Y)
#define SIG_s_YZ (SIG_Y - SIG_Z)
#define SIG_s_XZ (SIG_X - SIG_Z)
#define REPLICA_COUNT 3
static enc_t cored_res;
static enc_t sum_out[REPLICA_COUNT];
#define XC sum_out[0]
#define YC sum_out[1]
#define ZC sum_out[2]
// The prints here can't happen because they're disabled under test ¯\_(ツ)_/¯
static INLINE enc_t apply(enc_t vc, sign_t bdyn) {
if (bdyn > SIG_MAX) {
HOST_PRINT("signature overflow.\n");
fail_marker_detected();
}
return vc + bdyn;
}
static sign_t cored_vote(void) {
if (equals(XC, YC, SIG_X, SIG_Y)) {
if (equals(XC, ZC, SIG_X, SIG_Z)) {
cored_res = apply(XC, (XC - YC) + (XC - ZC));
return SIG_s_XYZ;
} else {
cored_res = apply(XC, (XC - YC));
return SIG_s_XY;
}
} else if (equals(YC, ZC, SIG_Y, SIG_Z)) {
cored_res = apply(YC, (YC - ZC));
return SIG_s_YZ;
} else if (equals(XC, ZC, SIG_X, SIG_Z)) {
cored_res = apply(XC, (XC - ZC));
return SIG_s_XZ;
} else {
HOST_PRINT("all replicas differ.\n");
fail_marker_detected();
return 0;
}
}
template <const unsigned int N, const sign_t S> static INLINE void sum(void) {
int sum = 0;
for (int i = 0; i < 5; ++i) {
sum += 1;
}
sum_out[N] = encode(sum, THE_A, S);
}
extern "C" EXPORT("wasm_module") int wasm_module(void) {
XC = 0;
YC = 0;
ZC = 0;
fail_start_trace();
sum<0, SIG_X>();
sum<1, SIG_Y>();
sum<2, SIG_Z>();
sign_t static_sig = cored_vote();
fail_stop_trace();
sign_t vote_result_sig;
switch (static_sig) {
case SIG_s_XYZ:
case SIG_s_XY:
case SIG_s_XZ:
vote_result_sig = SIG_X;
break;
case SIG_s_YZ:
vote_result_sig = SIG_Y;
break;
default:
HOST_PRINT("unknown static_sig.\n");
break;
}
// Inversely apply constant program flow signature.
cored_res -= static_sig;
/* Validate Vote result */
if (!check(cored_res, THE_A, vote_result_sig)) {
HOST_PRINT("voted result invalid.\n");
fail_marker_detected();
return 2;
}
plain_t res = decode(cored_res, THE_A, vote_result_sig);
if (res == 5) {
HOST_PRINT("cored success.\n");
fail_marker_positive();
return 0;
} else {
HOST_PRINT("undetected error.\n");
fail_marker_negative();
return 1;
}
}

2503
targets/wasm_export.h Normal file

File diff suppressed because it is too large Load Diff