diff --git a/src/experiments/nanojpeg/CMakeLists.txt b/src/experiments/nanojpeg/CMakeLists.txt index 64a44f98..c6603c35 100644 --- a/src/experiments/nanojpeg/CMakeLists.txt +++ b/src/experiments/nanojpeg/CMakeLists.txt @@ -15,24 +15,30 @@ set(MY_CAMPAIGN_SRCS instantiateExperiment.cc experiment.hpp experiment.cc + campaign.hpp + campaign.cc + UDIS86.hpp + UDIS86.cc ) -# experimentInfo.hpp -# campaign.hpp -# campaign.cc #### PROTOBUFS #### -#find_package(Protobuf REQUIRED) -#include_directories(${PROTOBUF_INCLUDE_DIRS}) -#include_directories(${CMAKE_CURRENT_BINARY_DIR}) +find_package(Protobuf REQUIRED) +include_directories(${PROTOBUF_INCLUDE_DIRS}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) -#PROTOBUF_GENERATE_CPP(PROTO_SRCS PROTO_HDRS ${MY_PROTOS}) +PROTOBUF_GENERATE_CPP(PROTO_SRCS PROTO_HDRS ${MY_PROTOS}) + +# dependency: libudis86 +find_package(LibUdis86 REQUIRED) +include_directories(${LIBUDIS86_INCLUDE_DIRS}) +link_directories(${LIBUDIS86_LINK_DIRS}) ## Build library add_library(fail-${EXPERIMENT_NAME} ${PROTO_SRCS} ${PROTO_HDRS} ${MY_CAMPAIGN_SRCS}) add_dependencies(fail-${EXPERIMENT_NAME} fail-tracing) -target_link_libraries(fail-${EXPERIMENT_NAME} ${PROTOBUF_LIBRARY}) +target_link_libraries(fail-${EXPERIMENT_NAME} ${LIBUDIS86_LIBRARIES} ${PROTOBUF_LIBRARY}) ## This is the example's campaign server distributing experiment parameters -#add_executable(${EXPERIMENT_NAME}-server main.cc) -#target_link_libraries(${EXPERIMENT_NAME}-server fail-${EXPERIMENT_NAME} fail ${PROTOBUF_LIBRARY} ${Boost_THREAD_LIBRARY}) -#install(TARGETS ${EXPERIMENT_NAME}-server RUNTIME DESTINATION bin) +add_executable(${EXPERIMENT_NAME}-server main.cc) +target_link_libraries(${EXPERIMENT_NAME}-server fail-${EXPERIMENT_NAME} fail ${Boost_THREAD_LIBRARY}) +install(TARGETS ${EXPERIMENT_NAME}-server RUNTIME DESTINATION bin) diff --git a/src/experiments/nanojpeg/UDIS86.cc b/src/experiments/nanojpeg/UDIS86.cc new file mode 100644 index 00000000..1fa35c2e --- /dev/null +++ b/src/experiments/nanojpeg/UDIS86.cc @@ -0,0 +1,30 @@ +#include "UDIS86.hpp" + +Udis86::Udis86(fail::address_t ip) +: udis_instr(NULL), udis_instr_size(0) +{ + // initialise the internal data structure + ud_init(&ud_obj); + ud_set_mode(&ud_obj, 32); + ud_set_syntax(&ud_obj, UD_SYN_ATT); + ud_set_pc(&ud_obj, ip); +} + +void Udis86::setInputBuffer(unsigned char const *instr, size_t size) +{ + // initialise the buffer + if (size > udis_instr_size) { + void *new_instr = realloc(udis_instr, size); + if (new_instr == NULL) { + // highly improbable + return; + } + udis_instr = reinterpret_cast(new_instr); + } + + udis_instr_size = size; + memcpy(udis_instr, instr, udis_instr_size); + + // assign the buffer to the data structure + ud_set_input_buffer(&ud_obj, udis_instr, udis_instr_size); +} diff --git a/src/experiments/nanojpeg/UDIS86.hpp b/src/experiments/nanojpeg/UDIS86.hpp new file mode 100644 index 00000000..b2b08219 --- /dev/null +++ b/src/experiments/nanojpeg/UDIS86.hpp @@ -0,0 +1,95 @@ +#ifndef __UDIS86_HPP__ + #define __UDIS86_HPP__ + +#include +#include "sal/bochs/BochsController.hpp" +#include "sal/bochs/BochsRegister.hpp" + +/** + * \class Udis86 + * + * \brief Class to disassemble instructions + * + * This class disassembles a stream of machine code instruction + * by instruction. + * It provides a (thin) wrapper around the C API of UDIS86. + */ +class Udis86 +{ +private: + ud_t ud_obj; // 0); } + /** + * Returns the FailBochs equivalent to a UDIS86 GPR identifier. + * Attention: this only returns either 32-bit or 64-bit registers, no general IDs + * @param udisReg the udis86 GPR ID + * @returns the FailBochs GPR ID, usable with the BochsRegisterManager class + */ + static inline fail::GPRegisterId udisGPRToFailBochsGPR(ud_type_t udisReg) + { + #define REG_CASE(REG) case UD_R_##REG: return fail::RID_##REG + switch (udisReg) { + #if BX_SUPPORT_X86_64 // 64 bit register id's: + REG_CASE(RAX); + REG_CASE(RCX); + REG_CASE(RDX); + REG_CASE(RBX); + REG_CASE(RSP); + REG_CASE(RBP); + REG_CASE(RSI); + REG_CASE(RDI); + REG_CASE(R8); + REG_CASE(R9); + REG_CASE(R10); + REG_CASE(R11); + REG_CASE(R12); + REG_CASE(R13); + REG_CASE(R14); + REG_CASE(R15); + #else + REG_CASE(EAX); + REG_CASE(ECX); + REG_CASE(EDX); + REG_CASE(EBX); + REG_CASE(ESP); + REG_CASE(EBP); + REG_CASE(ESI); + REG_CASE(EDI); + #endif + default: + return fail::RID_LAST_GP_ID; + } + #undef REG_CASE + } +}; + +#endif // __UDIS86_HPP__ diff --git a/src/experiments/nanojpeg/campaign.cc b/src/experiments/nanojpeg/campaign.cc new file mode 100644 index 00000000..fcf7046c --- /dev/null +++ b/src/experiments/nanojpeg/campaign.cc @@ -0,0 +1,142 @@ +#include +#include +#include +#include +#include + +#include "campaign.hpp" +#include "experimentInfo.hpp" +#include "cpn/CampaignManager.hpp" +#include "util/Logger.hpp" +#include "util/ProtoStream.hpp" + +#include "UDIS86.hpp" + +#include "../plugins/tracing/TracingPlugin.hpp" + +using namespace std; +using namespace fail; + +bool get_file_contents(const char *filename, string& contents) +{ + std::ifstream in(filename, std::ios::in | std::ios::binary); + if (!in) { + return false; + } + in.seekg(0, std::ios::end); + contents.resize(in.tellg()); + in.seekg(0, std::ios::beg); + in.read(&contents[0], contents.size()); + in.close(); + return true; +} + +bool NanoJPEGCampaign::run() +{ + // read already existing results + bool file_exists = false; +/* + set existing_results; + ifstream oldresults(results_filename, ios::in); + if (oldresults.is_open()) { + char buf[16*1024]; + uint64_t addr; + int count = 0; + m_log << "scanning existing results ..." << endl; + file_exists = true; + while (oldresults.getline(buf, sizeof(buf)).good()) { + stringstream ss; + ss << buf; + ss >> addr; + if (ss.fail()) { + continue; + } + ++count; + if (!existing_results.insert(addr).second) { + m_log << "duplicate: " << addr << endl; + } + } + m_log << "found " << dec << count << " existing results" << endl; + oldresults.close(); + } +*/ + + // non-destructive: due to the CSV header we can always manually recover + // from an accident (append mode) + ofstream results(NANOJPEG_RESULTS, ios::out | ios::app); + if (!results.is_open()) { + m_log << "failed to open " << NANOJPEG_RESULTS << endl; + return false; + } + // only write CSV header if file didn't exist before + if (!file_exists) { + results << "instr_offset\tinstr_address\tregister_id\ttimeout\tinjection_ip\tbitnr\tresulttype\tlatest_ip\tpsnr\tdetails" << endl; + } + + // load binary image (objcopy'ed system.elf = system.bin) + string binimage; + if (!get_file_contents(NANOJPEG_BIN, binimage)) { + m_log << "couldn't open " << NANOJPEG_BIN << endl; + return false; + } + Udis86 udis(0); + + // load trace + ifstream tracef(NANOJPEG_TRACE); + if (tracef.fail()) { + m_log << "couldn't open " << NANOJPEG_TRACE << endl; + return false; + } + ProtoIStream ps(&tracef); + + // experiment count + int count = 0; + + // instruction counter within trace + int instr = 0; + + Trace_Event ev; + // for every event in the trace ... + while (ps.getNext(&ev) && instr < NANOJPEG_INSTR_LIMIT) { + // sanity check: skip memory access entries + if (ev.has_memaddr()) { + continue; + } + instr++; + + // disassemble instruction + if (ev.ip() < NANOJPEG_BIN_OFFSET || ev.ip() - NANOJPEG_BIN_OFFSET > binimage.size()) { + m_log << "traced IP 0x" << hex << ev.ip() << " outside system image" << endl; + continue; + } + udis.setIP(ev.ip()); + size_t ip_offset = ev.ip() - NANOJPEG_BIN_OFFSET; + udis.setInputBuffer((unsigned char *) &binimage[ip_offset], min((size_t) 20, binimage.size() - ip_offset)); + + if (!udis.fetchNextInstruction()) { + m_log << "fatal: cannot disassemble instruction at 0x" << hex << ev.ip() << endl; + return false; + } + + ud_t ud = udis.getCurrentState(); + + // for now: debug output + m_log << "0x" << hex << ev.ip() << " " << ::ud_insn_asm(&ud) << endl; + for (int i = 0; i < 3; ++i) { + switch (ud.operand[i].type) { + case UD_NONE: cout << "-"; break; + case UD_OP_MEM: cout << "M"; break; + case UD_OP_PTR: cout << "P"; break; + case UD_OP_IMM: cout << "I"; break; + case UD_OP_JIMM: cout << "J"; break; + case UD_OP_CONST: cout << "C"; break; + case UD_OP_REG: cout << "R"; break; + default: m_log << "WAT" << endl; + } + std::cout << " "; + } + cout << endl; + } + + return true; +} diff --git a/src/experiments/nanojpeg/campaign.hpp b/src/experiments/nanojpeg/campaign.hpp new file mode 100644 index 00000000..85e81713 --- /dev/null +++ b/src/experiments/nanojpeg/campaign.hpp @@ -0,0 +1,22 @@ +#ifndef __NANOJPEG_CAMPAIGN_HPP__ + #define __NANOJPEG_CAMPAIGN_HPP__ + +#include "cpn/Campaign.hpp" +#include "comm/ExperimentData.hpp" +#include "util/Logger.hpp" +#include "nanojpeg.pb.h" + +class NanoJPEGExperimentData : public fail::ExperimentData { +public: + NanoJPEGProtoMsg msg; + NanoJPEGExperimentData() : fail::ExperimentData(&msg) {} +}; + +class NanoJPEGCampaign : public fail::Campaign { + fail::Logger m_log; +public: + NanoJPEGCampaign() : m_log("nJPEG Campaign") {} + virtual bool run(); +}; + +#endif // __NANOJPEG_CAMPAIGN_HPP__ diff --git a/src/experiments/nanojpeg/main.cc b/src/experiments/nanojpeg/main.cc new file mode 100644 index 00000000..030b4a3f --- /dev/null +++ b/src/experiments/nanojpeg/main.cc @@ -0,0 +1,11 @@ +#include +#include + +#include "cpn/CampaignManager.hpp" +#include "campaign.hpp" + +int main(int argc, char **argv) +{ + NanoJPEGCampaign c; + return !fail::campaignmanager.runCampaign(&c); +} diff --git a/src/experiments/nanojpeg/nanojpeg.proto b/src/experiments/nanojpeg/nanojpeg.proto new file mode 100644 index 00000000..ad4aec12 --- /dev/null +++ b/src/experiments/nanojpeg/nanojpeg.proto @@ -0,0 +1,56 @@ +message NanoJPEGProtoMsg { + // Input: experiment parameters + // (client executes one experiment for every specified bit in the target register) + + // FI at #instructions from experiment start + required int32 instr_offset = 1; + // the exact IP value at this point in time (from golden run) + optional int32 instr_address = 2; // for sanity checks + // ID of the register to inject faults into + required int32 register_id = 3; + // first bit to inject a flip into + required int32 bit_start = 4; + // last bit to inject a flip into (inclusive) + required int32 bit_end = 5; + // timeout in ms + required int32 timeout = 6; + + // ---------------------------------------------------- + + // Output: experiment results + + // IP where we did the injection: for debugging purposes, must be identical + // to instr_address + optional int32 injection_ip = 8; + + repeated group Result = 9 { + // single experiment bit number + required int32 bitnr = 1; + + // result type: + // FINISHED = planned number of instructions were executed + // BROKEN = finished, but resulting image is broken (dimensions) + // TRAP = premature guest "crash" + // OUTSIDE = IP left text segment + // TIMEOUT = none of the above happened for /timeout/ ms + enum ResultType { + FINISHED = 1; + BROKEN = 2; + TRAP = 3; + OUTSIDE = 4; + DETECTED = 5; // unused for now + TIMEOUT = 6; + UNKNOWN = 7; + } + required ResultType resulttype = 2; + + // especially interesting for TRAP/UNKNOWN: latest IP + required uint32 latest_ip = 3; + + // PSNR golden run <-> this run + required float psnr = 4; + + // optional textual description of what happened + optional string details = 5; + } +}