Skip to main content

asc_smali/
lib.rs

1//! baksmali-style disassembly of one class.
2//!
3//! This is the built-in "decompile" backend: the rebuilt
4//! single-class DEX is rendered as smali so the output is always available
5//! without an external Java decompiler. The rebuilt DEX itself is a valid
6//! file that external tools (jadx, baksmali, dexdump) can consume as well.
7
8mod 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/// Rendering options.
36#[derive(Debug, Clone, Copy)]
37pub struct Options {
38    /// Emit `.line` directives from debug info.
39    pub line_numbers: bool,
40    /// Emit `.param` names from debug info.
41    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
50/// Disassemble the class with the given descriptor.
51pub fn disassemble_class(dex: &Dex<'_>, descriptor: &str, opts: Options) -> Result<String> {
52    disassemble_class_method(dex, descriptor, opts, None)
53}
54
55/// Disassemble the class with the given descriptor, keeping only the method
56/// whose `name(params)ret` is `method` when one is given (fields are left out
57/// then, like the Python `disassemble --method`). The signature is
58/// normalised with [`normalize_method_signature`]; a class that defines no
59/// such method is an error.
60pub 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
83/// Disassemble class definition number `index`.
84pub 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/// One method rendered on its own (`.method` .. `.end method`), with the line
92/// each instruction starts on.
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct MethodListing {
95    pub text: String,
96    /// `(pc, line)` per instruction in pc order: `pc` in code units from the
97    /// start of the method's code, `line` a 0-based index into `text.lines()`.
98    pub insns: Vec<(u32, u32)>,
99    /// `(pc, source line)` from the debug info, in pc order (empty without it).
100    pub positions: Vec<(u32, u32)>,
101}
102
103impl MethodListing {
104    /// Line of `text` holding the instruction at `pc` (code units).
105    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    /// Source line (debug info) covering the instruction at `pc`.
110    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
118/// Render method `method_idx` of `dex` (it must be defined by a class of
119/// this DEX), annotations and parameters included, as in a class listing.
120pub 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    // Byte offset in `text` -> line index, walking both in order.
142    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
152/// Normalise a `--method` signature such as `run(I)V` or `put(I, Ljava/lang/String;) V`:
153/// whitespace is dropped from the parameter and return descriptors. The name,
154/// both parentheses and a return type are required; a bare name is rejected.
155pub 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
167/// Per-class memo of rendered type descriptors, prototypes and member
168/// signatures, keyed by index (the same method or field is typically
169/// referenced many times inside one class).
170pub(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    /// Only this normalised `name(params)ret` is rendered (and no fields).
224    method: Option<&'m str>,
225    /// When set, `(pc in bytes, byte offset in out)` of every rendered instruction.
226    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    /// Whether method `idx` passes the `method` filter (always without one).
284    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        // Parameters: names from debug info, annotations from the directory.
467        // Methods without code (abstract / native) still carry both, and their
468        // parameter registers start at 0 (or 1 after `this`), exactly as if
469        // `registers_size == ins_size` (baksmali's `writeEmptyMethodTo`).
470        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}