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