/// \file HexDump.cpp // // This file is distributed under the MIT License. See LICENSE.md for details. // #include "boost/icl/interval_map.hpp" #include "llvm/ADT/StringExtras.h" #include "llvm/IR/InstIterator.h" #include "llvm/Support/Error.h" #include "llvm/Support/FormatVariadic.h" #include "revng/EarlyFunctionAnalysis/CFGStringMap.h" #include "revng/EarlyFunctionAnalysis/ControlFlowGraphCache.h" #include "revng/Model/RawBinaryView.h" #include "revng/PTML/Tag.h" #include "revng/Pipeline/AllRegistries.h" #include "revng/Pipeline/Location.h" #include "revng/Pipes/FileContainer.h" #include "revng/Pipes/Kinds.h" #include "revng/Pipes/Ranks.h" #include "revng/Support/IRHelpers.h" #include "revng/Support/MetaAddress/IntervalContainers.h" using namespace llvm; namespace revng::pipes { static const size_t BytesInLine = 16; static FormattedNumber formatNumber(uint64_t Number, unsigned Width = 8) { return FormattedNumber(Number, 0, Width, true, false, false); }; static void outputHexDump(const TupleTree &Binary, const pipeline::LLVMContainer &ModuleContainer, const CFGMap &CFGMap, const BinaryFileContainer &SourceBinary, StringRef OutputPath) { auto BufferOrError = MemoryBuffer::getFileOrSTDIN(*SourceBinary.path()); auto Buffer = cantFail(errorOrToExpected(std::move(BufferOrError))); RawBinaryView BinaryView(*Binary.get(), Buffer->getBuffer()); std::error_code ErrorCode; raw_fd_ostream Output(OutputPath, ErrorCode, sys::fs::CD_CreateAlways); revng_assert(not ErrorCode, "Could not open file!"); ControlFlowGraphCache ControlFlowGraphCache(CFGMap); using boost::icl::discrete_interval; using boost::icl::inplace_plus; using boost::icl::inter_section; using boost::icl::interval_map; using boost::icl::partial_absorber; using IntervalType = discrete_interval; using Map = interval_map, partial_absorber, std::less, inplace_plus, inter_section, IntervalType>; Map Instructions; ptml::MarkupBuilder B; auto CreateTag = [&B](const std::string &Location) -> ptml::Tag { auto Tag = B.getTag("span"); Tag.addAttribute("data-location-definition", Location); return Tag; }; for (const Function &F : FunctionTags::Isolated.functions(&ModuleContainer.getModule())) { const efa::ControlFlowGraph &Metadata = ControlFlowGraphCache .getControlFlowGraph(&F); MetaAddress EntryAddress = Metadata.Entry(); for (const Instruction &I : llvm::instructions(F)) { if (auto *Call = getCallTo(&I, "newpc")) { const BasicBlock *JumpTarget = getJumpTargetBlock(I.getParent()); revng_assert(JumpTarget != nullptr); auto BasicBlockID = blockIDFromNewPC(JumpTarget->getFirstNonPHI()); MetaAddress Address = MetaAddress::fromValue(Call->getArgOperand(0)); auto *SizeValue = dyn_cast(Call->getArgOperand(1)); uint64_t Size = SizeValue->getZExtValue(); MetaAddress Begin = Address.toGeneric(); MetaAddress End = Begin + Size; auto Interval = IntervalType::right_open(Begin, End); std::string Str = locationString(ranks::Instruction, EntryAddress, BasicBlockID, Address); std::set Set{ Str }; Instructions.add(std::make_pair(Interval, Set)); } } } ptml::Tag DivTag = B.getTag("div"); Output << DivTag.open(); MetaAddress CurrentAddress; Map::const_iterator Current = Instructions.begin(); Map::const_iterator Next = std::next(Current); const Map::const_iterator End = Instructions.end(); std::stack OpenedTags; for (const auto &[Segment, SegmentBinary] : BinaryView.segments()) { MetaAddress CurrentAddress = Segment.StartAddress(); size_t Counter = 0; // Stores bytes from current line as printable characters SmallString<16> PrintableChars; for (size_t Index = 0; Index < SegmentBinary.size(); ++Index) { // If there is still some interval to process, set it to CurrentInterval. // Otherwise, create invalid interval. IntervalType CurrentInterval = (Current != End) ? Current->first : IntervalType{ IntervalMetaAddress{}, IntervalMetaAddress{} }; bool LineBegins = Index % BytesInLine == 0; if (LineBegins) { // Print address of first byte in line Output << formatNumber(CurrentAddress.address()) << " "; } // Open tags if this is beginning of next interval or line begins bool IsIntervalValid = CurrentInterval.lower().isValid() and CurrentInterval.upper().isValid(); // Check if current byte is inside currently processed interval bool AfterStart = CurrentInterval.lower() <= CurrentAddress; // Check if current byte is before end of current interval bool BeforeEnd = CurrentAddress < CurrentInterval.upper(); // If current interval is valid and current byte is after start and before // end of interval, this byte belongs to some interval and should be // wrapped with location tags. bool IsInsideInterval = IsIntervalValid and AfterStart and BeforeEnd; // Is current byte the first byte of interval? bool AtStart = CurrentInterval.lower() == CurrentAddress; // If interval is valid and current byte is the first byte, location tag // should be printed before byte. bool IsStartOfInterval = IsIntervalValid and AtStart; // Tag opening is printed in two situations: // 1. new interval is beginning on current byte // 2. new line begins and previously opened (and closed on line end) // interval is continued. if (IsStartOfInterval or (LineBegins and IsInsideInterval)) { for (const std::string &Tag : Current->second) { auto PTMLTag = CreateTag(Tag); OpenedTags.push(PTMLTag); Output << PTMLTag.open(); } } // Format number and put it to the output uint64_t B = SegmentBinary[Index]; Output << formatNumber(B, 2); // Increment counter of bytes printed in current line. ++Counter; // Append printable character. if (std::isprint(B)) { PrintableChars += B; } else { PrintableChars += '.'; } MetaAddress NextAddress = CurrentAddress + 1; const bool EndOfLine = Counter == BytesInLine; // If current byte (just printed) is in current interval, but next byte // isn't, this place is end of interval. const bool EndOfInterval = CurrentAddress < CurrentInterval.upper() and NextAddress >= CurrentInterval.upper(); const bool EndOfSegment = Index + 1 == SegmentBinary.size(); // All opened tags has to be closed now if current byte is still inside // some interval and: // 1. next byte is not in current interval (end of interval) OR // 2. after just printed by there is end of line if (IsInsideInterval and (EndOfInterval or EndOfLine)) { while (not OpenedTags.empty()) { auto &PTMLTag = OpenedTags.top(); Output << PTMLTag.close(); OpenedTags.pop(); } } // At the end of each printed line of bytes in hex format (with location // tags), ASCII representation of current line is printed. if (EndOfLine) { // Output ASCII representation at the end of the line std::string Temp; raw_string_ostream Printable(Temp); printHTMLEscaped(PrintableChars, Printable); Output << " | " << Printable.str() << " |\n"; PrintableChars.clear(); // At the end of line, Counter is set to 0. Counter = 0; } else { // Put space separating consecutive bytes Output << ' '; if (Counter == 8) { // After every 8 bytes, put extra space Output << ' '; } } // At the end of interval, we try to go to the next interval (if it // exists). if (EndOfInterval) { Current = Next; if (Next != End) { Next = std::next(Next); } } CurrentAddress = NextAddress; } // After each binary segment, we add extra empty line. Output << '\n'; } Output << DivTag.close(); } class HexDumpPipe { public: static constexpr auto Name = "hex-dump"; std::array getContract() const { using namespace pipeline; return { ContractGroup({ Contract(kinds::Binary, 0, kinds::HexDump, 3, InputPreservation::Preserve), Contract(kinds::Isolated, 1, kinds::HexDump, 3, InputPreservation::Preserve), Contract(kinds::CFG, 2, kinds::HexDump, 3, InputPreservation::Preserve) }) }; } void run(pipeline::ExecutionContext &EC, const BinaryFileContainer &SourceBinary, const pipeline::LLVMContainer &ModuleContainer, const CFGMap &CFGMap, HexDumpFileContainer &Output) { // This pipe works only if we have all the targets pipeline::TargetsList FunctionList = ModuleContainer.enumerate(); if (not FunctionList.contains(kinds::Isolated.allTargets(EC.getContext()))) return; if (not SourceBinary.exists()) return; pipeline::TargetsList CFGList = CFGMap.enumerate(); if (not CFGList.contains(kinds::CFG.allTargets(EC.getContext()))) return; // Proceed with emission outputHexDump(getModelFromContext(EC), ModuleContainer, CFGMap, SourceBinary, Output.getOrCreatePath()); EC.commitUniqueTarget(Output); } }; } // namespace revng::pipes // static pipeline::RegisterPipe X;