diff --git a/CMakeLists.txt b/CMakeLists.txt index 072a4b476..3e46c5369 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,9 +43,11 @@ include_directories(argparse/) set(CMAKE_INSTALL_RPATH "\$ORIGIN/../lib${LLVM_LIBDIR_SUFFIX}") add_executable(revamb ptcdump.cpp main.cpp debughelper.cpp variablemanager.cpp - jumptargetmanager.cpp instructiontranslator.cpp codegenerator.cpp - debug.cpp osra.cpp set.cpp simplifycomparisons.cpp reachingdefinitions.cpp - functionboundariesdetection.cpp noreturnanalysis.cpp argparse/argparse.c) + jumptargetmanager.cpp instructiontranslator.cpp codegenerator.cpp debug.cpp + osra.cpp set.cpp simplifycomparisons.cpp reachingdefinitions.cpp + functionboundariesdetection.cpp noreturnanalysis.cpp binaryfile.cpp + argparse/argparse.c) + target_link_libraries(revamb dl m ${LLVM_LIBRARIES}) install(TARGETS revamb RUNTIME DESTINATION bin) diff --git a/binaryfile.cpp b/binaryfile.cpp new file mode 100644 index 000000000..3eaa19329 --- /dev/null +++ b/binaryfile.cpp @@ -0,0 +1,192 @@ +/// \file binaryfile.cpp +/// \brief + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +// Standard includes +#include + +// LLVM includes +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Triple.h" +#include "llvm/Object/ELF.h" +#include "llvm/Object/ELFTypes.h" +#include "llvm/Object/ObjectFile.h" +#include "llvm/Support/ELF.h" + +// Local includes +#include "binaryfile.h" + +// using directives +using namespace llvm; + +using std::make_pair; + +BinaryFile::BinaryFile(std::string FilePath, bool UseSections) { + auto BinaryOrErr = object::createBinary(FilePath); + assert(BinaryOrErr && "Couldn't open the input file"); + + BinaryHandle = std::move(BinaryOrErr.get()); + + auto *TheBinary = cast(BinaryHandle.getBinary()); + + // TODO: QEMU should provide this information + unsigned InstructionAlignment = 0; + StringRef SyscallHelper = ""; + StringRef SyscallNumberRegister = ""; + ArrayRef NoReturnSyscalls = { }; + switch (TheBinary->getArch()) { + case Triple::x86_64: + InstructionAlignment = 1; + SyscallHelper = "helper_syscall"; + SyscallNumberRegister = "rax"; + NoReturnSyscalls = { + 0xe7, // exit_group + 0x3c, // exit + 0x3b // execve + }; + break; + case Triple::arm: + InstructionAlignment = 4; + SyscallHelper = "helper_exception_with_syndrome"; + SyscallNumberRegister = "r7"; + NoReturnSyscalls = { + 0xf8, // exit_group + 0x1, // exit + 0xb // execve + }; + break; + case Triple::mips: + InstructionAlignment = 4; + SyscallHelper = "helper_raise_exception"; + SyscallNumberRegister = "v0"; + NoReturnSyscalls = { + 0x1096, // exit_group + 0xfa1, // exit + 0xfab // execve + }; + break; + default: + assert(false); + } + + TheArchitecture = Architecture(TheBinary->getArch(), + InstructionAlignment, + 1, + TheBinary->isLittleEndian(), + TheBinary->getBytesInAddress() * 8, + SyscallHelper, + SyscallNumberRegister, + NoReturnSyscalls); + + assert(TheBinary->getFileFormatName().startswith("ELF") + && "Only the ELF file format is currently supported"); + + if (TheArchitecture.pointerSize() == 32) { + if (TheArchitecture.isLittleEndian()) { + parseELF(TheBinary, UseSections); + } else { + parseELF(TheBinary, UseSections); + } + } else if (TheArchitecture.pointerSize() == 64) { + if (TheArchitecture.isLittleEndian()) { + parseELF(TheBinary, UseSections); + } else { + parseELF(TheBinary, UseSections); + } + } else { + assert("Unexpect address size"); + } +} + +template +void BinaryFile::parseELF(object::ObjectFile *TheBinary, bool UseSections) { + // Parse the ELF file + std::error_code EC; + object::ELFFile TheELF(TheBinary->getData(), EC); + assert(!EC && "Error while loading the ELF file"); + + // Look for static or dynamic symbols + using Elf_ShdrPtr = decltype(&(*TheELF.sections().begin())); + Elf_ShdrPtr Symtab = nullptr; + for (auto &Section : TheELF.sections()){ + auto Name = TheELF.getSectionName(&Section); + if (Name && Name.get() == ".symtab") { + Symtab = &Section; + break; + } else if (Name && Name.get() == ".dynsym") { + Symtab = &Section; + } + } + + // If we found a symbol table + if (Symtab != nullptr && Symtab->sh_link != 0) { + // Obtain a reference to the string table + auto *Strtab = TheELF.getSection(Symtab->sh_link).get(); + auto StrtabArray = TheELF.getSectionContents(Strtab).get(); + StringRef StrtabContent(reinterpret_cast(StrtabArray.data()), + StrtabArray.size()); + + // Collect symbol names + for (auto &Symbol : TheELF.symbols(Symtab)) { + Symbols.push_back({ + Symbol.getName(StrtabContent).get(), + Symbol.st_value, + Symbol.st_size + }); + } + } + + const auto *ElfHeader = TheELF.getHeader(); + EntryPoint = static_cast(ElfHeader->e_entry); + ProgramHeaders.Count = ElfHeader->e_phnum; + ProgramHeaders.Size = ElfHeader->e_phentsize; + + // Loop over the program headers looking for PT_LOAD segments, read them out + // and create a global variable for each one of them (writable or read-only), + // assign them a section and output information about them in the linking info + // CSV + using Elf_Phdr = const typename object::ELFFile::Elf_Phdr; + for (Elf_Phdr &ProgramHeader : TheELF.program_headers()) { + if (ProgramHeader.p_type == ELF::PT_LOAD) { + SegmentInfo Segment; + Segment.StartVirtualAddress = ProgramHeader.p_vaddr; + Segment.EndVirtualAddress = ProgramHeader.p_vaddr + ProgramHeader.p_memsz; + Segment.IsReadable = ProgramHeader.p_flags & ELF::PF_R; + Segment.IsWriteable = ProgramHeader.p_flags & ELF::PF_W; + Segment.IsExecutable = ProgramHeader.p_flags & ELF::PF_X; + + auto ActualAddress = TheELF.base() + ProgramHeader.p_offset; + Segment.Data = ArrayRef(ActualAddress, ProgramHeader.p_filesz); + + // If it's an executable segment, and we've been asked so, register which + // sections actually contain code + if (UseSections && Segment.IsExecutable) { + using Elf_Shdr = const typename object::ELFFile::Elf_Shdr; + auto Inserter = std::back_inserter(Segment.ExecutableSections); + for (Elf_Shdr &SectionHeader : TheELF.sections()) + if (SectionHeader.sh_flags & ELF::SHF_EXECINSTR) + Inserter = make_pair(SectionHeader.sh_addr, + SectionHeader.sh_addr + SectionHeader.sh_size); + } + + Segments.push_back(Segment); + + // Check if it's the segment containing the program headers + auto ProgramHeaderStart = ProgramHeader.p_offset; + auto ProgramHeaderEnd = ProgramHeader.p_offset + ProgramHeader.p_filesz; + if (ProgramHeaderStart <= ElfHeader->e_phoff + && ElfHeader->e_phoff < ProgramHeaderEnd) { + auto PhdrAddress = static_cast(ProgramHeader.p_vaddr + + ElfHeader->e_phoff + - ProgramHeader.p_offset); + ProgramHeaders.Address = PhdrAddress; + } + + } + + } +} diff --git a/binaryfile.h b/binaryfile.h new file mode 100644 index 000000000..9bea88f33 --- /dev/null +++ b/binaryfile.h @@ -0,0 +1,126 @@ +#ifndef _BINARYFILE_H +#define _BINARYFILE_H + +// +// This file is distributed under the MIT License. See LICENSE.md for details. +// + +// Standard includes +#include +#include + +// LLVM includes +#include "llvm/Object/Binary.h" + +// Local includes +#include "revamb.h" + +namespace llvm { +namespace object { +class ObjectFile; +} +} + +/// \brief Simple data structure to describe an ELF segment +// TODO: information hiding +struct SegmentInfo { + /// Produce a name for this segment suitable for human understanding + std::string generateName(); + + llvm::GlobalVariable *Variable; ///< \brief LLVM variable containing this + /// segment's data + uint64_t StartVirtualAddress; + uint64_t EndVirtualAddress; + bool IsWriteable; + bool IsExecutable; + bool IsReadable; + std::vector> ExecutableSections; + llvm::ArrayRef Data; + + bool contains(uint64_t Address) const { + return StartVirtualAddress <= Address && Address < EndVirtualAddress; + } + + bool contains(uint64_t Start, uint64_t Size) const { + return contains(Start) && contains(Start + Size - 1); + } + + uint64_t size() const { return EndVirtualAddress - StartVirtualAddress; } + + template + void insertExecutableRanges(std::back_insert_iterator Inserter) const { + if (!IsExecutable) + return; + + if (ExecutableSections.size() > 0) { + std::copy(ExecutableSections.begin(), + ExecutableSections.end(), + Inserter); + } else { + Inserter = std::make_pair(StartVirtualAddress, EndVirtualAddress); + } + } + +}; + +/// \brief Simple data structure to describe a symbol in an image format +/// independent way +// TODO: information hiding +struct SymbolInfo { + llvm::StringRef Name; + uint64_t Address; + uint64_t Size; + + bool operator<(const SymbolInfo &Other) const { + return Address < Other.Address; + } + + bool operator==(const SymbolInfo &Other) const { + return Name == Other.Name && Address == Other.Address && Size == Other.Size; + } +}; + +/// \brief BinaryFile describes an input image file in a semi-architecture +/// independent way +class BinaryFile { +public: + /// \param FilePath the path to the input file. + /// \param UseSections whether information in sections, if available, should + /// be employed or not. This is useful to precisely identify exeutable + /// code. + BinaryFile(std::string FilePath, bool UseSections); + + // Accessors + const Architecture &architecture() const { return TheArchitecture; } + std::vector &segments() { return Segments; } + const std::vector &segments() const { return Segments; } + const std::vector &symbols() const { return Symbols; } + uint64_t entryPoint() const { return EntryPoint; } + + // ELF specific accessors + uint64_t programHeadersAddress() const { return ProgramHeaders.Address; } + unsigned programHeaderSize() const { return ProgramHeaders.Size; } + unsigned programHeadersCount() const { return ProgramHeaders.Count; } + +private: + template + void parseELF(llvm::object::ObjectFile *TheBinary, + bool UseSections); + +private: + llvm::object::OwningBinary BinaryHandle; + Architecture TheArchitecture; + std::vector Symbols; + std::vector Segments; + + uint64_t EntryPoint; + + // ELF specific fields + struct { + uint64_t Address; + unsigned Count; + unsigned Size; + } ProgramHeaders; +}; + +#endif // _BINARYFILE_H diff --git a/codegenerator.cpp b/codegenerator.cpp index ddb36e891..dea96b26a 100644 --- a/codegenerator.cpp +++ b/codegenerator.cpp @@ -28,7 +28,6 @@ #include "llvm/IRReader/IRReader.h" #include "llvm/Linker/Linker.h" #include "llvm/Support/Casting.h" -#include "llvm/Support/ELF.h" #include "llvm/Support/raw_os_ostream.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Transforms/Scalar.h" @@ -63,7 +62,7 @@ make_array(Args&&... args) { // Outline the destructor for the sake of privacy in the header CodeGenerator::~CodeGenerator() = default; -CodeGenerator::CodeGenerator(std::string Input, +CodeGenerator::CodeGenerator(BinaryFile &Binary, Architecture& Target, std::string Output, std::string Helpers, @@ -73,13 +72,13 @@ CodeGenerator::CodeGenerator(std::string Input, std::string Coverage, std::string BBSummary, bool EnableOSRA, - bool EnableTracing, - bool UseSections) : + bool EnableTracing) : TargetArchitecture(Target), Context(getGlobalContext()), TheModule((new Module("top", Context))), OutputPath(Output), Debug(new DebugHelper(Output, Debug, TheModule.get(), DebugInfo)), + Binary(Binary), EnableOSRA(EnableOSRA), EnableTracing(EnableTracing) { @@ -103,134 +102,6 @@ CodeGenerator::CodeGenerator(std::string Input, BBSummary = Output + ".bbsummary.csv"; this->BBSummaryPath = BBSummary; - auto BinaryOrErr = object::createBinary(Input); - assert(BinaryOrErr && "Couldn't open the input file"); - - BinaryHandle = std::move(BinaryOrErr.get()); - - // We only support ELF for now - auto *TheBinary = cast(BinaryHandle.getBinary()); - - // TODO: QEMU should provide this information - unsigned InstructionAlignment = 0; - StringRef SyscallHelper = ""; - StringRef SyscallNumberRegister = ""; - ArrayRef NoReturnSyscalls = { }; - switch (TheBinary->getArch()) { - case Triple::x86_64: - InstructionAlignment = 1; - SyscallHelper = "helper_syscall"; - SyscallNumberRegister = "rax"; - NoReturnSyscalls = { - 0xe7, // exit_group - 0x3c, // exit - 0x3b // execve - }; - break; - case Triple::arm: - InstructionAlignment = 4; - SyscallHelper = "helper_exception_with_syndrome"; - SyscallNumberRegister = "r7"; - NoReturnSyscalls = { - 0xf8, // exit_group - 0x1, // exit - 0xb // execve - }; - break; - case Triple::mips: - InstructionAlignment = 4; - SyscallHelper = "helper_raise_exception"; - SyscallNumberRegister = "v0"; - NoReturnSyscalls = { - 0x1096, // exit_group - 0xfa1, // exit - 0xfab // execve - }; - break; - default: - assert(false); - } - - SourceArchitecture = Architecture(InstructionAlignment, - 1, - TheBinary->isLittleEndian(), - TheBinary->getBytesInAddress() * 8, - SyscallHelper, - SyscallNumberRegister, - NoReturnSyscalls); - - if (SourceArchitecture.pointerSize() == 32) { - if (SourceArchitecture.isLittleEndian()) { - parseELF(TheBinary, LinkingInfo, UseSections); - } else { - parseELF(TheBinary, LinkingInfo, UseSections); - } - } else if (SourceArchitecture.pointerSize() == 64) { - if (SourceArchitecture.isLittleEndian()) { - parseELF(TheBinary, LinkingInfo, UseSections); - } else { - parseELF(TheBinary, LinkingInfo, UseSections); - } - } else { - assert("Unexpect address size"); - } -} - -std::string SegmentInfo::generateName() { - // Create name from start and size - std::stringstream NameStream; - NameStream << ".o_" - << (IsReadable ? "r" : "") - << (IsWriteable ? "w" : "") - << (IsExecutable ? "x" : "") - << "_0x" << std::hex << StartVirtualAddress; - - return NameStream.str(); -} - -template -void CodeGenerator::parseELF(object::ObjectFile *TheBinary, - std::string LinkingInfo, - bool UseSections) { - // Parse the ELF file - std::error_code EC; - object::ELFFile TheELF(TheBinary->getData(), EC); - assert(!EC && "Error while loading the ELF file"); - - // Look for static or dynamic symbols - using Elf_ShdrPtr = decltype(&(*TheELF.sections().begin())); - Elf_ShdrPtr Symtab = nullptr; - for (auto &Section : TheELF.sections()){ - auto Name = TheELF.getSectionName(&Section); - if (Name && Name.get() == ".symtab") { - Symtab = &Section; - break; - } else if (Name && Name.get() == ".dynsym") { - Symtab = &Section; - } - } - - // If we found a symbol table - if (Symtab != nullptr && Symtab->sh_link != 0) { - // Obtain a reference to the string table - auto *Strtab = TheELF.getSection(Symtab->sh_link).get(); - auto StrtabArray = TheELF.getSectionContents(Strtab).get(); - StringRef StrtabContent(reinterpret_cast(StrtabArray.data()), - StrtabArray.size()); - - // Collect symbol names - for (auto &Symbol : TheELF.symbols(Symtab)) { - Binary.Symbols.push_back({ - Symbol.getName(StrtabContent).get(), - Symbol.st_value, - Symbol.st_size - }); - } - } - - const auto *ElfHeader = TheELF.getHeader(); - EntryPoint = static_cast(ElfHeader->e_entry); - // Prepare the linking info CSV if (LinkingInfo.size() == 0) LinkingInfo = OutputPath + ".li.csv"; @@ -247,7 +118,8 @@ void CodeGenerator::parseELF(object::ObjectFile *TheBinary, ElfHeaderHelper->setAlignment(1); ElfHeaderHelper->setSection(".elfheaderhelper"); - auto *RegisterType = Type::getIntNTy(Context, T::Is64Bits ? 64 : 32); + auto *RegisterType = Type::getIntNTy(Context, + Binary.architecture().pointerSize()); auto createConstGlobal = [this, &RegisterType] (const Twine &Name, uint64_t Value) { return new GlobalVariable(*TheModule, @@ -259,100 +131,74 @@ void CodeGenerator::parseELF(object::ObjectFile *TheBinary, }; // These values will be used to populate the auxiliary vectors - createConstGlobal("e_phentsize", ElfHeader->e_phentsize); - createConstGlobal("e_phnum", ElfHeader->e_phnum); + createConstGlobal("e_phentsize", Binary.programHeaderSize()); + createConstGlobal("e_phnum", Binary.programHeadersCount()); + createConstGlobal("phdr_address", Binary.programHeadersAddress()); - // Loop over the program headers looking for PT_LOAD segments, read them out - // and create a global variable for each one of them (writable or read-only), - // assign them a section and output information about them in the linking info - // CSV - using Elf_Phdr = const typename object::ELFFile::Elf_Phdr; - for (Elf_Phdr &ProgramHeader : TheELF.program_headers()) - if (ProgramHeader.p_type == ELF::PT_LOAD) { - SegmentInfo Segment; - Segment.StartVirtualAddress = ProgramHeader.p_vaddr; - Segment.EndVirtualAddress = ProgramHeader.p_vaddr + ProgramHeader.p_memsz; - Segment.IsReadable = ProgramHeader.p_flags & ELF::PF_R; - Segment.IsWriteable = ProgramHeader.p_flags & ELF::PF_W; - Segment.IsExecutable = ProgramHeader.p_flags & ELF::PF_X; - - // If it's an executable segment, and we've been asked so, register which - // sections actually contain code - if (UseSections && Segment.IsExecutable) { - using Elf_Shdr = const typename object::ELFFile::Elf_Shdr; - auto Inserter = std::back_inserter(Segment.ExecutableSections); - for (Elf_Shdr &SectionHeader : TheELF.sections()) - if (SectionHeader.sh_flags & ELF::SHF_EXECINSTR) - Inserter = make_pair(SectionHeader.sh_addr, - SectionHeader.sh_addr + SectionHeader.sh_size); - } - - auto ActualStartAddress = TheELF.base() + ProgramHeader.p_offset; - - // If it's executable register it as a valid code area - if (Segment.IsExecutable) { - // We ignore possible p_filesz-p_memsz mismatches, zeros wouldn't be - // useful code anyway - ptc.mmap(static_cast(ProgramHeader.p_vaddr), - static_cast(ActualStartAddress), - static_cast(ProgramHeader.p_filesz)); - } - - std::string Name = Segment.generateName(); - - // Get data and size - auto *DataType = ArrayType::get(Uint8Ty, ProgramHeader.p_memsz); - - Constant *TheData = nullptr; - if (ProgramHeader.p_memsz == ProgramHeader.p_filesz) { - // Create the array directly from the mmap'd ELF - auto FileData = ArrayRef(ActualStartAddress, - ProgramHeader.p_filesz); - TheData = ConstantDataArray::get(Context, FileData); - } else { - // If we have extra data at the end we need to create a copy of the - // segment and append the NULL bytes - auto FullData = make_unique(ProgramHeader.p_memsz); - ::memcpy(FullData.get(), - ActualStartAddress, - ProgramHeader.p_filesz); - ::bzero(FullData.get() + ProgramHeader.p_filesz, - ProgramHeader.p_memsz - ProgramHeader.p_filesz); - auto DataRef = ArrayRef(FullData.get(), ProgramHeader.p_memsz); - TheData = ConstantDataArray::get(Context, DataRef); - } - - // Create a new global variable - Segment.Variable = new GlobalVariable(*TheModule, - DataType, - !Segment.IsWriteable, - GlobalValue::ExternalLinkage, - TheData, - Name); - - // Force alignment to 1 and assign the variable to a specific section - Segment.Variable->setAlignment(1); - Segment.Variable->setSection(Name); - - // Check if it's the segment containing the program headers - auto ProgramHeaderStart = ProgramHeader.p_offset; - auto ProgramHeaderEnd = ProgramHeader.p_offset + ProgramHeader.p_filesz; - if (ProgramHeaderStart <= ElfHeader->e_phoff - && ElfHeader->e_phoff < ProgramHeaderEnd) { - auto PhdrAddress = static_cast(ProgramHeader.p_vaddr - + ElfHeader->e_phoff - - ProgramHeader.p_offset); - createConstGlobal("phdr_address", PhdrAddress); - } - - // Write the linking info CSV - LinkingInfoStream << Name - << ",0x" << std::hex << Segment.StartVirtualAddress - << ",0x" << std::hex << Segment.EndVirtualAddress - << std::endl; - - Binary.Segments.push_back(Segment); + for (SegmentInfo &Segment : Binary.segments()) { + // If it's executable register it as a valid code area + if (Segment.IsExecutable) { + // We ignore possible p_filesz-p_memsz mismatches, zeros wouldn't be + // useful code anyway + ptc.mmap(Segment.StartVirtualAddress, + static_cast(Segment.Data.data()), + static_cast(Segment.Data.size())); } + + std::string Name = Segment.generateName(); + + // Get data and size + auto *DataType = ArrayType::get(Uint8Ty, Segment.size()); + + Constant *TheData = nullptr; + if (Segment.size() == Segment.Data.size()) { + // Create the array directly from the mmap'd ELF + TheData = ConstantDataArray::get(Context, Segment.Data); + } else { + // If we have extra data at the end we need to create a copy of the + // segment and append the NULL bytes + auto FullData = make_unique(Segment.size()); + ::memcpy(FullData.get(), + Segment.Data.data(), + Segment.Data.size()); + ::bzero(FullData.get() + Segment.Data.size(), + Segment.size() - Segment.Data.size()); + auto DataRef = ArrayRef(FullData.get(), Segment.size()); + TheData = ConstantDataArray::get(Context, DataRef); + } + + // Create a new global variable + Segment.Variable = new GlobalVariable(*TheModule, + DataType, + !Segment.IsWriteable, + GlobalValue::ExternalLinkage, + TheData, + Name); + + // Force alignment to 1 and assign the variable to a specific section + Segment.Variable->setAlignment(1); + Segment.Variable->setSection(Name); + + // Write the linking info CSV + LinkingInfoStream << Name + << ",0x" << std::hex << Segment.StartVirtualAddress + << ",0x" << std::hex << Segment.EndVirtualAddress + << std::endl; + + } + +} + +std::string SegmentInfo::generateName() { + // Create name from start and size + std::stringstream NameStream; + NameStream << ".o_" + << (IsReadable ? "r" : "") + << (IsWriteable ? "w" : "") + << (IsExecutable ? "x" : "") + << "_0x" << std::hex << StartVirtualAddress; + + return NameStream.str(); } static BasicBlock *replaceFunction(Function *ToReplace) { @@ -736,13 +582,12 @@ void CodeGenerator::translate(uint64_t VirtualAddress, auto *PCReg = Variables.getByEnvOffset(ptc.pc, "pc").first; JumpTargetManager JumpTargets(MainFunction, PCReg, - SourceArchitecture, Binary, EnableOSRA); if (VirtualAddress == 0) { JumpTargets.harvestGlobalData(); - VirtualAddress = EntryPoint; + VirtualAddress = Binary.entryPoint(); } dbg << "Entry address: 0x" << std::hex << VirtualAddress << std::endl; @@ -763,7 +608,7 @@ void CodeGenerator::translate(uint64_t VirtualAddress, Variables, JumpTargets, Blocks, - SourceArchitecture, + Binary.architecture(), TargetArchitecture); while (Entry != nullptr) { diff --git a/codegenerator.h b/codegenerator.h index 14f7a3624..27677b382 100644 --- a/codegenerator.h +++ b/codegenerator.h @@ -14,6 +14,7 @@ #include "llvm/ADT/ArrayRef.h" // Local includes +#include "binaryfile.h" #include "revamb.h" // Forward declarations @@ -42,7 +43,7 @@ public: /// another, writing the corresponding LLVM IR and other useful information to /// the specified paths. /// - /// \param Input path to the program executable (e.g. the ELF file). + /// \param Binary reference to a BinaryFile object describing the input. /// \param Target target architecture. /// \param Output path where the generate LLVM IR must be saved. /// \param Helpers path of the LLVM IR file containing the QEMU helpers. @@ -62,8 +63,8 @@ public: /// \param EnableTracing specify whether tracing in the ouptut binary should /// be enabled, that is, whether calls to an external `newPC` function /// should be removed at the end of the translation or not. - CodeGenerator(std::string Input, - Architecture& Target, + CodeGenerator(BinaryFile &Binary, + Architecture &Target, std::string Output, std::string Helpers, DebugInfoType DebugInfo, @@ -72,8 +73,7 @@ public: std::string Coverage, std::string BBSummary, bool EnableOSRA, - bool EnableTracing, - bool UseSections); + bool EnableTracing); ~CodeGenerator(); @@ -104,16 +104,13 @@ private: bool UseSections); private: - Architecture SourceArchitecture; Architecture TargetArchitecture; llvm::LLVMContext& Context; std::unique_ptr TheModule; std::unique_ptr HelpersModule; std::string OutputPath; std::unique_ptr Debug; - llvm::object::OwningBinary BinaryHandle; - BinaryInfo Binary; - uint64_t EntryPoint; + BinaryFile &Binary; unsigned OriginalInstrMDKind; unsigned PTCInstrMDKind; diff --git a/instructiontranslator.cpp b/instructiontranslator.cpp index 16bb0d01d..6e144df96 100644 --- a/instructiontranslator.cpp +++ b/instructiontranslator.cpp @@ -33,6 +33,8 @@ using namespace llvm; +using IT = InstructionTranslator; + namespace PTC { template @@ -464,13 +466,13 @@ static Value *CreateICmp(T& Builder, SecondOperand); } -using LBM = InstructionTranslator::LabeledBlocksMap; -InstructionTranslator::InstructionTranslator(IRBuilder<>& Builder, - VariableManager& Variables, - JumpTargetManager& JumpTargets, - std::vector Blocks, - Architecture& SourceArchitecture, - Architecture& TargetArchitecture) : +using LBM = IT::LabeledBlocksMap; +IT::InstructionTranslator(IRBuilder<>& Builder, + VariableManager& Variables, + JumpTargetManager& JumpTargets, + std::vector Blocks, + const Architecture &SourceArchitecture, + const Architecture &TargetArchitecture) : Builder(Builder), Variables(Variables), JumpTargets(JumpTargets), @@ -497,8 +499,7 @@ InstructionTranslator::InstructionTranslator(IRBuilder<>& Builder, &TheModule); } -void InstructionTranslator::finalizeNewPCMarkers(std::string &CoveragePath, - bool EnableTracing) { +void IT::finalizeNewPCMarkers(std::string &CoveragePath, bool EnableTracing) { std::vector ToDelete; std::ofstream Output(CoveragePath); @@ -537,15 +538,12 @@ void InstructionTranslator::finalizeNewPCMarkers(std::string &CoveragePath, } } -std::tuple -InstructionTranslator::newInstruction(PTCInstruction *Instr, - PTCInstruction *Next, - uint64_t EndPC, - bool IsFirst, - bool ForceNew) { +std::tuple +IT::newInstruction(PTCInstruction *Instr, + PTCInstruction *Next, + uint64_t EndPC, + bool IsFirst, + bool ForceNew) { using R = std::tuple; assert(Instr != nullptr); const PTC::Instruction TheInstruction(Instr); @@ -654,8 +652,7 @@ static StoreInst *getLastUniqueWrite(BasicBlock *BB, Value *Register) { return Result; } -InstructionTranslator::TranslationResult -InstructionTranslator::translateCall(PTCInstruction *Instr) { +IT::TranslationResult IT::translateCall(PTCInstruction *Instr) { const PTC::CallInstruction TheCall(Instr); std::vector InArgs; @@ -710,10 +707,9 @@ InstructionTranslator::translateCall(PTCInstruction *Instr) { return Success; } -InstructionTranslator::TranslationResult -InstructionTranslator::translate(PTCInstruction *Instr, - uint64_t PC, - uint64_t NextPC) { +IT::TranslationResult IT::translate(PTCInstruction *Instr, + uint64_t PC, + uint64_t NextPC) { const PTC::Instruction TheInstruction(Instr); std::vector InArgs; @@ -765,9 +761,9 @@ InstructionTranslator::translate(PTCInstruction *Instr, } ErrorOr> -InstructionTranslator::translateOpcode(PTCOpcode Opcode, - std::vector ConstArguments, - std::vector InArguments) { +IT::translateOpcode(PTCOpcode Opcode, + std::vector ConstArguments, + std::vector InArguments) { LLVMContext& Context = TheModule.getContext(); unsigned RegisterSize = getRegisterSize(Opcode); Type *RegisterType = nullptr; diff --git a/instructiontranslator.h b/instructiontranslator.h index 299de658a..b74673da0 100644 --- a/instructiontranslator.h +++ b/instructiontranslator.h @@ -50,8 +50,8 @@ public: VariableManager& Variables, JumpTargetManager& JumpTargets, std::vector Blocks, - Architecture& SourceArchitecture, - Architecture& TargetArchitecture); + const Architecture &SourceArchitecture, + const Architecture &TargetArchitecture); /// \brief Result status of the translation of a PTC opcode enum TranslationResult { @@ -137,8 +137,8 @@ private: llvm::Function *TheFunction; - Architecture& SourceArchitecture; - Architecture& TargetArchitecture; + const Architecture &SourceArchitecture; + const Architecture &TargetArchitecture; llvm::Function *NewPCMarker; diff --git a/jumptargetmanager.cpp b/jumptargetmanager.cpp index cb9e5f763..1b7a0190f 100644 --- a/jumptargetmanager.cpp +++ b/jumptargetmanager.cpp @@ -318,7 +318,7 @@ Optional JumpTargetManager::readRawValue(uint64_t Address, // TODO: create a IsLittleEndian field in JumpTargetManager? const DataLayout &DL = TheModule.getDataLayout(); - for (auto &Segment : Binary.Segments) { + for (auto &Segment : Binary.segments()) { // Note: we also consider writeable memory areas because, despite being // modifiable, can contain useful information if (Segment.contains(Address, Size) && Segment.IsReadable) { @@ -360,7 +360,7 @@ Optional JumpTargetManager::readRawValue(uint64_t Address, Constant *JumpTargetManager::readConstantPointer(Constant *Address, Type *PointerTy) { auto *Value = readConstantInt(Address, - SourceArchitecture.pointerSize() / 8); + Binary.architecture().pointerSize() / 8); if (Value != nullptr) { return ConstantExpr::getIntToPtr(Value, PointerTy); } else { @@ -374,7 +374,8 @@ ConstantInt *JumpTargetManager::readConstantInt(Constant *ConstantAddress, if (ConstantAddress->getType()->isPointerTy()) { using CE = ConstantExpr; - auto IntPtrTy = Type::getIntNTy(Context, SourceArchitecture.pointerSize()); + auto IntPtrTy = Type::getIntNTy(Context, + Binary.architecture().pointerSize()); ConstantAddress = CE::getPtrToInt(ConstantAddress, IntPtrTy); } @@ -398,8 +399,7 @@ static cl::opt *getOption(StringMap& Options, JumpTargetManager::JumpTargetManager(Function *TheFunction, Value *PCReg, - Architecture& SourceArchitecture, - const BinaryInfo &Binary, + const BinaryFile &Binary, bool EnableOSRA) : TheModule(*TheFunction->getParent()), Context(TheModule.getContext()), @@ -411,16 +411,15 @@ JumpTargetManager::JumpTargetManager(Function *TheFunction, Dispatcher(nullptr), DispatcherSwitch(nullptr), Binary(Binary), - SourceArchitecture(SourceArchitecture), EnableOSRA(EnableOSRA), - NoReturn(SourceArchitecture) { + NoReturn(Binary.architecture()) { FunctionType *ExitTBTy = FunctionType::get(Type::getVoidTy(Context), { Type::getInt32Ty(Context) }, false); ExitTB = cast(TheModule.getOrInsertFunction("exitTB", ExitTBTy)); createDispatcher(TheFunction, PCReg, true); - for (auto &Segment : Binary.Segments) + for (auto &Segment : Binary.segments()) Segment.insertExecutableRanges(std::back_inserter(ExecutableRanges)); initializeSymbolMap(); @@ -436,10 +435,10 @@ JumpTargetManager::JumpTargetManager(Function *TheFunction, void JumpTargetManager::initializeSymbolMap() { // Collect how many times each name is used std::map SeenCount; - for (const SymbolInfo &Symbol : Binary.Symbols) + for (const SymbolInfo &Symbol : Binary.symbols()) SeenCount[std::string(Symbol.Name)]++; - for (const SymbolInfo &Symbol : Binary.Symbols) { + for (const SymbolInfo &Symbol : Binary.symbols()) { // Discard symbols pointing to 0, with zero-sized names or present multiple // times. Note that we keep zero-size symbols. if (Symbol.Address == 0 @@ -455,6 +454,7 @@ void JumpTargetManager::initializeSymbolMap() { } } +// TODO: move this in BinaryFile? std::string JumpTargetManager::nameForAddress(uint64_t Address) const { std::stringstream Result; @@ -484,15 +484,15 @@ std::string JumpTargetManager::nameForAddress(uint64_t Address) const { } void JumpTargetManager::harvestGlobalData() { - for (auto& Segment : Binary.Segments) { + for (auto& Segment : Binary.segments()) { auto *Data = cast(Segment.Variable->getInitializer()); uint64_t StartVirtualAddress = Segment.StartVirtualAddress; const unsigned char *DataStart = Data->getRawDataValues().bytes_begin(); const unsigned char *DataEnd = Data->getRawDataValues().bytes_end(); using endianness = support::endianness; - if (SourceArchitecture.pointerSize() == 64) { - if (SourceArchitecture.isLittleEndian()) + if (Binary.architecture().pointerSize() == 64) { + if (Binary.architecture().isLittleEndian()) findCodePointers(StartVirtualAddress, DataStart, DataEnd); @@ -500,8 +500,8 @@ void JumpTargetManager::harvestGlobalData() { findCodePointers(StartVirtualAddress, DataStart, DataEnd); - } else if (SourceArchitecture.pointerSize() == 32) { - if (SourceArchitecture.isLittleEndian()) + } else if (Binary.architecture().pointerSize() == 32) { + if (Binary.architecture().isLittleEndian()) findCodePointers(StartVirtualAddress, DataStart, DataEnd); diff --git a/jumptargetmanager.h b/jumptargetmanager.h index 248ad47ae..400264430 100644 --- a/jumptargetmanager.h +++ b/jumptargetmanager.h @@ -18,6 +18,7 @@ #include "llvm/ADT/Optional.h" // Local includes +#include "binaryfile.h" #include "datastructures.h" #include "ir-helpers.h" #include "noreturnanalysis.h" @@ -196,14 +197,12 @@ public: /// \param TheFunction the translated function. /// \param PCReg the global variable representing the program counter. - /// \param SourceArchitecture the input architecture. /// \param Binary reference to the information about a given binary, such as /// segments and symbols. /// \param EnableOSRA whether OSRA is enabled or not. JumpTargetManager(llvm::Function *TheFunction, llvm::Value *PCReg, - Architecture& SourceArchitecture, - const BinaryInfo &Binary, + const BinaryFile &Binary, bool EnableOSRA); /// \brief Collect jump targets from the program's segments @@ -279,7 +278,7 @@ public: /// \brief Return true if the given PC respects the input architecture's /// instruction alignment constraints bool isInstructionAligned(uint64_t PC) const { - return PC % SourceArchitecture.instructionAlignment() == 0; + return PC % Binary.architecture().instructionAlignment() == 0; } /// \brief Return true if the given PC can be executed by the current @@ -460,7 +459,7 @@ public: /// that have never been touched by SET will be considered and their pointee /// will be marked with UnusedGlobalData. void finalizeJumpTargets() { - unsigned ReadSize = SourceArchitecture.pointerSize() / 8; + unsigned ReadSize = Binary.architecture().pointerSize() / 8; for (uint64_t MemoryAddress : UnusedCodePointers) { uint64_t PC = readRawValue(MemoryAddress, ReadSize).getValue(); registerJT(PC, UnusedGlobalData); @@ -556,8 +555,7 @@ private: llvm::BasicBlock *DispatcherFail; std::set Visited; - const BinaryInfo &Binary; - Architecture &SourceArchitecture; + const BinaryFile &Binary; bool EnableOSRA; diff --git a/main.cpp b/main.cpp index 994b06a44..8ac2d8b36 100644 --- a/main.cpp +++ b/main.cpp @@ -28,18 +28,18 @@ extern "C" { #include "llvm/Object/ELF.h" // Local includes -#include "debug.h" -#include "revamb.h" #include "argparse.h" -#include "ptcinterface.h" +#include "binaryfile.h" #include "codegenerator.h" +#include "debug.h" +#include "ptcinterface.h" +#include "revamb.h" PTCInterface ptc = {}; ///< The interface with the PTC library. static std::string LibTinycodePath; static std::string LibHelpersPath; struct ProgramParameters { - const char *Architecture; const char *InputPath; const char *OutputPath; size_t EntryPointAddress; @@ -62,6 +62,7 @@ static const char *const Usage[] = { }; static void findQemu(const char *Architecture) { + // TODO: make this optional char *FullPath = realpath("/proc/self/exe", nullptr); assert(FullPath != nullptr); std::string Directory(dirname(FullPath)); @@ -153,9 +154,6 @@ static int parseArgs(int Argc, const char *Argv[], struct argparse_option Options[] = { OPT_HELP(), OPT_GROUP("Input description"), - OPT_STRING('a', "architecture", - &Parameters->Architecture, - "the input architecture."), OPT_STRING('e', "entry", &EntryPointAddressString, "virtual address of the entry point where to start."), @@ -206,11 +204,6 @@ static int parseArgs(int Argc, const char *Argv[], Parameters->OutputPath = Argv[1]; // Check parameters - if (Parameters->Architecture == nullptr) { - fprintf(stderr, "Please specify the input architecture.\n"); - return EXIT_FAILURE; - } - if (EntryPointAddressString != nullptr) { if (sscanf(EntryPointAddressString, "%lld", &EntryPointAddress) != 1) { fprintf(stderr, "Entry point parameter (-e, --entry) is not a" @@ -267,7 +260,9 @@ int main(int argc, const char *argv[]) { if (parseArgs(argc, argv, &Parameters) != EXIT_SUCCESS) return EXIT_FAILURE; - findQemu(Parameters.Architecture); + BinaryFile TheBinary(Parameters.InputPath, Parameters.UseSections); + + findQemu(TheBinary.architecture().name()); // Load the appropriate libtyncode version LibraryPointer PTCLibrary; @@ -276,7 +271,7 @@ int main(int argc, const char *argv[]) { // Translate everything Architecture TargetArchitecture; - CodeGenerator Generator(std::string(Parameters.InputPath), + CodeGenerator Generator(TheBinary, TargetArchitecture, std::string(Parameters.OutputPath), LibHelpersPath, @@ -286,8 +281,7 @@ int main(int argc, const char *argv[]) { std::string(Parameters.CoveragePath), std::string(Parameters.BBSummaryPath), !Parameters.NoOSRA, - Parameters.EnableTracing, - Parameters.UseSections); + Parameters.EnableTracing); Generator.translate(Parameters.EntryPointAddress, "root"); diff --git a/revamb.h b/revamb.h index cf73ad88a..1888127a2 100644 --- a/revamb.h +++ b/revamb.h @@ -14,6 +14,7 @@ // LLVM includes #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Triple.h" namespace llvm { class GlobalVariable; @@ -28,65 +29,6 @@ enum class DebugInfoType { LLVMIR ///< produce an LLVM IR with debug metadata referring to itself. }; -/// \brief Simple data structure to describe an ELF segment -// TODO: information hiding -struct SegmentInfo { - /// Produce a name for this segment suitable for human understanding - std::string generateName(); - - llvm::GlobalVariable *Variable; ///< \brief LLVM variable containing this - /// segment's data - uint64_t StartVirtualAddress; - uint64_t EndVirtualAddress; - bool IsWriteable; - bool IsExecutable; - bool IsReadable; - - bool contains(uint64_t Address) const { - return StartVirtualAddress <= Address && Address < EndVirtualAddress; - } - - bool contains(uint64_t Start, uint64_t Size) const { - return contains(Start) && contains(Start + Size - 1); - } - - std::vector> ExecutableSections; - - template - void insertExecutableRanges(std::back_insert_iterator Inserter) const { - if (!IsExecutable) - return; - - if (ExecutableSections.size() > 0) { - std::copy(ExecutableSections.begin(), - ExecutableSections.end(), - Inserter); - } else { - Inserter = std::make_pair(StartVirtualAddress, EndVirtualAddress); - } - } - -}; - -struct SymbolInfo { - llvm::StringRef Name; - uint64_t Address; - uint64_t Size; - - bool operator<(const SymbolInfo &Other) const { - return Address < Other.Address; - } - - bool operator==(const SymbolInfo &Other) const { - return Name == Other.Name && Address == Other.Address && Size == Other.Size; - } -}; - -struct BinaryInfo { - std::vector Segments; - std::vector Symbols; -}; - /// \brief Basic information about an input/output architecture class Architecture { public: @@ -97,19 +39,21 @@ public: }; public: - Architecture() : + Architecture() : InstructionAlignment(1), DefaultAlignment(1), Endianess(LittleEndian), PointerSize(64) { } - Architecture(unsigned InstructionAlignment, - unsigned DefaultAlignment, - bool IsLittleEndian, - unsigned PointerSize, - llvm::StringRef SyscallHelper, - llvm::StringRef SyscallNumberRegister, - llvm::ArrayRef NoReturnSyscalls) : + Architecture(unsigned Type, + unsigned InstructionAlignment, + unsigned DefaultAlignment, + bool IsLittleEndian, + unsigned PointerSize, + llvm::StringRef SyscallHelper, + llvm::StringRef SyscallNumberRegister, + llvm::ArrayRef NoReturnSyscalls) : + Type(static_cast(Type)), InstructionAlignment(InstructionAlignment), DefaultAlignment(DefaultAlignment), Endianess(IsLittleEndian ? LittleEndian : BigEndian), @@ -118,17 +62,21 @@ public: SyscallNumberRegister(SyscallNumberRegister), NoReturnSyscalls(NoReturnSyscalls) { } - unsigned instructionAlignment() { return InstructionAlignment; } - unsigned defaultAlignment() { return DefaultAlignment; } - EndianessType endianess() { return Endianess; } - unsigned pointerSize() { return PointerSize; } - bool isLittleEndian() { return Endianess == LittleEndian; } - llvm::StringRef syscallHelper() { return SyscallHelper; } - llvm::StringRef syscallNumberRegister() { return SyscallNumberRegister; } - llvm::ArrayRef noReturnSyscalls() { return NoReturnSyscalls; } - + unsigned instructionAlignment() const { return InstructionAlignment; } + unsigned defaultAlignment() const { return DefaultAlignment; } + EndianessType endianess() const { return Endianess; } + unsigned pointerSize() const { return PointerSize; } + bool isLittleEndian() const { return Endianess == LittleEndian; } + llvm::StringRef syscallHelper() const { return SyscallHelper; } + llvm::StringRef syscallNumberRegister() const { + return SyscallNumberRegister; + } + llvm::ArrayRef noReturnSyscalls() const { return NoReturnSyscalls; } + const char *name() const { return llvm::Triple::getArchTypeName(Type); } private: + llvm::Triple::ArchType Type; + unsigned InstructionAlignment; unsigned DefaultAlignment; EndianessType Endianess; diff --git a/tests/Tests.cmake b/tests/Tests.cmake index 28b2954b1..23c29f4bd 100644 --- a/tests/Tests.cmake +++ b/tests/Tests.cmake @@ -180,7 +180,7 @@ foreach(ARCH ${SUPPORTED_ARCHITECTURES}) foreach(TEST_NAME ${TESTS}) # Test to translate the compiled binary add_test(NAME translate-${TEST_NAME}-${ARCH} - COMMAND sh -c "$ --use-sections -g ll --architecture ${ARCH} ${BIN}/${TEST_NAME} ${BIN}/${TEST_NAME}.ll") + COMMAND sh -c "$ --use-sections -g ll ${BIN}/${TEST_NAME} ${BIN}/${TEST_NAME}.ll") set_tests_properties(translate-${TEST_NAME}-${ARCH} PROPERTIES LABELS "translate;${TEST_NAME};${ARCH}") diff --git a/translate b/translate index 178d58087..7b558d74f 100755 --- a/translate +++ b/translate @@ -7,7 +7,6 @@ SCRIPT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" INPUT="" -ARCH="" OPTIMIZE=0 SKIP=0 @@ -39,9 +38,7 @@ do ;; *) # unknown option - if [ -z "$ARCH" ]; then - ARCH="$key" - elif [ -z "$INPUT" ]; then + if [ -z "$INPUT" ]; then INPUT="$key" else break; @@ -75,8 +72,36 @@ if [ '!' -e "$SUPPORTC" ]; then fi fi +# Read endianess and architecture bytes +ARCHID=$(python -c ' +from binascii import hexlify +import sys +file = open(sys.argv[1], "rb") +file.seek(5) +result = hexlify(file.read(1)) +file.seek(17) +result += hexlify(file.read(2)) +file.close() +print(result.decode("utf-8"))' "$INPUT") + +case "$ARCHID" in + 010028) + ARCH=arm; + ;; + 020200) + ARCH=mips; + ;; + 01003e) + ARCH=x86_64; + ;; + *) + echo "Unknown architecture: $ARCHID" + exit 1 + ;; +esac + if [ "$SKIP" -eq 0 ]; then - "$REVAMB" -g ll --debug jtcount,osrjts --use-sections --architecture "$ARCH" "$INPUT" "$LL" "$@" |& tee "$REVAMB_LOG" + "$REVAMB" -g ll --debug jtcount,osrjts --use-sections "$INPUT" "$LL" "$@" |& tee "$REVAMB_LOG" fi OUTPUT="$INPUT.translated" @@ -84,12 +109,10 @@ if [ "$OPTIMIZE" -eq 0 ]; then "$LLC" -O0 -filetype=obj "$LL" -o "$OBJ" elif [ "$OPTIMIZE" -eq 1 ]; then "$LLC" -O2 -filetype=obj "$LL" -o "$OBJ" -regalloc=fast - #OUTPUT="$INPUT.lopt2.translated" elif [ "$OPTIMIZE" -eq 2 ]; then "$OPT" -O2 -S -o "$LL_OPT" "$LL" LL="$LL_OPT" "$LLC" -O2 -filetype=obj "$LL" -o "$OBJ" -regalloc=fast - #OUTPUT="$INPUT.lopt2.translated" fi "$CC" $("$TOOPT" "$CSV") \