idakit-sys 0.2.0

Raw FFI bindings to IDA Pro's idalib runtime and the idakit C++ facade
Documentation
// cxx extern "Rust" opaque-visitor type walk (namespace bridge). visit_walker_t (declared in
// type_walker.h) does a depth-first tinfo_t recursion guarded by a placeholder plus a
// `defined`-set dedup, so a self-referential type resolves instead of looping. It emits through the
// extern "Rust" opaque visitor's member functions (vis->scalar(...), vis->named_ref(...),
// vis->fill_struct(...)) that cxx generates, not a C function-pointer table. Type names and
// struct-member names cross as owned rust::String, decoded leniently (IDA emits UTF-8, undecodable
// units become U+FFFD); arrays cross as rust::Slice.

#include <pro.h>

#include <ida.hpp>

#include <frame.hpp>   // get_func_frame, get_frame_size, soff_to_fpoff
#include <funcs.hpp>   // get_func
#include <typeinf.hpp> // tinfo_t and the *_type_data_t detail structs; get_tinfo, get_idati

#include <set>
#include <stdexcept>
#include <string>
#include <vector>

#include "type_walker.h"
#include "typewalk_bridge.h"
// The generated visitor-bridge header defines the TypeWalkVisitor class (its member functions) and
// the MemberInfo / EnumConstInfo / FrameVar / FrameWalk shared structs. OUT_DIR is on this TU's
// include path; the ctree walk drives visit_walker_t only through the opaque handle in
// type_walker.h.
#include "gen_facade_consts.h" // gen::NONE
#include "gen_visitors.h"

namespace bridge {

namespace {

// Lenient-decode a type name into an owning rust::String for the one visitor call. IDA emits UTF-8,
// so lossy never rejects, yet a stray bad byte degrades to U+FFFD instead of unwinding.
rust::String lossy_str(const qstring &s) { return rust::String::lossy(s.c_str(), s.length()); }

// Same lossy decode, for a name materialized as a raw C string rather than a qstring.
rust::String lossy_str(const char *p, size_t n) { return rust::String::lossy(p, n); }

// Whether a value_repr_t FRB_* nibble falls in idakit's modeled numeric subset (binary, octal,
// hex, decimal, char); false for FRB_UNK and every info-carrying/float/segment nibble.
bool is_modeled_frb_vtype(uint64 vtype) {
  switch (vtype) {
  case FRB_NUMB:
  case FRB_NUMO:
  case FRB_NUMH:
  case FRB_NUMD:
  case FRB_CHAR:
    return true;
  default:
    return false;
  }
}

// TypeWalkVisitor::scalar's `kind` tag: opaque/unknown, void, bool, integral, floating.
constexpr uint32_t SCALAR_UNKNOWN = 0;
constexpr uint32_t SCALAR_VOID = 1;
constexpr uint32_t SCALAR_BOOL = 2;
constexpr uint32_t SCALAR_INTEGRAL = 3;
constexpr uint32_t SCALAR_FLOAT = 4;

// FrameVar flag bits: reserved return-address slot, reserved saved-registers slot.
constexpr uint32_t FRAME_VAR_RETADDR = 1u;
constexpr uint32_t FRAME_VAR_SAVED_REGS = 2u;

} // namespace

// Full definition of the walker the opaque handle in type_walker.h fronts. Holds the visitor
// by pointer so the ctree walk can create one and rebind it per walk.
struct visit_walker_t {
  TypeWalkVisitor *vis = nullptr;
  std::set<std::string> defined; // named types already filled (recursion + dedup guard)

