Skip to main content

chipi_core/backend/
binja.rs

1//! Binary Ninja Architecture plugin backend.
2//!
3//! Generates a Python Architecture plugin for Binary Ninja
4//! from a validated `.chipi` definition.
5
6use std::collections::HashMap;
7
8use crate::codegen_binja;
9use crate::config::GenTarget;
10use crate::tree;
11use crate::types::ValidatedDef;
12
13use super::{CodegenBackend, CodegenError, FlowConfig, OperandKind};
14
15/// The Binary Ninja code generation backend.
16pub struct BinjaBackend;
17
18/// Parsed Binary Ninja-specific configuration from `lang_options`.
19pub struct BinjaOptions {
20    pub architecture_name: String,
21    pub address_size: u32,
22    pub default_int_size: u32,
23    pub max_instr_length: u32,
24    pub endianness: String,
25    pub register_names: Vec<String>,
26    pub register_size: u32,
27    pub stack_pointer: Option<String>,
28    pub link_register: Option<String>,
29    pub bytes_per_unit: u32,
30    pub display_prefixes: HashMap<String, String>,
31    pub operand_types: HashMap<String, OperandKind>,
32    pub flow: FlowConfig,
33}
34
35impl CodegenBackend for BinjaBackend {
36    fn lang(&self) -> &str {
37        "binja"
38    }
39
40    fn validate_lang_options(&self, options: &toml::Value) -> Result<(), Vec<String>> {
41        let table = match options {
42            toml::Value::Table(t) => t,
43            _ => return Ok(()),
44        };
45
46        let mut errors = Vec::new();
47
48        if !table.contains_key("architecture_name") {
49            errors.push("missing required lang_option: architecture_name".to_string());
50        }
51        if !table.contains_key("register_names") {
52            errors.push("missing required lang_option: register_names".to_string());
53        }
54
55        let known_keys = [
56            "architecture_name",
57            "address_size",
58            "default_int_size",
59            "max_instr_length",
60            "endianness",
61            "register_names",
62            "register_size",
63            "stack_pointer",
64            "link_register",
65            "bytes_per_unit",
66            "display_prefixes",
67            "operand_types",
68            "flow",
69        ];
70        for key in table.keys() {
71            if !known_keys.contains(&key.as_str()) {
72                errors.push(format!("unknown lang_option: {}", key));
73            }
74        }
75
76        if errors.is_empty() {
77            Ok(())
78        } else {
79            Err(errors)
80        }
81    }
82
83    fn generate(&self, ir: &ValidatedDef, config: &GenTarget) -> Result<String, CodegenError> {
84        let tree = tree::build_tree(ir);
85        let opts = parse_binja_options(config).map_err(CodegenError::Internal)?;
86        Ok(codegen_binja::generate_binja_code(ir, &tree, &opts))
87    }
88
89    fn formatter_command(&self) -> Option<&[&str]> {
90        Some(&["ruff", "format"])
91    }
92}
93
94fn parse_binja_options(config: &GenTarget) -> Result<BinjaOptions, String> {
95    let table = match &config.lang_options {
96        Some(toml::Value::Table(t)) => t.clone(),
97        Some(_) => return Err("lang_options must be a table".to_string()),
98        None => return Err("Binary Ninja backend requires lang_options configuration".to_string()),
99    };
100
101    let architecture_name = table
102        .get("architecture_name")
103        .and_then(|v| v.as_str())
104        .ok_or("architecture_name must be a string")?
105        .to_string();
106
107    let register_names = table
108        .get("register_names")
109        .and_then(|v| v.as_array())
110        .ok_or("register_names must be an array")?
111        .iter()
112        .map(|v| {
113            v.as_str()
114                .ok_or("register_names entries must be strings")
115                .map(|s| s.to_string())
116        })
117        .collect::<Result<Vec<String>, _>>()?;
118
119    if register_names.is_empty() {
120        return Err("register_names must not be empty".to_string());
121    }
122
123    let address_size = table
124        .get("address_size")
125        .and_then(|v| v.as_integer())
126        .unwrap_or(4) as u32;
127
128    let default_int_size = table
129        .get("default_int_size")
130        .and_then(|v| v.as_integer())
131        .unwrap_or(address_size as i64) as u32;
132
133    let max_instr_length = table
134        .get("max_instr_length")
135        .and_then(|v| v.as_integer())
136        .unwrap_or(4) as u32;
137
138    let endianness = table
139        .get("endianness")
140        .and_then(|v| v.as_str())
141        .unwrap_or("LittleEndian")
142        .to_string();
143
144    let register_size = table
145        .get("register_size")
146        .and_then(|v| v.as_integer())
147        .unwrap_or(address_size as i64) as u32;
148
149    let stack_pointer = table
150        .get("stack_pointer")
151        .and_then(|v| v.as_str())
152        .map(|s| s.to_string());
153
154    let link_register = table
155        .get("link_register")
156        .and_then(|v| v.as_str())
157        .map(|s| s.to_string());
158
159    let bytes_per_unit = table
160        .get("bytes_per_unit")
161        .and_then(|v| v.as_integer())
162        .unwrap_or(1) as u32;
163
164    let display_prefixes = if let Some(dp) = table.get("display_prefixes") {
165        let dp_table = dp.as_table().ok_or("display_prefixes must be a table")?;
166        let mut map = HashMap::new();
167        for (key, val) in dp_table {
168            let prefix = val
169                .as_str()
170                .ok_or("display_prefixes values must be strings")?;
171            map.insert(key.clone(), prefix.to_string());
172        }
173        map
174    } else {
175        HashMap::new()
176    };
177
178    let operand_types = if let Some(ot) = table.get("operand_types") {
179        let ot_table = ot.as_table().ok_or("operand_types must be a table")?;
180        let mut map = HashMap::new();
181        for (key, val) in ot_table {
182            let kind_str = val.as_str().ok_or("operand_types values must be strings")?;
183            let kind = match kind_str {
184                "register" | "reg" => OperandKind::Register,
185                "immediate" | "imm" => OperandKind::Immediate,
186                "address" | "addr" => OperandKind::Address,
187                "memory" | "mem" => OperandKind::Memory,
188                _ => {
189                    return Err(format!(
190                        "unknown operand type '{}' for field '{}'",
191                        kind_str, key
192                    ));
193                }
194            };
195            map.insert(key.clone(), kind);
196        }
197        map
198    } else {
199        HashMap::new()
200    };
201
202    let flow = if let Some(f) = table.get("flow") {
203        let f_table = f.as_table().ok_or("flow must be a table")?;
204        FlowConfig {
205            calls: parse_string_array(f_table, "calls")?,
206            branches: parse_string_array(f_table, "branches")?,
207            unconditional_branches: parse_string_array(f_table, "unconditional_branches")?,
208            returns: parse_string_array(f_table, "returns")?,
209            stops: parse_string_array(f_table, "stops")?,
210        }
211    } else {
212        FlowConfig::default()
213    };
214
215    Ok(BinjaOptions {
216        architecture_name,
217        address_size,
218        default_int_size,
219        max_instr_length,
220        endianness,
221        register_names,
222        register_size,
223        stack_pointer,
224        link_register,
225        bytes_per_unit,
226        display_prefixes,
227        operand_types,
228        flow,
229    })
230}
231
232fn parse_string_array(
233    table: &toml::map::Map<String, toml::Value>,
234    key: &str,
235) -> Result<Vec<String>, String> {
236    if let Some(v) = table.get(key) {
237        v.as_array()
238            .ok_or(format!("{} must be an array", key))?
239            .iter()
240            .map(|v| {
241                v.as_str()
242                    .ok_or(format!("{} entries must be strings", key))
243                    .map(|s| s.to_string())
244            })
245            .collect()
246    } else {
247        Ok(Vec::new())
248    }
249}