idakit-sys 0.2.0

Raw FFI bindings to IDA Pro's idalib runtime and the idakit C++ facade
Documentation
// Hand-written Custom bodies for the generated bytes domain (namespace gen): raw and typed byte
// reads, string-literal decode, item flags and navigation, binary pattern search, and patching.
// Throwing accessors signal failure with std::runtime_error, which cxx turns into a Rust Err. SDK
// calls that share a name with a gen:: function are ::-qualified to avoid recursing into this
// namespace; min_ea/max_ea are templated elsewhere, not here.

#include <ida.hpp>
#include <pro.h>

#include <bytes.hpp> // get_bytes, get_byte/word/dword/qword, is_loaded, is_mapped, get_flags,
                     // get_item_head, get_item_end, next_head, prev_head, get_cmt,
                     // get_strlit_contents, get_max_strlit_length, parse_binpat_str, bin_search,
                     // patch_bytes, compiled_binpat_vec_t, GMB_READALL, ALOPT_IGNHEADS,
                     // STRCONV_REPLCHAR, PBSENC_DEF1BPU, BIN_SEARCH_NOBREAK, BIN_SEARCH_NOSHOW

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

#include "gen_bytes.h"
// The generated bridge header defines the shared structs (BinpatStats needs its full definition to
// construct below); gen_bytes.h only forward-declares them.
#include "gen_bridge.h"
// idakit_get_bytes below is a raw extern "C" declared here, not a cxx-generated fn.
#include "abi.h"

