Files
revng-revng/lib/Yield/HexDump.cpp
Kacper Kołodziej 46e9ea0e10 HexDump implementation
HexDumpPipe dumps content of binary file in the similar way as hexdump
tool with addition of PTML markup for instructions addresses.

Continuous parts of binary code are wrapped with <span
data-location-definition=""></span> where data-location-definition
attribute contains Entry/BasicBlock/Instruction addresses in generic
form. <span> tags can be nested if byte(s) belong to many instructions
in code.

At the end of the line every <span> is closed and opened on the next
line again if it still applies to the next byte.

MetaAddress are converted to IntervalMetaAddress (which implements own,
optional-less operator-) and stored in boost::icl::intruval_map. This
map is used to get addresses of instructions to which each byte belongs.
2023-07-31 11:29:41 +02:00

288 lines
10 KiB
C++

/// \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/FunctionMetadataCache.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<model::Binary> &Binary,
const pipeline::LLVMContainer &Module,
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!");
FunctionMetadataCache FunctionMetadataCache;
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<IntervalMetaAddress>;
using Map = interval_map<IntervalMetaAddress,
std::set<std::string>,
partial_absorber,
std::less,
inplace_plus,
inter_section,
IntervalType>;
Map Instructions;
ptml::PTMLBuilder PTMLBuilder;
auto CreateTag = [&PTMLBuilder](const std::string &Location) -> ptml::Tag {
auto Tag = PTMLBuilder.getTag("span");
Tag.addAttribute("data-location-definition", Location);
return Tag;
};
for (const Function &F :
FunctionTags::Isolated.functions(&Module.getModule())) {
const efa::FunctionMetadata &Metadata = FunctionMetadataCache
.getFunctionMetadata(&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<ConstantInt>(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 = serializedLocation(ranks::Instruction,
EntryAddress,
BasicBlockID,
Address);
std::set<std::string> Set{ Str };
Instructions.add(std::make_pair(Interval, Set));
}
}
}
ptml::Tag DivTag = PTMLBuilder.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<ptml::Tag> 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
// sohuld 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
const 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 = "HexDump";
std::array<pipeline::ContractGroup, 2> getContract() const {
const pipeline::Contract
FunctionsContract(kinds::Isolated,
1,
kinds::HexDump,
2,
pipeline::InputPreservation::Preserve);
const pipeline::Contract
BinaryContract(kinds::Binary,
0,
kinds::HexDump,
2,
pipeline::InputPreservation::Preserve);
return { pipeline::ContractGroup({ FunctionsContract, BinaryContract }) };
}
void run(const pipeline::Context &Ctx,
const BinaryFileContainer &SourceBinary,
const pipeline::LLVMContainer &Module,
HexDumpFileContainer &Output) {
pipeline::TargetsList Enumeration = Module.enumerate();
if (not Enumeration.contains(kinds::Isolated.allTargets(Ctx)))
return;
if (not SourceBinary.exists())
return;
const TupleTree<model::Binary> &Binary = getModelFromContext(Ctx);
StringRef OutputPath = Output.getOrCreatePath();
outputHexDump(Binary, Module, SourceBinary, OutputPath);
}
void print(const pipeline::Context &Ctx,
raw_ostream &Os,
ArrayRef<std::string> ContainerNames) const {
Os << "revng artifact HexDump " << ContainerNames[0] << " -o "
<< ContainerNames[2] << "-m ModelFile\n";
};
};
} // namespace revng::pipes
//
static pipeline::RegisterPipe<revng::pipes::HexDumpPipe> X;