  uint32_t ty(const tinfo_t &t);
  uint32_t ty_resolved(const tinfo_t &t);
  uint32_t placeholder(const tinfo_t &t, bool *first);
  uint32_t ty_udt(const tinfo_t &t, uint64_t size, uint32_t has_size);
  uint32_t ty_enum(const tinfo_t &t, uint64_t size, uint32_t has_size);
  uint32_t ty_typedef(const tinfo_t &t);
  uint32_t ty_func(const tinfo_t &t);
  uint32_t ty_bitfield(const tinfo_t &t);
  uint32_t ty_opaque(const tinfo_t &t);
};

// Walk any tinfo_t into a handle, peeling off one typedef indirection before resolving.
uint32_t visit_walker_t::ty(const tinfo_t &t) {
  if (!t.empty() && t.is_typedef())
    return ty_typedef(t);
  return ty_resolved(t);
}

// Dispatch a non-typedef tinfo_t to its kind-specific walker by the SDK's own predicates,
// falling back to ty_opaque for anything this facade doesn't model.
uint32_t visit_walker_t::ty_resolved(const tinfo_t &t) {
  size_t sz = t.get_size();
  uint32_t has_size = (sz != BADSIZE && sz != 0) ? 1 : 0;
  uint64_t size = has_size ? static_cast<uint64_t>(sz) : 0;
  uint32_t bytes = has_size ? static_cast<uint32_t>(sz) : 0;

  if (t.empty())
    return ty_opaque(t);
  if (t.is_ptr())
    return vis->ptr(ty(t.get_pointed_object()), size, has_size);
  if (t.is_array())
    return vis->array(ty(t.get_array_element()), static_cast<uint64_t>(t.get_array_nelems()), size,
                      has_size);
  if (t.is_func())
    return ty_func(t);
  if (t.is_udt())
    return ty_udt(t, size, has_size);
  if (t.is_enum())
    return ty_enum(t, size, has_size);
  if (t.is_bool())
    return vis->scalar(SCALAR_BOOL, 0, 0, size, has_size);
  if (t.is_void())
    return vis->scalar(SCALAR_VOID, 0, 0, size, has_size);
  if (t.is_floating())
    return vis->scalar(SCALAR_FLOAT, bytes, 0, size, has_size);
  if (t.is_integral())
    return vis->scalar(SCALAR_INTEGRAL, bytes, t.is_signed() ? 1 : 0, size, has_size);
  if (t.is_bitfield())
    return ty_bitfield(t);
  return ty_opaque(t);
}

// A bitfield member's scalar shape (width, signedness); falls back to ty_opaque if the SDK
// can't report its details.
uint32_t visit_walker_t::ty_bitfield(const tinfo_t &t) {
  bitfield_type_data_t bi;
  if (t.get_bitfield_details(&bi)) {
    uint32_t nb = static_cast<uint32_t>(bi.nbytes);
    return vis->scalar(SCALAR_INTEGRAL, nb, bi.is_unsigned ? 0 : 1, nb, nb != 0 ? 1 : 0);
  }
  return ty_opaque(t);
}

// Last-resort emission for a type this facade can't structure: its declared name, else its
// printed representation, else a bare "?".
uint32_t visit_walker_t::ty_opaque(const tinfo_t &t) {
  qstring nm;
  if (t.get_type_name(&nm) && !nm.empty())
    return vis->opaque(lossy_str(nm));
  if (t.print(&nm) && !nm.empty())
    return vis->opaque(lossy_str(nm));
  return vis->opaque(lossy_str("?", 1));
}

// Mint a placeholder id for t before recursing into its body, so a self-referential type's cycle
// resolves against an id that already exists; `*first` says whether t's name is newly seen.
uint32_t visit_walker_t::placeholder(const tinfo_t &t, bool *first) {
  qstring nm;
  if (t.get_type_name(&nm) && !nm.empty()) {
    uint32_t id = vis->named_ref(lossy_str(nm));
    *first = defined.insert(std::string(nm.c_str(), nm.length())).second;
    return id;
  }
  *first = true;
  return vis->anon();
}

// Struct/union: mint the placeholder id, then on first sight only, walk its members and fill
// the body through fill_struct.
uint32_t visit_walker_t::ty_udt(const tinfo_t &t, uint64_t size, uint32_t has_size) {
  bool first;
  uint32_t id = placeholder(t, &first);
  if (first) {
    udt_type_data_t udt;
    // Handles must be minted before the members slice is built (the members carry them), and `ms`
    // must outlive the fill_struct call its slice points into, so keep both alive across the call.
    std::vector<MemberInfo> ms;
    if (t.get_udt_details(&udt)) {
      ms.reserve(udt.size());
      for (const udm_t &m : udt) {
        MemberInfo md;
        md.name = rust::String::lossy(std::string(m.name.c_str(), m.name.length()));
        md.bit_offset = m.offset;
        md.ty = ty(m.type);
        md.bitfield_width = m.is_bitfield() ? static_cast<uint32_t>(m.size) : 0;
        uint64 vtype = m.repr.get_vtype();
        bool modeled = is_modeled_frb_vtype(vtype);
        md.repr_vtype = modeled ? static_cast<uint32_t>(vtype) : 0;
        md.repr_signed = modeled && m.repr.is_signed();
        md.repr_leading_zeros = modeled && m.repr.has_lzeroes();
        ms.push_back(md);
      }
    }
    rust::Slice<const MemberInfo> slice = ms.empty()
                                              ? rust::Slice<const MemberInfo>()
                                              : rust::Slice<const MemberInfo>(ms.data(), ms.size());
    vis->fill_struct(id, t.is_union(), slice, size, has_size);
  }
  return id;
}

// Enum: mint the placeholder id, then on first sight only, walk its constants and fill the body
// through fill_enum.
uint32_t visit_walker_t::ty_enum(const tinfo_t &t, uint64_t size, uint32_t has_size) {
  bool first;
  uint32_t id = placeholder(t, &first);
  if (first) {
    enum_type_data_t ed;
    std::vector<EnumConstInfo> cs;
    bool sgn = false;
    if (t.get_enum_details(&ed)) {
      sgn = ed.is_number_signed();
      cs.reserve(ed.size());
      for (const edm_t &m : ed) {
        EnumConstInfo ec;
        ec.name = rust::String::lossy(std::string(m.name.c_str(), m.name.length()));
        ec.value = m.value;
        cs.push_back(ec);
      }
    }
    uint32_t base_bytes = has_size ? static_cast<uint32_t>(size) : 4;
    uint32_t underlying = vis->scalar(SCALAR_INTEGRAL, base_bytes, sgn ? 1 : 0, size, has_size);
    rust::Slice<const EnumConstInfo> slice =
        cs.empty() ? rust::Slice<const EnumConstInfo>()
                   : rust::Slice<const EnumConstInfo>(cs.data(), cs.size());
    value_repr_t repr;
    bool modeled = t.get_enum_repr(&repr) == TERR_OK && is_modeled_frb_vtype(repr.get_vtype());
    uint32_t repr_vtype = modeled ? static_cast<uint32_t>(repr.get_vtype()) : 0;
    bool repr_signed = modeled && repr.is_signed();
    bool repr_leading_zeros = modeled && repr.has_lzeroes();
    vis->fill_enum(id, underlying, slice, size, has_size, t.is_bitmask_enum(), repr_vtype,
                   repr_signed, repr_leading_zeros);
  }
  return id;
}

// Typedef: mint the placeholder id, then on first sight only, resolve one step to the next named
// type in the chain (or straight to the resolved type if there is none) and fill_typedef it.
uint32_t visit_walker_t::ty_typedef(const tinfo_t &t) {
  bool first;
  uint32_t id = placeholder(t, &first);
  if (first) {
    qstring next;
    tinfo_t und;
    uint32_t under;
    if (t.get_next_type_name(&next) &&
        und.get_named_type(get_idati(), next.c_str(), BTF_TYPEDEF, false))
      under = ty(und);
    else
      under = ty_resolved(t);
    vis->fill_typedef(id, under);
  }
  return id;
}

// A function type's return and parameter handles; falls back to an unknown-scalar return with no
// params if the SDK can't report the signature's details.
uint32_t visit_walker_t::ty_func(const tinfo_t &t) {
  func_type_data_t fd;
  std::vector<uint32_t> params;
  uint32_t ret;
  uint32_t vararg = 0;
  if (t.get_func_details(&fd)) {
    ret = ty(fd.rettype);
    params.reserve(fd.size());
    for (const funcarg_t &a : fd)
      params.push_back(ty(a.type));
    vararg = fd.is_vararg_cc() ? 1 : 0;
  } else {
    ret = vis->scalar(SCALAR_UNKNOWN, 0, 0, 0, 0);
  }
  rust::Slice<const uint32_t> slice =
      params.empty() ? rust::Slice<const uint32_t>()
                     : rust::Slice<const uint32_t>(params.data(), params.size());
  return vis->func(ret, slice, vararg);
}

// Create a walker bound to the opaque TypeWalkVisitor* at `visitor`, owned by the caller through
// type_walker.h's handle until visit_walker_free.
visit_walker_t *visit_walker_new(void *visitor) {
  visit_walker_t *walker = new visit_walker_t;
  walker->vis = reinterpret_cast<TypeWalkVisitor *>(visitor);
  return walker;
}

// Walk one tinfo_t through an existing walker, reusing its `defined` dedup set.
uint32_t visit_walker_ty(visit_walker_t *walker, const tinfo_t &t) { return walker->ty(t); }

// Release a walker created by visit_walker_new.
void visit_walker_free(visit_walker_t *walker) { delete walker; }

// Look up a named type by spelling and walk it into a fresh, single-use walker.
uint32_t type_walk_visit_named(rust::Str name, TypeWalkVisitor &visitor) {
  tinfo_t tif;
  // rust::Str is not NUL-terminated; get_named_type wants a C string, so materialize one.
  std::string nm(name.data(), name.size());
  if (!tif.get_named_type(get_idati(), nm.c_str()))
    throw std::runtime_error("no such named type");
  visit_walker_t walker;
  walker.vis = &visitor;
  return walker.ty(tif);
}

// Look up a type by its local type library ordinal and walk it into a fresh, single-use walker.
uint32_t type_walk_visit_ordinal(uint32_t ordinal, TypeWalkVisitor &visitor) {
  tinfo_t tif;
  if (!tif.get_numbered_type(get_idati(), ordinal))
    throw std::runtime_error("no type at ordinal");
  visit_walker_t walker;
  walker.vis = &visitor;
  return walker.ty(tif);
}

// Read a function's declared type at addr and walk it into a fresh, single-use walker.
uint32_t func_type_walk_visit(uint64_t addr, TypeWalkVisitor &visitor) {
  tinfo_t tif;
  if (!get_tinfo(&tif, static_cast<ea_t>(addr)) || tif.empty())
    throw std::runtime_error("function has no type info");
  visit_walker_t walker;
  walker.vis = &visitor;
  return walker.ty(tif);
}

// Walk the function at addr's stack frame member by member into a FrameWalk, resolving each
// real variable's type through a fresh walker (reserved slots and untyped members skip it).
FrameWalk frame_type_walk_visit(uint64_t addr, TypeWalkVisitor &visitor) {
  func_t *pfn = get_func(static_cast<ea_t>(addr));
  if (pfn == nullptr)
    throw std::runtime_error("no function at ea");
  tinfo_t tif;
  udt_type_data_t udt;
  if (!get_func_frame(&tif, pfn) || !tif.get_udt_details(&udt))
    throw std::runtime_error("function has no frame");

  FrameWalk out;
  out.size = static_cast<uint64_t>(get_frame_size(pfn));
  visit_walker_t walker;
  walker.vis = &visitor;
  for (const udm_t &m : udt) {
    // Both clear = an ordinary variable/argument.
    uint32_t flags =
        (m.is_retaddr() ? FRAME_VAR_RETADDR : 0u) | (m.is_savregs() ? FRAME_VAR_SAVED_REGS : 0u);
    // Only a real, typed variable carries a structured type; reserved and untyped slots report
    // NONE, so the table holds only types a variable references.
    uint32_t ty = (flags == 0 && !m.type.empty()) ? walker.ty(m.type) : gen::NONE;
    FrameVar fv;
    fv.name = rust::String::lossy(std::string(m.name.c_str(), m.name.length()));
    // udm offset/size are in bits; soff_to_fpoff wants the byte struct offset.
    fv.offset = static_cast<int64_t>(soff_to_fpoff(pfn, static_cast<uval_t>(m.offset / 8)));
    fv.size = m.size / 8;
    fv.flags = flags;
    fv.ty = ty;
    out.vars.push_back(std::move(fv));
  }
  return out;
}

} // namespace bridge