namespace gen {

namespace {
// A typed read must fail cleanly on a partly-mapped range, so gate on every covered byte.
bool range_loaded(ea_t addr, size_t n) {
  for (size_t i = 0; i < n; i++)
    if (!is_loaded(addr + i))
      return false;
  return true;
}
} // namespace

// `size` bytes read from addr into an owned buffer; throws if any part of the range is unmapped.
rust::Vec<uint8_t> get_bytes(uint64_t addr, size_t size) {
  std::vector<uint8_t> buf(size);
  int64_t r = static_cast<int64_t>(
      ::get_bytes(buf.data(), static_cast<ssize_t>(size), static_cast<ea_t>(addr), GMB_READALL));
  if (r < 0)
    throw std::runtime_error("get_bytes failed: unmapped range");
  return to_rust_bytes(buf.data(), size);
}

// Byte at addr; throws when it is uninitialized/unmapped.
uint8_t get_u8(uint64_t addr) {
  if (!range_loaded(static_cast<ea_t>(addr), 1))
    throw std::runtime_error("get_u8: byte uninitialized");
  return static_cast<uint8_t>(get_byte(static_cast<ea_t>(addr)));
}

// 16-bit value at addr; throws when either covered byte is uninitialized.
uint16_t get_u16(uint64_t addr) {
  if (!range_loaded(static_cast<ea_t>(addr), 2))
    throw std::runtime_error("get_u16: covered byte uninitialized");
  return static_cast<uint16_t>(get_word(static_cast<ea_t>(addr)));
}

// 32-bit value at addr; throws when any covered byte is uninitialized.
uint32_t get_u32(uint64_t addr) {
  if (!range_loaded(static_cast<ea_t>(addr), 4))
    throw std::runtime_error("get_u32: covered byte uninitialized");
  return static_cast<uint32_t>(get_dword(static_cast<ea_t>(addr)));
}

// 64-bit value at addr; throws when any covered byte is uninitialized.
uint64_t get_u64(uint64_t addr) {
  if (!range_loaded(static_cast<ea_t>(addr), 8))
    throw std::runtime_error("get_u64: covered byte uninitialized");
  return static_cast<uint64_t>(get_qword(static_cast<ea_t>(addr)));
}

// Decoded contents of the string literal at addr; throws when there is no literal there or it
// can't be decoded.
rust::String get_strlit(uint64_t addr, int32_t strtype) {
  size_t len = get_max_strlit_length(static_cast<ea_t>(addr), strtype, ALOPT_IGNHEADS);
  if (len == 0)
    throw std::runtime_error("no string literal at address");
  qstring out;
  ssize_t r =
      get_strlit_contents(&out, static_cast<ea_t>(addr), len, strtype, nullptr, STRCONV_REPLCHAR);
  if (r < 0)
    throw std::runtime_error("undecodable string literal");
  return to_rust_string(out);
}

// The flags word for addr: byte/data/code kind, name presence, and the other per-byte bits.
uint64_t get_flags(uint64_t addr) {
  return static_cast<uint64_t>(::get_flags(static_cast<ea_t>(addr)));
}

// Start address of the item (instruction or data) containing addr.
uint64_t get_item_head(uint64_t addr) {
  return static_cast<uint64_t>(::get_item_head(static_cast<ea_t>(addr)));
}

// Address just past the item (instruction or data) containing addr.
uint64_t get_item_end(uint64_t addr) {
  return static_cast<uint64_t>(::get_item_end(static_cast<ea_t>(addr)));
}

// Next defined item head after addr, capped at max_addr; BADADDR when there is none.
uint64_t get_next_head(uint64_t addr, uint64_t max_addr) {
  return static_cast<uint64_t>(next_head(static_cast<ea_t>(addr), static_cast<ea_t>(max_addr)));
}

// Previous defined item head before addr, capped at min_addr; BADADDR when there is none.
uint64_t get_prev_head(uint64_t addr, uint64_t min_addr) {
  return static_cast<uint64_t>(prev_head(static_cast<ea_t>(addr), static_cast<ea_t>(min_addr)));
}

// Comment at addr (repeatable when rptble is set); throws when there is none.
rust::String get_cmt(uint64_t addr, bool rptble) {
  qstring out;
  if (::get_cmt(&out, static_cast<ea_t>(addr), rptble) < 0)
    throw std::runtime_error("no comment at address");
  return to_rust_string(out);
}

// Compiles a textual byte pattern (e.g. "90 ?? 8B") into a searchable pattern; throws on a parse
// error, using the SDK's own message when it has one.
std::unique_ptr<::compiled_binpat_vec_t> binpat_compile(uint64_t addr, rust::Str pattern,
                                                        int32_t radix) {
  auto out = std::make_unique<compiled_binpat_vec_t>();
  std::string pat(pattern.data(), pattern.size());
  qstring err;
  if (!parse_binpat_str(out.get(), static_cast<ea_t>(addr), pat.c_str(), radix, PBSENC_DEF1BPU,
                        &err))
    throw std::runtime_error(err.empty() ? "unparseable pattern" : err.c_str());
  return out;
}

// Builds a searchable pattern directly from literal bytes and an optional wildcard mask; an
// empty mask means every byte must match exactly.
std::unique_ptr<::compiled_binpat_vec_t> binpat_from_bytes(rust::Slice<const uint8_t> bytes,
                                                           rust::Slice<const uint8_t> mask) {
  auto out = std::make_unique<compiled_binpat_vec_t>();
  compiled_binpat_t &b = out->push_back();
  b.bytes.append(bytes.data(), bytes.size());
  if (!mask.empty())
    b.mask.append(mask.data(), mask.size());
  return out;
}

// Summarizes a compiled pattern's total length and how many bytes in it are concrete
// (non-wildcard).
BinpatStats binpat_stats(const ::compiled_binpat_vec_t &pat) {
  BinpatStats out{};
  if (!pat.empty()) {
    const compiled_binpat_t &b = pat.front();
    out.total = b.bytes.size();
    if (b.mask.empty()) {
      out.anchors = out.total;
    } else {
      for (size_t i = 0; i < b.mask.size(); i++)
        if (b.mask[i] != 0)
          out.anchors++;
    }
  }
  return out;
}

// First match of pat in [start, end); returns BADADDR when nothing matches.
uint64_t bin_search(uint64_t start, uint64_t end, const ::compiled_binpat_vec_t &pat,
                    int32_t flags) {
  // NOBREAK/NOSHOW are mandatory headless: no Ctrl-Break polling, no UI progress.
  ea_t hit = ::bin_search(static_cast<ea_t>(start), static_cast<ea_t>(end), pat,
                          flags | BIN_SEARCH_NOBREAK | BIN_SEARCH_NOSHOW);
  return static_cast<uint64_t>(hit);
}

// Overwrites bytes at addr with data; returns false without writing anything if the range
// overflows the address space or any target byte is unmapped.
bool patch_bytes(uint64_t addr, rust::Slice<const uint8_t> bytes) {
  if (bytes.empty())
    return true;
  // Reject a write whose last byte wraps past 2^64: unsigned `addr + i` would otherwise fold to a
  // low address and silently patch the wrong region.
  if (addr + (bytes.size() - 1) < addr)
    return false;
  // Reject the whole write if any target byte is outside the address space, so a bad address fails
  // cleanly instead of silently patching a truncated prefix.
  for (size_t i = 0; i < bytes.size(); i++)
    if (!is_mapped(static_cast<ea_t>(addr) + i))
      return false;
  ::patch_bytes(static_cast<ea_t>(addr), bytes.data(), bytes.size());
  return true;
}

} // namespace gen

// Zero-copy read of size bytes at addr into a caller-owned buffer; returns the byte count read,
// or a negative value if the range is unmapped. A plain extern "C" companion to gen::get_bytes.
extern "C" int64_t idakit_get_bytes(uint64_t addr, void *buf, size_t size) {
  return static_cast<int64_t>(
      get_bytes(buf, static_cast<ssize_t>(size), static_cast<ea_t>(addr), GMB_READALL));
}