From afe99cab8b049288477cb0cd07156b71e1dd9534 Mon Sep 17 00:00:00 2001 From: Lukas Auer Date: Fri, 8 Apr 2022 12:02:16 +0200 Subject: [PATCH 01/11] Fix support for 128-bit fault masks This adds a new column, fault_mask_upper, to the HDF5 file format to be able to store the 128-bit mask value. It stores the upper 64-bit of the column fault_mask. --- faultclass.py | 7 +++---- hdf5logger.py | 4 +++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/faultclass.py b/faultclass.py index d406f91..fb00ef9 100644 --- a/faultclass.py +++ b/faultclass.py @@ -73,10 +73,9 @@ def write_to_fifo_new(self, fifo): self.trigger.address, self.trigger.hitcounter, ) - tmp = self.mask - pow(2, 64) - if tmp < 0: - tmp = 0 - out = out + " {:d} {:d} ".format(tmp, self.mask - tmp) + mask_upper = (self.mask >> 64) & (pow(2, 64) - 1) + mask_lower = self.mask & (pow(2, 64) - 1) + out = out + " {:d} {:d} ".format(mask_upper, mask_lower) out = out + "| {:d} \n".format(self.num_bytes) out = out + "$$[Fault_Ende]\n" tmp = fifo.write(out) diff --git a/hdf5logger.py b/hdf5logger.py index 24a2df8..f1d0e0f 100644 --- a/hdf5logger.py +++ b/hdf5logger.py @@ -44,6 +44,7 @@ class fault_table(tables.IsDescription): fault_type = tables.UInt8Col() fault_model = tables.UInt8Col() fault_lifespan = tables.UInt64Col() + fault_mask_upper = tables.UInt64Col() fault_mask = tables.UInt64Col() fault_num_bytes = tables.UInt8Col() @@ -225,7 +226,8 @@ def process_faults(f, group, faultlist, endpoint, myfilter): faultrow["fault_type"] = fault.type faultrow["fault_model"] = fault.model faultrow["fault_lifespan"] = fault.lifespan - faultrow["fault_mask"] = fault.mask + faultrow["fault_mask_upper"] = (fault.mask >> 64) & (pow(2, 64) - 1) + faultrow["fault_mask"] = fault.mask & (pow(2, 64) - 1) faultrow["fault_num_bytes"] = fault.num_bytes faultrow.append() faulttable.flush() From 6fa523f6f3ed8c0de94360b8835abacb5e327925 Mon Sep 17 00:00:00 2001 From: Lukas Auer Date: Mon, 18 Jul 2022 12:05:31 +0200 Subject: [PATCH 02/11] Accept integers in fault configuration directly without list --- controller.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/controller.py b/controller.py index f93a802..12cac88 100644 --- a/controller.py +++ b/controller.py @@ -46,6 +46,8 @@ def build_ranges(fault_range): """ if isinstance(fault_range, dict): return build_ranges_dict(fault_range) + if type(fault_range) == int: + return range(fault_range, fault_range + 1, 1) if len(fault_range) == 3: return range(fault_range[0], fault_range[1], fault_range[2]) elif len(fault_range) == 1: From 0d9ebc2e362cd4acc162d291aac63c345044c8ff Mon Sep 17 00:00:00 2001 From: Lukas Auer Date: Mon, 23 May 2022 17:40:25 +0200 Subject: [PATCH 03/11] Fix unintended cast of large integer registers to floats The call to pandas.concat in write_output_wrt_goldenrun() causes large integer registers values to be cast to floats. Since the register values do not have to be deduplicated, do not process them with write_output_wrt_goldenrun(). See also #41 and #42 --- faultclass.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/faultclass.py b/faultclass.py index fb00ef9..1f182c3 100644 --- a/faultclass.py +++ b/faultclass.py @@ -525,9 +525,12 @@ def readout_data( for (flag, keyword, data) in datasets: if not flag: continue - output[keyword] = write_output_wrt_goldenrun( - keyword, data, goldenrun_data - ) + if keyword.endswith("registers"): + output[keyword] = data.to_dict("records") + else: + output[keyword] = write_output_wrt_goldenrun( + keyword, data, goldenrun_data + ) if tbfaulted == 1: output["tbfaulted"] = tbfaultedlist From 2cddbb94ce3fccb0c0de313e9a0f1f84f494fec5 Mon Sep 17 00:00:00 2001 From: Lukas Auer Date: Mon, 7 Feb 2022 17:23:59 +0100 Subject: [PATCH 04/11] Fix memorydump function signatures in faultplugin The functions for reading memory dumps do not have a return value. Adjust their function signatures accordingly. --- faultplugin/faultdata.c | 6 +++--- faultplugin/faultdata.h | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/faultplugin/faultdata.c b/faultplugin/faultdata.c index 6478354..5a089c2 100644 --- a/faultplugin/faultdata.c +++ b/faultplugin/faultdata.c @@ -233,7 +233,7 @@ int read_memoryregion(uint64_t memorydump_position) return ret; } -int readout_memorydump_dump(uint64_t memorydump_position, uint64_t dump_pos) +void readout_memorydump_dump(uint64_t memorydump_position, uint64_t dump_pos) { g_autoptr(GString) out = g_string_new(""); memorydump_t *current = *(memdump + memorydump_position); @@ -267,7 +267,7 @@ int readout_memorydump_dump(uint64_t memorydump_position, uint64_t dump_pos) } } -int readout_memorydump(uint64_t memorydump_position) +void readout_memorydump(uint64_t memorydump_position) { g_autoptr(GString) out = g_string_new(""); memorydump_t *current = *(memdump + memorydump_position); @@ -284,7 +284,7 @@ int readout_memorydump(uint64_t memorydump_position) } -int readout_all_memorydump(void) +void readout_all_memorydump(void) { g_autoptr(GString) out = g_string_new(""); g_string_printf(out, "$$$[Memdump] \n"); diff --git a/faultplugin/faultdata.h b/faultplugin/faultdata.h index c671e8d..54725f7 100644 --- a/faultplugin/faultdata.h +++ b/faultplugin/faultdata.h @@ -105,7 +105,7 @@ int read_memoryregion(uint64_t memorydump_position); * @param memorydump_position: select which region should be read in vector element * @param dump_pos: select which data dump should be written to pipe. Multiple can be taken during the execution of the config. */ -int readout_memorydump_dump(uint64_t memorydump_position, uint64_t dump_pos); +void readout_memorydump_dump(uint64_t memorydump_position, uint64_t dump_pos); /** * readout_memorydump @@ -114,13 +114,13 @@ int readout_memorydump_dump(uint64_t memorydump_position, uint64_t dump_pos); * dumps are printed to data pipe. Also print config for this memorydump to data pipe * */ -int readout_memorydump(uint64_t memorydump_position); +void readout_memorydump(uint64_t memorydump_position); /** * readout_all_memorydump * * This function will send all memorydumps through the data pipe */ -int readout_all_memorydump(void); +void readout_all_memorydump(void); #endif From 08b8cc98d23b4d44295253c540f8885a2b8e1566 Mon Sep 17 00:00:00 2001 From: Lukas Auer Date: Fri, 8 Apr 2022 11:11:17 +0200 Subject: [PATCH 05/11] faultclass: remove unused function --- faultclass.py | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/faultclass.py b/faultclass.py index 1f182c3..c3b0abc 100644 --- a/faultclass.py +++ b/faultclass.py @@ -48,22 +48,6 @@ def __init__( self.num_bytes = num_bytes def write_to_fifo(self, fifo): - "Write data to the config fifo, which sends binary data" - numbytes = fifo.write(self.address.to_bytes(8, byteorder="big")) - numbytes = numbytes + fifo.write(self.type.to_bytes(8, byteorder="big")) - numbytes = numbytes + fifo.write(self.model.to_bytes(8, byteorder="big")) - numbytes = numbytes + fifo.write(self.lifespan.to_bytes(8, byteorder="big")) - numbytes = numbytes + fifo.write(self.mask.to_bytes(16, byteorder="big")) - numbytes = numbytes + fifo.write( - self.trigger.address.to_bytes(8, byteorder="big") - ) - numbytes = numbytes + fifo.write( - self.trigger.hitcounter.to_bytes(8, byteorder="big") - ) - fifo.flush() - return numbytes - - def write_to_fifo_new(self, fifo): out = "\n$$[Fault]\n" out = out + "% {:d} | {:d} | {:d} | {:d} | {:d} | {:d} | ".format( self.address, @@ -754,7 +738,7 @@ def python_worker( logger.debug("Started QEMU") """Write faults to config pipe""" for fault in fault_list: - fault.write_to_fifo_new(config_fifo) + fault.write_to_fifo(config_fifo) logger.debug("Wrote config to qemu") """ From here Qemu has started execution. Now prepare for From 95817df59839a09c48ddb7c43ba7af422bd30609 Mon Sep 17 00:00:00 2001 From: Lukas Auer Date: Sun, 8 May 2022 23:14:22 +0200 Subject: [PATCH 06/11] Make get_system_ram() function more robust In some situations, the get_system_ram() function is not able to read from the subprocess. Change the function to use communicate() instead of poll() to read from the subprocess. This fixes this issue. --- controller.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/controller.py b/controller.py index 12cac88..57524ad 100644 --- a/controller.py +++ b/controller.py @@ -173,10 +173,8 @@ def get_system_ram(): ps = subprocess.Popen( command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT ) - tmp = " " - while ps.poll() is None: - tmp = tmp + ps.stdout.read().decode("utf-8") - sp = tmp.split("kB") + tmp, _ = ps.communicate() + sp = str(tmp).split("kB") t = sp[0] mem = int(t.split(":")[1], 0) clogger.info("system ram is {}kB".format(mem)) From 6044cea710e8bd97f7546d6dda2931cfd35d6453 Mon Sep 17 00:00:00 2001 From: Lukas Auer Date: Mon, 9 May 2022 23:25:38 +0200 Subject: [PATCH 07/11] QEMU configuration must be terminated by a new line --- faultclass.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/faultclass.py b/faultclass.py index c3b0abc..712863c 100644 --- a/faultclass.py +++ b/faultclass.py @@ -635,21 +635,21 @@ def configure_qemu(control, config_qemu, num_faults, memorydump_list): if "tb_exec_list" in config_qemu: if config_qemu["tb_exec_list"] is False: - out = out + "$$disable_tb_exec_list" + out = out + "$$disable_tb_exec_list\n" else: - out = out + "$$enable_tb_exec_list" + out = out + "$$enable_tb_exec_list\n" if "tb_info" in config_qemu: if config_qemu["tb_info"] is False: - out = out + "$$disable_tb_info" + out = out + "$$disable_tb_info\n" else: - out = out + "$$enable_tb_info" + out = out + "$$enable_tb_info\n" if "mem_info" in config_qemu: if config_qemu["mem_info"] is False: - out = out + "$$disable_mem_info" + out = out + "$$disable_mem_info\n" else: - out = out + "$$enable_mem_info" + out = out + "$$enable_mem_info\n" if "start" in config_qemu: out = out + "$$ start_address: {}\n".format((config_qemu["start"])["address"]) From dcc44270067f4f96c8d15c580124d97dacedaa05 Mon Sep 17 00:00:00 2001 From: Lukas Auer Date: Mon, 23 May 2022 16:55:21 +0200 Subject: [PATCH 08/11] Unlock end point depending on state of start point not its hit counter --- faultplugin/faultplugin.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/faultplugin/faultplugin.c b/faultplugin/faultplugin.c index 6d7aa8c..0cf62e3 100644 --- a/faultplugin/faultplugin.c +++ b/faultplugin/faultplugin.c @@ -804,7 +804,7 @@ void plugin_end_information_dump() void tb_exec_end_max_event(unsigned int vcpu_index, void *vcurrent) { size_t ins = (size_t) vcurrent; - if(start_point.hitcounter != 3) + if(start_point.trignum != 3) { if(tb_counter >= tb_counter_max) { @@ -817,7 +817,7 @@ void tb_exec_end_max_event(unsigned int vcpu_index, void *vcurrent) void tb_exec_end_cb(unsigned int vcpu_index, void *vcurrent) { - if(start_point.hitcounter != 3) + if(start_point.trignum != 3) { qemu_plugin_outs("[End]: CB called\n"); if(end_point.hitcounter == 0) From e75304cacdf5e63b2b5c9866cffe59f6547d3430 Mon Sep 17 00:00:00 2001 From: Lukas Auer Date: Mon, 1 Aug 2022 11:37:13 +0200 Subject: [PATCH 09/11] Validate HDF5-file argument by ensuring parent exists --- controller.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/controller.py b/controller.py index 57524ad..c98de43 100644 --- a/controller.py +++ b/controller.py @@ -3,6 +3,7 @@ import lzma from multiprocessing import Manager, Process import pandas as pd +from pathlib import Path import pickle import prctl import subprocess @@ -471,6 +472,14 @@ def process_arguments(args): if args.compressionlevel is None: parguments["compressionlevel"] = 1 + hdf5file = Path(args.hdf5file) + if hdf5file.parent.exists() is False: + print( + f"Parent folder of specified HDF5 file does not exist: " + f"{hdf5file.parent}" + ) + exit(1) + qemu_conf = json.load(args.qemu) args.qemu.close() print(qemu_conf) From fd3d9fb68ab67a3edc8c49096e76923fed7ea826 Mon Sep 17 00:00:00 2001 From: Lukas Auer Date: Mon, 1 Aug 2022 11:39:09 +0200 Subject: [PATCH 10/11] Remove unused submodules --- .gitmodules | 6 ------ faultplugin/lib/README.md | 2 -- faultplugin/lib/avl | 1 - miniblink/libopencm3-examples | 1 - 4 files changed, 10 deletions(-) delete mode 160000 faultplugin/lib/avl delete mode 160000 miniblink/libopencm3-examples diff --git a/.gitmodules b/.gitmodules index 215631c..a1a8b17 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,3 @@ [submodule "qemu"] path = qemu url = https://github.com/Fraunhofer-AISEC/archie-qemu.git -[submodule "faultplugin/lib/avl"] - path = faultplugin/lib/avl - url = https://git.savannah.gnu.org/git/avl.git -[submodule "miniblink/libopencm3-examples"] - path = miniblink/libopencm3-examples - url = https://github.com/libopencm3/libopencm3-examples.git diff --git a/faultplugin/lib/README.md b/faultplugin/lib/README.md index 7514178..ea06e33 100644 --- a/faultplugin/lib/README.md +++ b/faultplugin/lib/README.md @@ -2,5 +2,3 @@ This avl library was taken from the GNU libavl. The source can be clonded from [here](git clone https://git.savannah.gnu.org/git/avl.git) The Website of the project can be found under [https://savannah.gnu.org/projects/avl/](https://savannah.gnu.org/projects/avl/) - -Furthermore the git is included as a subgit to this repository. diff --git a/faultplugin/lib/avl b/faultplugin/lib/avl deleted file mode 160000 index 9f30f77..0000000 --- a/faultplugin/lib/avl +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9f30f77d3738c3a8ea199130ae98b9bf7bf8ac19 diff --git a/miniblink/libopencm3-examples b/miniblink/libopencm3-examples deleted file mode 160000 index 3a89116..0000000 --- a/miniblink/libopencm3-examples +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3a8911627d45e35ca081e3dbcc891fe3fcd1b13a From 9260c8dad263aaefcd05d8c6a578e4b018ca9fc4 Mon Sep 17 00:00:00 2001 From: Lukas Auer Date: Mon, 1 Aug 2022 11:39:56 +0200 Subject: [PATCH 11/11] Remove old artifact --- faultplugin/lib/avl.o | Bin 19336 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 faultplugin/lib/avl.o diff --git a/faultplugin/lib/avl.o b/faultplugin/lib/avl.o deleted file mode 100644 index f6e57bd61a1ad70eba89ac2ae290a8abf499cb63..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19336 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 RRJfeCT3%^+1;1|j{{z;q^6LNq