diff --git a/scripts/l4sys/l4sys-prepare.sh b/scripts/l4sys/l4sys-prepare.sh index 9712661f..33995797 100755 --- a/scripts/l4sys/l4sys-prepare.sh +++ b/scripts/l4sys/l4sys-prepare.sh @@ -1,97 +1,43 @@ #!/bin/bash # Script to automate the preparation steps for an L4Sys experiment -# -# The L4Sys experiment manual lists four steps that need to be performed -# in order to launch an L4Sys campaign. This script automates the final -# three steps. -# -# Usage: Follow the manual. Find injection addresses and CR3 value and -# adjust experimentInfo.hpp accordingly. Then run this script. -# -# TODO: Adjust $FAILDIR to your needs! -BAK=experimentInfo.hpp.bak -CFG=experimentInfo.hpp -FAIL_CMD="fail-client -q" # -rc bochs-dbg.rc" -FAILDIR=/home/doebel/src/fail -DBNAME=fail +FAIL_CMD="fail-client" # -rc bochs-dbg.rc" +FAIL_ARGS="-f bochsrc-bd -q" +FAILDIR=/home/tstumpf/code/fail +BUILDDIR=/home/tstumpf/obj/fail +DBNAME=failtobias +BINDIR=~/local/bin -if [ -n "$1" ] ; then - if [ "$1" = "mem" ]; then - IMPORTER=MemoryImporter; - fi -fi +TYPE=$1 -if [ "$IMPORTER" = "MemoryImporter" ]; then - echo "Preparing memory injection experiment." +echo -e "\033[32;1m==========================================================================================" +echo "[$(date)] Prepare FI-Experiment ...." +echo -e "==========================================================================================\033[0m" + +if [ -n $TYPE ] ; then + if [ $TYPE = "mem" ]; then + IMPORTER=MemoryImporter; + echo "Preparing memory injection experiment." + elif [ $TYPE = "reg" ]; then + IMPORTER=RegisterImporter; + echo "Preparing register injection experiment." + else + echo "Specified experiment type not knwon"; + exit + fi else - IMPORTER=RegisterImporter; - echo "Preparing register injection experiment." + echo "Specify your experiment type (mem/reg)" + exit fi -function buildfail { - echo -e "\033[33mCompiling....\033[0m" - cd $FAILDIR/build - FAIL_BUILD_PARALLEL=16 $FAILDIR/scripts/rebuild-bochs.sh - >/dev/null - cd - -} - -function BuildNRun { - buildfail - echo -e "\033[33mRunning...\033[0m" - $FAIL_CMD $@ -} - -# backup experiment config -cp $CFG $BAK - blink_addr=$(nm -C fiasco.image| grep blink | cut -d\ -f 1) longjmp_addr=$(nm -C fiasco.image| grep longjmp | cut -d\ -f 1) -#echo -e "\033[35;1m[$(date)] ================== Step 0: Getting CR3 =================\033[0m" -#cat $BAK | sed -e 's/PREPARATION_STEP.*/PREPARATION_STEP 4/' >$CFG -#buildfail -#cr3=`$FAIL_CMD -f bochsrc-bd 2>/dev/null | grep CR3 | sed -e 's/ //g' | cut -d\= -f 2` -#echo \#defne L4SYS_ADDRESS_SPACE 0x$cr3 -#cat $BAK | sed -e "s/L4SYS_ADDRESS_SPACE .*/L4SYS_ADDRESS_SPACE 0x$cr3/" >$CFG -mv $CFG $BAK - -cat $BAK | sed -e "s/L4SYS_BREAK_BLINK .*/L4SYS_BREAK_BLINK 0x$blink_addr/" >$CFG -mv $CFG $BAK -cat $BAK | sed -e "s/L4SYS_BREAK_LONGJMP .*/L4SYS_BREAK_LONGJMP 0x$longjmp_addr/" >$CFG -mv $CFG $BAK - -echo -e "\033[35;1m[$(date)] ================== Step 1: Generating Snapshot =================\033[0m" -cat $BAK | sed -e 's/PREPARATION_STEP.*/PREPARATION_STEP 1/' >$CFG -BuildNRun -f bochsrc-bd - -echo -e "\033[35;1m[$(date)] ================== Step 2: Get Instruction Count ===============\033[0m" -cat $BAK | sed -e 's/PREPARATION_STEP.*/PREPARATION_STEP 2/' >$CFG -buildfail - -echo -e "\033[32mRunning...\033[0m" -values=`$FAIL_CMD 2>/dev/null | grep instructions\; | sed -e 's/.*after \(.*\) instructions;\(.*\) accepted/\1 \2/'` -echo $values -filtered_instr=`echo $values | cut -d\ -f 2` -total_instr=`echo $values | cut -d\ -f 1` - -echo -e "\033[35;1m[$(date)] ================== Step 3: Golden Run ==========================\033[0m" -cat $BAK | sed -e 's/PREPARATION_STEP.*/PREPARATION_STEP 3/' >$CFG -BuildNRun - -# now get ready to rumble... -echo -e "\033[35;1m[$(date)] ================== Step 4: Build Injection Client ==============\033[0m" -cat $BAK | sed -e "s/L4SYS_NUMINSTR.*/L4SYS_NUMINSTR $filtered_instr/" >$BAK.2 -cat $BAK.2 | sed -e "s/L4SYS_TOTINSTR.*/L4SYS_TOTINSTR $total_instr/" >$BAK.3 -cat $BAK.3 | sed -e "s/PREPARATION_STEP.*/PREPARATION_STEP 0/">$CFG -rm $BAK $BAK.2 $BAK.3 -buildfail - -echo -e "\033[35;1m[$(date)] ================== Step 5: Trace Import/Prune ==============\033[0m" -import-trace --importer $IMPORTER -e fiasco.image -d $DBNAME -t trace.pb -prune-trace -d $DBNAME +$BINDIR/fail-client -Wf,--step=all -Wf,--blink_addr=$blink_addr -Wf,--longjmp_addr=$longjmp_addr $FAIL_ARGS +$BINDIR/import-trace --importer $IMPORTER -e fiasco.image -d $DBNAME -t trace.pb +$BINDIR/prune-trace -d $DBNAME echo -e "\033[32;1m==========================================================================================" echo "[$(date)] Preparations are finished. Happy injecting...." diff --git a/src/experiments/l4-sys/CMakeLists.txt b/src/experiments/l4-sys/CMakeLists.txt index fb2a7202..0d42f23a 100644 --- a/src/experiments/l4-sys/CMakeLists.txt +++ b/src/experiments/l4-sys/CMakeLists.txt @@ -14,14 +14,15 @@ set(MY_CAMPAIGN_SRCS aluinstr.cc experiment.hpp experiment.cc + experimentFI.cc + experimentParameter.cc + experimentPreparation.cc campaign.hpp campaign.cc UDIS86.hpp UDIS86.cc InstructionFilter.hpp InstructionFilter.cc - conversion.hpp - conversion.cc ) #### PROTOBUFS #### diff --git a/src/experiments/l4-sys/campaign.cc b/src/experiments/l4-sys/campaign.cc index 5815ec33..0b111863 100644 --- a/src/experiments/l4-sys/campaign.cc +++ b/src/experiments/l4-sys/campaign.cc @@ -1,164 +1,20 @@ -#include -#include - #include "campaign.hpp" -#include "experimentInfo.hpp" -#include "conversion.hpp" -#include "comm/DatabaseCampaignMessage.pb.h" +#include "experiment.hpp" #include "cpn/CampaignManager.hpp" -#include "util/Logger.hpp" -#include "sal/SALConfig.hpp" - -using namespace std; -using namespace fail; - -char const * const results_csv = "l4sys.csv"; - -extern L4SysConversion l4sysResultConversion; -extern L4SysConversion l4sysExperimentConversion; -extern L4SysConversion l4sysRegisterConversion; - -#if 0 -bool L4SysCampaign::run() { - Logger log("L4SysCampaign"); - - ifstream test(results_csv); - if (test.is_open()) { - log << results_csv << " already exists" << endl; - return false; - } - - ofstream results(results_csv); - if (!results.is_open()) { - log << "failed to open " << results_csv << endl; - return false; - } - - log << "startup" << endl; - - int count = 0; - srand(time(NULL)); - - for (int i = 0; i < L4SYS_NUMINSTR; ++i) { - for (int r = 1; r < 9; ++r) { - for (int b = 0; b < 32; ++b) { - L4SysExperimentData *d = new L4SysExperimentData; - d->msg.set_exp_type(d->msg.GPRFLIP); - d->msg.set_register_offset(static_cast(r)); - d->msg.set_instr_offset(i); - d->msg.set_bit_offset(b); - campaignmanager.addParam(d); - ++count; - } - } - } - - for (int i = 0; i < 20000; ++i) { - L4SysExperimentData *d = new L4SysExperimentData; - d->msg.set_exp_type(d->msg.GPRFLIP); - // affect a random register - int reg_offset = rand() % 8 + 1; - d->msg.set_register_offset( - static_cast(reg_offset)); - // modify for a random instruction - int instr_offset = rand() % L4SYS_NUMINSTR; - d->msg.set_instr_offset(instr_offset); - // modify a random bit - int bit_offset = rand() % 32; - d->msg.set_bit_offset(bit_offset); - - campaignmanager.addParam(d); - ++count; - } - for (int i = 0; i < 20000; ++i) { - L4SysExperimentData *d = new L4SysExperimentData; - d->msg.set_exp_type(d->msg.ALUINSTR); - // modify for a random instruction - int instr_offset = rand() % L4SYS_NUMINSTR; - d->msg.set_instr_offset(instr_offset); - // this value is not required for this experiment, so set it to an arbitrary value - d->msg.set_bit_offset(0); - - campaignmanager.addParam(d); - ++count; - } - for (int i = 0; i < 20000; ++i) { - L4SysExperimentData *d = new L4SysExperimentData; - d->msg.set_exp_type(d->msg.IDCFLIP); - // modify for a random instruction - int instr_offset = rand() % L4SYS_NUMINSTR; - d->msg.set_instr_offset(instr_offset); - // modify a random bit - Bochs supports at most 15 bytes of instruction - int bit_offset = rand() % 125; - d->msg.set_bit_offset(bit_offset); - - campaignmanager.addParam(d); - ++count; - } - for (int i = 0; i < 20000; ++i) { - L4SysExperimentData *d = new L4SysExperimentData; - d->msg.set_exp_type(d->msg.RATFLIP); - // modify for a random instruction - int instr_offset = rand() % L4SYS_NUMINSTR; - d->msg.set_instr_offset(instr_offset); - // this value is not required for this experiment, so set it to an arbitrary value - d->msg.set_bit_offset(0); - - campaignmanager.addParam(d); - ++count; - } - - campaignmanager.noMoreParameters(); - log << "done enqueueing parameter sets (" << count << ")." << endl; - - // collect results - L4SysExperimentData *res; - int rescount = 0; - results - << "exp_type,injection_ip,register,instr_offset,injection_bit,resulttype,resultdata,output,details" - << endl; - while ((res = static_cast(campaignmanager.getDone()))) { - rescount++; - - results << l4sysExperimentConversion.output(res->msg.exp_type()) - << "," << hex << res->msg.injection_ip() << dec << ","; - if (res->msg.has_register_offset()) - results << l4sysRegisterConversion.output(res->msg.register_offset()); - else - results << "None"; - results << "," << res->msg.instr_offset() << "," << res->msg.bit_offset() - << "," - << l4sysResultConversion.output(res->msg.resulttype()) << "," - << res->msg.resultdata(); - if (res->msg.has_output()) - results << "," << res->msg.output(); - if (res->msg.has_details()) - results << "," << res->msg.details(); - results << endl; - delete res; - } - - log << "done. sent " << count << " received " << rescount << endl; - results.close(); - - return true; -} -#endif - -using namespace google::protobuf; void L4SysCampaign::cb_send_pilot(DatabaseCampaignMessage p) { -#if 0 - int inj_instr = p.injection_instr(); - int data_addr = p.data_address(); - int reg = (data_addr >> 8) & 0xF; - int width = (data_addr >> 4) & 0xF; - int offs = data_addr & 0xF; -#endif L4SysExperimentData *d = new L4SysExperimentData; d->msg.mutable_fsppilot()->CopyFrom(p); - d->msg.set_exp_type(d->msg.GPRFLIP); - //d->msg.set_exp_type(d->msg.MEM); - campaignmanager.addParam(d); + + if(!type.compare("mem")) { + d->msg.set_exp_type(d->msg.MEM); + } else if(!type.compare("reg")) { + d->msg.set_exp_type(d->msg.GPRFLIP); + } else { + log << "Specified FI-type not supported" << std::endl; + exit(-1); + } + + fail::campaignmanager.addParam(d); } diff --git a/src/experiments/l4-sys/campaign.hpp b/src/experiments/l4-sys/campaign.hpp index ec68f624..22066c75 100644 --- a/src/experiments/l4-sys/campaign.hpp +++ b/src/experiments/l4-sys/campaign.hpp @@ -1,11 +1,16 @@ #ifndef __L4SYS_CAMPAIGN_HPP__ #define __L4SYS_CAMPAIGN_HPP__ + #include "cpn/DatabaseCampaign.hpp" #include "comm/ExperimentData.hpp" #include "l4sys.pb.h" #include +#include "util/Logger.hpp" + +#include + class L4SysExperimentData : public fail::ExperimentData { public: L4SysProtoMsg msg; @@ -13,15 +18,15 @@ public: }; class L4SysCampaign : public fail::DatabaseCampaign { -#if 0 -public: - virtual bool run(); -#else virtual const google::protobuf::Descriptor * cb_result_message() { return google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName("L4SysProtoMsg"); } virtual void cb_send_pilot(DatabaseCampaignMessage pilot); -#endif + + fail::Logger log; //= m_ElementCount) { - return m_Strings[0]; - } else { - return m_Strings[res]; - } - } -private: - char const **m_Strings; - size_t m_ElementCount; -}; - -#endif // __L4SYS_CONVERSION_HPP__ diff --git a/src/experiments/l4-sys/experiment.cc b/src/experiments/l4-sys/experiment.cc index df69ddb5..728cb6f7 100644 --- a/src/experiments/l4-sys/experiment.cc +++ b/src/experiments/l4-sys/experiment.cc @@ -1,190 +1,15 @@ #include -#include -#include -#include -#include -#include -#include #include "experiment.hpp" -#include "experimentInfo.hpp" -#include "UDIS86.hpp" -#include "InstructionFilter.hpp" -#include "aluinstr.hpp" #include "campaign.hpp" -#include "conversion.hpp" #include "sal/SALConfig.hpp" -#include "sal/SALInst.hpp" -#include "sal/Memory.hpp" -#include "sal/Listener.hpp" -#include "config/FailConfig.hpp" -#include "util/ProtoStream.hpp" -#include "TracePlugin.pb.h" -#include "util/gzstream/gzstream.h" -#include using namespace std; using namespace fail; -// Check if configuration dependencies are satisfied: -#if !defined(CONFIG_EVENT_BREAKPOINTS) || !defined(CONFIG_SR_RESTORE) || \ - !defined(CONFIG_EVENT_MEMREAD) || !defined(CONFIG_EVENT_MEMWRITE) || \ - !defined(CONFIG_SR_SAVE) || \ - !defined(CONFIG_EVENT_IOPORT) -#error This experiment needs: breakpoints, memory accesses, I/O port events, \ - save, and restore. Enable these in the configuration. -#endif - -//string golden_run; -extern L4SysConversion l4sysRegisterConversion; - -string L4SysExperiment::sanitised(const string &in_str) { - string result; - int in_str_size = in_str.size(); - result.reserve(in_str_size); - for (int idx = 0; idx < in_str_size; idx++) { - char cur_char = in_str[idx]; - unsigned cur_char_value = static_cast(cur_char); - // also exclude the delimiter (',') - if (cur_char_value < 0x20 || cur_char_value > 0x7E || cur_char_value == ',') { - char str_nr[5]; - sprintf(str_nr, "\\%03o", cur_char_value); - result += str_nr; - } else { - result += cur_char; - } - } - return result; -} - -BaseListener* L4SysExperiment::waitIOOrOther(bool clear_output) { - IOPortListener ev_ioport(0x3F8, true); - BaseListener* ev = NULL; - if (clear_output) - currentOutput.clear(); - while (true) { - simulator.addListener(&ev_ioport); - ev = simulator.resume(); - //log << "hello " << simulator.getListenerCount() << std::endl; - //simulator.removeListener(&ev_ioport); - if (ev == &ev_ioport) { - currentOutput += ev_ioport.getData(); - //log << currentOutput << std::endl; - } else { - break; - } - } - return ev; -} - -Bit32u L4SysExperiment::eipBiased() { - BX_CPU_C *cpu_context = simulator.getCPUContext(); - Bit32u EIP = cpu_context->gen_reg[BX_32BIT_REG_EIP].dword.erx; - return EIP + cpu_context->eipPageBias; -} - -const Bit8u *L4SysExperiment::calculateInstructionAddress() { - // pasted in from various nested Bochs functions and macros - I hope - // they will not change too soon (as do the Bochs developers, probably) - BX_CPU_C *cpu_context = simulator.getCPUContext(); - const Bit8u *result = cpu_context->eipFetchPtr + eipBiased(); - return result; -} - -bx_bool L4SysExperiment::fetchInstruction(BX_CPU_C *instance, - const Bit8u *instr, bxInstruction_c *iStorage) { - unsigned remainingInPage = instance->eipPageWindowSize - eipBiased(); - int ret; - -#if BX_SUPPORT_X86_64 - if (BX_CPU_THIS_PTR cpu_mode == BX_MODE_LONG_64) - ret = instance->fetchDecode64(instr, iStorage, remainingInPage); - else -#endif - ret = instance->fetchDecode32(instr, iStorage, remainingInPage); - - if (ret < 0) { - // handle instrumentation callback inside boundaryFetch - instance->boundaryFetch(instr, remainingInPage, iStorage); - return 0; - } - - return 1; -} - -void L4SysExperiment::logInjection() { -// XXX fixme -#if 0 - // explicit type assignment necessary before sending over output stream - int id = param->getWorkloadID(); - int instr_offset = param->msg.instr_offset(); - int bit_offset = param->msg.bit_offset(); - int exp_type = param->msg.exp_type(); - address_t injection_ip = param->msg.injection_ip(); - - log << "job " << id << " exp_type " << exp_type << endl; - log << "inject @ ip " << hex << injection_ip << " (offset " << dec << instr_offset - << ")" << " bit " << bit_offset << endl; -#endif -} - -BaseListener *L4SysExperiment::singleStep(bool preserveAddressSpace) { - // XXX: fixme - return 0; -#if 0 - address_t aspace = (preserveAddressSpace ? L4SYS_ADDRESS_SPACE : ANY_ADDR); - BPSingleListener singlestepping_event(ANY_ADDR, aspace); - simulator.addListener(&singlestepping_event); - /* prepare for the case that the kernel panics and never - switches back to this thread by introducing a scheduling timeout - of 10 seconds */ - TimerListener schedTimeout(10000000); - simulator.addListener(&schedTimeout); - BaseListener *ev = waitIOOrOther(false); - simulator.removeListener(&singlestepping_event); - simulator.removeListener(&schedTimeout); - - if (ev == &schedTimeout) { - // otherwise we just assume this thread is never scheduled again - log << "Result TIMEOUT" << endl; - param->msg.set_resulttype(param->msg.TIMEOUT); - param->msg.set_resultdata( - simulator.getCPU(0).getInstructionPointer()); - param->msg.set_output(sanitised(currentOutput.c_str())); - param->msg.set_details("Timed out immediately after injecting"); - - m_jc.sendResult(*param); - terminate(0); - } - return ev; -#endif -} - -void L4SysExperiment::injectInstruction( - bxInstruction_c *oldInstr, bxInstruction_c *newInstr) { - // backup the current and insert the faulty instruction - bxInstruction_c backupInstr; - memcpy(&backupInstr, oldInstr, sizeof(bxInstruction_c)); - memcpy(oldInstr, newInstr, sizeof(bxInstruction_c)); - - // execute the faulty instruction, then return - singleStep(false); - - //restore the old instruction - memcpy(oldInstr, &backupInstr, sizeof(bxInstruction_c)); -} - -unsigned L4SysExperiment::calculateTimeout(unsigned instr_left) { - // the timeout in seconds, plus one backup second (avoids rounding overhead) - // [instr] / [instr / s] = [s] - unsigned seconds = instr_left / L4SYS_BOCHS_IPS + 1; - // 1.1 (+10 percent) * 1000000 mus/s * [s] - return 1100000 * seconds; -} - L4SysExperiment::L4SysExperiment() - : m_jc("localhost"), log("L4Sys", false) + : log("L4Sys", false) { param = new L4SysExperimentData; } @@ -203,900 +28,56 @@ void L4SysExperiment::terminate(int reason) { simulator.terminate(reason); } -void L4SysExperiment::terminateWithError(string details, int reason, - L4SysProtoMsg_Result *r = 0) { - L4SysProtoMsg_Result *result; - - if (r) - result = r; - else - result = param->msg.add_result(); - - result->set_resulttype(param->msg.UNKNOWN); - result->set_resultdata(simulator.getCPU(0).getInstructionPointer()); - result->set_output(sanitised(currentOutput.c_str())); - result->set_details(details); - - m_jc.sendResult(*param); - terminate(reason); -} - - -void L4SysExperiment::startAndSaveInitState(fail::BPSingleListener* bp) -{ - bp->setWatchInstructionPointer(L4SYS_FUNC_ENTRY); - simulator.addListenerAndResume(bp); - - log << "test function entry reached, saving state" << endl; - log << "EIP: expected " << hex << bp->getTriggerInstructionPointer() - << " and actually got " - << simulator.getCPU(0).getInstructionPointer() - << endl; - log << "check the source code if the two instruction pointers are not equal" << endl; - simulator.save(L4SYS_STATE_FOLDER); - delete bp; -} - - -void L4SysExperiment::CR3run(fail::BPSingleListener *bp) -{ - log << "CR3Run: Watching for instruction " << hex << L4SYS_FUNC_ENTRY << endl; - bp->setWatchInstructionPointer(L4SYS_FUNC_ENTRY); - simulator.addListenerAndResume(bp); - log << "Reached entry point @ " << hex << bp->getTriggerInstructionPointer() - << " == " << simulator.getCPU(0).getInstructionPointer() - << endl; - log << "CR3 = " << hex << BX_CPU(0)->cr3 << endl; -} - - -BaseListener* L4SysExperiment::afterInjection(L4SysProtoMsg_Result* res) -{ - BaseListener *bl = 0; - - simtime_t t_inject = simulator.getTimerTicks(); - simtime_t t_bailout; - - ifstream instr_list_file(L4SYS_INSTRUCTION_LIST, ios::binary); - instr_list_file.seekg((1 + res->instr_offset()) * sizeof(TraceInstr)); - - RangeSetInstructionFilter filtering(L4SYS_FILTER); - - for (;;) { - // Step over _all_ instructions in the trace AS - BPSingleListener stepListener(ANY_ADDR, L4SYS_ADDRESS_SPACE_TRACE); - - TraceInstr curr_instr; - instr_list_file.read(reinterpret_cast(&curr_instr), - sizeof(TraceInstr)); - - t_bailout = simulator.getTimerTicks(); - - // step until next traced instruction - simulator.addListener(&stepListener); - bl = waitIOOrOther(false); - - // bail out if we hit a listener other than the single step - // one -> in this case the experiment is over prematurely - if (bl != &stepListener) { - // Note, the difference in this case is the diff between the - // last correct instruction and the starting point -> this is - // useful for TIMEOUT events where the actual time now would be - // the complete TIMEOUT whereas we are interested in the time - // until execution deviates from the original trace - res->set_deviate_steps(t_bailout - t_inject); - res->set_deviate_eip(-1); - log << "bailing out of single-stepping mode" << endl; - break; - } - - address_t eip = stepListener.getTriggerInstructionPointer(); - - if (!filtering.isValidInstr(eip)) - continue; - - if (eip != curr_instr.trigger_addr) { - // In the case where we see an actual instruction stream deviation, we - // want the real diff between NOW and the injection start point - t_bailout = simulator.getTimerTicks(); - log << "got " << hex << eip << " expected " - << curr_instr.trigger_addr << endl; - - log << "mismatch found after " << (t_bailout - t_inject) << " instructions." << endl; - res->set_deviate_steps(t_bailout - t_inject); - res->set_deviate_eip(eip); - - return waitIOOrOther(false); - } - } - - log << "left single-stepping mode after " << (t_bailout - t_inject) - << " instructions." << endl; - return bl; -} - -void L4SysExperiment::collectInstructionTrace(fail::BPSingleListener* bp) -{ - fail::MemAccessListener ML(ANY_ADDR, MemAccessEvent::MEM_READWRITE); - ogzstream out("trace.pb"); - ProtoOStream *os = new ProtoOStream(&out); - - size_t count = 0, inst_accepted = 0, mem = 0, mem_valid = 0; - simtime_t prevtime = 0, currtime; - simtime_diff_t deltatime; - - log << "restoring state" << endl; - simulator.restore(L4SYS_STATE_FOLDER); - currtime = simulator.getTimerTicks(); - - log << "EIP = " << hex - << simulator.getCPU(0).getInstructionPointer() - << endl; - - if (!simulator.addListener(&ML)) { - log << "did not add memory listener..." << std::endl; - exit(1); - } - if (!simulator.addListener(bp)) { - log << "did not add breakpoint listener..." << std::endl; - exit(1); - } - -#if L4SYS_FILTER_INSTRUCTIONS - ofstream instr_list_file(L4SYS_INSTRUCTION_LIST, ios::binary); - RangeSetInstructionFilter filtering(L4SYS_FILTER); - bp->setWatchInstructionPointer(ANY_ADDR); - - map times_called_map; - bool injecting = false; - - while (bp->getTriggerInstructionPointer() != L4SYS_FUNC_EXIT) { - fail::BaseListener *res = simulator.resume(); - address_t curr_addr = 0; - - // XXX: See the API problem below! - if (res == &ML) { - curr_addr = ML.getTriggerInstructionPointer(); - simulator.addListener(&ML); - if ((L4SYS_ADDRESS_SPACE_TRACE != ANY_ADDR) && (BX_CPU(0)->cr3 != L4SYS_ADDRESS_SPACE_TRACE)) { - continue; - } - ++mem; - } else if (res == bp) { - curr_addr = bp->getTriggerInstructionPointer(); - assert(curr_addr == simulator.getCPU(0).getInstructionPointer()); - simulator.addListener(bp); - ++count; - } - - currtime = simulator.getTimerTicks(); - deltatime = currtime - prevtime; - - if (curr_addr == L4SYS_FILTER_ENTRY) { - injecting = true; - } - - if (curr_addr == L4SYS_FILTER_EXIT) { - injecting = false; - } - - // Only trace if: - // 1) we are between FILTER_ENTRY and FILTER_EXIT, and - // 2) we have a valid instruction according to filter rules, and - // 3) we are in the TRACE address space - if (!injecting or - !filtering.isValidInstr(curr_addr, reinterpret_cast(calculateInstructionAddress())) - or - (BX_CPU(0)->cr3 != L4SYS_ADDRESS_SPACE_TRACE) - ) { - //log << "connt..." << std::endl; - continue; - } - - if (res == &ML) { -#if 0 - log << "Memory event IP " << std::hex << ML.getTriggerInstructionPointer() - << " @ " << ML.getTriggerAddress() << "(" - << ML.getTriggerAccessType() << "," << ML.getTriggerWidth() - << ")" << std::endl; -#endif - ++mem_valid; - - Trace_Event te; - if (deltatime != 0) { te.set_time_delta(1); }; - te.set_ip(curr_addr); - te.set_memaddr(ML.getTriggerAddress()); - te.set_accesstype( (ML.getTriggerAccessType() & MemAccessEvent::MEM_READ) ? te.READ : te.WRITE ); - te.set_width(ML.getTriggerWidth()); - os->writeMessage(&te); - } else if (res == bp) { - unsigned times_called = times_called_map[curr_addr]; - ++times_called; - times_called_map[curr_addr] = times_called; - - //log << "breakpoint event" << std::endl; - // now check if we want to add the instruction for fault injection - ++inst_accepted; - - // 1) The 'old' way of logging instructions -> DEPRECATE soon - // BUT: we are currently using the bp_counter stored in this - // file! - TraceInstr new_instr; - //log << "writing IP " << hex << curr_addr << " counter " - // << dec << times_called << "(" << hex << BX_CPU(0)->cr3 << ")" - // << endl; - new_instr.trigger_addr = curr_addr; - new_instr.bp_counter = times_called; - - instr_list_file.write(reinterpret_cast(&new_instr), sizeof(TraceInstr)); - - // 2) The 'new' way -> generate Events that can be processed by - // the generic *-trace tools - // XXX: need to log CR3 if we want multiple binaries here - Trace_Event e; - if (deltatime != 0) { e.set_time_delta(1); }; - e.set_ip(curr_addr); - os->writeMessage(&e); - } else { - printf("Unknown res? %p\n", res); - } - prevtime = currtime; - - //short sanity check - //log << "continue..." << std::endl; - } - log << "saving instructions triggered during normal execution" << endl; - instr_list_file.close(); - log << "test function calculation position reached after " - << dec << count << " instructions; " << inst_accepted << " accepted" << endl; - log << "mem accesses: " << mem << ", valid: " << mem_valid << std::endl; -#else - bp->setWatchInstructionPointer(ANY_ADDR); - while (bp->getTriggerInstructionPointer() != L4SYS_FUNC_EXIT) - { - fail::BaseListener *res = simulator.resume(); - address_t curr_addr = 0; - - // XXX: See the API problem below! - if (res == &ML) { - curr_addr = ML.getTriggerInstructionPointer(); - simulator.addListener(&ML); - if ((L4SYS_ADDRESS_SPACE_TRACE != ANY_ADDR) && (BX_CPU(0)->cr3 != L4SYS_ADDRESS_SPACE_TRACE)) { - continue; - } - ++mem; - } else if (res == bp) { - curr_addr = bp->getTriggerInstructionPointer(); - assert(curr_addr == simulator.getCPU(0).getInstructionPointer()); - simulator.addListener(bp); - ++count; - } - - if (curr_addr < 0xC0000000) // XXX filter for kernel-only experiment - continue; - - currtime = simulator.getTimerTicks(); - deltatime = currtime - prevtime; - - if (res == &ML) { -#if 0 - log << "Memory event IP " << std::hex << ML.getTriggerInstructionPointer() - << " @ " << ML.getTriggerAddress() << "(" - << ML.getTriggerAccessType() << "," << ML.getTriggerWidth() - << ")" << std::endl; -#endif - ++mem_valid; - - Trace_Event te; - if (deltatime != 0) { te.set_time_delta(deltatime); }; - te.set_ip(curr_addr); - te.set_memaddr(ML.getTriggerAddress()); - te.set_accesstype( (ML.getTriggerAccessType() & MemAccessEvent::MEM_READ) ? te.READ : te.WRITE ); - te.set_width(ML.getTriggerWidth()); - os->writeMessage(&te); - } else if (res == bp) { - Trace_Event e; - if (deltatime != 0) { e.set_time_delta(deltatime); }; - e.set_ip(curr_addr); - os->writeMessage(&e); - } else { - printf("Unknown res? %p\n", res); - } - prevtime = currtime; - } - log << "test function calculation position reached after " - << dec << count << " instructions; " << count << " accepted" << endl; - log << "mem accesses: " << mem << ", valid: " << mem_valid << std::endl; -#endif - delete bp; -} - -void L4SysExperiment::goldenRun(fail::BPSingleListener* bp) -{ - log << "restoring state" << endl; - simulator.restore(L4SYS_STATE_FOLDER); - log << "EIP = " << hex - << simulator.getCPU(0).getInstructionPointer() - << endl; - - std::string golden_run; - ofstream golden_run_file(L4SYS_CORRECT_OUTPUT); - bp->setWatchInstructionPointer(L4SYS_FUNC_EXIT); - simulator.addListener(bp); - BaseListener* ev = waitIOOrOther(true); - if (ev == bp) { - golden_run.assign(currentOutput.c_str()); - golden_run_file << currentOutput.c_str(); - log << "Output successfully logged!" << endl; - } else { - log - << "Obviously, there is some trouble with" - << " the events registered - aborting simulation!" - << endl; - golden_run_file.close(); - terminate(10); - } - - log << "saving output generated during normal execution" << endl; - golden_run_file.close(); - delete bp; -} - - -void L4SysExperiment::getJobParameters() -{ - // get the experiment parameters - log << "asking job server for experiment parameters" << endl; - if (!m_jc.getParam(*param)) { - log << "Dying." << endl; - // communicate that we were told to die - terminate(1); - } -} - - -void L4SysExperiment::validatePrerequisites() -{ - struct stat teststruct; - if (stat(L4SYS_STATE_FOLDER, &teststruct) == -1 || - stat(L4SYS_CORRECT_OUTPUT, &teststruct) == -1) { - log << "Important data missing - call \"prepare\" first." << endl; - terminate(10); - } -} - - -void L4SysExperiment::readGoldenRun(std::string& target) -{ - ifstream golden_run_file(L4SYS_CORRECT_OUTPUT); - - if (!golden_run_file.good()) { - log << "Could not open file " << L4SYS_CORRECT_OUTPUT << endl; - terminate(20); - } - - target.assign((istreambuf_iterator(golden_run_file)), - istreambuf_iterator()); - - golden_run_file.close(); -} - - -void L4SysExperiment::setupFilteredBreakpoint(fail::BPSingleListener* bp, int instOffset) -{ - /* - * The L4Sys experiment uses instruction filtering to restrict the range - * of fault injection to only e.g., kernel instructions. - * - * To speed up injection, L4Sys furthermore does not use per-instruction - * breakpoints but only places a breakpoint on the actually interesting - * instruction (e.g., the injection EIP). Hence, we also do not count - * instructions from the beginning of the experiment, but we count how - * often a certain EIP was hit before the injection. - * - * To achieve these properties, we use an additional trace file that - * provides us with a 'hit counter' of each injection candidate. We use - * the global instruction ID (DataBaseCampaign: instruction_offset) to - * index into this trace file and determine the value for the breakpoint - * counter. - */ - ifstream instr_list_file(L4SYS_INSTRUCTION_LIST, ios::binary); - - if (!instr_list_file.good()) { - log << "Missing instruction trace" << endl; - terminate(21); - } - - log << "inst offset " << dec << instOffset << " sizeof(TraceInstr) " << sizeof(TraceInstr) << endl; - TraceInstr curr_instr; - instr_list_file.seekg(instOffset * sizeof(TraceInstr)); - log << instr_list_file.eof() << " " << instr_list_file.bad() << " " - << instr_list_file.fail() << endl; - if (instr_list_file.eof()) { - log << "Job parameters indicate position outside the traced instruction list." << endl; - terminate(1); - } - instr_list_file.read(reinterpret_cast(&curr_instr), sizeof(TraceInstr)); - instr_list_file.close(); - - log << "setting watchpoint at " << hex << curr_instr.trigger_addr << endl; - bp->setWatchInstructionPointer(curr_instr.trigger_addr); - log << "setting bp counter " << hex << curr_instr.bp_counter << endl; - bp->setCounter(curr_instr.bp_counter); -} - - -fail::BPSingleListener* -L4SysExperiment::prepareMemoryExperiment(int ip, int offset, int dataAddress) -{ - fail::BPSingleListener *bp = new BPSingleListener(0, L4SYS_ADDRESS_SPACE_TRACE); - log << "\033[34;1mMemory fault injection\033[0m at instruction " << std::hex << offset - << ", ip " << ip << ", address " << dataAddress << std::endl; - -#if L4SYS_FILTER_INSTRUCTIONS - setupFilteredBreakpoint(bp, offset); - assert(bp->getWatchInstructionPointer() == (address_t)(ip & 0xFFFFFFFF)); -#else - bp->setWatchInstructionPointer(ANY_ADDR); - bp->setCounter(offset); -#endif - return bp; -} - - -fail::BPSingleListener* -L4SysExperiment::prepareRegisterExperiment(int ip, int offset, int dataAddress) -{ - fail::BPSingleListener *bp = new BPSingleListener(0, L4SYS_ADDRESS_SPACE_TRACE); - - int reg, regOffset; - reg = ((dataAddress >> 4) & 0xF) + 1; // regs start at 1 - regOffset = dataAddress & 0xF; - - log << "\033[32;1mGPR bitflip\033[0m at instr. offset " << offset - << " reg data (" << reg << ", " - << regOffset << ")" << std::endl; - -#if L4SYS_FILTER_INSTRUCTIONS - setupFilteredBreakpoint(bp, offset); - log << bp->getWatchInstructionPointer() << std::endl; - log << ip << std::endl; - assert(bp->getWatchInstructionPointer() == (address_t)(ip & 0xFFFFFFFF)); - log << bp->getCounter() << std::endl; -#else - log << "Exp offset: " << offset << std::endl; - bp->setWatchInstructionPointer(ANY_ADDR); - bp->setCounter(offset); -#endif - return bp; -} - - -bool L4SysExperiment::doMemoryInjection(int address, int bit) -{ - MemoryManager& mm = simulator.getMemoryManager(); - - // XXX: evil, but I need to bail out if memory access is invalid - host_address_t addr = reinterpret_cast(&mm)->guestToHost(address); - if (addr == (host_address_t)ADDR_INV) - return false; - - byte_t data = mm.getByte(address); - byte_t newdata = data ^ (1 << bit); - mm.setByte(address, newdata); - log << "[" << std::hex << address << "] " << (int)data - << " -> " << (int)newdata << std::endl; - return true; -} - - -void L4SysExperiment::doRegisterInjection(int regDesc, int bit) -{ - int reg, offset; - reg = (regDesc >> 4) + 1; // regs start at 1 - offset = regDesc & 0xF; - - ConcreteCPU& cpu = simulator.getCPU(0); - Register *reg_target = cpu.getRegister(reg - 1); - regdata_t data = cpu.getRegisterContent(reg_target); - regdata_t newdata = data ^ (1 << (bit + 8 * offset)); - cpu.setRegisterContent(reg_target, newdata); - log << "Reg[" << reg << "]: " << std::hex << data << " -> " - << newdata << std::endl; -} - - bool L4SysExperiment::run() { - BPSingleListener *bp = 0; srand(time(NULL)); - log << "Starting L4Sys Experiment, phase " << PREPARATION_STEP << endl; + parseOptions(conf); -#if PREPARATION_STEP == 1 - // STEP 1: run until interesting function starts, and save state - // -> needs L4SYS_ADDRESS_SPACE, because it runs until L4SYS_FUNC_ENTRY - startAndSaveInitState(new BPSingleListener(0, L4SYS_ADDRESS_SPACE)); -#elif PREPARATION_STEP == 2 - // STEP 2: determine instructions executed - collectInstructionTrace(new BPSingleListener(0, ANY_ADDR)); - -#elif PREPARATION_STEP == 3 - // STEP 3: determine the output of a "golden run" - // -> golden run needs L4SYS_ADDRESS_SPACE as it breaks on - // L4SYS_FUNC_EXIT - goldenRun(new BPSingleListener(0, L4SYS_ADDRESS_SPACE)); - -#elif PREPARATION_STEP == 4 - CR3run(new BPSingleListener(0)); - -#elif PREPARATION_STEP == 0 - // LAST STEP: The actual experiment. - validatePrerequisites(); - - // Read the golden run output for validation purposes - std::string golden_run; - readGoldenRun(golden_run); - - getJobParameters(); - - int exp_type = param->msg.exp_type(); - int instr_offset = param->msg.fsppilot().injection_instr(); - int regData = param->msg.fsppilot().data_address(); - - if (exp_type == param->msg.MEM) { - bp = prepareMemoryExperiment(param->msg.fsppilot().injection_instr_absolute(), - param->msg.fsppilot().injection_instr(), - param->msg.fsppilot().data_address()); - } else if (exp_type == param->msg.GPRFLIP) { - bp = prepareRegisterExperiment(param->msg.fsppilot().injection_instr_absolute(), - param->msg.fsppilot().injection_instr(), - param->msg.fsppilot().data_address()); - } else { - log << "Unsupported experiment type: " << exp_type << std::endl; - terminate(1); - } - - assert(bp); - - for (unsigned bit = 0; bit < 8; ++bit) { - - L4SysProtoMsg_Result *result = param->msg.add_result(); - result->set_instr_offset(instr_offset); - - simulator.clearListeners(); - - log << "Bit " << bit << ", restoring state." << endl; - simulator.restore(L4SYS_STATE_FOLDER); - log << " ... EIP = " << std::hex << simulator.getCPU(0).getInstructionPointer() << std::endl; - - simulator.addListener(bp); - - simtime_t now = simulator.getTimerTicks(); - fail::BaseListener *go = waitIOOrOther(true); - assert(go == bp); - - log << "Hit BP @ " << hex << bp->getTriggerInstructionPointer() << " " << bp->getWatchInstructionPointer() - << " Start time " << now << ", new time " << simulator.getTimerTicks() - << ", diff = " << simulator.getTimerTicks() - now << std::endl; - -#if L4SYS_FILTER_INSTRUCTIONS - assert(bp->getTriggerInstructionPointer() == bp->getWatchInstructionPointer()); -#endif - result->set_injection_ip(bp->getTriggerInstructionPointer()); - - if (exp_type == param->msg.MEM) { - result->set_bit_offset(bit); - log << "injection addr: " - << std::hex << param->msg.fsppilot().data_address() - << std::endl; - result->set_injection_address(param->msg.fsppilot().data_address()); - if (!doMemoryInjection(param->msg.fsppilot().data_address(), bit)) - { - terminateWithError("invalid mem access", 51, result); - } - } else if (exp_type == param->msg.GPRFLIP) { - int reg = (param->msg.fsppilot().data_address() >> 4) + 1; - result->set_register_offset(static_cast(reg)); - result->set_bit_offset(bit + 8 * (param->msg.fsppilot().data_address() & 0xF)); - doRegisterInjection(param->msg.fsppilot().data_address(), bit); - } else { - log << "doing nothing for experiment type " << exp_type << std::endl; - } - - BPSingleListener ev_done(L4SYS_FUNC_EXIT, L4SYS_ADDRESS_SPACE); - simulator.addListener(&ev_done); - - // Well-known bailout point -- if we hit L4SYS_BREAK_BLINK, which - // is the entry of Vga::blink_cursor(), we know that we are in some - // kind of error handler - BPSingleListener ev_blink(L4SYS_BREAK_BLINK); - simulator.addListener(&ev_blink); - BPSingleListener ev_longjmp(L4SYS_BREAK_LONGJMP); - simulator.addListener(&ev_longjmp); - - unsigned instr_left = L4SYS_TOTINSTR - instr_offset; // XXX offset is in NUMINSTR, TOTINSTR is higher - BPSingleListener ev_incomplete(ANY_ADDR, L4SYS_ADDRESS_SPACE); - /* - * Use hard-coded value for incomplete counter. We are currently looking at short-running pieces - * of code. This means that in the error case, where a lot of data is still to be printed to serial - * line, the benchmark does not complete this within * <1.x> cycles. Instead, we use - * a frame large enough to catch some more output even at the end of a run. - */ - ev_incomplete.setCounter(2000000); - simulator.addListener(&ev_incomplete); - - /* - * This timeout will always be at least one second - see calculateTimeout() - */ - TimerListener ev_timeout(calculateTimeout(instr_left)); - simulator.addListener(&ev_timeout); - log << "continue... (" << simulator.getListenerCount() - << " breakpoints, timeout @ " << ev_timeout.getTimeout() - << std::endl; - - log << "TOListener " << (void*)&ev_timeout << " incompListener " - << (void*)&ev_incomplete << endl; - BaseListener *ev = afterInjection(result); - log << "afterInj: res.devstep = " << result->deviate_steps() << endl; -#if 0 - //do not discard output recorded so far - BaseListener *ev = waitIOOrOther(false); -#endif - - /* copying a string object that contains control sequences - * unfortunately does not work with the library I am using, - * which is why output is passed on as C string and - * the string compare is done on C strings - */ - if (ev == &ev_done) { - if (strcmp(currentOutput.c_str(), golden_run.c_str()) == 0) { - log << "Result DONE" << endl; - result->set_resulttype(param->msg.DONE); - } else { - log << "Result WRONG" << endl; - result->set_resulttype(param->msg.WRONG); - result->set_output(sanitised(currentOutput.c_str())); - } - } else if ((ev == &ev_incomplete) || - (ev == &ev_blink) || - (ev == &ev_longjmp)) { - log << "Result INCOMPLETE" << endl; - result->set_resulttype(param->msg.INCOMPLETE); - result->set_resultdata(simulator.getCPU(0).getInstructionPointer()); - result->set_output(sanitised(currentOutput.c_str())); - } else if (ev == &ev_timeout) { - log << "Result TIMEOUT" << endl; - result->set_resulttype(param->msg.TIMEOUT); - result->set_resultdata(simulator.getCPU(0).getInstructionPointer()); - result->set_output(sanitised(currentOutput.c_str())); - } else { - log << "Result WTF?" << endl; - stringstream ss; - ss << "eventid " << ev; - terminateWithError(ss.str(), 50); - } - } - - m_jc.sendResult(*param); - -// XXX: Fixme to work with database campaign! -#if 0 - else if (exp_type == param->msg.IDCFLIP) { - // this is a twisted one - - // initial definitions - bxInstruction_c *currInstr = simulator.getCurrentInstruction(); - unsigned length_in_bits = currInstr->ilen() << 3; - - // get the instruction in plain text and inject the error there - // Note: we need to fetch some extra bytes into the array - // in case the faulty instruction is interpreted to be longer - // than the original one - Bit8u curr_instr_plain[MAX_INSTR_BYTES]; - const Bit8u *addr = calculateInstructionAddress(); - memcpy(curr_instr_plain, addr, MAX_INSTR_BYTES); - - // CampaignManager has no idea of the instruction length - // (neither do we), therefore this small adaption - bit_offset %= length_in_bits; - param->msg.set_bit_offset(bit_offset); - - // do some access calculation - int byte_index = bit_offset >> 3; - Bit8u bit_index = bit_offset & 7; - - // apply the fault - curr_instr_plain[byte_index] ^= 0x80 >> bit_index; - - // decode the instruction - bxInstruction_c bochs_instr; - memset(&bochs_instr, 0, sizeof(bxInstruction_c)); - fetchInstruction(simulator.getCPUContext(), curr_instr_plain, - &bochs_instr); - - // inject it - injectInstruction(currInstr, &bochs_instr); - - // do the logging - logInjection(); - } else if (exp_type == param->msg.RATFLIP) { - ud_type_t which = UD_NONE; - unsigned rnd = 0; - Udis86 udis(injection_ip); - do { - bxInstruction_c *currInstr = simulator.getCurrentInstruction(); - udis.setInputBuffer(calculateInstructionAddress(), currInstr->ilen()); - if (!udis.fetchNextInstruction()) { - terminateWithError( - "Could not decode instruction using UDIS86", 32); - } - ud_t _ud = udis.getCurrentState(); - - /* start Bjoern Doebel's code (slightly modified) */ - /* ============================================== */ - unsigned opcount = 0; - unsigned operands[4] = { ~0U, ~0U, ~0U, ~0U }; - enum { - RAT_IDX_MASK = 0x0FF, - RAT_IDX_OFFSET = 0x100 - }; - - for (unsigned i = 0; i < 3; ++i) { - /* - * Case 1: operand is a register - */ - if (_ud.operand[i].type == UD_OP_REG) { - operands[opcount++] = i; - } else if (_ud.operand[i].type == UD_OP_MEM) { - /* - * Case 2: operand is memory op. - * - * In this case, we may have 2 registers involved for the - * index-scale address calculation. - */ - if (_ud.operand[i].base != 0) // 0 if hard-wired mem operand - operands[opcount++] = i; - if (_ud.operand[i].index != 0) - operands[opcount++] = i + RAT_IDX_OFFSET; - } - } - - if (opcount == 0) { - // try the next instruction - singleStep(true); - } else { - // assign the necessary variables - rnd = rand() % opcount; - - if (operands[rnd] > RAT_IDX_OFFSET) { - which = _ud.operand[operands[rnd] - RAT_IDX_OFFSET].index; - } else { - which = _ud.operand[operands[rnd]].base; - } - } - /* ============================================ */ - /* end Bjoern Doebel's code (slightly modified) */ - - } while (which == UD_NONE && - simulator.getCPU(0).getInstructionPointer() != L4SYS_FUNC_EXIT); - - if (simulator.getCPU(0).getInstructionPointer() == L4SYS_FUNC_EXIT) { - stringstream ss; - ss << "Reached the end of the experiment "; - ss << "without finding an appropriate instruction"; - - terminateWithError(ss.str(), 33); - } - - // store the real injection point - param->msg.set_injection_ip(simulator.getCPU(0).getInstructionPointer()); - - // so we are able to flip the associated registers - // for details on the algorithm, see Bjoern Doebel's SWIFI/RATFlip class - - // some declarations - GPRegisterId bochs_reg = Udis86::udisGPRToFailBochsGPR(which); - param->msg.set_register_offset(static_cast(bochs_reg + 1)); - ConcreteCPU &cpu = simulator.getCPU(0); - Register *bochsRegister = cpu.getRegister(bochs_reg); - Register *exchangeRegister = NULL; - - // first, decide if the fault hits a register bound to this thread - // (ten percent chance) - if (rand() % 10 == 0) { - // assure exchange of registers - unsigned int exchg_reg = rand() % 7; - if (exchg_reg == bochs_reg) - exchg_reg++; - exchangeRegister = cpu.getRegister(exchg_reg); - param->msg.set_details(l4sysRegisterConversion.output(exchg_reg + 1)); - } - - // prepare the fault - regdata_t data = cpu.getRegisterContent(bochsRegister); - if (rnd > 0) { - //input register - do the fault injection here - regdata_t newdata = 0; - if (exchangeRegister != NULL) { - // the data is taken from a process register chosen before - newdata = cpu.getRegisterContent(exchangeRegister); - } else { - // the data comes from an uninitialised register - newdata = rand(); - stringstream ss; - ss << "0x" << hex << newdata; - param->msg.set_details(ss.str()); - } - cpu.setRegisterContent(bochsRegister, newdata); - } - - // execute the instruction - singleStep(true); - - // restore the register if we are still in the thread - if (rnd == 0) { - // output register - do the fault injection here - if (exchangeRegister != NULL) { - // write the result into the wrong local register - regdata_t newdata = cpu.getRegisterContent(bochsRegister); - cpu.setRegisterContent(exchangeRegister, newdata); - } - // otherwise, just assume it is stored in an unused register - } - // restore the actual value of the register - // in reality, it would never have been overwritten - cpu.setRegisterContent(bochsRegister, data); - - // log the injection - logInjection(); - - } else if (exp_type == param->msg.ALUINSTR) { - static BochsALUInstructions aluInstrObject(aluInstructions, aluInstructionsSize); - // find the closest ALU instruction after the current IP - - bxInstruction_c *currInstr; - while (!aluInstrObject.isALUInstruction( - currInstr = simulator.getCurrentInstruction()) && - simulator.getCPU(0).getInstructionPointer() != L4SYS_FUNC_EXIT) { - singleStep(true); - } - - if (simulator.getCPU(0).getInstructionPointer() == L4SYS_FUNC_EXIT) { - stringstream ss; - ss << "Reached the end of the experiment "; - ss << "without finding an appropriate instruction"; - - terminateWithError(ss.str(), 34); - } - - // store the real injection point - param->msg.set_injection_ip(simulator.getCPU(0).getInstructionPointer()); - - // now exchange it with a random equivalent - bxInstruction_c newInstr; - string details; - aluInstrObject.randomEquivalent(newInstr, details); - if (memcmp(&newInstr, currInstr, sizeof(bxInstruction_c)) == 0) { - // something went wrong - exit experiment - terminateWithError( - "Did not hit an ALU instruction - correct the source code please!", - 40); - } - // record information on the new instruction - param->msg.set_details(details); - - // inject it - injectInstruction(currInstr, &newInstr); - - // do the logging - logInjection(); + switch(conf.step) { + case L4SysConfig::GET_CR3: { + log << "CR_3Run: Watching for instruction " << hex << conf.func_entry << endl; + runToStart(new BPSingleListener(0)); + log << "CR3 = " << hex << conf.address_space << endl; + break; } -#endif + case L4SysConfig::CREATE_CHECKPOINT: { + // STEP 1: run until interesting function starts, and save state + // -> needs L4SYS_ADDRESS_SPACE, because it runs until L4SYS_FUNC_ENTRY + runToStart(new BPSingleListener(0, conf.address_space)); + simulator.save(conf.state_folder); + break; + } + case L4SysConfig::COLLECT_INSTR_TRACE: { + // STEP 2: determine instructions executed + collectInstructionTrace(new BPSingleListener(0, ANY_ADDR)); + break; + } + case L4SysConfig::GOLDEN_RUN: { + // STEP 3: determine the output of a "golden run" + // -> golden run needs L4SYS_ADDRESS_SPACE as it breaks on + // L4SYS_FUNC_EXIT + goldenRun(new BPSingleListener(0, conf.address_space)); + break; + } + case L4SysConfig::FULL_PREPARATION: { + runToStart(new BPSingleListener(0)); + simulator.save(conf.state_folder); + simulator.clearListeners(); + collectInstructionTrace(new BPSingleListener(0, ANY_ADDR)); + simulator.clearListeners(); + goldenRun(new BPSingleListener(0, conf.address_space)); + break; + } + default: { + BPSingleListener *bp = 0; + m_jc = fail::JobClient(conf.campain_server.c_str()); + doExperiments(bp); + } + } -#endif - terminate(0); + terminate(0); // experiment successfully conducted return true; } diff --git a/src/experiments/l4-sys/experiment.conf b/src/experiments/l4-sys/experiment.conf new file mode 100644 index 00000000..05713001 --- /dev/null +++ b/src/experiments/l4-sys/experiment.conf @@ -0,0 +1,78 @@ +#experiment specific config +address_space=0x1fd77000 + +# if address_space_trace is not set then we use address_space for tracing +#address_space_trace=0x0 + +# specifies the range that needs to be captured to log program output properly +func_entry=0x20000216 +func_exit=0x2000029c + +# specifies the range that injections should be carried out on (should be a +subset of the above) +# if filter entry/exit is not set, then (filter_entry=func_entry) and +# (filter_exit = func_exit), the filter range have to be a supset of +# the function +filter_entry=0x2000022f +filter_exit=0x20000252 + +#default break_* value is 0 +#break_blink=0xdead +#break_longjmp=0xdead +#break_exit=0xdead + +#experiment values calculated during preparation +numinstr=16 +totinstr=58401 + +#default config +emul_ips=5000000 +max_instr_bytes=15 +campain_server=localhost + +#default files +state_folder=l4sys.state +instruction_list=ip.list +golden_run=golden.out +filter=filter.list +trace=trace.pb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/experiments/l4-sys/experiment.hpp b/src/experiments/l4-sys/experiment.hpp index a250e066..9d1f9cd5 100644 --- a/src/experiments/l4-sys/experiment.hpp +++ b/src/experiments/l4-sys/experiment.hpp @@ -26,10 +26,9 @@ typedef struct TraceInstrType { unsigned bp_counter; } TraceInstr; -typedef std::vector TraceVector; - class L4SysExperiment : public fail::ExperimentFlow { private: + class L4SysConfig; fail::JobClient m_jc; //!< the job client connecting to the campaign server fail::Logger log; // + +#include "experiment.hpp" +#include "InstructionFilter.hpp" +#include "UDIS86.hpp" +#include "aluinstr.hpp" +#include "campaign.hpp" + +#include +#include "sal/SALConfig.hpp" + +using namespace std; +using namespace fail; + +string L4SysExperiment::sanitised(const string &in_str) { + string result; + int in_str_size = in_str.size(); + result.reserve(in_str_size); + for (int idx = 0; idx < in_str_size; idx++) { + char cur_char = in_str[idx]; + unsigned cur_char_value = static_cast(cur_char); + // also exclude the delimiter (',') + if (cur_char_value < 0x20 || cur_char_value > 0x7E || cur_char_value == ',') { + char str_nr[5]; + sprintf(str_nr, "\\%03o", cur_char_value); + result += str_nr; + } else { + result += cur_char; + } + } + return result; +} + +BaseListener* L4SysExperiment::waitIOOrOther(bool clear_output) { + IOPortListener ev_ioport(0x3F8, true); + BaseListener* ev = NULL; + if (clear_output) + currentOutput.clear(); + while (true) { + simulator.addListener(&ev_ioport); + ev = simulator.resume(); + //log << "hello " << simulator.getListenerCount() << std::endl; + //simulator.removeListener(&ev_ioport); + if (ev == &ev_ioport) { + currentOutput += ev_ioport.getData(); + //log << currentOutput << std::endl; + } else { + break; + } + } + return ev; +} + +unsigned L4SysExperiment::calculateTimeout(unsigned instr_left, unsigned ips) { + // the timeout in seconds, plus one backup second (avoids rounding overhead) + // [instr] / [instr / s] = [s] + unsigned seconds = instr_left / ips + 1; + // 1.1 (+10 percent) * 1000000 mus/s * [s] + return 1100000 * seconds; +} + + +BaseListener* L4SysExperiment::afterInjection(L4SysProtoMsg_Result* res) +{ + BaseListener *bl = 0; + + simtime_t t_inject = simulator.getTimerTicks(); + simtime_t t_bailout; + + ifstream instr_list_file(conf.instruction_list.c_str(), ios::binary); + instr_list_file.seekg((1 + res->instr_offset()) * sizeof(TraceInstr)); + + RangeSetInstructionFilter filtering(conf.filter.c_str()); + + for (;;) { + // Step over _all_ instructions in the trace AS + BPSingleListener stepListener(ANY_ADDR, conf.address_space_trace); + + TraceInstr curr_instr; + instr_list_file.read(reinterpret_cast(&curr_instr), + sizeof(TraceInstr)); + + t_bailout = simulator.getTimerTicks(); + + // step until next traced instruction + simulator.addListener(&stepListener); + bl = waitIOOrOther(false); + + // bail out if we hit a listener other than the single step + // one -> in this case the experiment is over prematurely + if (bl != &stepListener) { + // Note, the difference in this case is the diff between the + // last correct instruction and the starting point -> this is + // useful for TIMEOUT events where the actual time now would be + // the complete TIMEOUT whereas we are interested in the time + // until execution deviates from the original trace + res->set_deviate_steps(t_bailout - t_inject); + res->set_deviate_eip(-1); + log << "bailing out of single-stepping mode" << endl; + break; + } + + address_t eip = stepListener.getTriggerInstructionPointer(); + + if (!filtering.isValidInstr(eip)) + continue; + + if (eip != curr_instr.trigger_addr) { + // In the case where we see an actual instruction stream deviation, we + // want the real diff between NOW and the injection start point + t_bailout = simulator.getTimerTicks(); + log << "got " << hex << eip << " expected " + << curr_instr.trigger_addr << endl; + + log << "mismatch found after " << (t_bailout - t_inject) << " instructions." << endl; + res->set_deviate_steps(t_bailout - t_inject); + res->set_deviate_eip(eip); + + return waitIOOrOther(false); + } + } + + log << "left single-stepping mode after " << (t_bailout - t_inject) + << " instructions." << endl; + return bl; +} + +void L4SysExperiment::getJobParameters() +{ + // get the experiment parameters + log << "asking job server for experiment parameters" << endl; + if (!m_jc.getParam(*param)) { + log << "Dying." << endl; + // communicate that we were told to die + terminate(1); + } +} + + +void L4SysExperiment::validatePrerequisites(std::string state, std::string output) +{ + struct stat teststruct; + if (stat(state.c_str(), &teststruct) == -1 || + stat(output.c_str(), &teststruct) == -1) { + log << "Important data missing - call \"prepare\" first." << endl; + terminate(10); + } +} + + +void L4SysExperiment::readGoldenRun(std::string& target, std::string golden_run) +{ + ifstream golden_run_file(golden_run.c_str()); + + if (!golden_run_file.good()) { + log << "Could not open file " << golden_run.c_str() << endl; + terminate(20); + } + + target.assign((istreambuf_iterator(golden_run_file)), + istreambuf_iterator()); + + golden_run_file.close(); +} + + +void L4SysExperiment::setupFilteredBreakpoint(fail::BPSingleListener* bp, int instOffset, std::string instr_list) +{ + /* + * The L4Sys experiment uses instruction filtering to restrict the range + * of fault injection to only e.g., kernel instructions. + * + * To speed up injection, L4Sys furthermore does not use per-instruction + * breakpoints but only places a breakpoint on the actually interesting + * instruction (e.g., the injection EIP). Hence, we also do not count + * instructions from the beginning of the experiment, but we count how + * often a certain EIP was hit before the injection. + * + * To achieve these properties, we use an additional trace file that + * provides us with a 'hit counter' of each injection candidate. We use + * the global instruction ID (DataBaseCampaign: instruction_offset) to + * index into this trace file and determine the value for the breakpoint + * counter. + */ + ifstream instr_list_file(instr_list.c_str(), ios::binary); + + if (!instr_list_file.good()) { + log << "Missing instruction trace" << endl; + terminate(21); + } + + log << "inst offset " << dec << instOffset << " sizeof(TraceInstr) " << sizeof(TraceInstr) << endl; + TraceInstr curr_instr; + instr_list_file.seekg(instOffset * sizeof(TraceInstr)); + log << instr_list_file.eof() << " " << instr_list_file.bad() << " " + << instr_list_file.fail() << endl; + if (instr_list_file.eof()) { + log << "Job parameters indicate position outside the traced instruction list." << endl; + terminate(1); + } + instr_list_file.read(reinterpret_cast(&curr_instr), sizeof(TraceInstr)); + instr_list_file.close(); + + log << "setting watchpoint at " << hex << curr_instr.trigger_addr << endl; + bp->setWatchInstructionPointer(curr_instr.trigger_addr); + log << "setting bp counter " << hex << curr_instr.bp_counter << endl; + bp->setCounter(curr_instr.bp_counter); +} + + +fail::BPSingleListener* +L4SysExperiment::prepareMemoryExperiment(int ip, int offset, int dataAddress) +{ + fail::BPSingleListener *bp = new BPSingleListener(0, conf.address_space_trace); + log << "\033[34;1mMemory fault injection\033[0m at instruction " << std::hex << offset + << ", ip " << ip << ", address " << dataAddress << std::endl; + + setupFilteredBreakpoint(bp, offset, conf.instruction_list); + assert(bp->getWatchInstructionPointer() == (address_t)(ip & 0xFFFFFFFF)); + return bp; +} + + +fail::BPSingleListener* +L4SysExperiment::prepareRegisterExperiment(int ip, int offset, int dataAddress) +{ + fail::BPSingleListener *bp = new BPSingleListener(0, conf.address_space_trace); + + int reg, regOffset; + reg = ((dataAddress >> 4) & 0xF) + 1; // regs start at 1 + regOffset = dataAddress & 0xF; + + log << "\033[32;1mGPR bitflip\033[0m at instr. offset " << offset + << " reg data (" << reg << ", " + << regOffset << ")" << std::endl; + + setupFilteredBreakpoint(bp, offset, conf.instruction_list); + log << bp->getWatchInstructionPointer() << std::endl; + log << ip << std::endl; + assert(bp->getWatchInstructionPointer() == (address_t)(ip & 0xFFFFFFFF)); + log << bp->getCounter() << std::endl; + return bp; +} + + +bool L4SysExperiment::doMemoryInjection(int address, int bit) +{ + MemoryManager& mm = simulator.getMemoryManager(); + + // XXX: evil, but I need to bail out if memory access is invalid + host_address_t addr = reinterpret_cast(&mm)->guestToHost(address); + if (addr == (host_address_t)ADDR_INV) + return false; + + byte_t data = mm.getByte(address); + byte_t newdata = data ^ (1 << bit); + mm.setByte(address, newdata); + log << "[" << std::hex << address << "] " << (int)data + << " -> " << (int)newdata << std::endl; + return true; +} + + +void L4SysExperiment::doRegisterInjection(int regDesc, int bit) +{ + int reg, offset; + reg = (regDesc >> 4) + 1; // regs start at 1 + offset = regDesc & 0xF; + + ConcreteCPU& cpu = simulator.getCPU(0); + Register *reg_target = cpu.getRegister(reg - 1); + regdata_t data = cpu.getRegisterContent(reg_target); + regdata_t newdata = data ^ (1 << (bit + 8 * offset)); + cpu.setRegisterContent(reg_target, newdata); + log << "Reg[" << reg << "]: " << std::hex << data << " -> " + << newdata << std::endl; +} + +void L4SysExperiment::terminateWithError(string details, int reason, + L4SysProtoMsg_Result *r = 0) { + L4SysProtoMsg_Result *result; + + if (r) + result = r; + else + result = param->msg.add_result(); + + result->set_resulttype(param->msg.UNKNOWN); + result->set_resultdata(simulator.getCPU(0).getInstructionPointer()); + result->set_output(sanitised(currentOutput.c_str())); + result->set_details(details); + + m_jc.sendResult(*param); + terminate(reason); +} + +void L4SysExperiment::doExperiments(fail::BPSingleListener* bp) { + // LAST STEP: The actual experiment. + validatePrerequisites(conf.state_folder, conf.golden_run); + + // Read the golden run output for validation purposes + std::string golden_run; + readGoldenRun(golden_run, conf.golden_run); + + getJobParameters(); + + int exp_type = param->msg.exp_type(); + int instr_offset = param->msg.fsppilot().injection_instr(); + int regData = param->msg.fsppilot().data_address(); + + if (exp_type == param->msg.MEM) { + bp = prepareMemoryExperiment(param->msg.fsppilot().injection_instr_absolute(), + param->msg.fsppilot().injection_instr(), + param->msg.fsppilot().data_address()); + } else if (exp_type == param->msg.GPRFLIP) { + bp = prepareRegisterExperiment(param->msg.fsppilot().injection_instr_absolute(), + param->msg.fsppilot().injection_instr(), + param->msg.fsppilot().data_address()); + } else { + log << "Unsupported experiment type: " << exp_type << std::endl; + terminate(1); + } + + assert(bp); + + for (unsigned bit = 0; bit < 8; ++bit) { + + L4SysProtoMsg_Result *result = param->msg.add_result(); + result->set_instr_offset(instr_offset); + + simulator.clearListeners(); + + log << "Bit " << bit << ", restoring state." << endl; + simulator.restore(conf.state_folder.c_str()); + log << " ... EIP = " << std::hex << simulator.getCPU(0).getInstructionPointer() << std::endl; + + simulator.addListener(bp); + + simtime_t now = simulator.getTimerTicks(); + fail::BaseListener *go = waitIOOrOther(true); + assert(go == bp); + + log << "Hit BP @ " << hex << bp->getTriggerInstructionPointer() << " " << bp->getWatchInstructionPointer() + << " Start time " << now << ", new time " << simulator.getTimerTicks() + << ", diff = " << simulator.getTimerTicks() - now << std::endl; + + assert(bp->getTriggerInstructionPointer() == bp->getWatchInstructionPointer()); + result->set_injection_ip(bp->getTriggerInstructionPointer()); + + if (exp_type == param->msg.MEM) { + result->set_bit_offset(bit); + log << "injection addr: " + << std::hex << param->msg.fsppilot().data_address() + << std::endl; + result->set_injection_address(param->msg.fsppilot().data_address()); + if (!doMemoryInjection(param->msg.fsppilot().data_address(), bit)) + { + terminateWithError("invalid mem access", 51, result); + } + } else if (exp_type == param->msg.GPRFLIP) { + int reg = (param->msg.fsppilot().data_address() >> 4) + 1; + result->set_register_offset(static_cast(reg)); + result->set_bit_offset(bit + 8 * (param->msg.fsppilot().data_address() & 0xF)); + doRegisterInjection(param->msg.fsppilot().data_address(), bit); + } else { + log << "doing nothing for experiment type " << exp_type << std::endl; + } + + BPSingleListener ev_done(conf.func_exit, conf.address_space); + simulator.addListener(&ev_done); + + // Well-known bailout point -- if we hit L4SYS_BREAK_BLINK, which + // is the entry of Vga::blink_cursor(), we know that we are in some + // kind of error handler + BPSingleListener ev_blink(conf.break_blink); + simulator.addListener(&ev_blink); + BPSingleListener ev_longjmp(conf.break_longjmp); + simulator.addListener(&ev_longjmp); + + //If we come to our own exit function, we can stop + BPSingleListener ev_exit(conf.break_exit); + simulator.addListener(&ev_exit); + + unsigned instr_left = conf.totinstr - instr_offset; // XXX offset is in NUMINSTR, TOTINSTR is higher + BPSingleListener ev_incomplete(ANY_ADDR, conf.address_space); + /* + * Use hard-coded value for incomplete counter. We are currently looking at short-running pieces + * of code. This means that in the error case, where a lot of data is still to be printed to serial + * line, the benchmark does not complete this within * <1.x> cycles. Instead, we use + * a frame large enough to catch some more output even at the end of a run. + */ + ev_incomplete.setCounter(2000000); + simulator.addListener(&ev_incomplete); + + /* + * This timeout will always be at least one second - see calculateTimeout() + */ + TimerListener ev_timeout(calculateTimeout(instr_left, conf.emul_ips)); + simulator.addListener(&ev_timeout); + log << "continue... (" << simulator.getListenerCount() + << " breakpoints, timeout @ " << ev_timeout.getTimeout() + << std::endl; + + log << "TOListener " << (void*)&ev_timeout << " incompListener " + << (void*)&ev_incomplete << endl; + BaseListener *ev = afterInjection(result); + log << "afterInj: res.devstep = " << result->deviate_steps() << endl; + + /* copying a string object that contains control sequences + * unfortunately does not work with the library I am using, + * which is why output is passed on as C string and + * the string compare is done on C strings + */ + if (ev == &ev_done) { + if (strcmp(currentOutput.c_str(), golden_run.c_str()) == 0) { + log << "Result DONE" << endl; + result->set_resulttype(param->msg.DONE); + } else { + log << "Result WRONG" << endl; + result->set_resulttype(param->msg.WRONG); + result->set_output(sanitised(currentOutput.c_str())); + } + } else if ((ev == &ev_incomplete) || + (ev == &ev_blink) || + (ev == &ev_longjmp)) { + log << "Result INCOMPLETE" << endl; + result->set_resulttype(param->msg.INCOMPLETE); + result->set_resultdata(simulator.getCPU(0).getInstructionPointer()); + result->set_output(sanitised(currentOutput.c_str())); + } else if (ev == &ev_timeout) { + log << "Result TIMEOUT" << endl; + result->set_resulttype(param->msg.TIMEOUT); + result->set_resultdata(simulator.getCPU(0).getInstructionPointer()); + result->set_output(sanitised(currentOutput.c_str())); + } else if (ev == &ev_exit) { + log << "Result FAILSTOP" << endl; + result->set_resulttype(param->msg.FAILSTOP); + result->set_resultdata(simulator.getCPU(0).getInstructionPointer()); + result->set_output(sanitised(currentOutput.c_str())); + } else { + log << "Result WTF?" << endl; + stringstream ss; + ss << "eventid " << ev; + terminateWithError(ss.str(), 50); + } + } + + m_jc.sendResult(*param); +} + diff --git a/src/experiments/l4-sys/experimentInfo.hpp b/src/experiments/l4-sys/experimentInfo.hpp deleted file mode 100644 index 3901beb0..00000000 --- a/src/experiments/l4-sys/experimentInfo.hpp +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef __L4SYS_EXPERIMENT_INFO_HPP__ - #define __L4SYS_EXPERIMENT_INFO_HPP__ - -// the maximum number of bytes in a Bochs instruction -#define MAX_INSTR_BYTES 15 - -// the bounds of the program (space, instructions and time) -// client -#define L4SYS_ADDRESS_SPACE 0x1fd4c000 -// server -#define L4SYS_ADDRESS_SPACE_TRACE L4SYS_ADDRESS_SPACE -//#define L4SYS_ADDRESS_SPACE_TRACE 0x1fd4c000 - -// FUNC_{ENTRY,EXIT} specifies the range that needs to -// be captured to log program output properly -#define L4SYS_FUNC_ENTRY 0x20000220 -#define L4SYS_FUNC_EXIT 0x20000216 -// FILTER_{ENTRY,EXIT} specifies the range that injections -// should be carried out on (should be a subset of the above) -// and only works with FILTER_INSTRUCTIONS turned on -#define L4SYS_FILTER_ENTRY 0x200002ba -#define L4SYS_FILTER_EXIT 0x20000444 - -#define L4SYS_BREAK_BLINK 0xf004b800 -#define L4SYS_BREAK_LONGJMP 0xf004c88e - -// select instruction filtering -// XXX: this should be always on and the code should be -// reworked to do the non-filtering work with an empty -// filter list -#define L4SYS_FILTER_INSTRUCTIONS 1 - -// kernel: 2377547, userland: 79405472 -#define L4SYS_NUMINSTR 27025 -#define L4SYS_TOTINSTR 189122 -#define L4SYS_BOCHS_IPS 5000000 - -// several file names used -#define L4SYS_STATE_FOLDER "l4sys.state" -#define L4SYS_INSTRUCTION_LIST "ip.list" -#define L4SYS_ALU_INSTRUCTIONS "alu.list" -#define L4SYS_CORRECT_OUTPUT "golden.out" -#define L4SYS_FILTER "filter.list" - -// flags -// 0 - preparation complete -// >0 - next step to execute -#define PREPARATION_STEP 0 - -#endif // __L4SYS_EXPERIMENT_INFO_HPP__ diff --git a/src/experiments/l4-sys/experimentParameter.cc b/src/experiments/l4-sys/experimentParameter.cc new file mode 100644 index 00000000..3a978360 --- /dev/null +++ b/src/experiments/l4-sys/experimentParameter.cc @@ -0,0 +1,315 @@ +#include +#include +#include + +#include "experiment.hpp" + +#include "util/CommandLine.hpp" + +using namespace std; +using namespace fail; + +#define EXPERIMENT_CONF "experiment.conf" + + +static void parameterMissing(fail::Logger log, string c) { + log << "Error: Missing config parameter (" << c << ")" << endl; + simulator.terminate(1); +} + +int L4SysExperiment::updateConfig(string parameter, string value) { + + bool replaced = false; + + std::list buf; + + ifstream is(EXPERIMENT_CONF); + + if (is) { + while(!is.eof()) { + string tmp; + getline(is, tmp); + if( !tmp.compare(0, parameter.length() + 1, string(parameter + "=")) ) { + buf.push_back(string(parameter + "=" + value) ); + replaced = true; + } else { + buf.push_back(tmp); + } + } + } else { + cerr << "Open config file failed" << endl; + return -1; + } + is.close(); + + ofstream os(EXPERIMENT_CONF); + + if (os) { + if(!replaced) + os << parameter << "=" << value << endl; + + while(!buf.empty()) { + os << buf.front() << endl; + buf.pop_front(); + } + } else { + cerr << "Open config file for update failed" << endl; + return -1; + } + + return 0; +} + +void L4SysExperiment::parseOptions(L4SysConfig &conf) { + CommandLine &cmd = CommandLine::Inst(); + + ifstream fin(EXPERIMENT_CONF); + + //Currently we don't delete the parameterstring to avoid deling ptr in the cmd + //Nevertheless, we shouldn't need them, so deleting should be safe + + //We interpret the file content as parameters to our main function, so we don't + //need an own parser. Program parameters appear earlier in the parameter list + //and will overwrite the parameters from the config file. + while(fin.good()) { + string buffer; + string prefix("--"); + + getline(fin, buffer, '\n'); + + if( !(strncmp(buffer.c_str(),"#",1) == 0) && + !(strncmp(buffer.c_str()," ",1) == 0 ) && + !(strncmp(buffer.c_str(),"",1) == 0 ) ) { + string t(prefix + buffer); + //Workarround, if we just alloc t.length some of the arguments values + //disappear + char *c = (char *) malloc(t.length() * 2); + strcpy(c, t.c_str()); + cmd.add_args( c ); + } + } + + cout << "end of config file" << endl; + + cmd.addOption("", "", Arg::None, "USAGE: fail-client -Wf,[option] -Wf,[option] ... "); + CommandLine::option_handle HELP = + cmd.addOption("h", "help", Arg::None, "-h,--help \tPrint usage and exit"); + CommandLine::option_handle STEP = + cmd.addOption("s", "step", Arg::Optional, "-s,--step \tSpecify preparation step, without this argumnt fail-client start in experiment mode (cr3: get CR3, cc: Create Checkpoint, it: collect instruction trace, gr: golden run, all: do the whole preparation)"); + + CommandLine::option_handle OPT_MAX_INSTR_BYTES = + cmd.addOption("", "max_instr_bytes", Arg::Optional, "--max_instr_bytes \t define MAX_INSTR_BYTES"); + CommandLine::option_handle OPT_ADDRESS_SPACE = + cmd.addOption("", "address_space", Arg::Optional, "--address_space \t define L4SYS_ADDRESS_SPACE"); + CommandLine::option_handle OPT_ADDRESS_SPACE_TRACE = + cmd.addOption("", "address_space_trace", Arg::Optional, "--address_space_trace \t define L4SYS_ADDRESS_SPACE_TRACE"); + CommandLine::option_handle OPT_FUNC_ENTRY = + cmd.addOption("", "func_entry", Arg::Optional, "--func_entry \t define L4SYS_FUNC_ENTRY"); + CommandLine::option_handle OPT_FUNC_EXIT = + cmd.addOption("", "func_exit", Arg::Optional, "--func_exit \t define L4SYS_FUNC_EXIT"); + CommandLine::option_handle OPT_FILTER_ENTRY = + cmd.addOption("", "filter_entry", Arg::Optional, "--filter_entry \t define L4SYS_FILTER_ENTRY"); + CommandLine::option_handle OPT_FILTER_EXIT = + cmd.addOption("", "filter_exit", Arg::Optional, "--filter_exit \t define L4SYS_FILTER_EXIT"); + CommandLine::option_handle OPT_BREAK_BLINK = + cmd.addOption("", "break_blink", Arg::Optional, "--break_blink \t define L4SYS_BREAK_BLINK"); + CommandLine::option_handle OPT_BREAK_LONGJMP = + cmd.addOption("", "break_longjmp", Arg::Optional, "--break_longjmp \t define L4SYS_BREAK_LONGJMP"); + CommandLine::option_handle OPT_BREAK_EXIT = + cmd.addOption("", "break_exit", Arg::Optional, "--break_exit \t define L4SYS_BREAK_EXIT"); + CommandLine::option_handle OPT_NUMINSTR = + cmd.addOption("", "numinstr", Arg::Optional, "--numinstr \t define L4SYS_NUMINSTR"); + CommandLine::option_handle OPT_TOTINSTR = + cmd.addOption("", "totinstr", Arg::Optional, "--totinstr \t define L4SYS_TOTINSTR"); + CommandLine::option_handle OPT_EMUL_IPS = + cmd.addOption("", "emul_ips", Arg::Optional, "--emul_ips \t define L4SYS_BOCHS_IPS"); + CommandLine::option_handle OPT_STATE_FOLDER = + cmd.addOption("", "state_folder", Arg::Optional, "--state_folder \t define L4SYS_STATE_FOLDER"); + CommandLine::option_handle OPT_INSTRUCTION_LIST = + cmd.addOption("", "instruction_list", Arg::Optional, "--instruction_list \t define L4SYS_INSTRUCTION_LIST"); + CommandLine::option_handle OPT_CORRECT_OUTPUT = + cmd.addOption("", "golden_run", Arg::Optional, "--correct_output \t define L4SYS_CORRECT_OUTPUT"); + CommandLine::option_handle OPT_FILTER = + cmd.addOption("", "filter", Arg::Optional, "--filter \t define L4SYS_FILTER"); + CommandLine::option_handle OPT_TRACE = + cmd.addOption("", "trace", Arg::Optional, "--trace \t define outputfile for trace (default trace.pb)"); + CommandLine::option_handle OPT_CAMPAIN_SERVER = + cmd.addOption("", "campain_server", Arg::Optional, "--campain_server \t specify the hostname of the campain server (default localhost)"); + + + if (!cmd.parse()) { + cerr << "Error parsing arguments." << endl; + simulator.terminate(1); + } else if (cmd[HELP]) { + cmd.printUsage(); + simulator.terminate(0); + } + + if (cmd[OPT_MAX_INSTR_BYTES]) { + conf.max_instr_bytes = strtol(cmd[OPT_MAX_INSTR_BYTES].arg, NULL, 10); + log << "max_instr_bytes: "<< dec << conf.max_instr_bytes << endl; + } else { + parameterMissing(log, "max_instr_bytes"); + } + + if (cmd[OPT_ADDRESS_SPACE]) { + conf.address_space = strtol(cmd[OPT_ADDRESS_SPACE].arg, NULL, 16); + log << "address_space: "<< hex << conf.address_space << endl; + + //Confi address_space=0 means no address space filtering + if (conf.address_space == 0) + conf.address_space = fail::ANY_ADDR; + } else { + conf.address_space = fail::ANY_ADDR; + } + + if (cmd[OPT_ADDRESS_SPACE_TRACE]) { + conf.address_space_trace = strtol(cmd[OPT_ADDRESS_SPACE_TRACE].arg, NULL, 16); + if( conf.address_space_trace == 0 ) + conf.address_space_trace = conf.address_space; + else + log << "address_space_trace: "<< hex << conf.address_space_trace << endl; + } else { + conf.address_space_trace = conf.address_space; + } + + + if (conf.address_space_trace == 0) + conf.address_space_trace = fail::ANY_ADDR; + + if (cmd[OPT_FUNC_ENTRY]) { + conf.func_entry = strtol(cmd[OPT_FUNC_ENTRY].arg, NULL, 16); + log << "func_entry: "<< hex << conf.func_entry << endl; + } else{ + parameterMissing(log, "func_entry"); + } + + if (cmd[OPT_FUNC_EXIT]) { + conf.func_exit = strtol(cmd[OPT_FUNC_EXIT].arg, NULL, 16); + log << "func_exit: "<< hex << conf.func_exit << endl; + } else { + parameterMissing(log, "func_exit"); + } + + if (cmd[OPT_FILTER_ENTRY]) { + conf.filter_entry = strtol(cmd[OPT_FILTER_ENTRY].arg, NULL, 16); + log << "filter_entry: "<< hex << conf.filter_entry << endl; + } else { + conf.filter_entry = conf.func_entry; + } + + if (cmd[OPT_FILTER_EXIT]) { + conf.filter_exit = strtol(cmd[OPT_FILTER_EXIT].arg, NULL, 16); + log << "filter_exit: "<< hex << conf.filter_exit << endl; + } else { + conf.filter_exit = conf.func_exit; + } + + if (cmd[OPT_BREAK_BLINK]) { + conf.break_blink = strtol(cmd[OPT_BREAK_BLINK].arg, NULL, 16); + log << "break_blink: "<< hex << conf.break_blink << endl; + } else { + conf.break_blink = 0; + } + + if (cmd[OPT_BREAK_LONGJMP]) { + conf.break_longjmp = strtol(cmd[OPT_BREAK_LONGJMP].arg, NULL, 16); + log << "break_longjmp: "<< hex << conf.break_longjmp << endl; + } else { + conf.break_longjmp = 0; + } + + if (cmd[OPT_BREAK_EXIT]) { + conf.break_exit = strtol(cmd[OPT_BREAK_EXIT].arg, NULL, 16); + log << "break_exit: "<< hex << conf.break_exit << endl; + } else { + conf.break_exit = 0; + } + + if (cmd[OPT_NUMINSTR]) { + conf.numinstr = strtol(cmd[OPT_NUMINSTR].arg, NULL, 10); + log << "numinstr: "<< dec << conf.numinstr << endl; + } else { + parameterMissing(log, "numinstr"); + } + + if (cmd[OPT_TOTINSTR]) { + conf.totinstr = strtol(cmd[OPT_TOTINSTR].arg, NULL, 10); + log << "totinstr: "<< dec << conf.totinstr << endl; + } else { + parameterMissing(log, "totinstr"); + } + + if (cmd[OPT_EMUL_IPS]) { + conf.emul_ips = strtol(cmd[OPT_EMUL_IPS].arg, NULL, 10); + log << "emul_ips = " << dec << conf.emul_ips << endl; + } else { + parameterMissing(log, "emul_ips"); + } + + if (cmd[OPT_STATE_FOLDER]) { + conf.state_folder = std::string(cmd[OPT_STATE_FOLDER].arg); + log << "state_folder: "<< conf.state_folder << endl; + } else { + conf.state_folder = "l4sys.state"; + } + + if (cmd[OPT_INSTRUCTION_LIST]) { + conf.instruction_list = std::string(cmd[OPT_INSTRUCTION_LIST].arg); + log << "instruction_list: "<< conf.instruction_list << endl; + } else { + conf.instruction_list = "ip.list"; + } + + if (cmd[OPT_CORRECT_OUTPUT]) { + conf.golden_run = std::string(cmd[OPT_CORRECT_OUTPUT].arg); + log << "golden_run: "<< conf.golden_run << endl; + } else { + conf.golden_run = "golden.out"; + } + + if (cmd[OPT_FILTER]) { + conf.filter = std::string(cmd[OPT_FILTER].arg); + log << "filter: "<< conf.filter << endl; + } else { + conf.filter = "filter.list"; + } + + if (cmd[OPT_TRACE]) { + conf.trace = std::string(cmd[OPT_TRACE].arg); + log << "trace: "<< conf.trace << endl; + } else { + conf.trace = "trace.pb"; + } + + if (cmd[OPT_CAMPAIN_SERVER]) { + conf.campain_server = std::string(cmd[OPT_CAMPAIN_SERVER].arg); + log << "campain_server: "<< conf.campain_server << endl; + } else { + conf.campain_server = "localhost"; + } + + if (cmd[STEP]) { + if (!std::string("cr3").compare(cmd[STEP].arg) ) { + log << "calculate cr3" << endl; + conf.step = L4SysConfig::GET_CR3; + } else if (!std::string("cc").compare(cmd[STEP].arg) ) { + log << "Create Checkpoint" << endl; + conf.step = L4SysConfig::CREATE_CHECKPOINT; + } else if (!std::string("it").compare(cmd[STEP].arg) ) { + log << "collect instruction trace" << endl; + conf.step = L4SysConfig::COLLECT_INSTR_TRACE; + } else if (!std::string("gr").compare(cmd[STEP].arg) ) { + log << "golden run" << endl; + conf.step = L4SysConfig::GOLDEN_RUN; + } else if (!std::string("all").compare(cmd[STEP].arg) ) { + log << "do all preparation steps" << endl; + conf.step = L4SysConfig::FULL_PREPARATION; + } else { + cerr << "Wrong argument for option '--step'" << endl; + simulator.terminate(1); + } + } else { + conf.step = L4SysConfig::NO_PREP; + } +} diff --git a/src/experiments/l4-sys/experimentPreparation.cc b/src/experiments/l4-sys/experimentPreparation.cc new file mode 100644 index 00000000..644de464 --- /dev/null +++ b/src/experiments/l4-sys/experimentPreparation.cc @@ -0,0 +1,240 @@ +#include +#include +#include +#include + +#include "experiment.hpp" +#include "InstructionFilter.hpp" +#include "aluinstr.hpp" + +#include "sal/SALConfig.hpp" +#include "util/gzstream/gzstream.h" +#include "util/ProtoStream.hpp" +#include "TracePlugin.pb.h" +#include "sal/Listener.hpp" + +using namespace std; +using namespace fail; + +Bit32u L4SysExperiment::eipBiased() { + BX_CPU_C *cpu_context = simulator.getCPUContext(); + Bit32u EIP = cpu_context->gen_reg[BX_32BIT_REG_EIP].dword.erx; + return EIP + cpu_context->eipPageBias; +} + +const Bit8u *L4SysExperiment::calculateInstructionAddress() { + // pasted in from various nested Bochs functions and macros - I hope + // they will not change too soon (as do the Bochs developers, probably) + BX_CPU_C *cpu_context = simulator.getCPUContext(); + const Bit8u *result = cpu_context->eipFetchPtr + eipBiased(); + return result; +} + +void L4SysExperiment::runToStart(fail::BPSingleListener *bp) +{ + bp->setWatchInstructionPointer(conf.func_entry); + + log << "run until ip reaches 0x" << hex << conf.func_entry << endl; + + simulator.addListenerAndResume(bp); + + log << "test function entry reached, saving state" << endl; + log << "EIP: expected " << hex << bp->getTriggerInstructionPointer() + << " and actually got " + << simulator.getCPU(0).getInstructionPointer() + << endl; + log << "check the source code if the two instruction pointers are not equal" << endl; + + if(conf.address_space == conf.address_space_trace) { + conf.address_space_trace = BX_CPU(0)->cr3; + } + + conf.address_space = BX_CPU(0)->cr3; + + char tmp[20]; + sprintf(tmp, "0x%lx", BX_CPU(0)->cr3); + updateConfig("address_space", tmp ); +} + + +void L4SysExperiment::collectInstructionTrace(fail::BPSingleListener* bp) +{ + fail::MemAccessListener ML(ANY_ADDR, MemAccessEvent::MEM_READWRITE); + ogzstream out(conf.trace.c_str()); + ProtoOStream *os = new ProtoOStream(&out); + + size_t count = 0, inst_accepted = 0, mem = 0, mem_valid = 0; + simtime_t prevtime = 0, currtime; + simtime_diff_t deltatime; + + log << "restoring state" << endl; + simulator.restore(conf.state_folder.c_str()); + currtime = simulator.getTimerTicks(); + + log << "EIP = " << hex + << simulator.getCPU(0).getInstructionPointer() + << endl; + + if (!simulator.addListener(&ML)) { + log << "did not add memory listener..." << std::endl; + exit(1); + } + if (!simulator.addListener(bp)) { + log << "did not add breakpoint listener..." << std::endl; + exit(1); + } + + ofstream instr_list_file(conf.instruction_list.c_str(), ios::binary); + RangeSetInstructionFilter filtering(conf.filter.c_str()); + bp->setWatchInstructionPointer(ANY_ADDR); + + map times_called_map; + bool injecting = false; + + while (bp->getTriggerInstructionPointer() != conf.func_exit) { + fail::BaseListener *res = simulator.resume(); + address_t curr_addr = 0; + + // XXX: See the API problem below! + if (res == &ML) { + curr_addr = ML.getTriggerInstructionPointer(); + simulator.addListener(&ML); + if ((conf.address_space_trace != ANY_ADDR) && (BX_CPU(0)->cr3 != conf.address_space_trace)) { + continue; + } + ++mem; + } else if (res == bp) { + curr_addr = bp->getTriggerInstructionPointer(); + assert(curr_addr == simulator.getCPU(0).getInstructionPointer()); + simulator.addListener(bp); + ++count; + } + + currtime = simulator.getTimerTicks(); + deltatime = currtime - prevtime; + + if (curr_addr == conf.filter_entry) { + injecting = true; + } + + if (curr_addr == conf.filter_exit) { + injecting = false; + } + + // Only trace if: + // 1) we are between FILTER_ENTRY and FILTER_EXIT, and + // 2) we have a valid instruction according to filter rules, and + // 3) we are in the TRACE address space + if (!injecting or + !filtering.isValidInstr(curr_addr, reinterpret_cast(calculateInstructionAddress())) + or + (BX_CPU(0)->cr3 != conf.address_space_trace) + ) { + //log << "connt..." << std::endl; + continue; + } + + if (res == &ML) { +#if 0 + log << "Memory event IP " << std::hex << ML.getTriggerInstructionPointer() + << " @ " << ML.getTriggerAddress() << "(" + << ML.getTriggerAccessType() << "," << ML.getTriggerWidth() + << ")" << std::endl; +#endif + ++mem_valid; + + Trace_Event te; + if (deltatime != 0) { te.set_time_delta(1); }; + te.set_ip(curr_addr); + te.set_memaddr(ML.getTriggerAddress()); + te.set_accesstype( (ML.getTriggerAccessType() & MemAccessEvent::MEM_READ) ? te.READ : te.WRITE ); + te.set_width(ML.getTriggerWidth()); + os->writeMessage(&te); + } else if (res == bp) { + unsigned times_called = times_called_map[curr_addr]; + ++times_called; + times_called_map[curr_addr] = times_called; + + //log << "breakpoint event" << std::endl; + // now check if we want to add the instruction for fault injection + ++inst_accepted; + + // 1) The 'old' way of logging instructions -> DEPRECATE soon + // BUT: we are currently using the bp_counter stored in this + // file! + TraceInstr new_instr; + //log << "writing IP " << hex << curr_addr << " counter " + // << dec << times_called << "(" << hex << BX_CPU(0)->cr3 << ")" + // << endl; + new_instr.trigger_addr = curr_addr; + new_instr.bp_counter = times_called; + + instr_list_file.write(reinterpret_cast(&new_instr), sizeof(TraceInstr)); + + // 2) The 'new' way -> generate Events that can be processed by + // the generic *-trace tools + // XXX: need to log CR3 if we want multiple binaries here + Trace_Event e; + if (deltatime != 0) { e.set_time_delta(1); }; + e.set_ip(curr_addr); + os->writeMessage(&e); + } else { + printf("Unknown res? %p\n", res); + } + prevtime = currtime; + + //short sanity check + //log << "continue..." << std::endl; + } + log << "saving instructions triggered during normal execution" << endl; + instr_list_file.close(); + log << "test function calculation position reached after " + << dec << count << " instructions; " << inst_accepted << " accepted" << endl; + log << "mem accesses: " << mem << ", valid: " << mem_valid << std::endl; + + conf.numinstr = inst_accepted; + conf.totinstr = count; + + //Write new values into config file + char numimstr_str[20]; + + sprintf(numimstr_str, "%li", inst_accepted); + updateConfig("numinstr", numimstr_str ); + + char totinstr_str[20]; + sprintf(totinstr_str, "%li", count); + updateConfig("totinstr", totinstr_str); + + delete bp; +} + +void L4SysExperiment::goldenRun(fail::BPSingleListener* bp) +{ + log << "restoring state" << endl; + simulator.restore(conf.state_folder.c_str()); + log << "EIP = " << hex + << simulator.getCPU(0).getInstructionPointer() + << endl; + + std::string golden_run; + ofstream golden_run_file(conf.golden_run.c_str()); + bp->setWatchInstructionPointer(conf.func_exit); + simulator.addListener(bp); + BaseListener* ev = waitIOOrOther(true); + if (ev == bp) { + golden_run.assign(currentOutput.c_str()); + golden_run_file << currentOutput.c_str(); + log << "Output successfully logged!" << endl; + } else { + log + << "Obviously, there is some trouble with" + << " the events registered - aborting simulation!" + << endl; + golden_run_file.close(); + terminate(10); + } + + log << "saving output generated during normal execution" << endl; + golden_run_file.close(); + delete bp; +} diff --git a/src/experiments/l4-sys/l4sys.proto b/src/experiments/l4-sys/l4sys.proto index 73fb7ed0..6d876454 100644 --- a/src/experiments/l4-sys/l4sys.proto +++ b/src/experiments/l4-sys/l4sys.proto @@ -31,6 +31,7 @@ message L4SysProtoMsg { TIMEOUT = 3; WRONG = 4; UNKNOWN = 5; + FAILSTOP = 6; } required DatabaseCampaignMessage fsppilot = 1; diff --git a/src/experiments/l4-sys/main.cc b/src/experiments/l4-sys/main.cc index 3ef02502..85d6e24c 100644 --- a/src/experiments/l4-sys/main.cc +++ b/src/experiments/l4-sys/main.cc @@ -3,10 +3,36 @@ #include "cpn/CampaignManager.hpp" #include "campaign.hpp" +#include "util/CommandLine.hpp" int main(int argc, char **argv) { + L4SysCampaign c; + + fail::CommandLine &cmd = fail::CommandLine::Inst(); + + for (int i = 1; i < argc; ++i) { + if(strncmp(argv[i], "--type=", 7) == 0) + c.type = std::string(std::string(argv[i]), 7); + else + cmd.add_args(argv[i]); + } + + if (c.type.empty()) { + std::cerr << "You have to specify FI type" << std::endl; + exit(-1); + } + + if(!c.type.compare("mem")) { + std::cout << "We will do memory FI" << std::endl; + } else if(!c.type.compare("reg")) { + std::cout << "We will do register FI" << std::endl; + } else { + std::cout << "Specified FI-type not supported" << std::endl; + exit(-1); + } + if (fail::campaignmanager.runCampaign(&c)) { return 0; } else { diff --git a/src/experiments/l4-sys/manual.pdf b/src/experiments/l4-sys/manual.pdf index 0902a40f..9f340b93 100644 Binary files a/src/experiments/l4-sys/manual.pdf and b/src/experiments/l4-sys/manual.pdf differ diff --git a/src/experiments/l4-sys/manual.tex b/src/experiments/l4-sys/manual.tex index 83dcef58..bc2f985f 100644 --- a/src/experiments/l4-sys/manual.tex +++ b/src/experiments/l4-sys/manual.tex @@ -17,7 +17,8 @@ %opening \title{L4Sys Fault Injection Campaign -- User Manual} \author{Martin Unzner (\href{mailto:munzner@os.inf.tu-dresden.de}{munzner@os.inf.tu-dresden.de}), \\ - Björn Döbel (\href{mailto:doebel@os.inf.tu-dresden.de}{doebel@os.inf.tu-dresden.de})} + Björn Döbel (\href{mailto:doebel@os.inf.tu-dresden.de}{doebel@os.inf.tu-dresden.de}), \\ + Tobias Stumpf (\href{mailto:tstumpf@os.inf.tu-dresden.de}{tstumpf@os.inf.tu-dresden.de})} \begin{document} @@ -26,7 +27,7 @@ \begin{abstract} This document describes how to use the L4Sys experiment suite. However, this is not a complete documentation. When in doubt, -please read the source code or contact me. Still, I would like +please read the source code or contact us. Still, we would like you to read this whole document before investigating further. \end{abstract} @@ -37,14 +38,10 @@ The framework builds on Fail* and provides means to perform fault injection experiments for applications running on top of the Fiasco.OC/L4Re microkernel-based operating system as well as the underlying microkernel. -\noindent \lfs{} provides four experiment types: +\noindent \lfs{} provides two experiment types: \begin{enumerate}[topsep=0em,itemsep=0em] - \item \emph{GPRFlip} simulates bit flips in general purpose registers. - \item \emph{RATFlip} simulates errors in the association between the - physical register file and general purpose registers. - \item \emph{IDCFlip} simulates errors occurring during instruction decoding. - \item \emph{ALUInstrFlip} simulates errors in the processor's arithmetic - logic unit. + \item \emph{GPRFLIP} simulates bit flips in general purpose registers. + \item \emph{MEM} simulates memorry bit flipps. \end{enumerate} \noindent \lfs{} currently works for x86/32 running in Fail/Bochs only. @@ -72,7 +69,7 @@ The following CMake flags need to be set: Enabling \verb+CONFIG_FAST_BREAKPOINTS+ may speed up the experiment clients significantly. Enabling \verb+CONFIG_BOCHS_NO_ABORT+ is necessary to detect -whether Bochs stopped because of a bad instruction induced by IDCFlip. +whether Bochs stopped because of a bad instruction (currently not used). Keep in mind that this implies the risk of a deadlock in the campaign system, because packets (i.e. experiment descriptions) are resent if the client does not answer and finally, all clients @@ -80,7 +77,7 @@ might fail because they tried to execute the same faulty instruction. \section{Emulator Setup} -The next step is to prepare an L4Re application setup to run in Bochs. To +The next step is to prepare a L4Re application setup to run in Bochs. To setup your system, first, you need a dedicated \texttt{bochsrc} file. It has proven useful to have a Bochs resource file or an independent Bochs instance with GUI enabled for the initial testing, however the experiments are intended @@ -102,12 +99,15 @@ injection campaign. This requires three (+ one optional) steps: \begin{enumerate}[topsep=0em,itemsep=0em] \item \emph{OPTIONAL:} If we want to perform a campaign that only targets a single application, we need to determine this application's address - space ID. + space ID. Choosing a different link address (add + \verb+DEFAULT_RELOC = 0x20000000+ in your applications Makefile) + helps you to figure out your address space by stopping at a unique + instruction pointer. \item \emph{REQUIRED:} We perform an initial run of our setup in Bochs until the point where Bochs is booted and the application in question starts. At this point we take a snapshot of the emulator so that we can skip everything upfront in the remaining runs. - \item \emph{REQUIRED:} The \lfs{} campaign uses \verb+L4SYS_NUM_INSTR+ to + \item \emph{REQUIRED:} The \lfs{} campaign uses the number of instructions to determine the set of instructions to inject faults in. We need to perform one run of our setup to determine this number. \item \emph{REQUIRED:} We need to perform a \emph{golden run} without any @@ -115,146 +115,103 @@ injection campaign. This requires three (+ one optional) steps: \end{enumerate} All parameters of the \lfs{} experiment can be configured via file -\texttt{experimentInfo.hpp}. Normally, it should not be necessary to change -the program flow directly. However, the interested reader is invited to take a -look at \texttt{experiment.cc}, too. +\texttt{experiment.conf} or can be given to \verb+fail-client+ as command +line parameter. \subsection{Constants} -Some values are constant throughout all steps of the preparation and also when -the workload program is run. The most important constant is -\verb+L4SYS_BOCHS_IPS+, which has to be consistent with your \texttt{bochsrc} +Some configuration values are constant throughout all steps of the preparation +and also when the workload program is run. The most important configuration +parameter is \verb+emul_ipc+, which has to be consistent with your \texttt{bochsrc} setting and is used for several timely calculations in the client. -\subsection{Step 0: Determine the address space} +\subsection{Preparation} First, we need to find the start and end instruction addresses for our workload program and our given experiment. For this purpose, use a disassembler, such as \texttt{objdump} or \emph{IDA Pro}. Determine the first -and last instruction for your campaign and set \verb+L4SYS_FUNC_ENTRY+ and -\verb+L4SYS_FUNC_EXIT+ in the header file accordingly. \verb+L4SYS_NUMINSTR+ -is determined automatically in a later preparation step and can be ignored for -now. +and last instruction for your campaign and set \verb+func_entry+ and +\verb+func_exit+ in the configuration file accordingly. Additionally, you +can limit the range for FI by setting \verb+filter_entry+ and \verb+filter_exit+. -If you want your campaign only to affect a specific address space (e.g., -because you are only interested in faults at the application level), \lfs{} -leverages Fail*'s address space filtering mechanism. To determine the address -space identifier, you will have to use Bochs' -\href{http://bochs.sourceforge.net/doc/docbook/user/internal-debugger.html}{internal -debugger} and perform the following actions: -\begin{enumerate}[topsep=0em,itemsep=0em] - \item Compile Bochs with support for the internal debugger. This can either - be done by configuring and rebuilding the fail client accordingly or - using a separate Bochs installation - we don't need Fail* - functionality here.\footnote{BD: I saw differing values when using - another Bochs installation, though. Perhaps it's safer to use the same - Bochs build for testing and injection.} - \item Boot your system in Bochs. The debugger prompt (or window) will - appear. Use the \verb+lbreak+ command to set an instruction breakpoint - to an address in your application. (Hint: Remember you already figured - out \verb+L4SYS_FUNC_ENTRY+ previously.) - \item Run Bochs until the breakpoint is hit. Verify that you are in the - right address space (instruction pointers may be similar in different - applications as L4's BID links all programs to the same starting - address by default). - \item Use the \verb+creg+ command to look at the current control registers. - Set \verb+L4SYS_ADDRESS_SPACE+ to the value of the CR3 (page table - control) register. -\end{enumerate} +All preparations steps can be done together or step-by-step. To start +preparation call fail-client with the parameter \verb+-Wf,--step=OPTION+. +Use \verb+OPTION=all+ for doing all preparations steps together. + +\subsubsection{Step 0: Determine the address space (OPTION=cr3)} If you are not interested in address space filtering, you may set -\verb+L4SYS_ADDRESS_SPACE+ to \verb+ANY_ADDR+. Note that in this case you will +\verb+address_space+ to \verb+0+. Note that in this case you will probably encounter instruction pointers across various address spaces and may not get the unique results you want. -\subsection{Step 1: Save the initial state of the machine} +During this step, the option \verb+address_space+ of your configuration +file is is updated. -Make sure \verb+PREPARATION_STEP+ is still set to \texttt{1}, and -you have set \verb+L4SYS_ADDRESS_SPACE+ accordingly. + +\subsubsection{Step 1: Save the initial state of the machine} + +Make sure you have set \verb+address_space+ accordingly. Now recompile and execute the framework code again, this time with the graphical user interface disabled. The experiment client runs until -\verb+L4SYS_FUNC_ENTRY+ is reached and then saves -the complete configuration. +\verb+func_entry+ is reached and then saves the complete configuration. -\subsection{Step 2: Determine the instructions to execute} +\subsubsection{Step 2: Determine the instructions to execute} -For this part, it depends on how you want to conduct the injection -experiments. Setting \verb+L4SYS_FILTER_INSTRUCTIONS+ -stores all instructions by default, and -enables the filter functionality to store only those -instructions that match the filter. -Each instruction in the trace requires -an address plus an unsigned breakpoint counter, -which means~8 bytes per instruction on a 32-bit system -and 12~bytes per instruction on a 64-bit system. -If \verb+L4SYS_FILTER_INSTRUCTIONS+ is not set, the instruction -to perform the fault injection at is determined by single-stepping -through the program from the beginning, which is quite slow. -I only recommend it for long programs, where a complete -instruction trace would require several hundred megabytes of data. - -No matter which method you choose, the default implementation -of the campaign server reads the total instruction count -from \verb+L4SYS_NUMINSTR+. Thus, it is mandatory to set this -value to the number of instructions available. - -To obtain this number and optionally the instruction trace, -set \verb+PREPARATION_STEP+ to \texttt{2} and recompile, then execute -the experiment client. You do not have to pass parameters to Bochs -any more, because the configuration is overwritten with the -state saved in step~1. +For this part the filter-file (\verb+filter.list+ by default) is +read to filter out instruction which are not in any of the specified +ranges. After the program has finished, you will get a summary on the total of instructions executed. -If you have -\verb+L4SYS_FILTER_INSTRUCTIONS+ enabled, this is not the -value you look for; it merely claims how many -instructions have been processed at all. -To set \verb+L4SYS_NUMINSTR+ correctly, you need to look for the -number before the word \emph{accepted}, which points out how many -instructions have been accepted by the applied filter. Of course, -if no filtering is selected, these two figures should be equal. -Please contact me if that is not the case. +\subsubsection{Step 3: Determine the correct output} -If \verb+L4SYS_FILTER_INSTRUCTIONS+ is disabled, you should -get a statistical output on how many of the instructions -were executed in userland and kernel space, respectively, -but the interesting figure in this case is of course the overall -sum of executed instructions. - -\subsection{Step 3: Determine the correct output} - -This is the easiest step: Set \verb+PREPARATION_STEP+ to \texttt{3}, -recompile the client and execute it in the target directory. -It runs the complete program and logs the output. You can +The framework runs the complete program and logs the output. You can check the resulting file (by default \texttt{golden.out}), and if it does not comply with your expectations of a valid run, you should correct the entry and exit point, the address space or, in the worst case, your Bochs settings. +\subsubsection{Step 4: Fill the database with your experiments} + +Use the tools \verb+import-trace+ and \verb+prune-trace+ to +import the data and reducing the amount of necessary experiments. + +You can call both applications with parameters specifying your +database setup or using a default config file (\verb+~/.my.cnf+) +defining username, password and database name. For \verb+import-trace+ +you have to specify the following parameters: + +\begin{itemize} + \item \verb+--importer MemoryImport|RegisterImporter+ + \item \verb+-e+ YourImageName + \item \verb+-t+ NameOfYourTraceFile (trace.pb by default) +\end{itemize} + \section{Campaign Setup} -To setup the actual campaign, you need to edit \texttt{campaign.cc}. -The full language capabilities of \texttt{AspectC++} are at your hand to define -the course of your experiments; a sample covering all experiment -types at random is already provided. In the experiment client, -set \verb+PREPARATION_STEP+ to \texttt{0}, which means there is nothing more -to prepare. +To start a FI-experiment, you need to start both the campaign server +(\verb+l4-sys-server+) and the experiment client (texttt{l4-sys-server}) +needs a parameter to specify the experiment type. -After you have successfully compiled both programs, you need to -start both the campaign server (\texttt{l4-sys-server}) -and the experiment client. By default, they should run on the -same machine, but you can adapt the \texttt{L4SysExperiment} -constructor in \texttt{experiment.cc} to connect the \texttt{JobClient} -to a remote server instead of \texttt{localhost}. Each experiment client processes -exactly one experiment and exits. To complete your campaign, -you should use the \texttt{client.sh} script in the \texttt{scripts} -subdirectory of Fail*. +If \verb+l4-sys-server+ and \verb+fail-client+ are running on +different core then the config parameter \verb+campain_server+ should +be added to specify the host name or ip addr. of the machine running +\verb+l4-sys-server+. -\section{Format of the result file} +Each experiment client processes exactly one experiment +and exits. To complete your campaign, you should use the \verb+client.sh+ +script in the \texttt{scripts} subdirectory of Fail*. + +\section{Get your results} + +Your results are stored in your database. Look at the table \verb+result_L4SysProtoMsg+ +to see your results. + +\iffalse When the campaign is finished, the campaign server generates a report file (by default called \texttt{lfsys.csv}) in a primitive CSV dialect. The only syntax rules are that the columns are separated by commas, @@ -342,17 +299,11 @@ the campaign server, from left to right. provide the opcode of the new instruction. \end{enumerate} +\fi \section{Known bugs} If you need support for more than one processor, you will have to extend the code accordingly: at the moment, when in doubt, it uses the first CPU. -\section{To Be Continued} - -This is everything I consider important so far. If you still encounter -problems you may -contact me and I will try to set the record straight. -Happy experimenting! :) - \end{document}