use idakit::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let db = std::env::args()
.nth(1)
.expect("usage: edits <path/to/database.i64>");
Ida::run(move |ida| -> Result<(), Error> {
ida.call(move |idb| -> Result<(), Error> {
idb.open(&db).call()?;
let entry = idb
.functions()
.next()
.expect("the database has a function")
.address();
name_and_comment(idb, entry)?;
function_types(idb, entry)?;
signature_surgery(idb, entry)?;
defining_types(idb)?;
editing_members(idb)?;
building_types(idb, entry);
classifier_and_errors(idb, entry);
idb.close(false);
println!("\nEDITS OK");
Ok(())
})?
})??;
Ok(())
}
fn name_and_comment(idb: &mut Database, ea: Address) -> Result<(), Error> {
println!("== at_mut: rename + comment + patch (one read-capable cursor) ==");
let original_name = idb.at(ea).name();
println!(" {ea:#x} name before: {original_name:?}");
{
let mut loc = idb.at_mut(ea);
loc.rename("idakit_edit_demo")?;
loc.set_comment("renamed by the edits example", false)?;
loc.set_comment("shown at every xref", true)?;
println!(" name after: {:?}", loc.name());
println!(" regular cmt: {:?}", loc.comment());
println!(" repeatable cmt: {:?}", loc.repeatable_comment());
}
let original_bytes = idb.at(ea).bytes(4);
let flipped: Vec<u8> = original_bytes.iter().map(|b| !b).collect();
{
let mut loc = idb.at_mut(ea);
loc.patch(&flipped)?;
println!(
" patched 4 bytes, read-back matches: {}",
loc.bytes(4) == flipped
);
loc.patch(&original_bytes)?;
}
if let Some(name) = original_name {
idb.at_mut(ea).rename(name)?;
}
Ok(())
}
fn function_types(idb: &mut Database, ea: Address) -> Result<(), Error> {
println!("\n== function_mut: prototypes ==");
println!(" prototype before: {:?}", idb.function(ea).prototype());
if let Some(mut f) = idb.function_mut(ea) {
f.set_type("int edits_probe(int a, int b)")?;
}
println!(" prototype after: {:?}", idb.function(ea).prototype());
idb.with_function_mut(ea, |f| f.set_type("void edits_probe(void)"))
.transpose()?;
println!(" prototype now: {:?}", idb.function(ea).prototype());
let nowhere = Address::new_const(0xffff_ffff_f000);
println!(
" function_mut(unmapped) is_some: {}",
idb.function_mut(nowhere).is_some()
);
idb.at_mut(ea).clear_type()?;
println!(
" prototype after clear: {:?}",
idb.function(ea).prototype()
);
Ok(())
}
fn signature_surgery(idb: &mut Database, ea: Address) -> Result<(), Error> {
println!("\n== function_mut: signature surgery ==");
if let Some(mut f) = idb.function_mut(ea) {
f.set_type("int surgery_probe(int a, int b)")?;
}
println!(" seeded: {:?}", idb.function(ea).prototype());
if let Some(mut f) = idb.function_mut(ea) {
f.set_return_type(expr::char_().pointer())?; f.set_arg_type(0, expr::decl("unsigned int"))?; f.rename_arg(1, "count")?; f.prepend_this(expr::void().pointer())?; f.set_calling_convention(CallingConvention::Cdecl)?;
}
println!(" after surgery: {:?}", idb.function(ea).prototype());
if let Some(mut f) = idb.function_mut(ea)
&& let Err(Error::TypeWrite { source }) = f.set_arg_type(99, expr::int32())
{
println!(" set_arg_type(99, ..) -> TypeWrite: {source}");
}
Ok(())
}
fn defining_types(idb: &mut Database) -> Result<(), Error> {
println!("\n== types_mut: define ==");
idb.types_mut()
.define("struct edit_demo_t { int id; char *name; };")?;
let present = idb.named_types().any(|t| t.name() == "edit_demo_t");
println!(" defined edit_demo_t, present in named types: {present}");
match idb
.types_mut()
.define("typedef struct edit_demo_t edit_demo_alias;")
{
Ok(()) => println!(" typedef alias to it: ok"),
Err(e) => println!(" typedef alias: {e}"),
}
match idb.types_mut().define("struct broken { not valid") {
Err(Error::TypeDefineFailed { reason, .. }) => {
println!(" malformed define -> TypeDefineFailed: {reason}");
}
other => println!(" malformed define -> unexpected {other:?}"),
}
Ok(())
}
fn editing_members(idb: &mut Database) -> Result<(), Error> {
use idakit::types::expr;
println!("\n== types_mut: edit members ==");
idb.types_mut()
.define("struct edit_member_demo { int a; int b; };")?;
idb.types_mut()
.edit("edit_member_demo")
.add_member("c", expr::int32())?;
idb.types_mut()
.edit("edit_member_demo")
.member("a")
.set_type(expr::char_())?;
idb.types_mut()
.edit("edit_member_demo")
.member("b")
.rename("beta")?;
let names: Vec<String> = idb
.type_named("edit_member_demo")?
.members()
.unwrap_or_default()
.iter()
.map(|m| m.name.clone())
.collect();
println!(" after add + retype + rename, members: {names:?}");
match idb
.types_mut()
.edit("edit_member_demo")
.member("c")
.rename("a")
{
Err(Error::TypeWrite {
source: TypeWriteError::Rejected { code, .. },
}) => println!(" duplicate rename -> Rejected({code})"),
other => println!(" duplicate rename -> unexpected {other:?}"),
}
idb.types_mut()
.define("enum edit_enum_demo { DEMO_A = 1, DEMO_B = 2 };")?;
idb.types_mut()
.edit("edit_enum_demo")
.add_constant("DEMO_C", 3)?;
idb.types_mut()
.edit("edit_enum_demo")
.constant("DEMO_A")
.set_value(10)?;
let constants: Vec<(String, u64)> = match idb.type_named("edit_enum_demo")?.shape() {
TypeShape::Enum { members, .. } => {
members.iter().map(|m| (m.name.clone(), m.value)).collect()
}
_ => Vec::new(),
};
println!(" after add + revalue, constants: {constants:?}");
Ok(())
}
fn building_types(idb: &mut Database, ea: Address) {
println!("\n== TypeExpr builder: compose + apply ==");
let uint_array = expr::uint32().array(4); let ptr_to_named = expr::named("edit_demo_t").pointer(); let const_ptr = expr::int32().const_().pointer(); println!(" uint32().array(4) -> {uint_array:<14} ({uint_array:?})");
println!(" named(\"edit_demo_t\").pointer() -> {ptr_to_named}");
println!(" int32().const_().pointer() -> {const_ptr}");
println!(
" (edit_demo_t *).deref() == named: {}",
ptr_to_named.clone().deref() == expr::named("edit_demo_t")
);
report(
"set_type(uint32().array(4))",
idb.at_mut(ea).set_type(uint_array),
);
report(
"set_type(named(..).pointer())",
idb.at_mut(ea).set_type(ptr_to_named),
);
report(
"set_type(named(\"no_such\").pointer())",
idb.at_mut(ea)
.set_type(expr::named("no_such_zzz").pointer()),
);
let proto = expr::function(expr::int32())
.arg(expr::int32())
.named_arg("flags", expr::uint32())
.variadic()
.build();
println!(" function(int32).arg(int32).named_arg(\"flags\", uint32).variadic -> {proto}");
report(
"function_mut(entry).set_type(built prototype)",
idb.function_mut(ea)
.map_or(Ok(()), |mut f| f.set_type(proto)),
);
}
fn classifier_and_errors(idb: &mut Database, ea: Address) {
println!("\n== the &str classifier + type errors ==");
println!(" \"edit_demo_t\" -> {:?}", TypeExpr::from("edit_demo_t"));
println!(
" \"edit_demo_t *\" -> {:?}",
TypeExpr::from("edit_demo_t *")
);
println!(
" \"int\" -> {:?} (a keyword: parsed, not looked up)",
TypeExpr::from("int")
);
report(
"set_type(\"no_such_type_xyz\")",
idb.at_mut(ea).set_type("no_such_type_xyz"),
);
report(
"set_type(named(\"int\"))",
idb.at_mut(ea).set_type(expr::named("int")),
);
report(
"set_type(decl(\"%%% junk %%%\"))",
idb.at_mut(ea).set_type(expr::decl("%%% junk %%%")),
);
report("set_type(\"int\")", idb.at_mut(ea).set_type("int"));
}
fn report(call: &str, r: Result<(), Error>) {
match r {
Ok(()) => println!(" {call} -> Ok"),
Err(Error::TypeWrite {
source: TypeWriteError::NoType { name },
}) => {
println!(" {call} -> NoType {{ name: {name:?} }}");
}
Err(Error::TypeWrite {
source: TypeWriteError::ParseFailed { reason, .. },
}) => {
println!(" {call} -> ParseFailed: {reason}");
}
Err(Error::TypeWrite {
source: TypeWriteError::ApplyRejected { reason, .. },
}) => {
println!(" {call} -> ApplyRejected: {reason}");
}
Err(e) => println!(" {call} -> {e}"),
}
}