/// \file PTML.cpp // // This file is distributed under the MIT License. See LICENSE.md for details. // #include #include "llvm/ADT/PostOrderIterator.h" #include "revng/ADT/Concepts.h" #include "revng/EarlyFunctionAnalysis/CFGHelpers.h" #include "revng/Model/Binary.h" #include "revng/Model/Helpers.h" #include "revng/PTML/Constants.h" #include "revng/PTML/Tag.h" #include "revng/Pipeline/Location.h" #include "revng/Pipes/Ranks.h" #include "revng/Yield/ControlFlow/FallthroughDetection.h" #include "revng/Yield/Function.h" #include "revng/Yield/PTML.h" using pipeline::locationString; using ptml::Tag; namespace attributes = ptml::attributes; namespace ptmlScopes = ptml::scopes; namespace tags = ptml::tags; namespace ranks = revng::ranks; namespace tokenTypes { static constexpr auto LabelIndicator = "asm.label-indicator"; static constexpr auto RawBytes = "asm.raw-bytes"; static constexpr auto InstructionAddress = "asm.instruction-address"; } // namespace tokenTypes namespace scopes { static constexpr auto Function = "asm.function"; static constexpr auto BasicBlock = "asm.basic-block"; static constexpr auto Instruction = "asm.instruction"; } // namespace scopes static std::string targetPath(const BasicBlockID &Target, const yield::Function &Function, const model::Binary &Binary) { if (const auto *F = yield::tryGetFunction(Binary, Target)) { // The target is a function return locationString(ranks::Function, F->Entry()); } else if (auto Iterator = Function.Blocks().find(Target); Iterator != Function.Blocks().end()) { // The target is a basic block return locationString(ranks::BasicBlock, Function.Entry(), Iterator->ID()); } else if (Target.isValid()) { for (const auto &Block : Function.Blocks()) { if (Block.Instructions().contains(Target.start())) { // The target is an instruction return locationString(ranks::Instruction, Function.Entry(), Block.ID(), Target.start()); } } } revng_abort(("Unknown target:\n" + toString(Target)).c_str()); } static std::set targets(const yield::BasicBlock &BasicBlock, const yield::Function &Function, const model::Binary &Binary) { std::set Result; for (const auto &Edge : BasicBlock.Successors()) { auto [NextAddress, MaybeCall] = efa::parseSuccessor(*Edge, BasicBlock.nextBlock(), Binary); if (NextAddress.isValid()) Result.emplace(targetPath(NextAddress, Function, Binary)); if (MaybeCall.isValid()) Result.emplace(targetPath(BasicBlockID(MaybeCall), Function, Binary)); } // Explicitly remove the next target if there is only a single other target, // i.e. it's a conditional jump, call, etc. if (Result.size() == 2) { auto NextBlock = targetPath(BasicBlock.nextBlock(), Function, Binary); if (auto Iterator = llvm::find(Result, NextBlock); Iterator != Result.end()) Result.erase(Iterator); } return Result; } static std::string emitTagged(const ptml::MarkupBuilder &B, const yield::TaggedString &String) { llvm::StringRef Type = yield::TagType::toPTML(String.Type()); if (Type.empty()) { revng_assert(String.Attributes().empty()); return std::move(String.Content()); } auto Result = B.getTag(tags::Span, std::move(String.Content())) .addAttribute(attributes::Token, std::move(Type)); for (const yield::TagAttribute &Attribute : String.Attributes()) Result.addAttribute(Attribute.Name(), Attribute.Value()); return Result.toString(); } static std::string taggedLine(const ptml::MarkupBuilder &B, const SortedVector &Tagged) { std::string Result; for (const yield::TaggedString &String : Tagged) Result += emitTagged(B, String); return Result += '\n'; } /// An internal helper for managing instruction prefixes. /// /// It builds a map of instructions to prefixes for a passed function, and /// then allows extracting them one by one using `emit` method, while making /// sure all the calls to `emit` across the function returns strings of the same /// length. class InstructionPrefixManager { private: struct InstructionPrefix { std::string Address; std::string Bytes; }; std::map> Prefixes; uint64_t LongestAddressString = 0; uint64_t LongestByteString = 0; public: InstructionPrefixManager() {} InstructionPrefixManager(const yield::Function &Function, const model::Binary &Binary) { const auto Config = Binary.Configuration().Disassembly(); for (const yield::BasicBlock &BasicBlock : Function.Blocks()) { auto [Iterator, Success] = Prefixes.try_emplace(BasicBlock.ID()); revng_assert(Success, "Duplicate basic blocks?"); auto &BBPrefixes = Iterator->second; for (const yield::Instruction &Instruction : BasicBlock.Instructions()) { std::string Address; if (!Config.DisableEmissionOfInstructionAddress()) { Address = yield::sanitizedAddress(Instruction.Address(), Binary); if (llvm::StringRef(Address).take_front(2) == "0x") Address = Address.substr(2); LongestAddressString = std::max(LongestAddressString, Address.size()); } std::string Bytes; if (!Config.DisableEmissionOfRawBytes()) { for (uint8_t Byte : Instruction.RawBytes()) { std::string HexByte = Byte ? llvm::utohexstr(Byte, true, 2) : "00"; revng_assert(HexByte.size() == 2); Bytes += HexByte + ' '; } LongestByteString = std::max(LongestByteString, Bytes.size()); } InstructionPrefix Result = { .Address = std::move(Address), .Bytes = std::move(Bytes) }; auto [_, Success] = BBPrefixes.try_emplace(Instruction.Address(), std::move(Result)); revng_assert(Success, "Duplicate instructions?"); } } } public: /// \note This consumes the internal strings. /// Make sure to only call once per instruction. std::string emit(const ptml::MarkupBuilder &B, const MetaAddress &Instruction, const BasicBlockID &BasicBlock, const model::Binary &Binary) { if (!LongestAddressString && !LongestByteString) return B.getTag(tags::Span, " ") .addAttribute(attributes::Token, ptml::tokens::Indentation) .toString(); InstructionPrefix &Data = Prefixes.at(BasicBlock).at(Instruction); std::string Result; if (LongestAddressString != 0) { Result = B.getTag(tags::Span, std::move(Data.Address)) .addAttribute(attributes::Token, tokenTypes::InstructionAddress) .toString(); revng_assert(Data.Address.size() != 0); revng_assert(Data.Address.size() <= LongestAddressString); if (Data.Address.size() < LongestAddressString) { std::string Indentation(LongestAddressString - Data.Address.size(), ' '); Result = B.getTag(tags::Span, std::move(Indentation)) .addAttribute(attributes::Token, ptml::tokens::Indentation) .toString() + std::move(Result); } using model::Architecture::getAssemblyLabelIndicator; std::string Indicator(getAssemblyLabelIndicator(Binary.Architecture())); Result += B.getTag(tags::Span, std::move(Indicator)) .addAttribute(attributes::Token, tokenTypes::InstructionAddress) .toString(); Result += B.getTag(tags::Span, std::string(4, ' ')) .addAttribute(attributes::Token, ptml::tokens::Indentation) .toString(); } if (LongestByteString != 0) { Result += B.getTag(tags::Span, std::move(Data.Bytes)) .addAttribute(attributes::Token, tokenTypes::RawBytes) .toString(); revng_assert(Data.Bytes.size() != 0); revng_assert(Data.Bytes.size() <= LongestByteString); std::string Indentation(LongestByteString + 3 - Data.Bytes.size(), ' '); Result += B.getTag(tags::Span, std::move(Indentation)) .addAttribute(attributes::Token, ptml::tokens::Indentation) .toString(); } return B.getTag(tags::Span, " ") .addAttribute(attributes::Token, ptml::tokens::Indentation) .toString() + std::move(Result); } /// \note This does _not_ consume anything, feel free to call as many times /// as you need. std::string emitEmpty(const ptml::MarkupBuilder &B, const model::Binary &Binary) { uint64_t TotalPrefixSize = 2; if (LongestAddressString != 0) { using model::Architecture::getAssemblyLabelIndicator; auto Indicator = getAssemblyLabelIndicator(Binary.Architecture()); TotalPrefixSize += LongestAddressString + Indicator.size() + 4; } if (LongestByteString != 0) TotalPrefixSize += LongestByteString + 3; return B.getTag(tags::Span, std::string(TotalPrefixSize, ' ')) .addAttribute(attributes::Token, ptml::tokens::Indentation) .toString(); } }; static std::string instruction(const ptml::MarkupBuilder &B, const yield::Instruction &Instruction, const yield::BasicBlock &BasicBlock, const yield::Function &Function, const model::Binary &Binary, InstructionPrefixManager &&Prefixes, bool AddTargets = false) { revng_assert(Instruction.verify(true)); std::string Prefix = Prefixes.emit(B, Instruction.Address(), BasicBlock.ID(), Binary); // Tagged instruction body. std::string Result; for (const auto &Directive : Instruction.PrecedingDirectives()) { Result += B.getTag(tags::Div, std::move(Prefix) + taggedLine(B, Directive.Tags())) .toString(); Prefix = Prefixes.emitEmpty(B, Binary); } Result += B.getTag(tags::Div, std::move(Prefix) + taggedLine(B, Instruction.Disassembled())) .toString(); Prefix = Prefixes.emitEmpty(B, Binary); for (const auto &Directive : Instruction.FollowingDirectives()) { Result += B.getTag(tags::Div, std::move(Prefix) + taggedLine(B, Directive.Tags())) .toString(); Prefix = Prefixes.emitEmpty(B, Binary); } // Tag it with appropriate location data. std::string InstructionLocation = locationString(ranks::Instruction, Function.Entry(), BasicBlock.ID(), Instruction.Address()); Tag Location = B.getTag(tags::Span) .addAttribute(attributes::LocationDefinition, InstructionLocation); Tag Out = B.getTag(tags::Div, std::move(Result)) .addAttribute(attributes::Scope, scopes::Instruction) .addAttribute(attributes::ActionContextLocation, InstructionLocation); // And conditionally add target data. if (AddTargets) { auto Targets = targets(BasicBlock, Function, Binary); Out.addListAttribute(attributes::LocationReferences, Targets); } return Location + Out; } static std::string basicBlock(const ptml::MarkupBuilder &B, const yield::BasicBlock &BasicBlock, const yield::Function &Function, const model::Binary &Binary, std::string Label, InstructionPrefixManager &&Prefixes) { revng_assert(!BasicBlock.Instructions().empty()); auto FromIterator = BasicBlock.Instructions().begin(); auto ToIterator = std::prev(BasicBlock.Instructions().end()); std::string Result; for (auto Iterator = FromIterator; Iterator != ToIterator; ++Iterator) Result += instruction(B, *Iterator, BasicBlock, Function, Binary, std::move(Prefixes)); Result += instruction(B, *(ToIterator++), BasicBlock, Function, Binary, std::move(Prefixes), true); std::string LabelString; if (!Label.empty()) { LabelString = Label + "\n"; } else { std::string Location = locationString(ranks::BasicBlock, model::Function(Function.Entry()) .key(), BasicBlock.ID()); LabelString = B.getTag(tags::Span) .addAttribute(attributes::LocationDefinition, Location) .toString(); } return B.getTag(tags::Div, LabelString + Result) .addAttribute(attributes::Scope, scopes::BasicBlock) .toString(); } template static std::string labeledBlock(const ptml::MarkupBuilder &B, const yield::BasicBlock &FirstBlock, const yield::Function &Function, const model::Binary &Binary, InstructionPrefixManager &&Prefixes = {}) { std::string Result; std::string Label = emitTagged(B, std::move(FirstBlock.Label())); using model::Architecture::getAssemblyLabelIndicator; std::string Indicator(getAssemblyLabelIndicator(Binary.Architecture())); Label += B.getTag(tags::Span, std::move(Indicator)) .addAttribute(attributes::Token, tokenTypes::LabelIndicator) .toString(); if constexpr (ShouldMergeFallthroughTargets == false) { Result = basicBlock(B, FirstBlock, Function, Binary, std::move(Label), std::move(Prefixes)) + "\n"; } else { auto BasicBlocks = yield::cfg::labeledBlock(FirstBlock, Function, Binary); if (BasicBlocks.empty()) return ""; bool IsFirst = true; for (const auto &BasicBlock : BasicBlocks) { Result += basicBlock(B, *BasicBlock, Function, Binary, IsFirst ? std::move(Label) : std::string(), std::move(Prefixes)); IsFirst = false; } Result += "\n"; } return Result; } std::string yield::ptml::functionAssembly(const ::ptml::MarkupBuilder &B, const yield::Function &Function, const model::Binary &Binary) { std::string Result; InstructionPrefixManager P(Function, Binary); for (const auto &BasicBlock : Function.Blocks()) Result += labeledBlock(B, BasicBlock, Function, Binary, std::move(P)); return B.getTag(tags::Div, Result) .addAttribute(attributes::Scope, scopes::Function) .toString(); } std::string yield::ptml::controlFlowNode(const ::ptml::MarkupBuilder &B, const BasicBlockID &BasicBlock, const yield::Function &Function, const model::Binary &Binary) { auto Iterator = Function.Blocks().find(BasicBlock); revng_assert(Iterator != Function.Blocks().end()); auto Result = labeledBlock(B, *Iterator, Function, Binary); revng_assert(!Result.empty()); return Result; } namespace callGraphTokens { static constexpr auto NodeLabel = "call-graph.node-label"; static constexpr auto ShallowNodeLabel = "call-graph.shallow-node-label"; } // namespace callGraphTokens static model::Identifier functionNameHelper(llvm::StringRef Location, const model::Binary &Binary, model::NameBuilder &NameBuilder) { if (auto L = pipeline::locationFromString(revng::ranks::DynamicFunction, Location)) { auto Key = std::get<0>(L->at(revng::ranks::DynamicFunction)); auto Iterator = Binary.ImportedDynamicFunctions().find(Key); revng_assert(Iterator != Binary.ImportedDynamicFunctions().end()); return NameBuilder.name(*Iterator); } else if (auto L = pipeline::locationFromString(revng::ranks::Function, Location)) { auto Key = std::get<0>(L->at(revng::ranks::Function)); auto Iterator = Binary.Functions().find(Key); revng_assert(Iterator != Binary.Functions().end()); return NameBuilder.name(*Iterator); } else { revng_abort("Unsupported function type."); } } std::string yield::ptml::functionNameDefinition(const ::ptml::MarkupBuilder &B, llvm::StringRef Location, const model::Binary &Binary, model::NameBuilder &NameBuilder) { if (Location.empty()) return ""; ::ptml::Tag Result = B.getTag(tags::Div, functionNameHelper(Location, Binary, NameBuilder)); Result.addAttribute(attributes::Token, callGraphTokens::NodeLabel); Result.addAttribute(attributes::LocationDefinition, Location); return Result.toString(); } std::string yield::ptml::functionLink(const ::ptml::MarkupBuilder &B, llvm::StringRef Location, const model::Binary &Binary, model::NameBuilder &NameBuilder) { if (Location.empty()) return ""; ::ptml::Tag Result = B.getTag(tags::Div, functionNameHelper(Location, Binary, NameBuilder)); Result.addAttribute(attributes::Token, callGraphTokens::NodeLabel); Result.addAttribute(attributes::LocationReferences, Location); return Result.toString(); } std::string yield::ptml::shallowFunctionLink(const ::ptml::MarkupBuilder &B, llvm::StringRef Location, const model::Binary &Binary, model::NameBuilder &NameBuilder) { if (Location.empty()) return ""; ::ptml::Tag Result = B.getTag(tags::Div, functionNameHelper(Location, Binary, NameBuilder)); Result.addAttribute(attributes::Token, callGraphTokens::ShallowNodeLabel); Result.addAttribute(attributes::LocationReferences, Location); return Result.toString(); }