Merge branch 'tobias' Apply Code cleanup and restructuring with new config file to main branch.

This commit is contained in:
Tobias Stumpf
2014-06-30 15:01:45 +02:00
17 changed files with 1343 additions and 1563 deletions

View File

@ -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
TYPE=$1
echo -e "\033[32;1m=========================================================================================="
echo "[$(date)] Prepare FI-Experiment ...."
echo -e "==========================================================================================\033[0m"
if [ -n $TYPE ] ; then
if [ $TYPE = "mem" ]; then
IMPORTER=MemoryImporter;
fi
fi
if [ "$IMPORTER" = "MemoryImporter" ]; then
echo "Preparing memory injection experiment."
else
elif [ $TYPE = "reg" ]; then
IMPORTER=RegisterImporter;
echo "Preparing register injection experiment."
else
echo "Specified experiment type not knwon";
exit
fi
else
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...."

View File

@ -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 ####

View File

@ -1,164 +1,20 @@
#include <iostream>
#include <fstream>
#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<L4SysProtoMsg_RegisterType>(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<L4SysProtoMsg_RegisterType>(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<L4SysExperimentData *>(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);
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);
//d->msg.set_exp_type(d->msg.MEM);
campaignmanager.addParam(d);
} else {
log << "Specified FI-type not supported" << std::endl;
exit(-1);
}
fail::campaignmanager.addParam(d);
}

View File

@ -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 <google/protobuf/descriptor.h>
#include "util/Logger.hpp"
#include <string>
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; //<! the logger
public:
std::string type;
};
#endif // __L4SYS_CAMPAIGN_HPP__

View File

@ -1,16 +0,0 @@
#include "conversion.hpp"
char const *l4sys_output_result_strings[] = { "Unknown", "No effect", "Incomplete execution", "Crash", "Silent data corruption", "Error" };
char const *l4sys_output_experiment_strings[] = { "Unknown", "GPR Flip", "RAT Flip", "IDC Flip", "ALU Instr Flip" };
char const *l4sys_output_register_strings[] = { "Unknown", "EAX", "ECX", "EDX", "EBX", "ESP", "EBP", "ESI", "EDI" };
L4SysConversion l4sysResultConversion(
l4sys_output_result_strings,
sizeof(l4sys_output_result_strings));
L4SysConversion l4sysExperimentConversion(
l4sys_output_experiment_strings,
sizeof(l4sys_output_experiment_strings));
L4SysConversion l4sysRegisterConversion(
l4sys_output_register_strings,
sizeof(l4sys_output_register_strings));

View File

