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>
140 lines
4.3 KiB
C++
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;
|
|
}
|