diff --git a/CMakeLists.txt b/CMakeLists.txt index 63143ef84..74b25658a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,7 @@ foreach(ARCH arm mips x86_64 i386) ARGS "${CMAKE_CURRENT_SOURCE_DIR}/support.c" -o "${OUTPUT}" -S -emit-llvm -g -DTARGET_${ARCH} + -I"${CMAKE_CURRENT_SOURCE_DIR}" ${SUPPORT_MODULES_CONFIG_${CONFIG}}) add_custom_target("support-module-${OUTPUT}" ALL DEPENDS "${OUTPUT}") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT}" @@ -101,14 +102,15 @@ target_link_libraries(revamb dl m ${LLVM_LIBRARIES}) install(TARGETS revamb RUNTIME DESTINATION bin) add_executable(revamb-dump dump.cpp collectcfg.cpp collectnoreturn.cpp debug.cpp - collectfunctionboundaries.cpp stackanalysis.cpp generatedcodebasicinfo.cpp - argparse/argparse.c) + collectfunctionboundaries.cpp debughelper.cpp stackanalysis.cpp + generatedcodebasicinfo.cpp isolatefunctions.cpp argparse/argparse.c) target_link_libraries(revamb-dump ${LLVM_LIBRARIES}) install(TARGETS revamb-dump RUNTIME DESTINATION bin) configure_file(li-csv-to-ld-options "${CMAKE_BINARY_DIR}/li-csv-to-ld-options" COPYONLY) configure_file(support.c "${CMAKE_BINARY_DIR}/support.c" COPYONLY) +configure_file(support.h "${CMAKE_BINARY_DIR}/support.h" COPYONLY) configure_file(translate "${CMAKE_BINARY_DIR}/translate" COPYONLY) install(PROGRAMS translate li-csv-to-ld-options DESTINATION bin) install(FILES support.c DESTINATION share/revamb) diff --git a/codegenerator.cpp b/codegenerator.cpp index c535285ec..a6d3a79dc 100644 --- a/codegenerator.cpp +++ b/codegenerator.cpp @@ -581,8 +581,6 @@ void CodeGenerator::translate(uint64_t VirtualAddress) { "root", TheModule.get()); - Debug->newFunction(MainFunction); - // Create the first basic block and create a placeholder for variable // allocations BasicBlock *Entry = BasicBlock::Create(Context, "entrypoint", MainFunction); diff --git a/debughelper.cpp b/debughelper.cpp index 8abb3fbcc..92bc2cc3a 100644 --- a/debughelper.cpp +++ b/debughelper.cpp @@ -74,11 +74,17 @@ static void writeMetadataIfNew(const Instruction *TheInstruction, } } +/// Add a module flag, if not already present, using name and value provided. +/// Used for creating the Dwarf compliant debug info. +static void addModuleFlag(Module *TheModule, StringRef Flag, uint32_t Value) { + if (TheModule->getModuleFlag(Flag) == nullptr) { + TheModule->addModuleFlag(Module::Warning, Flag, Value); + } +} + DebugAnnotationWriter::DebugAnnotationWriter(LLVMContext& Context, - Metadata *Scope, bool DebugInfo) : Context(Context), - Scope(Scope), DebugInfo(DebugInfo) { OriginalInstrMDKind = Context.getMDKindID("oi"); @@ -88,9 +94,10 @@ DebugAnnotationWriter::DebugAnnotationWriter(LLVMContext& Context, void DebugAnnotationWriter::emitInstructionAnnot(const Instruction *Instr, formatted_raw_ostream &Output) { - // Ignore whatever is outside the root function - // TODO: comparing strings here is not very elegant - if (Instr->getParent()->getParent()->getName() != "root") + DISubprogram *Subprogram = Instr->getParent()->getParent()->getSubprogram(); + + // Ignore whatever is outside the root and the isolated functions + if (Subprogram == nullptr) return; writeMetadataIfNew(Instr, OriginalInstrMDKind, Output, "\n ; "); @@ -101,14 +108,14 @@ void DebugAnnotationWriter::emitInstructionAnnot(const Instruction *Instr, // will contain some reference to dangling pointers. So ignore the output // stream if you're using the annotator to generate debug info about the IR // itself. - assert(Scope != nullptr); + assert(Subprogram != nullptr); // Flushing is required to have correct line and column numbers Output.flush(); auto *Location = DILocation::get(Context, Output.getLine() + 1, Output.getColumn(), - Scope); + Subprogram); // Sorry Bjarne auto *NonConstInstruction = const_cast(Instr); @@ -149,42 +156,44 @@ DebugHelper::DebugHelper(std::string Output, "", 0 /* Runtime version */); - // Add the current debug info version into the module. - TheModule->addModuleFlag(Module::Warning, "Debug Info Version", - DEBUG_METADATA_VERSION); - TheModule->addModuleFlag(Module::Warning, "Dwarf Version", 4); - } -} - -void DebugHelper::newFunction(Function *Function) { - if (Type != DebugInfoType::None) { - DISubroutineType *EmptyType = nullptr; - EmptyType = Builder.createSubroutineType(Builder.getOrCreateTypeArray({})); - - CurrentFunction = Function; - assert(CompileUnit != nullptr); - CurrentSubprogram = Builder.createFunction(CompileUnit->getFile(), // Scope - Function->getName(), - StringRef(), // Linkage name - CompileUnit->getFile(), - 1, // Line - EmptyType, // Subroutine type - false, // isLocalToUnit - true, // isDefinition - 1, // ScopeLine - DINode::FlagPrototyped, - false /* isOptimized */); - CurrentFunction->setSubprogram(CurrentSubprogram); + // Add the current debug info version into the module after checking if it + // is already present. + addModuleFlag(TheModule, "Debug Info Version", DEBUG_METADATA_VERSION); + addModuleFlag(TheModule, "Dwarf Version", 4); } } void DebugHelper::generateDebugInfo() { + for (Function &F : TheModule->functions()) { + // TODO: find a better way to identify root and the isolated functions + if (F.getName() == "root" || F.getName().startswith("bb.")) { + if (Type != DebugInfoType::None) { + DISubroutineType *EmptyType = nullptr; + DITypeRefArray EmptyArrayType = Builder.getOrCreateTypeArray({}); + EmptyType = Builder.createSubroutineType(EmptyArrayType); + + assert(CompileUnit != nullptr); + DISubprogram *Subprogram = nullptr; + Subprogram = Builder.createFunction(CompileUnit->getFile(), // Scope + F.getName(), + StringRef(), // Linkage name + CompileUnit->getFile(), + 1, // Line + EmptyType, // Subroutine type + false, // isLocalToUnit + true, // isDefinition + 1, // ScopeLine + DINode::FlagPrototyped, + false /* isOptimized */); + F.setSubprogram(Subprogram); + } + } + } + switch (Type) { case DebugInfoType::PTC: case DebugInfoType::OriginalAssembly: { - assert(CurrentSubprogram != nullptr && CurrentFunction != nullptr); - // Generate the source file and the debugging information in tandem unsigned LineIndex = 1; @@ -193,22 +202,28 @@ void DebugHelper::generateDebugInfo() { MDString *Last = nullptr; std::ofstream Source(DebugPath); - for (BasicBlock& Block : *CurrentFunction) { - for (Instruction& Instruction : Block) { - MDString *Body = getMD(&Instruction, MetadataKind); + for (Function &CurrentFunction : TheModule->functions()) { + if (DISubprogram *CurrentSubprogram = CurrentFunction.getSubprogram()) { + for (BasicBlock& Block : CurrentFunction) { + for (Instruction& Instruction : Block) { + MDString *Body = getMD(&Instruction, MetadataKind); - if (Body != nullptr && Last != Body) { - Last = Body; - std::string BodyString = Body->getString().str(); + if (Body != nullptr && Last != Body) { + Last = Body; + std::string BodyString = Body->getString().str(); - Source << BodyString; + Source << BodyString; - auto *Location = DILocation::get(TheModule->getContext(), - LineIndex, - 0, - CurrentSubprogram); - Instruction.setMetadata(DbgMDKind, Location); - LineIndex += std::count(BodyString.begin(), BodyString.end(), '\n'); + auto *Location = DILocation::get(TheModule->getContext(), + LineIndex, + 0, + CurrentSubprogram); + Instruction.setMetadata(DbgMDKind, Location); + LineIndex += std::count(BodyString.begin(), + BodyString.end(), + '\n'); + } + } } } } @@ -259,7 +274,6 @@ bool DebugHelper::copySource() { DebugAnnotationWriter *DebugHelper::annotator(bool DebugInfo) { Annotator.reset(new DebugAnnotationWriter(TheModule->getContext(), - CurrentSubprogram, DebugInfo)); return Annotator.get(); } diff --git a/debughelper.h b/debughelper.h index 26d12b855..36edfe920 100644 --- a/debughelper.h +++ b/debughelper.h @@ -46,7 +46,6 @@ public: /// \param DebugInfo whether to decorate the IR being serialized with debug /// metadata refering to the produce IR itself or not. DebugAnnotationWriter(llvm::LLVMContext& Context, - llvm::Metadata *Scope, bool DebugInfo); virtual void emitInstructionAnnot(const llvm::Instruction *TheInstruction, @@ -54,7 +53,6 @@ public: private: llvm::LLVMContext &Context; - llvm::Metadata *Scope; unsigned OriginalInstrMDKind; unsigned PTCInstrMDKind; unsigned DbgMDKind; @@ -79,13 +77,8 @@ public: llvm::Module *TheModule, DebugInfoType Type); - /// \brief Handle a new function - /// - /// Generates the debug information for the given function and caches it for - /// future use. - void newFunction(llvm::Function *Function); - - /// Decorates the current function with the requested debug info + /// Decorates the root and the isolated functions with the requested debug + /// info void generateDebugInfo(); /// Serializes to the given stream the module, with or without debug info @@ -108,8 +101,6 @@ private: DebugInfoType Type; llvm::Module *TheModule; llvm::DICompileUnit *CompileUnit; - llvm::DISubprogram *CurrentSubprogram; - llvm::Function *CurrentFunction; std::unique_ptr Annotator; unsigned OriginalInstrMDKind; diff --git a/docs/GeneratedIRReference.rst b/docs/GeneratedIRReference.rst index b320d3908..ae815ed0e 100644 --- a/docs/GeneratedIRReference.rst +++ b/docs/GeneratedIRReference.rst @@ -87,7 +87,7 @@ helper variables used to compute the CPU flags. CSVs are used by the generated code and by the helper functions. This is also the reason why they cannot be promoted to local variables in the `root` -function` +function Note that since they are global variables, the generated code interacts with them using load and store operations, which might sound unusual for registers. @@ -511,5 +511,136 @@ best way to understand which helper function does what, is to create a simple assembly snippet using a specific feature (e.g., a performing a syscall) and translate it using revamb. +Function isolation pass output reference +======================================== + +This section of the document aims to describe how to apply the function +isolation pass of revamb-dump to a simple example, to describe what to expect +as output of this pass and the assumptions made in the isolation pass. + +All the following examples originate from the translation of the simple program +already shown in the beginning of this document. + +Once we have applied the translation to the original binary we can apply the +function isolation pass using the `revamb-dump` utility like this: + +.. code-block:: sh + + revamb-dump --functions-isolation=example.isolated-functions.ll example.ll + +As you can see by comparing the original IR and the one to which the function +isolation pass has been applied the main difference is that, on the basis of the +information recovered by the function boundaries analysis applied by revamb, now +the code is organized in different LLVM functions. + +As a reference we can see that the basic block `bb.myfunction` that belonged to +the `root` function after the isolation is in the LLVM function +`bb.myfunction`. + +.. code-block:: llvm + + define void @bb.myfunction() { + bb.myfunction: + call void (i64, i64, i32, i8*, ...) @newpc(i64 4194536, i64 5, i32 1, i8* null), !dbg !96, !oi !97, !pi !98 + ; ... + ret void + } + +Moreover, with this structure, instead of tagging the actual function calls with +a call to ``function_call`` we can place a real LLVM function call to the target +function. +Just after the function call we also add a branch to the identified return +address. + +As a reference take the call to ``my_function``. In the original IR it appeared in +this form: + +.. code-block:: llvm + + call void @function_call(i8* blockaddress(@root, %bb.myfunction), i8* blockaddress(@root, %bb._start.0x11), i32 4194559), !dbg !60 + br label %bb.myfunction, !dbg !61, !func.entry !62, !func.member.of !63 + +Now with the actual call appears like this: + +.. code-block:: llvm + + call void @bb.myfunction() + br label %bb._start.0x11 + +Always on the basis of the information recovered by the analysis performed by +revamb we are able to emit `ret` instructions where needed. + +As a reference at the end of the basic block ``bb.myfunction`` the branch to the +dispatcher: + +.. code-block:: llvm + + br label %dispatcher.entry, !func.entry !151, !func.member.of !152, !func.return !151 + +has been substituted by the `ret` instruction: + +.. code-block:: llvm + + ret void + +The fact that we are now not always operating inside the ```root`` function +means that we can't simply branch to the dispatcher when we need it. +For this purpose we have introduced a custom exception handling mechanism to be +able to restore the execution from the dispatcher when things do not go as +expected. + +The main idea is to have a sort of separation between the world of the isolated +functions and the `root` function. In this way, as soon as possible after the +start of the execution of the program, we try to jump in the *isolated* world +and continue the execution from there. When we are not anymore able to continue +the execution in the *isolated* world we generate an exception that restores the +execution in the other world. + +To do this we need to use the exception handling mechanism provided by the LLVM +framework, modifying it a little bit to suit our needs. + +The first thing that we do is substitute the code of each `func.entry` block in +the `root` function with an `invoke` instruction that calls the isolated +function. +In our example, examining the ``bb._start`` function, we substitute the code of +the entry block with this: + +.. code-block:: llvm + + bb._start: ; preds = %dispatcher.entry + invoke void @bb._start() + to label %invoke_return unwind label %catchblock + +In this way when we reach a point, inside the body of a function, where we need +the dispatcher we can use the ``_Unwind_RaiseException`` function provided by +``libunwind`` to restore the execution in the ``root`` function, where we take +care of doing the right action to correctly continue the execution(i.e. invoke) +the dispatcher. + +Due to implementation details, we do not rely on the standard mechanism used by +the C++ excpetion handling mechanism. For this reason the ``catchblock`` is not +used, but we always transfer the execution to the ``invoke_return`` block, and +we then check for the value of ``ExceptionFlag`` for deciding where to transfer +the execution. +After this we transfer the control flow to the ``dispatcher.entry`` block for +resuming the execution in the correct manner. + +We then need a ``function_dispatcher`` that acts as a normal dispatcher but is +used in presence of an indirect function call and assumes the form of a LLVM +function. Obviously the possible targets are only the function entry blocks, +since it is not possible that a function call requires to jump in the middle of +the code of a function. + +We also add an extra check after each call to the ``function_dispatcher`` to +ensure that the program counter value is the one that we expect to have after +the call. This mechanism is usefull to avoid errors due to a bad identification +of ``ret`` instructions by the function boundaries analysis. + +During the execution of the translated program, when an exception is raised, the +``exception_warning`` helper function is called, and it will print on ``stdout`` +useful informations about the conditions that caused the exception (e.g. the +current program counter at the moment of the exception, the next program +counter, etc.). + .. _LLVM Language Reference Manual: http://llvm.org/docs/LangRef.html .. _`FromIRToExecutable.rst`: FromIRToExecutable.rst diff --git a/docs/RevambDumpUsage.rst b/docs/RevambDumpUsage.rst index 03e1b717b..cf9f616cd 100644 --- a/docs/RevambDumpUsage.rst +++ b/docs/RevambDumpUsage.rst @@ -47,3 +47,6 @@ a basic block* as represented by `revamb` in the generated module (typically block of the function, and `basicblock`, the name of a basic block belonging to `function`. +:``-i``, ``--function-isolation``: Path where to store the LLVM module that is + the result of the function isolation pass on + the input module. diff --git a/docs/TranslateUsage.rst b/docs/TranslateUsage.rst index 2d36cf463..76f35d52a 100644 --- a/docs/TranslateUsage.rst +++ b/docs/TranslateUsage.rst @@ -48,3 +48,5 @@ OPTIONS instead of the `support-$ARCH-normal.ll`. Enabling this option introduces a non-negligible slow down in the output program, even if `REVAMB_TRACE_PATH` is not specified at run-time. +:``-i``: Optionally apply the function isolation pass before re-compiling the + program. diff --git a/dump.cpp b/dump.cpp index 3f4feabca..66ee93f8d 100644 --- a/dump.cpp +++ b/dump.cpp @@ -9,6 +9,7 @@ // LLVM includes #include "llvm/ADT/StringRef.h" +#include "llvm/IR/AssemblyAnnotationWriter.h" #include "llvm/IR/LegacyPassManager.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Module.h" @@ -21,6 +22,8 @@ #include "collectfunctionboundaries.h" #include "collectnoreturn.h" #include "debug.h" +#include "debughelper.h" +#include "isolatefunctions.h" #include "stackanalysis.h" using namespace llvm; @@ -31,6 +34,7 @@ struct ProgramParameters { const char *NoreturnPath; const char *FunctionBoundariesPath; const char *StackAnalysisPath; + const char *FunctionIsolationPath; }; static const char *const Usage[] = { @@ -61,6 +65,12 @@ static bool parseArgs(int Argc, const char *Argv[], ProgramParameters &Result) { OPT_STRING('s', "stack-analysis", &Result.StackAnalysisPath, "path where the result of the stack analysis should be stored."), + OPT_STRING('i', "functions-isolation", + &Result.FunctionIsolationPath, + "path where a new LLVM module containing the reorganization of " + "the basic blocks into the corresponding functions identified " + "by function boundaries analysis performed by revamb should be " + "stored."), OPT_END(), }; @@ -122,6 +132,12 @@ public: Analysis.serialize(pathToStream(Parameters.StackAnalysisPath, Output)); } + if (Parameters.FunctionIsolationPath != nullptr) { + auto &Analysis = getAnalysis(); + Module *ModifiedModule = Analysis.getModule(); + dumpModule(ModifiedModule, Parameters.FunctionIsolationPath); + } + return false; } @@ -140,6 +156,9 @@ public: if (Parameters.StackAnalysisPath != nullptr) AU.addRequired(); + if (Parameters.FunctionIsolationPath != nullptr) + AU.addRequired(); + } private: @@ -154,6 +173,20 @@ private: } } + void dumpModule(Module *Module, const char *Path) { + std::ofstream Output; + + // If output path is `-` print on stdout + // TODO: this solution is not portable, make DebugHelper accept streams + if (Path[0] == '-' && Path[1] == '\0') { + Path = "/dev/stdout"; + } + + // Initialize the debug helper object + DebugHelper Debug(Path, Path, Module, DebugInfoType::LLVMIR); + Debug.generateDebugInfo(); + } + private: ProgramParameters &Parameters; }; diff --git a/functionboundariesdetection.cpp b/functionboundariesdetection.cpp index 514022164..e7a43952c 100644 --- a/functionboundariesdetection.cpp +++ b/functionboundariesdetection.cpp @@ -251,8 +251,9 @@ void FBD::collectReturnInstructions() { for (BasicBlock *Successor : Terminator->successors()) { assert(!Successor->empty()); - // A return instruction must jump to JTM->anyPC, while all the other - // successors (if any) must be registered returns addresses + // A return instruction must jump to JTM->anyPC or to JTM->disptacher, + // while all the other successors (if any) must be registered returns + // addresses if (Successor == JTM->anyPC() || Successor == JTM->dispatcher()) { JumpsToDispatcher = true; } else if (ReturnPCs.count(JTM->getPC(&*Successor->begin()).first) == 0) { @@ -575,7 +576,6 @@ void FBD::createMetadata() { // Mark each return instruction for (TerminatorInst *T : Returns) T->setMetadata("func.return", MDNode::get(Context, { })); - } map> FBD::run() { diff --git a/isolatefunctions.cpp b/isolatefunctions.cpp new file mode 100644 index 000000000..5323a9154 --- /dev/null +++ b/isolatefunctions.cpp @@ -0,0 +1,984 @@ +/// \file isolatefunctions.cpp +/// \brief Implements the IsolateFunctions pass which applies function isolation +/// using the informations provided by FunctionBoundariesDetectionPass. + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +// LLVM includes +#include "llvm/ADT/PostOrderIterator.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/Verifier.h" +#include "llvm/Support/raw_os_ostream.h" +#include "llvm/Transforms/Utils/Cloning.h" + +// Local includes +#include "debug.h" +#include "generatedcodebasicinfo.h" +#include "ir-helpers.h" +#include "isolatefunctions.h" +#include "support.h" + +using namespace llvm; + +class IsolateFunctionsImpl; + +// Define an alias for the data structure that will contain the LLVM functions +using FunctionsMap = std::map; + +typedef DenseMap ValueToValueMap; + +using IF = IsolateFunctions; +using IFI = IsolateFunctionsImpl; + +char IF::ID = 0; +static RegisterPass X("if", "Isolate Functions Pass", true, true); + +class IsolateFunctionsImpl { +public: + IsolateFunctionsImpl(Function &RootFunction, + Module *NewModule, + GeneratedCodeBasicInfo &GCBI, + ValueToValueMapTy &ModuleCloningVMap) : + RootFunction(RootFunction), + NewModule(NewModule), + GCBI(GCBI), + ModuleCloningVMap(ModuleCloningVMap), + Context(getContext(NewModule)), + PCBitSize(8 * GCBI.pcRegSize()) { + } + + void run(); + +private: + + /// \brief Creates the call that simulates the throw of an exception + void throwException(Reason Code, BasicBlock *BB, uint64_t AdditionalPC); + + /// \brief Instantiate a basic block that consists only of an exception throw + BasicBlock *createUnreachableBlock(StringRef Name, + Function *CurrentFunction); + + /// \brief Populate the @function_dispatcher, needed to handle the indirect + /// function calls + void populateFunctionDispatcher(); + + /// \brief Create the basic blocks that are hit on exit after an invoke + /// instruction + BasicBlock *createInvokeReturnBlock(Function *Root, + BasicBlock *UnexpectedPC); + + /// \brief Create the basic blocks that represent the catch of the invoke + /// instruction + BasicBlock *createCatchBlock(Function *Root, + BasicBlock *UnexpectedPC); + + /// \brief Replace the call to the @function_call marker with the actual call + void replaceFunctionCall(BasicBlock *NewBB, + CallInst *Call, + const ValueToValueMap &LocalVMap); + + /// \brief Checks if an instruction is a terminator with an invalid successor + bool isTerminatorWithInvalidTarget(Instruction *I, + const ValueToValueMap &LocalVMap); + + /// \brief Handle the cloning of an instruction in the new basic block + /// + /// \return true if the function purged all the instructions after this one in + /// the current basic block + bool cloneInstruction(BasicBlock *NewBB, + Instruction *OldInstruction, + ValueToValueMap &LocalVMap); + + /// \brief Extract the string representing a function name starting from the + /// MDNode + /// \return StringRef representing the function name + StringRef getFunctionNameString(MDNode *Node); + +private: + Function &RootFunction; + Module *NewModule; + GeneratedCodeBasicInfo &GCBI; + ValueToValueMapTy &ModuleCloningVMap; + LLVMContext &Context; + Function *RaiseException; + Function *DebugException; + Function *FunctionDispatcher; + std::map NewToOldBBMap; + std::map FunctionsPC; + GlobalVariable *ExceptionFlag; + GlobalVariable *PC; + const unsigned PCBitSize; +}; + +void IFI::throwException(Reason Code, BasicBlock *BB, uint64_t AdditionalPC) { + assert(PC != nullptr); + assert(RaiseException != nullptr); + assert(DebugException != nullptr); + + // Create a builder object + IRBuilder<> Builder(Context); + Builder.SetInsertPoint(BB); + + // Set the exception flag to value one + ConstantInt *ConstantTrue = Builder.getTrue(); + Builder.CreateStore(ConstantTrue, ExceptionFlag); + + // Call the _debug_exception function to print usefull stuff + LoadInst *ProgramCounter = Builder.CreateLoad(PC, ""); + + uint64_t LastPC; + + if (Code == StandardTranslatedBlock) { + // Retrieve the value of the PC in the basic block where the exception has + // been raised, this is possible since BB should be a translated block + LastPC = GCBI.getPC(&*BB->rbegin()).first; + assert(LastPC != 0); + } else { + + // The current basic block has not been translated from the original binary + // (e.g. unexpectedpc or anypc), therefore we can't retrieve the + // corresponding PC. + LastPC = 0; + } + + // Get the PC register dimension and use it to instantiate the arguments of + // the call to exception_warning + ConstantInt *ReasonValue = Builder.getInt32(Code); + ConstantInt *ConstantLastPC = Builder.getIntN(PCBitSize, LastPC); + ConstantInt *ConstantAdditionalPC = Builder.getIntN(PCBitSize, AdditionalPC); + + // Emit the call to exception_warning + Builder.CreateCall(DebugException, + { + ReasonValue, + ConstantLastPC, + ProgramCounter, + ConstantAdditionalPC + }, + ""); + + // Emit the call to _Unwind_RaiseException + Builder.CreateCall(RaiseException); +} + +BasicBlock *IFI::createUnreachableBlock(StringRef Name, + Function *CurrentFunction) { + + // Create the basic block and add it in the function passed as parameter + BasicBlock* NewBB = BasicBlock::Create(Context, + Name, + CurrentFunction, + nullptr); + + throwException(StandardNonTranslatedBlock, NewBB, 0); + return NewBB; +} + +void IFI::populateFunctionDispatcher() { + + BasicBlock *DispatcherBB = BasicBlock::Create(Context, + "function_dispatcher", + FunctionDispatcher, + nullptr); + + BasicBlock *UnexpectedPC = BasicBlock::Create(Context, + "unexpectedpc", + FunctionDispatcher, + nullptr); + throwException(FunctionDispatcherFallBack, UnexpectedPC, 0); + new UnreachableInst(Context, UnexpectedPC); + + // Create a builder object for the DispatcherBB basic block + IRBuilder<> Builder(Context); + Builder.SetInsertPoint(DispatcherBB); + + LoadInst *ProgramCounter = Builder.CreateLoad(PC, ""); + + SwitchInst *Switch = Builder.CreateSwitch(ProgramCounter, UnexpectedPC); + + for (auto &Pair : FunctionsPC) { + Function *Function = Pair.first; + StringRef Name = Function->getName(); + + // Creation of a basic block correspondent to the trampoline for each + // function + BasicBlock *TrampolineBB = BasicBlock::Create(Context, + Name + "_trampoline", + FunctionDispatcher, + nullptr); + + CallInst::Create(Function, "", TrampolineBB); + ReturnInst::Create(Context, TrampolineBB); + + uint64_t FunctionPC = Pair.second; + auto *Label = Builder.getIntN(PCBitSize, FunctionPC); + Switch->addCase(Label, TrampolineBB); + } +} + +BasicBlock *IFI::createInvokeReturnBlock(Function *Root, + BasicBlock *UnexpectedPC) { + + // Create the first block + BasicBlock *InvokeReturnBlock = BasicBlock::Create(Context, + "invoke_return", + Root, + nullptr); + + // Create two basic blocks, one that we will hit if we have a normal exit + // from the invoke call and another for signaling the creation of an + // exception, and connect both of them to the unexpectedpc block + BasicBlock *NormalInvoke = BasicBlock::Create(Context, + "normal_invoke", + Root, + nullptr); + BranchInst::Create(UnexpectedPC, NormalInvoke); + + BasicBlock *AbnormalInvoke = BasicBlock::Create(Context, + "abnormal_invoke", + Root, + nullptr); + + // Create a builder object for the AbnormalInvokeReturn basic block + IRBuilder<> BuilderAbnormalBB(Context); + BuilderAbnormalBB.SetInsertPoint(AbnormalInvoke); + + ConstantInt *ConstantFalse = BuilderAbnormalBB.getFalse(); + BuilderAbnormalBB.CreateStore(ConstantFalse, ExceptionFlag); + BuilderAbnormalBB.CreateBr(UnexpectedPC); + + // Create a builder object for the InvokeReturnBlock basic block + IRBuilder<> BuilderReturnBB(Context); + BuilderReturnBB.SetInsertPoint(InvokeReturnBlock); + + // Add a conditional branch at the end of the invoke exit block that jumps to + // the right basic block on the basis of the flag. + LoadInst *Flag = BuilderReturnBB.CreateLoad(ExceptionFlag, ""); + BuilderReturnBB.CreateCondBr(Flag, AbnormalInvoke, NormalInvoke); + + return InvokeReturnBlock; +} + +BasicBlock *IFI::createCatchBlock(Function *Root, + BasicBlock *UnexpectedPC) { + + // Create a basic block that represents the catch part of the exception + BasicBlock *CatchBB = BasicBlock::Create(Context, + "catchblock", + Root, + nullptr); + + // Create a builder object + IRBuilder<> Builder(Context); + Builder.SetInsertPoint(CatchBB); + + // Create the StructType necessary for the landingpad + PointerType *RetTyPointerType = Type::getInt8PtrTy(Context); + IntegerType *RetTyIntegerType = Type::getInt32Ty(Context); + std::vector InArgsType { RetTyPointerType, RetTyIntegerType }; + StructType *RetTyStruct = StructType::create(Context, + ArrayRef(InArgsType), + "", + false); + + // Create the landingpad instruction + LandingPadInst *LandingPad = Builder.CreateLandingPad(RetTyStruct, 0); + + // Add a catch all (constructed with the null value as clause) + LandingPad->addClause(ConstantPointerNull::get(Type::getInt8PtrTy(Context))); + Builder.CreateUnreachable(); + + return CatchBB; +} + +void IFI::replaceFunctionCall(BasicBlock *NewBB, + CallInst *Call, + const ValueToValueMap &LocalVMap) { + + // Retrieve the called function and emit the call + StringRef FunctionNameString; + + if (BlockAddress *Callee = dyn_cast(Call->getOperand(0))){ + BasicBlock *CalleeEntry = Callee->getBasicBlock(); + TerminatorInst *Terminator = CalleeEntry->getTerminator(); + MDNode *Node = Terminator->getMetadata("func.entry"); + FunctionNameString = getFunctionNameString(Node); + } else { + FunctionNameString = FunctionDispatcher->getName(); + } + + Function *TargetFunction = NewModule->getFunction(FunctionNameString); + assert(TargetFunction != nullptr); + + // Create a builder object + IRBuilder<> Builder(Context); + Builder.SetInsertPoint(NewBB); + + Builder.CreateCall(TargetFunction); + + // Retrieve the fallthrough basic block and emit the branch + BlockAddress *FallThroughAddress = cast(Call->getOperand(1)); + BasicBlock *FallthroughOld = FallThroughAddress->getBasicBlock(); + + auto FallthroughOldIt = LocalVMap.find(FallthroughOld); + if (FallthroughOldIt != LocalVMap.end()) { + BasicBlock *FallthroughNew = cast(FallthroughOldIt->second); + + // Additional check for the return address PC + LoadInst *ProgramCounter = Builder.CreateLoad(PC, ""); + ConstantInt *ExpectedPC = cast(Call->getOperand(2)); + Value *Result = Builder.CreateICmpEQ(ProgramCounter, ExpectedPC); + + // Create a basic block that we hit if the current PC is not the one + // expected after the function call + Twine PCMismatchName = NewBB->getName() + "_bad_return_pc"; + BasicBlock *PCMismatch = BasicBlock::Create(Context, + PCMismatchName.str(), + NewBB->getParent(), + nullptr); + throwException(BadReturnAddress, PCMismatch, ExpectedPC->getZExtValue()); + new UnreachableInst(Context, PCMismatch); + + // Conditional branch to jump to the right block + Builder.CreateCondBr(Result, FallthroughNew, PCMismatch); + } else { + + // If the fallthrough basic block is not in the current function raise an + // exception + throwException(StandardTranslatedBlock, NewBB, 0); + Builder.CreateUnreachable(); + } +} + +bool IFI::isTerminatorWithInvalidTarget(Instruction *I, + const ValueToValueMap &LocalVMap) { + if (auto *Terminator = dyn_cast(I)) { + + // Here we check if among the successors of a terminator instruction + // there is one that doesn't belong anymore to the current function. + for (BasicBlock *Target : Terminator->successors()) { + if (LocalVMap.count(Target) == 0) { + return true; + } + } + } + + return false; +} + +bool IFI::cloneInstruction(BasicBlock *NewBB, + Instruction *OldInstruction, + ValueToValueMap &LocalVMap) { + + // Create a builder object + IRBuilder<> Builder(Context); + Builder.SetInsertPoint(NewBB); + + // Check if the function boundaries analysis has identified an instruction as + // a ret and in that case emit a ret instruction + if (OldInstruction->getMetadata("func.return") != nullptr) { + Builder.CreateRetVoid(); + + } else if (isTerminatorWithInvalidTarget(OldInstruction, LocalVMap)) { + + // If we are in presence of a terminator with a successor no more in the + // current function we throw an exception + throwException(StandardTranslatedBlock, NewBB, 0); + Builder.CreateUnreachable(); + + } else if (isCallTo(OldInstruction, "function_call")) { + + // Function call handling + CallInst *Call = cast(OldInstruction); + replaceFunctionCall(NewBB, + Call, + LocalVMap); + + // We return true if we emitted a function call to signal that we ended + // the inspection of the current basic block and that we should exit from + // the loop over the instructions + return true; + + } else { + + // Actual copy of the instructions if we aren't in any of the corner + // cases handled by the if before + Instruction *NewInstruction = OldInstruction->clone(); + + // Queue initialization with the base operand, the instruction + // herself + std::queue UserQueue; + UserQueue.push(NewInstruction); + + // "Recursive" visit of the queue + while (!UserQueue.empty()) { + User *CurrentUser = UserQueue.front(); + UserQueue.pop(); + + for (Use &CurrentUse : CurrentUser->operands()) { + auto *CurrentOperand = CurrentUse.get(); + + // Manage a standard value for which we find replacement in the + // ValueToValueMap + auto ReplacementIt = LocalVMap.find(CurrentOperand); + if (ReplacementIt != LocalVMap.end()) { + CurrentUse.set(ReplacementIt->second); + + } else if (auto *Address = dyn_cast(CurrentOperand)) { + // Manage a BlockAddress + Function *OldFunction = Address->getFunction(); + BasicBlock *OldBlock = Address->getBasicBlock(); + Function *NewFunction = cast(LocalVMap[OldFunction]); + BasicBlock *NewBlock = cast(LocalVMap[OldBlock]); + BlockAddress *B = BlockAddress::get(NewFunction, NewBlock); + + CurrentUse.set(B); + + } else if (isa(CurrentOperand)) { + // Assert if we encounter a basic block and we don't find a + // reference in the ValueToValueMap + assert(LocalVMap.count(CurrentOperand) != 0); + } else if (!isa(CurrentOperand)) { + // Manage values that are themself users (recursive exploration + // of the operands) taking care of avoiding to add operands of + // constants + auto *CurrentSubUser = cast(CurrentOperand); + if (CurrentSubUser->getNumOperands() >= 1) { + UserQueue.push(CurrentSubUser); + } + } + } + } + + if (OldInstruction->hasName()) { + NewInstruction->setName(OldInstruction->getName()); + } + + Builder.Insert(NewInstruction); + LocalVMap[OldInstruction] = NewInstruction; + } + + return false; +} + +StringRef IFI::getFunctionNameString(MDNode *Node) { + auto *Tuple = cast(Node); + QuickMetadata QMD(Context); + StringRef FunctionNameString = QMD.extract(Tuple, 0); + return FunctionNameString; +} + +void IFI::run() { + + // This function includes all the passages that realize the function + // isolation. In particular the main steps of the function are: + // + // 1. Initialization + // 2. Exception handling mechanism iniatilization + // 3. Alloca harvesting + // 4. Function call harvesting + // 5. Function creation + // 6. Function population + // 7. Function inspection + // 8. Function skeleton construction + // 9. Reverse post order instantiation + // 10. Removal of dummy switches + // 11. Dummy Entry block + // 12. Alloca placement + // 13. Basic blocks population + // 14. Exception handling control flow instantiation + // 15. Module verification + + // 1. Initialize all the needed data structures + + // Assert if we don't find @function_call, sign that the function boundaries + // analysis hasn't been run on the translated binary + assert(RootFunction.getParent()->getFunction("function_call") != nullptr); + Function *CallMarker = RootFunction.getParent()->getFunction("function_call"); + + // Fill the GlobalVMap to contain the mappings made by the CloneModule + // function, in order to have the mappings between global objects (global + // variables and functions). We'll initialize the LocalVMaps of the single + // functions with these mappings. + ValueToValueMap GlobalVMap; + for (auto Iter : ModuleCloningVMap) { + if (isa(Iter.first)) { + GlobalVMap[Iter.first] = Iter.second; + } + } + + // 2. Create the needed structure to handle the throw of an exception + + // Retrieve the global variable corresponding to the program counter + PC = NewModule->getGlobalVariable("pc", true); + + // Create a new global variable used as a flag for signaling the raise of an + // exception + auto *BoolTy = IntegerType::get(Context, 1); + auto *ConstantFalse = ConstantInt::get(BoolTy, 0); + new GlobalVariable(*NewModule, + Type::getInt1Ty(Context), + false, + GlobalValue::ExternalLinkage, + ConstantFalse, + "ExceptionFlag"); + ExceptionFlag = NewModule->getGlobalVariable("ExceptionFlag"); + + // Declare the _Unwind_RaiseException function that we will use as a throw + auto *RaiseExceptionFT = FunctionType::get(Type::getVoidTy(Context), false); + + RaiseException = Function::Create(RaiseExceptionFT, + Function::ExternalLinkage, + "raise_exception_helper", + NewModule); + + // Create the Arrayref necessary for the arguments of exception_warning + auto *IntegerType = IntegerType::get(Context, PCBitSize); + std::vector ArgsType { + Type::getInt32Ty(Context), + IntegerType, + IntegerType, + IntegerType + }; + + // Declare the exception_warning function + auto *DebugExceptionFT = FunctionType::get(Type::getVoidTy(Context), + ArgsType, + false); + + DebugException = Function::Create(DebugExceptionFT, + Function::ExternalLinkage, + "exception_warning", + NewModule); + + // Instantiate the dispatcher function, that is called in occurence of an + // indirect function call. + auto *FT = FunctionType::get(Type::getVoidTy(Context), false); + + // Creation of the function + FunctionDispatcher = Function::Create(FT, + Function::ExternalLinkage, + "function_dispatcher", + NewModule); + + // 3. Search for all the alloca instructions and place them in an helper data + // structure in order to copy them at the beginning of the function where + // they are used. The alloca initially are all placed in the entry block of + // the root function. + std::map> UsedAllocas; + for (Instruction &I : RootFunction.getEntryBlock()) { + + // If we encounter an alloca copy it in the data structure that contains + // all the allocas that we need to copy in the new basic block + if (AllocaInst *Alloca = dyn_cast(&I)) { + std::set FilteredUsers; + for (User *U : Alloca->users()) { + + // Handle standard instructions + Instruction *UserInstruction = cast(U); + FilteredUsers.insert(UserInstruction->getParent()); + + // Handle the case in which we have ConstantExpr casting an alloca to + // something else + if (Value *SkippedCast = skipCasts(UserInstruction)) { + FilteredUsers.insert(cast(SkippedCast)->getParent()); + } + + } + for (BasicBlock *Parent : FilteredUsers) { + UsedAllocas[Parent].push_back(Alloca); + } + } + } + + // 4. Search for all the users of the helper function @function_call and + // populate the AdditionalSucc structure in order to be able to identify + // all the successors of a basic block + std::map AdditionalSucc; + for (User *U : CallMarker->users()) { + if (CallInst *Call = dyn_cast(U)) { + BlockAddress *Fallthrough = cast(Call->getOperand(1)); + + // Add entry in the data structure used to populate the dummy switches + AdditionalSucc[Call->getParent()] = Fallthrough->getBasicBlock(); + } + } + + // 5. Creation of the new LLVM functions on the basis of what recovered by + // the function boundaries analysis and storage of the pointers in a + // dedicated data strucure. We also initialize each VMap contained in the + // MetaVMap structure with the mappings contained in GlobalVMap. + std::map Functions; + std::map MetaVMap; + + for (BasicBlock &BB : RootFunction) { + assert(!BB.empty()); + + TerminatorInst *Terminator = BB.getTerminator(); + if (MDNode *Node = Terminator->getMetadata("func.entry")) { + auto *FunctionNameMD = cast(&*Node->getOperand(0)); + + StringRef FunctionNameString = getFunctionNameString(Node); + + // We obtain a FunctionType of a function that has no arguments + auto *FT = FunctionType::get(Type::getVoidTy(Context), false); + + // Check if we already have an entry for a function with a certain name + if (Functions.count(FunctionNameMD) == 0) { + + // Actual creation of an empty instance of a function + Function *Function = Function::Create(FT, + Function::ExternalLinkage, + FunctionNameString, + NewModule); + + Functions[FunctionNameMD] = Function; + FunctionsPC[Function] = getBasicBlockPC(&BB); + + // Update v2v map with an ad-hoc mapping between the root function and + // the current function, useful for subsequent analysis + ValueToValueMap &LocalVMap = MetaVMap[Function]; + + // Copy all the mappings between global variables already created when + // we cloned the module + LocalVMap = GlobalVMap; + + // Add the mapping between root function and all the functions we + // will create + LocalVMap[&RootFunction] = Function; + } + } + } + + // 6. Population of the LLVM functions with the basic blocks that belong to + // them, always on the basis of the function boundaries analysis + for (BasicBlock &BB : RootFunction) { + assert(!BB.empty()); + + // We iterate over all the metadata that represent the functions a basic + // block belongs to, and add the basic block in each function + TerminatorInst *Terminator = BB.getTerminator(); + if (MDNode *Node = Terminator->getMetadata("func.member.of")) { + auto *Tuple = cast(Node); + for (const MDOperand &Op : Tuple->operands()) { + auto *FunctionMD = cast(Op); + auto *FunctionNameMD = cast(&*FunctionMD->getOperand(0)); + Function *ParentFunction = Functions[FunctionNameMD]; + + // We assert if we can't find the parent function of the basic block + assert(ParentFunction != nullptr); + + // Creation of a new empty BB in the new generated corresponding + // function, preserving the original name. We need to take care that if + // we are examining a basic block that is the entry point of a function + // we need to place it in the as the first block of the function. + BasicBlock* NewBB; + if (Terminator->getMetadata("func.entry") && !ParentFunction->empty()) { + NewBB = BasicBlock::Create(Context, + BB.getName(), + ParentFunction, + &ParentFunction->getEntryBlock()); + } else { + NewBB = BasicBlock::Create(Context, + BB.getName(), + ParentFunction, + nullptr); + } + + // Update v2v map with the mapping between basic blocks + ValueToValueMap &LocalVMap = MetaVMap[ParentFunction]; + LocalVMap[&BB] = NewBB; + + // Update the map that we will use later for filling the basic blocks + // with instructions + NewToOldBBMap[NewBB] = &BB; + } + } + } + + // 7. Analyze all the created functions and populate them + for (auto &Pair : Functions) { + + // We are iterating over a map, so we need to extract the element from the + // pair + Function *AnalyzedFunction = Pair.second; + + // Initialize a local ValueToValueMap with the mapping between basic + // blocks (done in the previous loop) and the global objects contained + // in the correspondig VMap in the MetaVMap structure + ValueToValueMap LocalVMap = std::move(MetaVMap[AnalyzedFunction]); + + // 8. We populate the basic blocks that are empty with a dummy switch + // instruction that has the role of preserving the actual shape of the + // function control flow. This will be helpful in order to traverse the + // BBs in reverse post-order. + BasicBlock* UnexpectedPC = nullptr; + BasicBlock* AnyPC = nullptr; + + for (BasicBlock &NewBB : *AnalyzedFunction) { + + BasicBlock *BB = NewToOldBBMap[&NewBB]; + TerminatorInst *Terminator = BB->getTerminator(); + + // Collect all the successors of a basic block and add them in a proper + // data structure + std::vector Successors; + for (BasicBlock *Successor : Terminator->successors()) { + + // Check if among the successors of the current basic block there is + // the unexpectedpc basic block, and if needed create it + if (GCBI.getType(Successor) == UnexpectedPCBlock) { + // Check if it already exists and create an unexpectedpc block + if (UnexpectedPC == nullptr) { + UnexpectedPC = createUnreachableBlock("unexpectedpc", + AnalyzedFunction); + LocalVMap[Successor] = UnexpectedPC; + NewToOldBBMap[UnexpectedPC] = Successor; + } + } + + // Check if among the successors of the current basic block there is + // the anypc basic block, and if needed create it + if (GCBI.getType(Successor) == AnyPCBlock) { + // Check if it already exists and create an anypc block + if (AnyPC == nullptr) { + AnyPC = createUnreachableBlock("anypc", + AnalyzedFunction); + LocalVMap[Successor] = AnyPC; + NewToOldBBMap[AnyPC] = Successor; + } + } + + assert(GCBI.isTranslated(Successor) + || GCBI.getType(Successor) == AnyPCBlock + || GCBI.getType(Successor) == UnexpectedPCBlock + || GCBI.getType(Successor) == DispatcherBlock); + auto SuccessorIt = LocalVMap.find(Successor); + + // We add a successor if it is not a revamb block type and it is present + // in the VMap. It may be that we don't find a reference for Successor + // in the LocalVMap in case the block it is no more in the current + // function. This happens for example in case we have a function call, + // the target block of the final branch will be the entry block of the + // callee, that for sure will not be in the current function and + // consequently in the LocalVMap. + if (GCBI.isTranslated(Successor) && SuccessorIt != LocalVMap.end()) { + Successors.push_back(cast(SuccessorIt->second)); + } + } + + // Add also the basic block that is executed after a function + // call, identified before (the fall through block) + if (BasicBlock *Successor = AdditionalSucc[BB]) { + auto SuccessorIt = LocalVMap.find(Successor); + + // In some occasions we have that the fallthrough block a function_call + // is a block that doesn't belong to the current function + // TODO: when the new function boundary detection algorithm will be in + // place check if this situation still occours or if we can assert + if (SuccessorIt != LocalVMap.end()) { + Successors.push_back(cast(SuccessorIt->second)); + } + } + + // Create a builder object + IRBuilder<> Builder(Context); + Builder.SetInsertPoint(&NewBB); + + // Handle the degenerate case in which we didn't identified successors + if(Successors.size() == 0) { + Builder.CreateUnreachable(); + } else { + + // Create the default case of the switch statement in an ad-hoc manner + ConstantInt *ZeroValue = Builder.getInt8(0); + SwitchInst *DummySwitch = Builder.CreateSwitch(ZeroValue, + Successors.front()); + + // Handle all the eventual successors except for the one already used + // in the default case + for (unsigned I = 1; I < Successors.size(); I++) { + ConstantInt *Label = Builder.getInt8(I); + DummySwitch->addCase(Label, Successors[I]); + } + } + } + + // 9. We instantiate the reverse post order on the skeleton we produced + // with the dummy switches + ReversePostOrderTraversal RPOT(AnalyzedFunction); + + // 10. We eliminate all the dummy switch instructions that we used before, + // and that should not appear in the output. The dummy switch are + // the first instruction of each basic block. + for (BasicBlock &BB : *AnalyzedFunction) { + + // We exclude the unexpectedpc and anypc blocks since they have not been + // populated with a dummy switch beforehand + if (&BB != UnexpectedPC && &BB != AnyPC) { + Instruction &I = *BB.begin(); + assert(isa(I) || isa(I)); + I.eraseFromParent(); + } + } + + // 11. We add a dummy entry basic block that is usefull for storing the + // alloca instructions used in each function and to avoid that the entry + // block has predecessors. The dummy entry basic blocks simply branches + // to the real entry block to have valid IR. + BasicBlock *EntryBlock = &AnalyzedFunction->getEntryBlock(); + + // If the entry block of the function has predecessors add a dummy block + BasicBlock *Dummy = BasicBlock::Create(Context, + "dummy_entry", + AnalyzedFunction, + &AnalyzedFunction->getEntryBlock()); + + // 12. We copy the allocas at the beginning of the function where they will + // be used + for (BasicBlock &BB : *AnalyzedFunction) { + + auto &InstructionList = AnalyzedFunction->getEntryBlock().getInstList(); + for (auto &OldAlloca : UsedAllocas[NewToOldBBMap[&BB]]) { + Instruction *NewAlloca = OldAlloca->clone(); + if (OldAlloca->hasName()) { + NewAlloca->setName(OldAlloca->getName()); + } + + // Please be aware that we are inserting the alloca after the dummy + // switches, so until their removal done in phase 13 we will have + // instruction after a terminator. This is done as we want to have the + // dummy switches as first instructions in the basic blocks in order to + // remove them by simply erasing the first instruction from each basic + // block, instead of keeping track of them with an additional data + // structure. + InstructionList.push_back(NewAlloca); + assert(NewAlloca->getParent() == &AnalyzedFunction->getEntryBlock()); + LocalVMap[&*OldAlloca] = NewAlloca; + } + } + + // Create the unconditional branch to the real entry block + BranchInst::Create(EntryBlock, Dummy); + + // 13. Visit of the basic blocks of the function in reverse post-order and + // population of them with the instructions + for (BasicBlock *NewBB : RPOT) { + BasicBlock *OldBB = NewToOldBBMap[NewBB]; + + // Actual copy of the instructions + for (Instruction &OldInstruction : *OldBB) { + bool IsCall = cloneInstruction(NewBB, + &OldInstruction, + LocalVMap); + + // If the cloneInstruction function returns true it means that we + // emitted a function call and also the branch to the fallthrough block, + // so we must end the inspection of the current basic block + if (IsCall == true) { + break; + } + } + } + } + + // 14. Create the functions and basic blocks needed for the correct execution + // of the exception handling mechanism + + // Populate the function_dispatcher + populateFunctionDispatcher(); + + // Retrieve the root function, we use it a lot. + Function *Root = NewModule->getFunction("root"); + + // Get the unexpectedpc block of the root function + // TODO: do this in a more elegant way (see if we have some helper) + BasicBlock *UnexpectedPC = nullptr; + for (BasicBlock &BB : *Root) { + if (GCBI.getType(&BB) == UnexpectedPCBlock) { + UnexpectedPC = &BB; + break; + } + } + + // Instantiate the basic block structure that handles the control flow after + // an invoke + BasicBlock *InvokeReturnBlock = createInvokeReturnBlock(Root, UnexpectedPC); + + // Instantiate the basic block structure that represents the catch of the + // invoke, please remember that this is not used at the moment (exceptions + // are handled in a customary way from the standard exit control flow path) + BasicBlock *CatchBB = createCatchBlock(Root, UnexpectedPC); + + // Declaration of an ad-hoc personality function that is implemented in the + // support.c source file + auto *PersonalityFT = FunctionType::get(Type::getInt32Ty(Context), true); + + Function *PersonalityFunction = Function::Create(PersonalityFT, + Function::ExternalLinkage, + "exception_personality", + NewModule); + + // Add the personality to the root function + Root->setPersonalityFn(PersonalityFunction); + + // Emit at the beginning of the basic blocks identified as function entries + // by revamb a call to the newly created corresponding LLVM function + for (BasicBlock &BB : *Root) { + assert(!BB.empty()); + + TerminatorInst *Terminator = BB.getTerminator(); + if (MDNode *Node = Terminator->getMetadata("func.entry")) { + StringRef FunctionNameString = getFunctionNameString(Node); + Function *TargetFunc = NewModule->getFunction(FunctionNameString); + + // Remove the old instruction that compose the entry block (note that we + // do not increment the iterator since the removal of the instruction + // seems to automatically do that) + auto It = BB.rbegin(); + while (It != BB.rend()) { + It->eraseFromParent(); + } + + // Emit the invoke instruction + InvokeInst::Create(TargetFunc, + InvokeReturnBlock, + CatchBB, + ArrayRef(), + "", + &BB); + } + } + + // 15. Before emitting it in output we check that the module in passes the + // verifyModule pass + raw_os_ostream Stream(dbg); + assert(verifyModule(*NewModule, &Stream) == false); + +} + +bool IF::runOnFunction(Function &F) { + + // Retrieve analysis of the GeneratedCodeBasicInfo pass + auto &GCBI = getAnalysis(); + + // Clone the starting module and take note of all the mappings between + // global objects. The new module will contain the newly generated + // functions. We additionaly store all the mappings created in the + // ModuleCloningVMap. + ValueToValueMapTy ModuleCloningVMap; + NewModule = CloneModule(F.getParent(), ModuleCloningVMap); + + // Create an object of type IsolateFunctionsImpl and run the pass + IFI Impl(F, NewModule.get(), GCBI, ModuleCloningVMap); + Impl.run(); + + return false; +} + +Module *IF::getModule() { + // Propagate the llvm module to the meta-pass + return NewModule.get(); +} diff --git a/isolatefunctions.h b/isolatefunctions.h new file mode 100644 index 000000000..305532524 --- /dev/null +++ b/isolatefunctions.h @@ -0,0 +1,37 @@ +#ifndef _ISOLATEFUNCTIONS_H +#define _ISOLATEFUNCTIONS_H + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +// Standard includes +#include + +// LLVM includes +#include "llvm/Pass.h" + +// Local includes +#include "generatedcodebasicinfo.h" + +class IsolateFunctions : public llvm::FunctionPass { +public: + static char ID; + +public: + IsolateFunctions() : FunctionPass(ID) { } + + bool runOnFunction(llvm::Function &F) override; + + void getAnalysisUsage(llvm::AnalysisUsage &AU) const override { + AU.setPreservesAll(); + AU.addRequired(); + } + + llvm::Module *getModule(); + +private: + std::unique_ptr NewModule; +}; + +#endif // _ISOLATEFUNCTIONS_H diff --git a/support.c b/support.c index a29d07b68..604ff5dfd 100644 --- a/support.c +++ b/support.c @@ -2,9 +2,11 @@ * This file is distributed under the MIT License. See LICENSE.md for details. */ +// Standard includes #include #include #include +#include #include #include #include @@ -16,6 +18,10 @@ #include #include #include +#include + +// Local includes +#include "support.h" // Save the program arguments for meaningful error reporting static int saved_argc; @@ -29,21 +35,25 @@ static char **saved_argv; typedef uint32_t target_reg; #define SWAP(x) (htole32(x)) +#define TARGET_REG_FORMAT PRIx32 #elif defined(TARGET_i386) typedef uint32_t target_reg; #define SWAP(x) (htole32(x)) +#define TARGET_REG_FORMAT PRIx32 #elif defined(TARGET_x86_64) typedef uint64_t target_reg; #define SWAP(x) (htole64(x)) +#define TARGET_REG_FORMAT PRIx64 #elif defined(TARGET_mips) typedef uint32_t target_reg; #define SWAP(x) (htobe32(x)) +#define TARGET_REG_FORMAT PRIx32 #else @@ -373,3 +383,69 @@ int main(int argc, char *argv[]) { SAFE_CAST(stack); root((target_reg) stack); } + +// Helper function used to raise an exception +void raise_exception_helper() { + + // Declare the exception object + struct _Unwind_Exception exc; + + // Raise the exception using the function provided by the unwind library + _Unwind_RaiseException(&exc); +} + +// Personality function +int exception_personality(int version, + _Unwind_Action actions, + uint64_t exceptionClass, + struct _Unwind_Exception *unwind_exception, + struct _Unwind_Context *context) { + + // Check the action parameter and match the correct expected return code, the + // other paramters are not used in our implementation + if (actions == _UA_SEARCH_PHASE) { + return _URC_HANDLER_FOUND; + } else if (actions == (_UA_CLEANUP_PHASE | _UA_HANDLER_FRAME)){ + return _URC_INSTALL_CONTEXT; + } + return _URC_NO_REASON; +} + +// Helper function to debug informations when an exception is about to be +// raised +void exception_warning(Reason Code, + target_reg Source, + target_reg Target, + target_reg ExpectedDestination) { + switch(Code) { + case StandardTranslatedBlock: + fprintf(stderr, + "Unexpected control-flow in isolated function: 0x%" + TARGET_REG_FORMAT " -> 0x%" TARGET_REG_FORMAT "\n", + Source, + Target); + break; + case StandardNonTranslatedBlock: + fprintf(stderr, + "Unexpected control-flow in isolated function after unexpectedpc " + "or anypc block: 0x%" TARGET_REG_FORMAT "\n", + Target); + break; + case BadReturnAddress: + fprintf(stderr, + "Expected and actual fallthrough after ret not corresponding: 0x%" + TARGET_REG_FORMAT " / 0x%" TARGET_REG_FORMAT "\n", + Target, + ExpectedDestination); + break; + case FunctionDispatcherFallBack: + fprintf(stderr, + "Erroneous call to function dispatcher: " + "0x%" TARGET_REG_FORMAT "\n", + Target); + break; + default: + assert(0 && "Reason code not supported"); + } + +} diff --git a/support.h b/support.h new file mode 100644 index 000000000..16d65c64c --- /dev/null +++ b/support.h @@ -0,0 +1,22 @@ +#ifndef _SUPPORT_H +#define _SUPPORT_H + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +/// \brief Type of generated message for debugging exceptions at runtime when +/// function isolation is applied +typedef enum { + StandardTranslatedBlock, ///< Unexpected control flow at the end + /// of a translated basic block + StandardNonTranslatedBlock, ///< Unexpected control flow at the + /// end of a non-translated basic + /// block (anypc or unexpectedpc) + BadReturnAddress, ///< Expected and actual return address after function call + /// not matching + FunctionDispatcherFallBack ///< Call to the function dispatcher with a PC not + /// corresponding to any function entry block +} Reason; + +#endif // _SUPPORT_H diff --git a/tests/Runtime/RuntimeTests.cmake b/tests/Runtime/RuntimeTests.cmake index 135e12a6b..707b66e1d 100644 --- a/tests/Runtime/RuntimeTests.cmake +++ b/tests/Runtime/RuntimeTests.cmake @@ -89,12 +89,25 @@ foreach(ARCH ${SUPPORTED_ARCHITECTURES}) set_tests_properties(translate-${TEST_NAME}-${ARCH} PROPERTIES LABELS "runtime;translate;${TEST_NAME};${ARCH}") + # Test of function isolation with revamb-dump + add_test(NAME function-isolation-${TEST_NAME}-${ARCH} + COMMAND sh -c "$ --functions-isolation ${BINARY}.isolated-functions.ll ${BINARY}.ll") + set_tests_properties(function-isolation-${TEST_NAME}-${ARCH} + PROPERTIES DEPENDS translate-${TEST_NAME}-${ARCH} + LABELS "runtime;function-isolation;${TEST_NAME}-${ARCH}") + # Compose the command line to link support.c and the translated binaries string(REPLACE "-" "_" NORMALIZED_ARCH "${ARCH}") compile_executable("$(${CMAKE_BINARY_DIR}/li-csv-to-ld-options ${BINARY}.ll.li.csv) ${BINARY}${CMAKE_C_OUTPUT_EXTENSION} ${CMAKE_BINARY_DIR}/support.c -DTARGET_${NORMALIZED_ARCH} -lz -lm -lrt -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast -g -fno-pie ${NO_PIE}" "${BINARY}.translated" COMPILE_TRANSLATED) + # Compose the command line to link support.c and the translated binaries to which we have applied function isolation + string(REPLACE "-" "_" NORMALIZED_ARCH "${ARCH}") + compile_executable("$(${CMAKE_BINARY_DIR}/li-csv-to-ld-options ${BINARY}.ll.li.csv) ${BINARY}.isolated-functions${CMAKE_C_OUTPUT_EXTENSION} ${CMAKE_BINARY_DIR}/support.c -DTARGET_${NORMALIZED_ARCH} -lz -lm -lrt -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast -g -fno-pie ${NO_PIE}" + "${BINARY}.isolated-functions.translated" + COMPILE_TRANSLATED_ISOLATED) + # Compile the translated LLVM IR with llc and link using the previously composed command line add_test(NAME compile-translated-${TEST_NAME}-${ARCH} COMMAND sh -c "${LLC} -O0 -filetype=obj ${BINARY}.ll -o ${BINARY}${CMAKE_C_OUTPUT_EXTENSION} && ${COMPILE_TRANSLATED}") @@ -102,6 +115,13 @@ foreach(ARCH ${SUPPORTED_ARCHITECTURES}) PROPERTIES DEPENDS translate-${TEST_NAME}-${ARCH} LABELS "runtime;compile-translated;${TEST_NAME};${ARCH}") + # Compile the translated LLVM IR after function isolation pass with llc and link using the previously composed command line + add_test(NAME compile-translated-isolated-${TEST_NAME}-${ARCH} + COMMAND sh -c "${LLC} -O0 -filetype=obj ${BINARY}.isolated-functions.ll -o ${BINARY}.isolated-functions${CMAKE_C_OUTPUT_EXTENSION} && ${COMPILE_TRANSLATED_ISOLATED}") + set_tests_properties(compile-translated-isolated-${TEST_NAME}-${ARCH} + PROPERTIES DEPENDS function-isolation-${TEST_NAME}-${ARCH} + LABELS "runtime;compile-translated;function-isolation;${TEST_NAME};${ARCH}") + # For each set of arguments foreach(RUN_NAME ${TEST_RUNS_${TEST_NAME}}) # Test to run the translated program @@ -111,6 +131,13 @@ foreach(ARCH ${SUPPORTED_ARCHITECTURES}) PROPERTIES DEPENDS compile-translated-${TEST_NAME}-${ARCH} LABELS "runtime;run-translated-test;${TEST_NAME};${RUN_NAME};${ARCH}") + # Test to run the translated program after function isolation pass + add_test(NAME run-translated-isolated-test-${TEST_NAME}-${RUN_NAME}-${ARCH} + COMMAND sh -c "${BINARY}.isolated-functions.translated ${TEST_ARGS_${TEST_NAME}_${RUN_NAME}} > ${BINARY}-run-translated-isolated-test-${RUN_NAME}-${ARCH}.log") + set_tests_properties(run-translated-isolated-test-${TEST_NAME}-${RUN_NAME}-${ARCH} + PROPERTIES DEPENDS compile-translated-isolated-${TEST_NAME}-${ARCH} + LABELS "runtime;run-translated-test;function-isolation;${TEST_NAME};${RUN_NAME};${ARCH}") + # Check the output of the translated binary corresponds to the native's one add_test(NAME check-with-native-${TEST_NAME}-${RUN_NAME}-${ARCH} COMMAND "${DIFF}" "${BINARY}-run-translated-test-${RUN_NAME}-${ARCH}.log" "${CMAKE_CURRENT_BINARY_DIR}/tests/run-test-native-${TEST_NAME}-${RUN_NAME}.log") @@ -121,6 +148,16 @@ foreach(ARCH ${SUPPORTED_ARCHITECTURES}) PROPERTIES DEPENDS "${DEPS}" LABELS "runtime;check-with-native;${TEST_NAME};${RUN_NAME};${ARCH}") + # Check the output of the translated and isolated binary corresponds to the native's one + add_test(NAME check-isolated-with-native-${TEST_NAME}-${RUN_NAME}-${ARCH} + COMMAND "${DIFF}" "${BINARY}-run-translated-isolated-test-${RUN_NAME}-${ARCH}.log" "${CMAKE_CURRENT_BINARY_DIR}/tests/run-test-native-${TEST_NAME}-${RUN_NAME}.log") + set(DEPS "") + list(APPEND DEPS "run-translated-isolated-test-${TEST_NAME}-${RUN_NAME}-${ARCH}") + list(APPEND DEPS "run-test-native-${TEST_NAME}-${RUN_NAME}") + set_tests_properties(check-isolated-with-native-${TEST_NAME}-${RUN_NAME}-${ARCH} + PROPERTIES DEPENDS "${DEPS}" + LABELS "runtime;check-with-native;function-isolation;${TEST_NAME};${RUN_NAME};${ARCH}") + # Test to run the compiled program under qemu-user add_test(NAME run-qemu-test-${TEST_NAME}-${RUN_NAME}-${ARCH} COMMAND sh -c "${QEMU_${ARCH}} ${BINARY} ${TEST_ARGS_${TEST_NAME}_${RUN_NAME}} > ${BINARY}-run-qemu-test-${RUN_NAME}.log") @@ -137,7 +174,20 @@ foreach(ARCH ${SUPPORTED_ARCHITECTURES}) set_tests_properties(check-with-qemu-${TEST_NAME}-${RUN_NAME}-${ARCH} PROPERTIES DEPENDS "${DEPS}" LABELS "runtime;check-with-qemu;${TEST_NAME};${RUN_NAME};${ARCH}") + + # Check the output of the translated and isolated binary corresponds to the qemu-user's + # one + add_test(NAME check-isolated-with-qemu-${TEST_NAME}-${RUN_NAME}-${ARCH} + COMMAND "${DIFF}" "${BINARY}-run-translated-isolated-test-${RUN_NAME}-${ARCH}.log" "${BINARY}-run-qemu-test-${RUN_NAME}.log") + set(DEPS "") + list(APPEND DEPS "run-translated-isolated-test-${TEST_NAME}-${RUN_NAME}-${ARCH}") + list(APPEND DEPS "run-qemu-test-${TEST_NAME}-${RUN_NAME}-${ARCH}") + set_tests_properties(check-isolated-with-qemu-${TEST_NAME}-${RUN_NAME}-${ARCH} + PROPERTIES DEPENDS "${DEPS}" + LABELS "runtime;check-with-qemu;function-isolation;${TEST_NAME};${RUN_NAME};${ARCH}") + endforeach() + endforeach() endforeach() diff --git a/translate b/translate index 15bd6ec06..86a19f806 100755 --- a/translate +++ b/translate @@ -9,7 +9,9 @@ SCRIPT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" INPUT="" OPTIMIZE=0 SKIP=0 +ISOLATE=0 SUPPORT_CONFIG=normal +EXTRA_OPTIONS="" set -e set -o pipefail @@ -38,6 +40,10 @@ do SKIP="1" shift # past argument ;; + -i) + ISOLATE="1" + shift # past argument + ;; --) shift break @@ -54,13 +60,8 @@ do esac done -# Output file names -LL="$INPUT.ll" -LINKED_LL="$INPUT.linked.ll" -REVAMB_LOG="$LL.log" -LL_OPT="$INPUT.opt.ll" -CSV="$LL.li.csv" -OBJ="$LL.o" +# Output file name +LL="$INPUT" # Required programs export PATH="$SCRIPT_PATH:$PATH" @@ -69,6 +70,7 @@ LINK="llvm-link" LLC="llc" OPT="opt" REVAMB="revamb" +REVAMBDUMP="revamb-dump" TOOPT="li-csv-to-ld-options" # Read endianess and architecture bytes @@ -113,20 +115,36 @@ if [ '!' -e "$SUPPORT_PATH" ]; then fi fi -if [ "$SKIP" -eq 0 ]; then - "$REVAMB" -g ll --debug jtcount,osrjts --use-sections "$INPUT" "$LL" "$@" |& tee "$REVAMB_LOG" +if [ "$ISOLATE" -eq 1 ]; then + EXTRA_OPTIONS="$EXTRA_OPTIONS --functions-boundaries" fi -"$LINK" "$LL" "$SUPPORT_PATH" -o "$LINKED_LL" -S +if [ "$SKIP" -eq 0 ]; then + REVAMB_LOG="$LL.log" + CSV="$LL.ll.li.csv" + "$REVAMB" -g ll --debug jtcount,osrjts --use-sections $EXTRA_OPTIONS "$INPUT" "$LL.ll" "$@" |& tee "$REVAMB_LOG" +fi + +if [ "$ISOLATE" -eq 1 ]; then + LL_ISOLATED="$LL.isolated" + "$REVAMBDUMP" -i "$LL_ISOLATED.ll" "$LL.ll" + LL="$LL_ISOLATED" +fi + +LL_LINKED="$LL.linked" +"$LINK" "$LL.ll" "$SUPPORT_PATH" -o "$LL_LINKED.ll" -S +LL="$LL_LINKED" OUTPUT="$INPUT.translated" if [ "$OPTIMIZE" -eq 0 ]; then - "$LLC" -O0 -filetype=obj "$LINKED_LL" -o "$OBJ" + "$LLC" -O0 -filetype=obj "$LL.ll" -o "$OBJ" elif [ "$OPTIMIZE" -eq 1 ]; then - "$LLC" -O2 -filetype=obj "$LINKED_LL" -o "$OBJ" -regalloc=fast -disable-machine-licm + "$LLC" -O2 -filetype=obj "$LL.ll" -o "$OBJ" -regalloc=fast -disable-machine-licm elif [ "$OPTIMIZE" -eq 2 ]; then - "$OPT" -O2 -S "$LINKED_LL" -o "$LL_OPT" - "$LLC" -O2 -filetype=obj "$LL_OPT" -o "$OBJ" -regalloc=fast -disable-machine-licm + LL_OPT="$LL.opt" + "$OPT" -O2 -S "$LL.ll" -o "$LL_OPT.ll" + "$LLC" -O2 -filetype=obj "$LL_OPT.ll" -o "$OBJ" -regalloc=fast -disable-machine-licm + LL="$LL_OPT" fi if "$CC" -no-pie |& grep 'unrecognized command line option'; then @@ -135,6 +153,7 @@ else DISABLE_PIE="-fno-pie -no-pie" fi +OBJ="$LL.o" "$CC" $("$TOOPT" "$CSV") \ "$OBJ" \ -lz -lm -lrt \