@ -1,25 +0,0 @@
#ifndef __L4SYS_CONVERSION_HPP__
#define __L4SYS_CONVERSION_HPP__
#include "l4sys.pb.h"
class L4SysConversion {
public:
L4SysConversion(char const **strings, size_t array_size)
: m_Strings(strings)
{
m_ElementCount = array_size / sizeof(char*);
}
char const * output(unsigned int res) {
if (res == 0 || res >= m_ElementCount) {
return m_Strings[0];
} else {
return m_Strings[res];
}
}
private:
char const **m_Strings;
size_t m_ElementCount;
};
#endif // __L4SYS_CONVERSION_HPP__

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -26,10 +26,9 @@ typedef struct TraceInstrType {
unsigned bp_counter;
} TraceInstr;
typedef std::vector<TraceInstr> TraceVector;
class L4SysExperiment : public fail::ExperimentFlow {
private:
class L4SysConfig;
fail::JobClient m_jc; //!< the job client connecting to the campaign server
fail::Logger log; //<! the logger
L4SysExperimentData *param; //<! the parameter set currently in use by the client
@ -74,63 +73,38 @@ private:
* @returns a value for Bochs' eipBiased variable
*/
Bit32u eipBiased();
/**
* Parses a raw instruction into a bxInstruction_c structure.
* This simple version of the function is taken from Bochs
* where it is currently disabled due to the TRACE_CACHE option,
* and has been modified to fit the needs of instruction modification.
* @param instance a pointer to the current Bochs CPU
* @param instr a pointer to the address the instruction is fetched from
* @param iStorage an outgoing value which contains the parsed instruction
* @returns \a false if the instruction continued on the following page in memory
*/
bx_bool fetchInstruction(BX_CPU_C *instance, const Bit8u *instr, bxInstruction_c *iStorage);
/**
* Write out the injection parameters to the logger.
*/
void logInjection();
/**
* Proceeds by one single instruction.
* @param preserveAddressSpace if set, the address space of the next instruction
* must match with the current address space
* (for example, this is important when debugging in the kernel)
* @returns the listener that was triggered, in case there were more than one
*/
fail::BaseListener *singleStep(bool preserveAddressSpace);
/**
* Injects a new instruction into the Bochs instruction stream and restores the previous one
* @param oldInstr address of the instruction to be replaced
* @param newInstr address of the instruction to replace it with
*/
void injectInstruction(bxInstruction_c *oldInstr, bxInstruction_c *newInstr);
/**
* Calculate the timeout of the current workload in milliseconds.
*/
unsigned calculateTimeout(unsigned instr_left);
unsigned calculateTimeout(unsigned instr_left, unsigned ips);
/**
* Send back the experiment parameter set with a description of the error.
*/
void terminateWithError(std::string details, int reason, L4SysProtoMsg_Result*);
/**
* Run until L4SYS_FUNC_ENTRY and save state (experiment preparation,
* phase 1)
* Run until reaching the entry point of the experiment and save
* state.
*/
void startAndSaveInitState(fail::BPSingleListener* bp);
void CR3run(fail::BPSingleListener *bp);
void runToStart(fail::BPSingleListener *bp);
/**
* Collect list of executed instructions, considering instruction
* filtering if configured (experiment preparation, phase 2).
* filtering if configured.
*/
void collectInstructionTrace(fail::BPSingleListener* bp);
/**
* Perform the golden run (experiment preparation, phase 3)
* Perform the golden run.
*/
void goldenRun(fail::BPSingleListener* bp);
/**
* Doing fault injection expiriments.
*/
void doExperiments(fail::BPSingleListener *bp);
/**
* Check that all required setup has been done before an experiment run.
*/
void validatePrerequisites();
void validatePrerequisites(std::string state, std::string output);
/**
* Load job parameters for an experiment.
@ -142,7 +116,7 @@ private:
/**
* Read the golden run output into the target string.
*/
void readGoldenRun(std::string& target);
void readGoldenRun(std::string& target, std::string golden_run);
/*
* Prepare memory experiment. Creates a breakpoint to run until the
@ -167,8 +141,55 @@ private:
*/
void doRegisterInjection(int regDesc, int bit);
void setupFilteredBreakpoint(fail::BPSingleListener* bp, int instOffset, std::string instr_list);
void setupFilteredBreakpoint(fail::BPSingleListener* bp, int instOffset);
/**
* Updates a parameter of the config file or adds a new one
* if the parameter was not specified.
*/
int updateConfig(std::string parameter, std::string value);
/**
* Adds the value of the config file to the parameter list and
* parse the parameter list. This function makes use of the
* CommandLine Parser from the fail* framework.
*/
void parseOptions(L4SysConfig&);
/**
* Configuration Setup.
*/
class L4SysConfig {
public:
unsigned long int max_instr_bytes;
unsigned long int address_space;
unsigned long int address_space_trace;
unsigned long int func_entry;
unsigned long int func_exit;
unsigned long int filter_entry;
unsigned long int filter_exit;
unsigned long int break_blink;
unsigned long int break_longjmp;
unsigned long int break_exit;
unsigned long int filter_instructions;
unsigned long int emul_ips;
std::string state_folder;
std::string instruction_list;
std::string alu_instructions;
std::string golden_run;
std::string filter;
std::string trace;
std::string campain_server;
unsigned long int numinstr;
unsigned long int totinstr;
enum {NO_PREP, GET_CR3, CREATE_CHECKPOINT, COLLECT_INSTR_TRACE, GOLDEN_RUN, FULL_PREPARATION} step;
};
private:
L4SysConfig conf;
};
#endif // __L4SYS_EXPERIMENT_HPP__

View File

@ -0,0 +1,450 @@
#include <iostream>
#include "experiment.hpp"
#include "InstructionFilter.hpp"
#include "UDIS86.hpp"
#include "aluinstr.hpp"
#include "campaign.hpp"
#include <sal/bochs/BochsMemory.hpp>
#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<unsigned>(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<char*>(&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<char>(golden_run_file)),
istreambuf_iterator<char>());
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<char*>(&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<BochsMemoryManager*>(&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<L4SysProtoMsg_RegisterType>(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 <short-time> * <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);
}

View File

@ -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__

View File

@ -0,0 +1,315 @@
#include <iostream>
#include <list>
#include <stdlib.h>
#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<std::string> 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] ... <BochsOptions...>");
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;
}
}

View File

@ -0,0 +1,240 @@
#include <iostream>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#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<address_t, unsigned> 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<char const*>(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<char*>(&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;
}

View File

@ -31,6 +31,7 @@ message L4SysProtoMsg {
TIMEOUT = 3;
WRONG = 4;
UNKNOWN = 5;
FAILSTOP = 6;
}
required DatabaseCampaignMessage fsppilot = 1;

View File

@ -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 {

Binary file not shown.

View File

@ -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}