fold/tools/pcode_dump.cpp
trvon bd1f289f06
feat(binary): dual-mode Ghidra lifter (raw + SSA) -> fold::ir::pcode (Phase 2)
Phase 2 of the Ghidra P-Code binary frontend. Adds the frontend-agnostic
P-Code IR and a dual-mode lifter, validated end-to-end.

- include/fold/ir/pcode.hpp: the ~25-op core P-Code IR (Op/Space/Varnode/
  PcodeOp/Function), no Ghidra/clang types (M30 invariant). Function carries a
  Source tag (Raw|SSA) so findings record provenance.
- include/fold/binary/ghidra_lifter.hpp + src/binary/ghidra_lifter.cpp
  (Ghidra types confined): a buffer LoadImage (no BFD) + two entry points
  producing the same ir::pcode::Function:
    * liftRaw  — Sleigh::oneInstruction, per-instruction RAW P-Code;
    * liftSSA  — SleighArchitecture + Funcdata + action pipeline, SSA high
      P-Code (needs CapabilityPoint::initializeAll + whole-archive link).
  Both serialized under one mutex (decompiler global state).
- meson: libghidra_decompiler gains the arch/loader infra (sleigh_arch,
  inject_sleigh, loadimage_xml, xml_arch, raw_arch); dep switched to
  link_whole so capability self-registration constructors run; pcode_lift
  smoke driver added (built at fold's std, links the c++11 lib).

Empirical validation on arm64 sym._div_by_zero_direct confirms the dual-path
rationale: RAW preserves `(w9)=COPY const 0` + `INT_SDIV(w8,w9)` (the hazard),
while SSA folds it to `COPY const 0` (ARM64 sdiv-by-0 returns 0; Ghidra's
optimizer constant-folds x/0 -> 0). The two levels are complementary streams
into the Z3 core (next phases): raw for div/overflow/null-deref, SSA for
UAF/dataflow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 19:39:21 -04:00

140 lines
4.3 KiB
C++

// Phase-1 smoke tool for the vendored Ghidra decompiler.
//
// Lifts a raw machine-code buffer to Ghidra P-Code using a compiled .sla and
// prints disassembly + P-Code per instruction. This validates the vendored
// engine builds, the .sla loads, and lifting works end-to-end — before any
// fold-side IR/encoder glue (Phase 2+). Bytes are served via a custom
// LoadImage (no BFD): fold's loader contract.
//
// pcode_dump <path/to.sla> <base_vaddr_hex> <raw_bytes_file>
//
#include <fstream>
#include <iostream>
#include <sstream>
#include <vector>
#include "loadimage.hh"
#include "sleigh.hh"
using namespace ghidra;
namespace {
// Serves a flat byte buffer at a fixed base address; zero-fills outside it.
class BufferLoadImage : public LoadImage {
uintb base_;
std::vector<uint1> data_;
public:
BufferLoadImage(uintb base, std::vector<uint1> data)
: LoadImage("nofile"), base_(base), data_(std::move(data)) {}
void loadFill(uint1* ptr, int4 size, const Address& addr) override {
uintb start = addr.getOffset();
for (int4 i = 0; i < size; ++i) {
uintb off = start + i;
ptr[i] = (off >= base_ && off < base_ + data_.size()) ? data_[off - base_] : 0;
}
}
std::string getArchType() const override { return "buffer"; }
void adjustVma(long) override {}
uintb end() const { return base_ + data_.size(); }
};
class AsmPrinter : public AssemblyEmit {
public:
void dump(const Address& addr, const std::string& mnem, const std::string& body) override {
addr.printRaw(std::cout);
std::cout << ": " << mnem << ' ' << body << '\n';
}
};
void printVar(std::ostream& s, const VarnodeData& v) {
s << '(' << v.space->getName() << ',';
v.space->printOffset(s, v.offset);
s << ',' << std::dec << v.size << ')';
}
class PcodePrinter : public PcodeEmit {
public:
void dump(const Address&, OpCode opc, VarnodeData* outvar, VarnodeData* vars,
int4 isize) override {
std::cout << " ";
if (outvar != nullptr) {
printVar(std::cout, *outvar);
std::cout << " = ";
}
std::cout << get_opname(opc);
for (int4 i = 0; i < isize; ++i) {
std::cout << ' ';
printVar(std::cout, vars[i]);
}
std::cout << '\n';
}
};
} // namespace
int main(int argc, char** argv) {
if (argc != 4) {
std::cerr << "usage: " << argv[0] << " <path.sla> <base_vaddr_hex> <raw_bytes_file>\n";
return 2;
}
const std::string sla_path = argv[1];
const uintb base = std::strtoull(argv[2], nullptr, 16);
std::ifstream bf(argv[3], std::ios::binary);
if (!bf) {
std::cerr << "cannot open bytes file: " << argv[3] << '\n';
return 2;
}
std::vector<uint1> bytes((std::istreambuf_iterator<char>(bf)),
std::istreambuf_iterator<char>());
if (bytes.empty()) {
std::cerr << "empty bytes file\n";
return 2;
}
AttributeId::initialize();
ElementId::initialize();
BufferLoadImage loader(base, bytes);
ContextInternal context;
Sleigh trans(&loader, &context);
std::istringstream slatag("<sleigh>" + sla_path + "</sleigh>");
DocumentStorage docstorage;
Element* root = docstorage.parseDocument(slatag)->getRoot();
docstorage.registerTag(root);
try {
trans.initialize(docstorage);
} catch (const LowlevelError& e) {
std::cerr << "sleigh init failed: " << e.explain << '\n';
return 1;
}
AsmPrinter asmp;
PcodePrinter pcodep;
Address addr(trans.getDefaultCodeSpace(), base);
Address last(trans.getDefaultCodeSpace(), loader.end());
int instrs = 0;
while (addr < last) {
try {
trans.printAssembly(asmp, addr);
int4 len = trans.oneInstruction(pcodep, addr);
if (len <= 0)
break;
addr = addr + len;
++instrs;
} catch (const BadDataError& e) {
std::cerr << "bad data at ";
addr.printRaw(std::cerr);
std::cerr << ": " << e.explain << '\n';
break;
} catch (const LowlevelError& e) {
std::cerr << "lift error: " << e.explain << '\n';
break;
}
}
std::cerr << "[pcode_dump] lifted " << instrs << " instruction(s)\n";
return 0;
}