1mod format;
9mod insn;
10
11use asc_dex::annotations::{AnnotationsDirectory, parse_annotation_set, parse_annotation_set_ref_list};
12use asc_dex::class_data::{EncodedField, EncodedMethod};
13use asc_dex::debug_info::DebugInfo;
14use asc_dex::encoded::{AnnotationItem, EncodedValue, parse_annotation_item, parse_array};
15use asc_dex::{ClassDef, Dex, NO_INDEX, access};
16use std::collections::HashMap;
17use std::fmt::Write as _;
18
19pub use format::{escape_string, field_signature, method_signature, proto_descriptor};
20
21#[derive(Debug, thiserror::Error)]
22pub enum SmaliError {
23 #[error(transparent)]
24 Dex(#[from] asc_dex::DexError),
25 #[error("class {0} not found")]
26 ClassNotFound(String),
27 #[error("method signature must include the name, parameter descriptor and return type")]
28 BadMethodSignature(String),
29 #[error("method not found or signature is not defined in the target DEX: {0}")]
30 MethodNotFound(String),
31}
32
33pub type Result<T> = std::result::Result<T, SmaliError>;
34
35#[derive(Debug, Clone, Copy)]
37pub struct Options {
38 pub line_numbers: bool,
40 pub param_names: bool,
42}
43
44impl Default for Options {
45 fn default() -> Self {
46 Options { line_numbers: true, param_names: true }
47 }
48}
49
50pub fn disassemble_class(dex: &Dex<'_>, descriptor: &str, opts: Options) -> Result<String> {
52 disassemble_class_method(dex, descriptor, opts, None)
53}
54
55pub fn disassemble_class_method(dex: &Dex<'_>, descriptor: &str, opts: Options, method: Option<&str>) -> Result<String> {
61 let filter = method.map(normalize_method_signature).transpose()?;
62 let (index, _) =
63 dex.find_class_def(&asc_dex::mutf8::encode(descriptor))?.ok_or_else(|| SmaliError::ClassNotFound(descriptor.to_string()))?;
64 let def = dex.class_def(index)?;
65 let mut w = Writer { names: Names::new(dex), out: String::with_capacity(16 * 1024), opts, method: filter.as_deref(), marks: None };
66 if let Some(sig) = w.method {
67 let cd = dex.class_data(def.class_data_off)?;
68 let mut found = false;
69 for m in cd.direct_methods.iter().chain(&cd.virtual_methods) {
70 if w.method_matches(m.method_idx)? {
71 found = true;
72 break;
73 }
74 }
75 if !found {
76 return Err(SmaliError::MethodNotFound(sig.to_string()));
77 }
78 }
79 w.class(&def)?;
80 Ok(w.out)
81}
82
83pub fn disassemble_class_def(dex: &Dex<'_>, index: u32, opts: Options) -> Result<String> {
85 let def = dex.class_def(index)?;
86 let mut w = Writer { names: Names::new(dex), out: String::with_capacity(16 * 1024), opts, method: None, marks: None };
87 w.class(&def)?;
88 Ok(w.out)
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct MethodListing {
95 pub text: String,
96 pub insns: Vec<(u32, u32)>,
99 pub positions: Vec<(u32, u32)>,
101}
102
103impl MethodListing {
104 pub fn line_of(&self, pc: u32) -> Option<u32> {
106 self.insns.binary_search_by_key(&pc, |&(p, _)| p).ok().map(|i| self.insns[i].1)
107 }
108
109 pub fn source_line(&self, pc: u32) -> Option<u32> {
111 match self.positions.partition_point(|&(p, _)| p <= pc) {
112 0 => None,
113 n => Some(self.positions[n - 1].1),
114 }
115 }
116}
117
118pub fn disassemble_method(dex: &Dex<'_>, method_idx: u32, opts: Options) -> Result<MethodListing> {
121 let id = dex.method_id(method_idx)?;
122 let not_found = || SmaliError::MethodNotFound(method_signature(dex, method_idx).unwrap_or_else(|_| format!("method@{method_idx}")));
123 let (_, def) = dex.find_class_def_by_type(id.class_idx as u32)?.ok_or_else(not_found)?;
124 let cd = dex.class_data(def.class_data_off)?;
125 let m = cd.direct_methods.iter().chain(&cd.virtual_methods).find(|m| m.method_idx == method_idx).ok_or_else(not_found)?;
126 let mut w = Writer { names: Names::new(dex), out: String::with_capacity(4 * 1024), opts, method: None, marks: Some(Vec::new()) };
127 let ann = w.load_annotations(&def)?;
128 w.method(m, &ann)?;
129 let mut positions = Vec::new();
130 if m.code_off != 0 {
131 let code = dex.code_item(m.code_off)?;
132 if code.debug_info_off != 0
133 && let Ok((d, _)) = DebugInfo::parse(dex.buf(), code.debug_info_off as usize)
134 {
135 positions = d.positions();
136 positions.sort_by_key(|&(pc, _)| pc);
137 }
138 }
139 let marks = w.marks.take().unwrap_or_default();
140 let text = w.out;
141 let mut insns = Vec::with_capacity(marks.len());
143 let (mut line, mut at) = (0u32, 0usize);
144 for (pc, off) in marks {
145 line += text.as_bytes()[at..off].iter().filter(|&&b| b == b'\n').count() as u32;
146 at = off;
147 insns.push(((pc / 2) as u32, line));
148 }
149 Ok(MethodListing { text, insns, positions })
150}
151
152pub fn normalize_method_signature(signature: &str) -> Result<String> {
156 let bad = || SmaliError::BadMethodSignature(signature.to_string());
157 let (name, rest) = signature.split_once('(').ok_or_else(bad)?;
158 let (params, ret) = rest.split_once(')').ok_or_else(bad)?;
159 let compact = |s: &str| s.split_whitespace().collect::<String>();
160 let (params, ret) = (compact(params), compact(ret));
161 if name.is_empty() || ret.is_empty() {
162 return Err(bad());
163 }
164 Ok(format!("{name}({params}){ret}"))
165}
166
167pub(crate) struct Names<'d, 'a> {
171 pub dex: &'d Dex<'a>,
172 types: HashMap<u32, String>,
173 protos: HashMap<u32, String>,
174 fields: HashMap<u32, String>,
175 methods: HashMap<u32, String>,
176}
177
178impl<'d, 'a> Names<'d, 'a> {
179 fn new(dex: &'d Dex<'a>) -> Self {
180 Names { dex, types: HashMap::new(), protos: HashMap::new(), fields: HashMap::new(), methods: HashMap::new() }
181 }
182
183 pub fn type_(&mut self, idx: u32) -> Result<&str> {
184 if !self.types.contains_key(&idx) {
185 self.types.insert(idx, self.dex.type_descriptor(idx)?);
186 }
187 Ok(self.types[&idx].as_str())
188 }
189
190 pub fn proto(&mut self, idx: u32) -> Result<&str> {
191 if !self.protos.contains_key(&idx) {
192 self.protos.insert(idx, proto_descriptor(self.dex, idx)?);
193 }
194 Ok(self.protos[&idx].as_str())
195 }
196
197 pub fn field(&mut self, idx: u32) -> Result<&str> {
198 if !self.fields.contains_key(&idx) {
199 self.fields.insert(idx, format::field_signature(self.dex, idx)?);
200 }
201 Ok(self.fields[&idx].as_str())
202 }
203
204 pub fn method(&mut self, idx: u32) -> Result<&str> {
205 if !self.methods.contains_key(&idx) {
206 self.methods.insert(idx, method_signature(self.dex, idx)?);
207 }
208 Ok(self.methods[&idx].as_str())
209 }
210}
211
212struct Annotations {
213 class: Vec<AnnotationItem>,
214 fields: HashMap<u32, Vec<AnnotationItem>>,
215 methods: HashMap<u32, Vec<AnnotationItem>>,
216 params: HashMap<u32, Vec<Vec<AnnotationItem>>>,
217}
218
219struct Writer<'d, 'a, 'm> {
220 names: Names<'d, 'a>,
221 out: String,
222 opts: Options,
223 method: Option<&'m str>,
225 marks: Option<Vec<(usize, usize)>>,
227}
228
229const CLASS_FLAGS: &[(u32, &str)] = &[
230 (access::PUBLIC, "public"),
231 (access::PRIVATE, "private"),
232 (access::PROTECTED, "protected"),
233 (access::STATIC, "static"),
234 (access::FINAL, "final"),
235 (access::INTERFACE, "interface"),
236 (access::ABSTRACT, "abstract"),
237 (access::SYNTHETIC, "synthetic"),
238 (access::ANNOTATION, "annotation"),
239 (access::ENUM, "enum"),
240];
241
242const FIELD_FLAGS: &[(u32, &str)] = &[
243 (access::PUBLIC, "public"),
244 (access::PRIVATE, "private"),
245 (access::PROTECTED, "protected"),
246 (access::STATIC, "static"),
247 (access::FINAL, "final"),
248 (access::VOLATILE, "volatile"),
249 (access::TRANSIENT, "transient"),
250 (access::SYNTHETIC, "synthetic"),
251 (access::ENUM, "enum"),
252];
253
254const METHOD_FLAGS: &[(u32, &str)] = &[
255 (access::PUBLIC, "public"),
256 (access::PRIVATE, "private"),
257 (access::PROTECTED, "protected"),
258 (access::STATIC, "static"),
259 (access::FINAL, "final"),
260 (access::SYNCHRONIZED, "synchronized"),
261 (access::BRIDGE, "bridge"),
262 (access::VARARGS, "varargs"),
263 (access::NATIVE, "native"),
264 (access::ABSTRACT, "abstract"),
265 (access::STRICT, "strictfp"),
266 (access::SYNTHETIC, "synthetic"),
267 (access::CONSTRUCTOR, "constructor"),
268 (access::DECLARED_SYNCHRONIZED, "declared-synchronized"),
269];
270
271fn flags(value: u32, table: &[(u32, &str)]) -> String {
272 let mut s = String::new();
273 for (bit, name) in table {
274 if value & bit != 0 {
275 s.push_str(name);
276 s.push(' ');
277 }
278 }
279 s
280}
281
282impl<'d, 'a, 'm> Writer<'d, 'a, 'm> {
283 fn method_matches(&mut self, idx: u32) -> Result<bool> {
285 let Some(sig) = self.method else { return Ok(true) };
286 let dex = self.names.dex;
287 let id = dex.method_id(idx)?;
288 let name = dex.string(id.name_idx)?;
289 let Some(rest) = sig.strip_prefix(name.as_str()) else { return Ok(false) };
290 Ok(rest == self.names.proto(id.proto_idx as u32)?)
291 }
292
293 fn load_annotations(&self, def: &ClassDef) -> Result<Annotations> {
294 let mut ann = Annotations { class: Vec::new(), fields: HashMap::new(), methods: HashMap::new(), params: HashMap::new() };
295 if def.annotations_off == 0 {
296 return Ok(ann);
297 }
298 let buf = self.names.dex.buf();
299 let dir = AnnotationsDirectory::parse(buf, def.annotations_off as usize)?;
300 let load_set = |off: u32| -> Result<Vec<AnnotationItem>> {
301 if off == 0 {
302 return Ok(Vec::new());
303 }
304 let mut items = Vec::new();
305 for item_off in parse_annotation_set(buf, off as usize)? {
306 if item_off != 0 {
307 items.push(parse_annotation_item(buf, item_off as usize)?.0);
308 }
309 }
310 Ok(items)
311 };
312 ann.class = load_set(dir.class_annotations_off)?;
313 for (f, off) in dir.field_annotations {
314 ann.fields.insert(f, load_set(off)?);
315 }
316 for (m, off) in dir.method_annotations {
317 ann.methods.insert(m, load_set(off)?);
318 }
319 for (m, off) in dir.parameter_annotations {
320 if off == 0 {
321 continue;
322 }
323 let mut params = Vec::new();
324 for set_off in parse_annotation_set_ref_list(buf, off as usize)? {
325 params.push(load_set(set_off)?);
326 }
327 ann.params.insert(m, params);
328 }
329 Ok(ann)
330 }
331
332 fn class(&mut self, def: &ClassDef) -> Result<()> {
333 let dex = self.names.dex;
334 let _ = writeln!(self.out, ".class {}{}", flags(def.access_flags, CLASS_FLAGS), self.names.type_(def.class_idx)?);
335 if def.superclass_idx != NO_INDEX {
336 let _ = writeln!(self.out, ".super {}", self.names.type_(def.superclass_idx)?);
337 }
338 if def.source_file_idx != NO_INDEX {
339 let _ = writeln!(self.out, ".source \"{}\"", escape_string(&dex.string(def.source_file_idx)?));
340 }
341 let interfaces = dex.type_list(def.interfaces_off)?;
342 if !interfaces.is_empty() {
343 self.out.push_str("\n\n# interfaces\n");
344 for t in interfaces {
345 let _ = writeln!(self.out, ".implements {}", self.names.type_(t as u32)?);
346 }
347 }
348 let ann = self.load_annotations(def)?;
349 if !ann.class.is_empty() {
350 self.out.push_str("\n\n# annotations\n");
351 for (i, item) in ann.class.iter().enumerate() {
352 if i > 0 {
353 self.out.push('\n');
354 }
355 self.annotation(item, 0)?;
356 }
357 }
358
359 let mut cd = dex.class_data(def.class_data_off)?;
360 if self.method.is_some() {
361 cd.static_fields.clear();
362 cd.instance_fields.clear();
363 for methods in [&mut cd.direct_methods, &mut cd.virtual_methods] {
364 let mut kept = Vec::new();
365 for m in methods.drain(..) {
366 if self.method_matches(m.method_idx)? {
367 kept.push(m);
368 }
369 }
370 *methods = kept;
371 }
372 }
373 let static_values = if def.static_values_off != 0 && self.method.is_none() {
374 parse_array(dex.buf(), def.static_values_off as usize)?.0
375 } else {
376 Vec::new()
377 };
378
379 if !cd.static_fields.is_empty() {
380 self.out.push_str("\n\n# static fields\n");
381 for (i, f) in cd.static_fields.iter().enumerate() {
382 if i > 0 {
383 self.out.push('\n');
384 }
385 self.field(f, static_values.get(i), ann.fields.get(&f.field_idx))?;
386 }
387 }
388 if !cd.instance_fields.is_empty() {
389 self.out.push_str("\n\n# instance fields\n");
390 for (i, f) in cd.instance_fields.iter().enumerate() {
391 if i > 0 {
392 self.out.push('\n');
393 }
394 self.field(f, None, ann.fields.get(&f.field_idx))?;
395 }
396 }
397 if !cd.direct_methods.is_empty() {
398 self.out.push_str("\n\n# direct methods\n");
399 for (i, m) in cd.direct_methods.iter().enumerate() {
400 if i > 0 {
401 self.out.push('\n');
402 }
403 self.method(m, &ann)?;
404 }
405 }
406 if !cd.virtual_methods.is_empty() {
407 self.out.push_str("\n\n# virtual methods\n");
408 for (i, m) in cd.virtual_methods.iter().enumerate() {
409 if i > 0 {
410 self.out.push('\n');
411 }
412 self.method(m, &ann)?;
413 }
414 }
415 Ok(())
416 }
417
418 fn field(&mut self, f: &EncodedField, value: Option<&EncodedValue>, annotations: Option<&Vec<AnnotationItem>>) -> Result<()> {
419 let dex = self.names.dex;
420 let id = dex.field_id(f.field_idx)?;
421 let _ = write!(
422 self.out,
423 ".field {}{}:{}",
424 flags(f.access_flags, FIELD_FLAGS),
425 dex.string(id.name_idx)?,
426 self.names.type_(id.type_idx as u32)?
427 );
428 if let Some(v) = value {
429 let _ = write!(self.out, " = {}", format::value(dex, v)?);
430 }
431 self.out.push('\n');
432 if let Some(items) = annotations.filter(|a| !a.is_empty()) {
433 for item in items {
434 self.annotation(item, 1)?;
435 }
436 self.out.push_str(".end field\n");
437 }
438 Ok(())
439 }
440
441 fn method(&mut self, m: &EncodedMethod, ann: &Annotations) -> Result<()> {
442 let dex = self.names.dex;
443 let id = dex.method_id(m.method_idx)?;
444 let _ = writeln!(
445 self.out,
446 ".method {}{}{}",
447 flags(m.access_flags, METHOD_FLAGS),
448 dex.string(id.name_idx)?,
449 self.names.proto(id.proto_idx as u32)?
450 );
451 let code = if m.code_off != 0 { Some(dex.code_item(m.code_off)?) } else { None };
452 if let Some(code) = &code {
453 let _ = writeln!(self.out, " .registers {}", code.registers_size);
454 if code.ins_size > code.registers_size {
455 let _ = writeln!(
456 self.out,
457 " # malformed code_item: ins_size {} exceeds registers_size {}",
458 code.ins_size, code.registers_size
459 );
460 }
461 }
462 let debug = match &code {
463 Some(c) if c.debug_info_off != 0 => DebugInfo::parse(dex.buf(), c.debug_info_off as usize).ok().map(|d| d.0),
464 _ => None,
465 };
466 let params = dex.proto_params(id.proto_idx as u32)?;
471 let param_annotations = ann.params.get(&m.method_idx);
472 let is_static = m.access_flags & access::STATIC != 0;
473 let mut reg = code.as_ref().map_or(0, |c| c.registers_size.saturating_sub(c.ins_size) as usize);
474 if !is_static {
475 reg += 1;
476 }
477 for (i, &pt) in params.iter().enumerate() {
478 let name = debug.as_ref().and_then(|d| d.parameter_names.get(i).copied().flatten());
479 let anns = param_annotations.and_then(|p| p.get(i)).filter(|a| !a.is_empty());
480 let desc = self.names.type_(pt as u32)?;
481 let wide = desc == "J" || desc == "D";
482 if (self.opts.param_names && name.is_some()) || anns.is_some() {
483 let _ = write!(self.out, " .param v{reg}");
484 if let Some(n) = name.filter(|_| self.opts.param_names) {
485 let _ = write!(self.out, ", \"{}\"", escape_string(&dex.string(n)?));
486 }
487 let _ = writeln!(self.out, " # {desc}");
488 if let Some(items) = anns {
489 for item in items {
490 self.annotation(item, 2)?;
491 }
492 self.out.push_str(" .end param\n");
493 }
494 }
495 reg += if wide { 2 } else { 1 };
496 }
497 if let Some(items) = ann.methods.get(&m.method_idx) {
498 for item in items {
499 self.annotation(item, 1)?;
500 }
501 }
502 if let Some(code) = &code {
503 self.out.push('\n');
504 insn::method_body(&mut self.names, code, debug.as_ref(), self.opts, &mut self.out, self.marks.as_mut())?;
505 }
506 self.out.push_str(".end method\n");
507 Ok(())
508 }
509
510 fn annotation(&mut self, item: &AnnotationItem, depth: usize) -> Result<()> {
511 let vis = match item.visibility {
512 0 => "build",
513 1 => "runtime",
514 2 => "system",
515 _ => "runtime",
516 };
517 let indent = " ".repeat(depth);
518 let _ = writeln!(self.out, "{indent}.annotation {vis} {}", self.names.type_(item.annotation.type_idx)?);
519 let dex = self.names.dex;
520 for (name, value) in &item.annotation.elements {
521 let _ = writeln!(self.out, "{indent} {} = {}", dex.string(*name)?, format::value_indented(dex, value, depth + 1)?);
522 }
523 let _ = writeln!(self.out, "{indent}.end annotation");
524 Ok(())
525 }
526}