diff --git a/include/fxos/analysis.h b/include/fxos/analysis.h index 9ff466f..01fbedc 100644 --- a/include/fxos/analysis.h +++ b/include/fxos/analysis.h @@ -49,6 +49,8 @@ struct ProgramState /* Lattice order. */ bool le(ProgramState const &other) const; + std::string str(int indent = 0) const; + private: /* Values for registers r0..r15 */ RelConst m_regs[16]; @@ -94,6 +96,8 @@ struct ProgramStateDiff m_target = static_cast(Target::Unknown); } + std::string str() const; + private: int m_target; RelConst m_value; diff --git a/include/fxos/function.h b/include/fxos/function.h index 69ae6db..e00512c 100644 --- a/include/fxos/function.h +++ b/include/fxos/function.h @@ -22,6 +22,7 @@ namespace FxOS { class Function; class BasicBlock; class Instruction; +class StaticFunctionAnalysis; // TODO: move this extern declaration of FxOS::insmap extern std::array, 65536> insmap; @@ -92,6 +93,11 @@ struct Function: public BinaryObject { return m_analysisVersion; } + /* Get analysis results if there are any. */ + StaticFunctionAnalysis const *getAnalysis() const + { + return m_analysisResult.get(); + } /* Construction functions to be used only by the analysis pass. */ bool exploreFunctionAt(u32 address); @@ -100,11 +106,16 @@ struct Function: public BinaryObject void updateFunctionSize(); void setAnalysisVersion(int version); + /* Analysis execution functions (also semi-private). */ + void runAnalysis(); + private: /* List of basic blocks (entry block is always number 0) */ std::vector m_blocks; /* Analysis version */ int m_analysisVersion = 0; + /* Analysis result */ + std::unique_ptr m_analysisResult; /* ID of the entry block */ int m_entryBlockIndex; }; @@ -293,7 +304,7 @@ struct BasicBlock auto successors() // -> [BasicBlock &] { return std::views::all(m_successors) - | std::views::transform([this](int index) { + | std::views::transform([this](int index) -> BasicBlock & { return parentFunction().basicBlockByIndex(index); }); } @@ -317,7 +328,7 @@ struct BasicBlock auto predecessors() // -> [BasicBlock &] { return std::views::all(m_predecessors) - | std::views::transform([this](int index) { + | std::views::transform([this](int index) -> BasicBlock & { return parentFunction().basicBlockByIndex(index); }); } diff --git a/include/fxos/view/assembly.h b/include/fxos/view/assembly.h index 724001c..690e382 100644 --- a/include/fxos/view/assembly.h +++ b/include/fxos/view/assembly.h @@ -53,6 +53,7 @@ struct Binary; struct Function; struct BasicBlock; struct Instruction; +struct StaticFunctionAnalysis; struct ViewAssemblyOptions { @@ -81,6 +82,12 @@ struct ViewAssemblyOptions /* Binary to get symbols from */ Binary *binary = nullptr; + /* Whether to print basic block details, ie. edges and flags */ + bool basicBlockDetails = false; + + /* Whether to print function analysis results from the binary */ + bool printFunctionAnalysis = false; + /* TODO: More view assembly options, including CFG layout */ /* TODO: View assembly options: syntax highlighting */ }; diff --git a/lib/ai/RelConst.cpp b/lib/ai/RelConst.cpp index 604a4bb..03c7aa1 100644 --- a/lib/ai/RelConst.cpp +++ b/lib/ai/RelConst.cpp @@ -320,9 +320,9 @@ std::string RelConst::str() const noexcept if(!base && !uval) return "0"; if(spe == Bottom) - return "Bottom"; + return "⊥"; if(spe == Top) - return "Top"; + return "⊤"; std::string str; if(arg) diff --git a/lib/analysis.cpp b/lib/analysis.cpp index 537bc11..6cd8a98 100644 --- a/lib/analysis.cpp +++ b/lib/analysis.cpp @@ -6,6 +6,7 @@ //---------------------------------------------------------------------------// #include +#include #include namespace FxOS { @@ -61,6 +62,36 @@ bool ProgramState::le(ProgramState const &other) const return true; } +std::string ProgramState::str(int indentLength) const +{ + std::string indent(indentLength, ' '); + std::string str; + + /* Registers */ + for(int i = 0; i < 16; i++) { + if(i % 4 == 0) { + str += (i > 0 ? "\n" : ""); + str += indent; + } + else + str += " "; + + str += fmt::format("r{}:{}", i, m_regs[i].str()); + } + + return str + "\n"; +} + +std::string ProgramStateDiff::str() const +{ + if(m_target == static_cast(Target::None)) + return "()"; + if(m_target == static_cast(Target::Unknown)) + return "⊤"; + + return fmt::format("r{} ← {}", m_target, m_value.str()); +} + /* Information stored for each block during the fixpoint iteration */ struct BlockStates { @@ -73,6 +104,19 @@ struct BlockStates static ProgramStateDiff interpretInstruction( Instruction const &ins, ProgramState const &PS) { + RelConstDomain RCD; + ProgramStateDiff diff; + diff.setUnknown(); + + // TODO: Do this properly + u16 opc = ins.opcode().opcode; + if((opc & 0xf000) == 0xe000) { + int reg = (opc >> 8) & 0xf; + int val = (int8_t)opc; + diff.setRegisterUpdate(reg, RCD.constant(val)); + } + + return diff; } static void interpretBlock(BasicBlock const &bb, BlockStates &states) @@ -114,8 +158,8 @@ std::unique_ptr analyzeFunction(Function const &f) BasicBlock const &bb = f.basicBlockByIndex(i); VBS[i].nextEntry.setBottom(); - for(auto succ: bb.successors()) - VBS[i].nextEntry.joinWith(VBS[succ.blockIndex()].exit); + for(int succIndex: bb.successorsByIndex()) + VBS[i].nextEntry.joinWith(VBS[succIndex].exit); } /* Determine whether a fixpoint has been reached yet */ @@ -141,6 +185,4 @@ std::unique_ptr analyzeFunction(Function const &f) return an; } -// TODO_need_bb_successors; - } /* namespace FxOS */ diff --git a/lib/function.cpp b/lib/function.cpp index 2ffbba6..7b90de3 100644 --- a/lib/function.cpp +++ b/lib/function.cpp @@ -6,6 +6,7 @@ //---------------------------------------------------------------------------// #include +#include #include #include @@ -14,7 +15,8 @@ namespace FxOS { //=== Function ===// Function::Function(Binary &binary, u32 address): - BinaryObject(binary, BinaryObject::Function, address, 0) + BinaryObject(binary, BinaryObject::Function, address, 0), + m_analysisResult {nullptr} { /* Size is not determined at first. */ @@ -68,6 +70,12 @@ void Function::sortBasicBlocks() break; } } + + /* Update instruction's parent block numbers */ + for(uint i = 0; i < m_blocks.size(); i++) { + for(Instruction &ins: m_blocks[i]) + ins.setBlockContext(i, ins.indexInBlock()); + } } /* Update the function's BinaryObject size by finding the last address covered @@ -91,6 +99,11 @@ void Function::setAnalysisVersion(int version) m_analysisVersion = version; } +void Function::runAnalysis() +{ + m_analysisResult = analyzeFunction(*this); +} + /* The first step in building function CFGs is delimiting the blocks. Starting from the entry point, we generate "superblocks" by reading instructions linearly until we find a terminator. @@ -272,20 +285,25 @@ bool Function::exploreFunctionAt(u32 functionAddress) else { successorAddresses[pc].push_back(*it); } - - // TODO: Set successors } } + /* Sort blocks now before creating CFG nodes, which are index-based */ sortBasicBlocks(); + /* Set block predecessors */ for(auto &[pc, succ]: successorAddresses) { BasicBlock &bb = basicBlockByAddress(pc); for(u32 a: succ) bb.addSuccessor(&basicBlockByAddress(a)); } - // TODO: Set predecessors + /* Set block predecessors */ + for(BasicBlock &bb: *this) { + for(BasicBlock &succ: bb.successors()) { + succ.addPredecessor(&bb); + } + } updateFunctionSize(); return true; diff --git a/lib/view/assembly.cpp b/lib/view/assembly.cpp index f670fb1..14d1542 100644 --- a/lib/view/assembly.cpp +++ b/lib/view/assembly.cpp @@ -6,6 +6,7 @@ //---------------------------------------------------------------------------// #include +#include #include #include #include @@ -53,7 +54,7 @@ static void renderArgument(AsmArgument const &arg, u32 pc, int opsize, else if(arg.kind == AsmArgument::PcAddr) type = PCAddr; - u32 value; + u32 value = 0; bool hasValue = false; OS *os = opts.binary ? opts.binary->OSAnalysis() : nullptr; VirtualSpace *vspace = opts.binary ? &opts.binary->vspace() : nullptr; @@ -246,25 +247,42 @@ void viewAssemblyInstruction(Instruction const &ins, ViewAssemblyOptions *opts) int spacing = opcode.arg_count ? 8 - strlen(opcode.mnemonic) - strlen(suffix) : 0; - printf(" %s%s%*s", opcode.mnemonic, suffix, spacing, ""); + std::string str = " "; + str += opcode.mnemonic; + str += suffix; + str += std::string(spacing, ' '); /* Arguments */ for(size_t n = 0; n < opcode.arg_count; n++) { if(n) - printf(", "); + str += ", "; renderArgument(opcode.args[n], pc, opcode.opsize, argout, *opts); for(size_t i = 0; i < argout.size(); i++) { if(i != 0) - printf(" "); - printf("%s", argout[i].second.c_str()); + str += " "; + str += argout[i].second; } argout.clear(); } - printf("\n"); + if(opts->printFunctionAnalysis) { + auto *an = ins.parentFunction().getAnalysis(); + + if(an) { + auto &block = an->blocks[ins.parentBlock().blockIndex()]; + ProgramStateDiff const &diff = block.diffs[ins.indexInBlock()]; + + if(str.size() < 32) + str += std::string(32 - str.size(), ' '); + str += "# "; + str += diff.str(); + } + } + + fmt::print("{}\n", str); } static std::string objectsAt( @@ -310,25 +328,43 @@ void viewAssemblyBasicBlock(BasicBlock const &bb, ViewAssemblyOptions *opts) } printf(":\n"); - printf(" Successors:"); - for(u32 succ: bb.successorsByAddress()) - printf(" bb.%08x", succ); - if(bb.successorCount() == 0) - printf(" (none)"); - printf("\n"); + if(opts->basicBlockDetails) { + printf(" Successors:"); + for(u32 succ: bb.successorsByAddress()) + printf(" bb.%08x", succ); + if(bb.successorCount() == 0) + printf(" (none)"); + printf("\n"); - printf(" Flags:"); - if(bb.isEntryBlock()) - printf(" IsEntryBlock"); - if(bb.isTerminator()) - printf(" IsTerminator"); - if(bb.hasDelaySlot()) - printf(" HasDelaySlot"); - if(bb.hasNoTerminator()) - printf(" NoTerminator"); - if(!(bb.getFlags() & BasicBlock::ValidFlags)) - printf(" (none)"); - printf("\n"); + printf(" Predecessors:"); + for(u32 succ: bb.predecessorsByAddress()) + printf(" bb.%08x", succ); + if(bb.predecessorCount() == 0) + printf(" (none)"); + printf("\n"); + + printf(" Flags:"); + if(bb.isEntryBlock()) + printf(" IsEntryBlock"); + if(bb.isTerminator()) + printf(" IsTerminator"); + if(bb.hasDelaySlot()) + printf(" HasDelaySlot"); + if(bb.hasNoTerminator()) + printf(" NoTerminator"); + if(!(bb.getFlags() & BasicBlock::ValidFlags)) + printf(" (none)"); + printf("\n"); + } + + if(opts->printFunctionAnalysis) { + auto *an = bb.parentFunction().getAnalysis(); + if(an) { + auto &block = an->blocks[bb.blockIndex()]; + printf(" Entry state:\n"); + fmt::print("{}", block.entry.str(6)); + } + } for(Instruction const &ins: bb) viewAssemblyInstruction(ins, opts); diff --git a/shell/a.cpp b/shell/a.cpp index 51b4c73..1d70b26 100644 --- a/shell/a.cpp +++ b/shell/a.cpp @@ -5,6 +5,7 @@ #include "util.h" #include +#include #include #include #include diff --git a/shell/d.cpp b/shell/d.cpp index 5e7123e..ac34296 100644 --- a/shell/d.cpp +++ b/shell/d.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -85,8 +86,11 @@ void _d(Session &session, std::variant location) Function f(*b, address); if(f.exploreFunctionAt(address)) { + f.runAnalysis(); + ViewAssemblyOptions opts; opts.binary = b; + opts.printFunctionAnalysis = true; viewAssemblyFunction(f, &opts); } }