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 _;
18use std::rc::Rc;
19
20pub use format::{escape_string, field_signature, method_signature, proto_descriptor};
21
22#[derive(Debug, thiserror::Error)]
24#[non_exhaustive]
25pub enum SmaliError {
26 #[error(transparent)]
28 Dex(#[from] asc_dex::DexError),
29 #[error("class {0} not found")]
31 ClassNotFound(String),
32 #[error("method signature must include the name, parameter descriptor and return type")]
34 BadMethodSignature(String),
35 #[error("method not found or signature is not defined in the target DEX: {0}")]
37 MethodNotFound(String),
38 #[error("listing exceeds {0} bytes, far more than the DEX could hold")]
42 TooLarge(usize),
43}
44
45pub(crate) fn listing_limit(dex: &Dex<'_>) -> usize {
48 dex.buf().len().saturating_mul(64).saturating_add(1 << 20)
49}
50
51pub(crate) fn check_size(out: &str, limit: usize) -> Result<()> {
53 if out.len() > limit { Err(SmaliError::TooLarge(limit)) } else { Ok(()) }
54}
55
56pub type Result<T> = std::result::Result<T, SmaliError>;
58
59#[derive(Debug, Clone, Copy)]
61#[non_exhaustive]
62pub struct Options {
63 pub line_numbers: bool,
65 pub param_names: bool,
67}
68
69impl Default for Options {
70 fn default() -> Self {
71 Options { line_numbers: true, param_names: true }
72 }
73}
74
75impl Options {
76 pub fn debug_info(on: bool) -> Options {
79 Options { line_numbers: on, param_names: on }
80 }
81
82 #[must_use]
84 pub fn line_numbers(self, line_numbers: bool) -> Options {
85 Options { line_numbers, ..self }
86 }
87
88 #[must_use]
90 pub fn param_names(self, param_names: bool) -> Options {
91 Options { param_names, ..self }
92 }
93}
94
95pub fn disassemble_class(dex: &Dex<'_>, descriptor: &str, opts: Options) -> Result<String> {
101 disassemble_class_method(dex, descriptor, opts, None)
102}
103
104pub fn disassemble_class_method(dex: &Dex<'_>, descriptor: &str, opts: Options, method: Option<&str>) -> Result<String> {
118 let filter = method.map(normalize_method_signature).transpose()?;
119 let (index, _) =
120 dex.find_class_def(&asc_dex::mutf8::encode(descriptor))?.ok_or_else(|| SmaliError::ClassNotFound(descriptor.to_string()))?;
121 let def = dex.class_def(index)?;
122 let mut w = Writer { names: Names::new(dex), out: String::with_capacity(16 * 1024), opts, method: filter.as_deref(), marks: None };
123 if let Some(sig) = w.method {
124 let cd = dex.class_data(def.class_data_off)?;
125 let mut found = false;
126 for m in cd.direct_methods.iter().chain(&cd.virtual_methods) {
127 if w.method_matches(m.method_idx)? {
128 found = true;
129 break;
130 }
131 }
132 if !found {
133 return Err(SmaliError::MethodNotFound(sig.to_string()));
134 }
135 }
136 w.class(&def)?;
137 Ok(w.out)
138}
139
140pub fn disassemble_class_def(dex: &Dex<'_>, index: u32, opts: Options) -> Result<String> {
147 let def = dex.class_def(index)?;
148 let mut w = Writer { names: Names::new(dex), out: String::with_capacity(16 * 1024), opts, method: None, marks: None };
149 w.class(&def)?;
150 Ok(w.out)
151}
152
153#[derive(Debug, Clone, PartialEq, Eq)]
156#[non_exhaustive]
157pub struct MethodListing {
158 pub text: String,
160 pub insns: Vec<(u32, u32)>,
163 pub positions: Vec<(u32, u32)>,
165}
166
167impl MethodListing {
168 pub fn line_of(&self, pc: u32) -> Option<u32> {
170 self.insns.binary_search_by_key(&pc, |&(p, _)| p).ok().map(|i| self.insns[i].1)
171 }
172
173 pub fn source_line(&self, pc: u32) -> Option<u32> {
175 match self.positions.partition_point(|&(p, _)| p <= pc) {
176 0 => None,
177 n => Some(self.positions[n - 1].1),
178 }
179 }
180}
181
182pub fn disassemble_method(dex: &Dex<'_>, method_idx: u32, opts: Options) -> Result<MethodListing> {
191 let id = dex.method_id(method_idx)?;
192 let not_found = || SmaliError::MethodNotFound(method_signature(dex, method_idx).unwrap_or_else(|_| format!("method@{method_idx}")));
193 let (_, def) = dex.find_class_def_by_type(id.class_idx as u32)?.ok_or_else(not_found)?;
194 let cd = dex.class_data(def.class_data_off)?;
195 let m = cd.direct_methods.iter().chain(&cd.virtual_methods).find(|m| m.method_idx == method_idx).ok_or_else(not_found)?;
196 let mut w = Writer { names: Names::new(dex), out: String::with_capacity(4 * 1024), opts, method: None, marks: Some(Vec::new()) };
197 let ann = w.load_annotations(&def)?;
198 w.method(m, &ann)?;
199 let mut positions = Vec::new();
200 if m.code_off != 0 {
201 let code = dex.code_item(m.code_off)?;
202 if code.debug_info_off != 0
203 && let Ok((d, _)) = DebugInfo::parse(dex.buf(), code.debug_info_off as usize)
204 {
205 positions = d.positions();
206 positions.sort_by_key(|&(pc, _)| pc);
207 }
208 }
209 let marks = w.marks.take().unwrap_or_default();
210 let text = w.out;
211 let mut insns = Vec::with_capacity(marks.len());
213 let (mut line, mut at) = (0u32, 0usize);
214 for (pc, off) in marks {
215 line += text.as_bytes()[at..off].iter().filter(|&&b| b == b'\n').count() as u32;
216 at = off;
217 insns.push(((pc / 2) as u32, line));
218 }
219 Ok(MethodListing { text, insns, positions })
220}
221
222pub fn normalize_method_signature(signature: &str) -> Result<String> {
230 let bad = || SmaliError::BadMethodSignature(signature.to_string());
231 let (name, rest) = signature.split_once('(').ok_or_else(bad)?;
232 let (params, ret) = rest.split_once(')').ok_or_else(bad)?;
233 let compact = |s: &str| s.split_whitespace().collect::<String>();
234 let (params, ret) = (compact(params), compact(ret));
235 if name.is_empty() || ret.is_empty() {
236 return Err(bad());
237 }
238 Ok(format!("{name}({params}){ret}"))
239}
240
241pub(crate) struct Names<'d, 'a> {
245 pub dex: &'d Dex<'a>,
246 types: HashMap<u32, String>,
247 protos: HashMap<u32, String>,
248 fields: HashMap<u32, String>,
249 methods: HashMap<u32, String>,
250}
251
252impl<'d, 'a> Names<'d, 'a> {
253 fn new(dex: &'d Dex<'a>) -> Self {
254 Names { dex, types: HashMap::new(), protos: HashMap::new(), fields: HashMap::new(), methods: HashMap::new() }
255 }
256
257 pub(crate) fn type_(&mut self, idx: u32) -> Result<&str> {
258 if !self.types.contains_key(&idx) {
259 self.types.insert(idx, self.dex.type_descriptor(idx)?);
260 }
261 Ok(self.types[&idx].as_str())
262 }
263
264 pub(crate) fn proto(&mut self, idx: u32) -> Result<&str> {
265 if !self.protos.contains_key(&idx) {
266 self.protos.insert(idx, proto_descriptor(self.dex, idx)?);
267 }
268 Ok(self.protos[&idx].as_str())
269 }
270
271 pub(crate) fn field(&mut self, idx: u32) -> Result<&str> {
272 if !self.fields.contains_key(&idx) {
273 self.fields.insert(idx, format::field_signature(self.dex, idx)?);
274 }
275 Ok(self.fields[&idx].as_str())
276 }
277
278 pub(crate) fn method(&mut self, idx: u32) -> Result<&str> {
279 if !self.methods.contains_key(&idx) {
280 self.methods.insert(idx, method_signature(self.dex, idx)?);
281 }
282 Ok(self.methods[&idx].as_str())
283 }
284}
285
286struct Annotations {
287 class: Vec<AnnotationItem>,
288 fields: HashMap<u32, AnnotationSet>,
289 methods: HashMap<u32, AnnotationSet>,
290 params: HashMap<u32, Rc<Vec<AnnotationSet>>>,
291}
292
293type AnnotationSet = Rc<Vec<AnnotationItem>>;
295
296struct Writer<'d, 'a, 'm> {
297 names: Names<'d, 'a>,
298 out: String,
299 opts: Options,
300 method: Option<&'m str>,
302 marks: Option<Vec<(usize, usize)>>,
304}
305
306const CLASS_FLAGS: &[(u32, &str)] = &[
307 (access::PUBLIC, "public"),
308 (access::PRIVATE, "private"),
309 (access::PROTECTED, "protected"),
310 (access::STATIC, "static"),
311 (access::FINAL, "final"),
312 (access::INTERFACE, "interface"),
313 (access::ABSTRACT, "abstract"),
314 (access::SYNTHETIC, "synthetic"),
315 (access::ANNOTATION, "annotation"),
316 (access::ENUM, "enum"),
317];
318
319const FIELD_FLAGS: &[(u32, &str)] = &[
320 (access::PUBLIC, "public"),
321 (access::PRIVATE, "private"),
322 (access::PROTECTED, "protected"),
323 (access::STATIC, "static"),
324 (access::FINAL, "final"),
325 (access::VOLATILE, "volatile"),
326 (access::TRANSIENT, "transient"),
327 (access::SYNTHETIC, "synthetic"),
328 (access::ENUM, "enum"),
329];
330
331const METHOD_FLAGS: &[(u32, &str)] = &[
332 (access::PUBLIC, "public"),
333 (access::PRIVATE, "private"),
334 (access::PROTECTED, "protected"),
335 (access::STATIC, "static"),
336 (access::FINAL, "final"),
337 (access::SYNCHRONIZED, "synchronized"),
338 (access::BRIDGE, "bridge"),
339 (access::VARARGS, "varargs"),
340 (access::NATIVE, "native"),
341 (access::ABSTRACT, "abstract"),
342 (access::STRICT, "strictfp"),
343 (access::SYNTHETIC, "synthetic"),
344 (access::CONSTRUCTOR, "constructor"),
345 (access::DECLARED_SYNCHRONIZED, "declared-synchronized"),
346];
347
348fn flags(value: u32, table: &[(u32, &str)]) -> String {
349 let mut s = String::new();
350 for (bit, name) in table {
351 if value & bit != 0 {
352 s.push_str(name);
353 s.push(' ');
354 }
355 }
356 s
357}
358
359impl<'d, 'a, 'm> Writer<'d, 'a, 'm> {
360 fn method_matches(&mut self, idx: u32) -> Result<bool> {
362 let Some(sig) = self.method else { return Ok(true) };
363 let dex = self.names.dex;
364 let id = dex.method_id(idx)?;
365 let name = dex.string(id.name_idx)?;
366 let Some(rest) = sig.strip_prefix(name.as_str()) else { return Ok(false) };
367 Ok(rest == self.names.proto(id.proto_idx as u32)?)
368 }
369
370 fn load_annotations(&self, def: &ClassDef) -> Result<Annotations> {
374 let mut ann = Annotations { class: Vec::new(), fields: HashMap::new(), methods: HashMap::new(), params: HashMap::new() };
375 if def.annotations_off == 0 {
376 return Ok(ann);
377 }
378 let buf = self.names.dex.buf();
379 let dir = AnnotationsDirectory::parse(buf, def.annotations_off as usize)?;
380 let mut sets: HashMap<u32, AnnotationSet> = HashMap::new();
381 let mut load_set = |off: u32| -> Result<AnnotationSet> {
382 if let Some(set) = sets.get(&off) {
383 return Ok(Rc::clone(set));
384 }
385 let mut items = Vec::new();
386 if off != 0 {
387 for item_off in parse_annotation_set(buf, off as usize)? {
388 if item_off != 0 {
389 items.push(parse_annotation_item(buf, item_off as usize)?.0);
390 }
391 }
392 }
393 let set = Rc::new(items);
394 sets.insert(off, Rc::clone(&set));
395 Ok(set)
396 };
397 ann.class = load_set(dir.class_annotations_off)?.to_vec();
398 for (f, off) in dir.field_annotations {
399 ann.fields.insert(f, load_set(off)?);
400 }
401 for (m, off) in dir.method_annotations {
402 ann.methods.insert(m, load_set(off)?);
403 }
404 let mut lists: HashMap<u32, Rc<Vec<AnnotationSet>>> = HashMap::new();
405 for (m, off) in dir.parameter_annotations {
406 if off == 0 {
407 continue;
408 }
409 let params = match lists.get(&off) {
410 Some(params) => Rc::clone(params),
411 None => {
412 let params =
413 parse_annotation_set_ref_list(buf, off as usize)?.into_iter().map(&mut load_set).collect::<Result<Vec<_>>>()?;
414 let params = Rc::new(params);
415 lists.insert(off, Rc::clone(¶ms));
416 params
417 }
418 };
419 ann.params.insert(m, params);
420 }
421 Ok(ann)
422 }
423
424 fn class(&mut self, def: &ClassDef) -> Result<()> {
425 let dex = self.names.dex;
426 let _ = writeln!(self.out, ".class {}{}", flags(def.access_flags, CLASS_FLAGS), self.names.type_(def.class_idx)?);
427 if def.superclass_idx != NO_INDEX {
428 let _ = writeln!(self.out, ".super {}", self.names.type_(def.superclass_idx)?);
429 }
430 if def.source_file_idx != NO_INDEX {
431 let _ = writeln!(self.out, ".source \"{}\"", escape_string(&dex.string(def.source_file_idx)?));
432 }
433 let interfaces = dex.type_list(def.interfaces_off)?;
434 if !interfaces.is_empty() {
435 self.out.push_str("\n\n# interfaces\n");
436 for t in interfaces {
437 let _ = writeln!(self.out, ".implements {}", self.names.type_(t as u32)?);
438 }
439 }
440 let ann = self.load_annotations(def)?;
441 if !ann.class.is_empty() {
442 self.out.push_str("\n\n# annotations\n");
443 for (i, item) in ann.class.iter().enumerate() {
444 if i > 0 {
445 self.out.push('\n');
446 }
447 self.annotation(item, 0)?;
448 }
449 }
450
451 let mut cd = dex.class_data(def.class_data_off)?;
452 if self.method.is_some() {
453 cd.static_fields.clear();
454 cd.instance_fields.clear();
455 for methods in [&mut cd.direct_methods, &mut cd.virtual_methods] {
456 let mut kept = Vec::new();
457 for m in methods.drain(..) {
458 if self.method_matches(m.method_idx)? {
459 kept.push(m);
460 }
461 }
462 *methods = kept;
463 }
464 }
465 let static_values = if def.static_values_off != 0 && self.method.is_none() {
466 parse_array(dex.buf(), def.static_values_off as usize)?.0
467 } else {
468 Vec::new()
469 };
470
471 if !cd.static_fields.is_empty() {
472 self.out.push_str("\n\n# static fields\n");
473 for (i, f) in cd.static_fields.iter().enumerate() {
474 if i > 0 {
475 self.out.push('\n');
476 }
477 self.field(f, static_values.get(i), ann.fields.get(&f.field_idx).map(|set| set.as_slice()))?;
478 }
479 }
480 if !cd.instance_fields.is_empty() {
481 self.out.push_str("\n\n# instance fields\n");
482 for (i, f) in cd.instance_fields.iter().enumerate() {
483 if i > 0 {
484 self.out.push('\n');
485 }
486 self.field(f, None, ann.fields.get(&f.field_idx).map(|set| set.as_slice()))?;
487 }
488 }
489 if !cd.direct_methods.is_empty() {
490 self.out.push_str("\n\n# direct methods\n");
491 for (i, m) in cd.direct_methods.iter().enumerate() {
492 if i > 0 {
493 self.out.push('\n');
494 }
495 self.method(m, &ann)?;
496 }
497 }
498 if !cd.virtual_methods.is_empty() {
499 self.out.push_str("\n\n# virtual methods\n");
500 for (i, m) in cd.virtual_methods.iter().enumerate() {
501 if i > 0 {
502 self.out.push('\n');
503 }
504 self.method(m, &ann)?;
505 }
506 }
507 Ok(())
508 }
509
510 fn field(&mut self, f: &EncodedField, value: Option<&EncodedValue>, annotations: Option<&[AnnotationItem]>) -> Result<()> {
511 let dex = self.names.dex;
512 let id = dex.field_id(f.field_idx)?;
513 let _ = write!(
514 self.out,
515 ".field {}{}:{}",
516 flags(f.access_flags, FIELD_FLAGS),
517 dex.string(id.name_idx)?,
518 self.names.type_(id.type_idx as u32)?
519 );
520 if let Some(v) = value {
521 let _ = write!(self.out, " = {}", format::value(dex, v)?);
522 }
523 self.out.push('\n');
524 if let Some(items) = annotations.filter(|a| !a.is_empty()) {
525 for item in items {
526 self.annotation(item, 1)?;
527 }
528 self.out.push_str(".end field\n");
529 }
530 Ok(())
531 }
532
533 fn method(&mut self, m: &EncodedMethod, ann: &Annotations) -> Result<()> {
534 let dex = self.names.dex;
535 let id = dex.method_id(m.method_idx)?;
536 let _ = writeln!(
537 self.out,
538 ".method {}{}{}",
539 flags(m.access_flags, METHOD_FLAGS),
540 dex.string(id.name_idx)?,
541 self.names.proto(id.proto_idx as u32)?
542 );
543 let code = if m.code_off != 0 { Some(dex.code_item(m.code_off)?) } else { None };
544 if let Some(code) = &code {
545 let _ = writeln!(self.out, " .registers {}", code.registers_size);
546 if code.ins_size > code.registers_size {
547 let _ = writeln!(
548 self.out,
549 " # malformed code_item: ins_size {} exceeds registers_size {}",
550 code.ins_size, code.registers_size
551 );
552 }
553 }
554 let debug = match &code {
555 Some(c) if c.debug_info_off != 0 => DebugInfo::parse(dex.buf(), c.debug_info_off as usize).ok().map(|d| d.0),
556 _ => None,
557 };
558 let params = dex.proto_params(id.proto_idx as u32)?;
563 let param_annotations = ann.params.get(&m.method_idx);
564 let is_static = m.access_flags & access::STATIC != 0;
565 let mut reg = code.as_ref().map_or(0, |c| c.registers_size.saturating_sub(c.ins_size) as usize);
566 if !is_static {
567 reg += 1;
568 }
569 for (i, &pt) in params.iter().enumerate() {
570 let name = debug.as_ref().and_then(|d| d.parameter_names.get(i).copied().flatten());
571 let anns = param_annotations.and_then(|p| p.get(i)).filter(|a| !a.is_empty());
572 let desc = self.names.type_(pt as u32)?;
573 let wide = desc == "J" || desc == "D";
574 if (self.opts.param_names && name.is_some()) || anns.is_some() {
575 let _ = write!(self.out, " .param v{reg}");
576 if let Some(n) = name.filter(|_| self.opts.param_names) {
577 let _ = write!(self.out, ", \"{}\"", escape_string(&dex.string(n)?));
578 }
579 let _ = writeln!(self.out, " # {desc}");
580 if let Some(items) = anns {
581 for item in items.iter() {
582 self.annotation(item, 2)?;
583 }
584 self.out.push_str(" .end param\n");
585 }
586 }
587 reg += if wide { 2 } else { 1 };
588 }
589 if let Some(items) = ann.methods.get(&m.method_idx) {
590 for item in items.iter() {
591 self.annotation(item, 1)?;
592 }
593 }
594 if let Some(code) = &code {
595 self.out.push('\n');
596 insn::method_body(&mut self.names, code, debug.as_ref(), self.opts, &mut self.out, self.marks.as_mut())?;
597 }
598 self.out.push_str(".end method\n");
599 Ok(())
600 }
601
602 fn annotation(&mut self, item: &AnnotationItem, depth: usize) -> Result<()> {
603 let vis = match item.visibility {
604 0 => "build",
605 1 => "runtime",
606 2 => "system",
607 _ => "runtime",
608 };
609 let indent = " ".repeat(depth);
610 let _ = writeln!(self.out, "{indent}.annotation {vis} {}", self.names.type_(item.annotation.type_idx)?);
611 let dex = self.names.dex;
612 for (name, value) in &item.annotation.elements {
613 let _ = writeln!(self.out, "{indent} {} = {}", dex.string(*name)?, format::value_indented(dex, value, depth + 1)?);
614 }
615 let _ = writeln!(self.out, "{indent}.end annotation");
616 check_size(&self.out, listing_limit(dex))
617 }
618}