Skip to main content

ddc_dex/
insn.rs

1//! Dalvik instruction decoding: all formats plus payload pseudo-instructions.
2//!
3//! Offsets are in 16-bit code units throughout (matching DEX try tables and
4//! the CFG builder). Payload bodies (`packed-switch`, `sparse-switch`,
5//! `fill-array-data`) are decoded out-of-line into a side map; linear
6//! decoding skips past them.
7
8use std::collections::HashMap;
9
10use crate::reader::Cursor;
11
12/// Comparison ops shared by `if-*` and `cmp*`.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum CmpOp {
15    Eq,
16    Ne,
17    Lt,
18    Ge,
19    Gt,
20    Le,
21}
22
23/// Numeric operation shared by the four binop families.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum ArithOp {
26    Add,
27    Sub,
28    Mul,
29    Div,
30    Rem,
31    And,
32    Or,
33    Xor,
34    Shl,
35    Shr,
36    Ushr,
37}
38
39/// `cmpl/cmpg` flavour.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum CmpKind {
42    CmplF,
43    CmpgF,
44    CmplD,
45    CmpgD,
46    CmpJ,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum InvokeKind {
51    Virtual,
52    Super,
53    Direct,
54    Static,
55    Interface,
56    Polymorphic,
57}
58
59/// Fully decoded instruction semantics.
60#[derive(Debug, Clone)]
61pub enum InsnKind {
62    Nop,
63    /// Plain register move (the lifter treats wide/object as typing hints).
64    Move {
65        dst: u16,
66        src: u16,
67    },
68    MoveResult {
69        dst: u16,
70    },
71    MoveException {
72        dst: u16,
73    },
74    ReturnVoid,
75    Return {
76        src: u16,
77    },
78    /// `dst = literal` with the value's natural width.
79    Const {
80        dst: u16,
81        val: i64,
82        wide: bool,
83    },
84    /// `dst = type` class literal.
85    ConstClass {
86        dst: u16,
87        type_idx: u32,
88    },
89    ConstString {
90        dst: u16,
91        str_idx: u32,
92    },
93    MonitorEnter {
94        reg: u16,
95    },
96    MonitorExit {
97        reg: u16,
98    },
99    /// In-place cast: the register's value becomes `Cast(type)`.
100    CheckCast {
101        reg: u16,
102        type_idx: u32,
103    },
104    InstanceOf {
105        dst: u16,
106        src: u16,
107        type_idx: u32,
108    },
109    ArrayLength {
110        dst: u16,
111        src: u16,
112    },
113    NewInstance {
114        dst: u16,
115        type_idx: u32,
116    },
117    NewArray {
118        dst: u16,
119        size: u16,
120        type_idx: u32,
121    },
122    FilledNewArray {
123        regs: Vec<u16>,
124        type_idx: u32,
125    },
126    /// Fill the array in `reg` from the payload at `payload_pc`.
127    FillArrayData {
128        reg: u16,
129        payload_pc: u32,
130    },
131    Throw {
132        reg: u16,
133    },
134    /// Absolute target pc.
135    Goto {
136        target: u32,
137    },
138    PackedSwitch {
139        reg: u16,
140        payload_pc: u32,
141    },
142    SparseSwitch {
143        reg: u16,
144        payload_pc: u32,
145    },
146    Cmp {
147        dst: u16,
148        a: u16,
149        b: u16,
150        kind: CmpKind,
151    },
152    /// `if-*` with `z=false` for two-register forms; `target` is absolute.
153    If {
154        op: CmpOp,
155        a: u16,
156        b: u16,
157        z: bool,
158        target: u32,
159    },
160    AGet {
161        dst: u16,
162        array: u16,
163        index: u16,
164        ty: char,
165    },
166    APut {
167        value: u16,
168        array: u16,
169        index: u16,
170        ty: char,
171    },
172    IGet {
173        dst: u16,
174        obj: u16,
175        field_idx: u32,
176    },
177    IPut {
178        value: u16,
179        obj: u16,
180        field_idx: u32,
181    },
182    SGet {
183        dst: u16,
184        field_idx: u32,
185    },
186    SPut {
187        value: u16,
188        field_idx: u32,
189    },
190    Invoke {
191        kind: InvokeKind,
192        regs: Vec<u16>,
193        method_idx: u32,
194    },
195    /// `dst = op(src)` (neg / not / conversion); `from`/`to` primitive chars.
196    Un {
197        dst: u16,
198        src: u16,
199        op: UnArith,
200        from: char,
201        to: char,
202    },
203    /// `dst = a op b` on primitive `ty`.
204    Bin {
205        op: ArithOp,
206        dst: u16,
207        a: u16,
208        b: u16,
209        ty: char,
210    },
211    /// `dst = a op lit` (or `lit - a` for `rsub`).
212    BinLit {
213        op: ArithOp,
214        dst: u16,
215        a: u16,
216        lit: i32,
217        rsub: bool,
218    },
219    /// `dst = method-handle constant` (DEX 037+).
220    ConstMethodHandle {
221        dst: u16,
222        handle_idx: u32,
223    },
224    /// `dst = proto constant` (DEX 037+).
225    ConstMethodType {
226        dst: u16,
227        proto_idx: u32,
228    },
229    /// invoke-custom: `regs` are the call site's dynamic arguments; BBBB is
230    /// a call_site_idx (NOT a method index).
231    InvokeCustom {
232        call_site_idx: u32,
233        regs: Vec<u16>,
234    },
235    /// ODEX quickened / unassigned opcodes: cannot be lifted.
236    Unknown,
237}
238
239#[derive(Debug, Clone, Copy, PartialEq, Eq)]
240pub enum UnArith {
241    Neg,
242    Not,
243    Conv,
244}
245
246#[derive(Debug, Clone)]
247pub enum Payload {
248    /// `ident 0x0100`: targets are offsets RELATIVE to the switch insn.
249    Packed { first_key: i32, targets: Vec<i32> },
250    /// `ident 0x0200`.
251    Sparse { pairs: Vec<(i32, i32)> },
252    /// `ident 0x0300`.
253    ArrayData {
254        elem_width: u16,
255        size: u32,
256        data: Vec<u8>,
257    },
258}
259
260/// One decoded instruction.
261#[derive(Debug, Clone)]
262pub struct Insn {
263    /// Code-unit offset of the first unit.
264    pub pc: u32,
265    /// Total code units consumed (payloads excluded — they are not Insns).
266    pub size: u32,
267    pub op: u8,
268    pub kind: InsnKind,
269}
270
271impl Insn {
272    pub fn is_branch(&self) -> bool {
273        matches!(
274            self.kind,
275            InsnKind::Goto { .. }
276                | InsnKind::If { .. }
277                | InsnKind::PackedSwitch { .. }
278                | InsnKind::SparseSwitch { .. }
279        )
280    }
281
282    pub fn is_terminator(&self) -> bool {
283        matches!(
284            self.kind,
285            InsnKind::ReturnVoid | InsnKind::Return { .. } | InsnKind::Throw { .. }
286        )
287    }
288}
289
290/// Sign-extend a 4-bit literal.
291fn sign4(v: u16) -> i64 {
292    let x = v as i64;
293    (x << 60) >> 60
294}
295
296fn u16s(bytes: &[u8]) -> Vec<u16> {
297    bytes
298        .as_chunks::<2>()
299        .0
300        .iter()
301        .map(|c| u16::from_le_bytes([c[0], c[1]]))
302        .collect()
303}
304
305/// Payload size in code units (0 when malformed).
306fn payload_units(bytes: &[u8], off: usize) -> usize {
307    let units = u16s(bytes);
308    let unit = |i: usize| -> u16 { units.get(i).copied().unwrap_or(0) };
309    match unit(off) {
310        0x0100 => {
311            let size = unit(off + 1) as usize;
312            4 + size * 2
313        }
314        0x0200 => {
315            let size = unit(off + 1) as usize;
316            2 + size * 4
317        }
318        0x0300 => {
319            let width = unit(off + 1) as usize;
320            let size = (unit(off + 2) as usize) | ((unit(off + 3) as usize) << 16);
321            4 + (size * width).div_ceil(2)
322        }
323        _ => 0,
324    }
325}
326
327fn parse_payload(bytes: &[u8], off: usize) -> Option<Payload> {
328    let units = u16s(bytes);
329    let unit = |i: usize| -> u16 { units.get(i).copied().unwrap_or(0) };
330    match unit(off) {
331        0x0100 => {
332            let size = unit(off + 1) as usize;
333            let first_key = (unit(off + 2) as i32) | ((unit(off + 3) as i32) << 16);
334            let mut targets = Vec::with_capacity(size);
335            for i in 0..size {
336                let t = (unit(off + 4 + 2 * i) as i32) | ((unit(off + 5 + 2 * i) as i32) << 16);
337                targets.push(t);
338            }
339            Some(Payload::Packed { first_key, targets })
340        }
341        0x0200 => {
342            let size = unit(off + 1) as usize;
343            let mut pairs = Vec::with_capacity(size);
344            for i in 0..size {
345                let k = (unit(off + 2 + 2 * i) as i32) | ((unit(off + 3 + 2 * i) as i32) << 16);
346                let t = (unit(off + 2 + 2 * size + 2 * i) as i32)
347                    | ((unit(off + 3 + 2 * size + 2 * i) as i32) << 16);
348                pairs.push((k, t));
349            }
350            Some(Payload::Sparse { pairs })
351        }
352        0x0300 => {
353            let elem_width = unit(off + 1);
354            let size = (unit(off + 2) as u32) | ((unit(off + 3) as u32) << 16);
355            let n = size as usize * elem_width as usize;
356            let start = (off + 4) * 2;
357            let data = if start + n <= bytes.len() {
358                bytes[start..start + n].to_vec()
359            } else {
360                Vec::new()
361            };
362            Some(Payload::ArrayData {
363                elem_width,
364                size,
365                data,
366            })
367        }
368        _ => None,
369    }
370}
371
372/// Instruction width in code units for a format number.
373fn format_units(fmt: u8) -> u32 {
374    match fmt {
375        10..=12 => 1,
376        20..=23 => 2,
377        30 | 31 | 32 | 35 | 3 => 3,
378        45 | 4 => 4,
379        51 | 5 => 5,
380        _ => 1,
381    }
382}
383
384/// Decode the semantics of one instruction at code-unit `pc` given the
385/// full unit stream. Returns `(size, kind)`.
386fn decode_one(units: &[u16], pc: usize) -> (u32, InsnKind) {
387    let op = (units[pc] & 0xff) as u8;
388    let aa = units[pc] >> 8; // the 8-bit A field
389    let u = |i: usize| -> u16 { units.get(i).copied().unwrap_or(0) };
390    let i32of = |i: usize| -> i32 {
391        let lo = u(i) as u32;
392        let hi = u(i + 1) as u32;
393        (lo | (hi << 16)) as i32
394    };
395
396    // 4-bit register fields of the first unit.
397    let a4 = (units[pc] >> 12) & 0xf;
398    let b4 = (units[pc] >> 8) & 0xf;
399
400    let mk = match op {
401        // ---- moves ----
402        0x00 => {
403            // nop; payload idents also land here but are handled by the
404            // caller before dispatch.
405            return (1, InsnKind::Nop);
406        }
407        0x01 | 0x04 | 0x07 => InsnKind::Move { dst: b4, src: a4 },
408        0x02 | 0x05 | 0x08 => {
409            return (
410                2,
411                InsnKind::Move {
412                    dst: aa,
413                    src: u(pc + 1),
414                },
415            );
416        }
417        0x03 | 0x06 | 0x09 => {
418            return (
419                3,
420                InsnKind::Move {
421                    dst: u(pc + 1),
422                    src: u(pc + 2),
423                },
424            );
425        }
426        0x0a..=0x0c => InsnKind::MoveResult { dst: aa },
427        0x0d => InsnKind::MoveException { dst: aa },
428        0x0e => InsnKind::ReturnVoid,
429        0x0f..=0x11 => InsnKind::Return { src: aa },
430
431        // ---- consts ----
432        0x12 => InsnKind::Const {
433            dst: b4,
434            val: sign4(a4),
435            wide: false,
436        },
437        0x13 | 0x16 => {
438            return (
439                2,
440                InsnKind::Const {
441                    dst: aa,
442                    val: (u(pc + 1) as i16) as i64,
443                    wide: op == 0x16,
444                },
445            );
446        }
447        0x14 | 0x17 => {
448            return (
449                3,
450                InsnKind::Const {
451                    dst: aa,
452                    val: i32of(pc + 1) as i64,
453                    wide: op == 0x17,
454                },
455            );
456        }
457        0x15 => {
458            return (
459                2,
460                InsnKind::Const {
461                    dst: aa,
462                    val: ((u(pc + 1) as u32) << 16) as i32 as i64,
463                    wide: false,
464                },
465            );
466        }
467        0x18 => {
468            let lo = u64::from(i32of(pc + 1) as u32);
469            let hi = u64::from(i32of(pc + 3) as u32);
470            return (
471                5,
472                InsnKind::Const {
473                    dst: aa,
474                    val: (lo | (hi << 32)) as i64,
475                    wide: true,
476                },
477            );
478        }
479        0x19 => {
480            return (
481                2,
482                InsnKind::Const {
483                    dst: aa,
484                    val: ((u(pc + 1) as u64) << 48) as i64,
485                    wide: true,
486                },
487            );
488        }
489        0x1a | 0x1b => {
490            let idx = if op == 0x1b {
491                (u(pc + 1) as u32) | ((u(pc + 2) as u32) << 16)
492            } else {
493                u(pc + 1) as u32
494            };
495            return (
496                if op == 0x1b { 3 } else { 2 },
497                InsnKind::ConstString {
498                    dst: aa,
499                    str_idx: idx,
500                },
501            );
502        }
503        0x1c => {
504            return (
505                2,
506                InsnKind::ConstClass {
507                    dst: aa,
508                    type_idx: u(pc + 1) as u32,
509                },
510            );
511        }
512
513        // ---- monitors / casts / arrays ----
514        0x1d => InsnKind::MonitorEnter { reg: aa },
515        0x1e => InsnKind::MonitorExit { reg: aa },
516        0x1f => {
517            return (
518                2,
519                InsnKind::CheckCast {
520                    reg: aa,
521                    type_idx: u(pc + 1) as u32,
522                },
523            );
524        }
525        0x20 => {
526            return (
527                2,
528                InsnKind::InstanceOf {
529                    dst: b4,
530                    src: a4,
531                    type_idx: u(pc + 1) as u32,
532                },
533            );
534        }
535        0x21 => InsnKind::ArrayLength { dst: b4, src: a4 },
536        0x22 => {
537            return (
538                2,
539                InsnKind::NewInstance {
540                    dst: aa,
541                    type_idx: u(pc + 1) as u32,
542                },
543            );
544        }
545        0x23 => {
546            return (
547                2,
548                InsnKind::NewArray {
549                    dst: b4,
550                    size: a4,
551                    type_idx: u(pc + 1) as u32,
552                },
553            );
554        }
555        0x24 | 0x25 => {
556            // filled-new-array: 35c / 3rc
557            let (regs, idx) = if op == 0x24 {
558                let g = b4;
559                let cnt = a4;
560                let second = u(pc + 2);
561                let mut regs: Vec<u16> = Vec::with_capacity(cnt as usize);
562                for i in 0..cnt {
563                    let shift = 4 * i;
564                    let r = if i == 4 { g } else { (second >> shift) & 0xf };
565                    regs.push(r);
566                }
567                (regs, u(pc + 1) as u32)
568            } else {
569                let cnt = aa;
570                let first = u(pc + 2);
571                ((first..first + cnt).collect(), u(pc + 1) as u32)
572            };
573            return (
574                3,
575                InsnKind::FilledNewArray {
576                    regs,
577                    type_idx: idx,
578                },
579            );
580        }
581        0x26 => {
582            let off = i32of(pc + 1);
583            return (
584                3,
585                InsnKind::FillArrayData {
586                    reg: aa,
587                    payload_pc: (pc as i64 + off as i64) as u32,
588                },
589            );
590        }
591
592        // ---- control flow ----
593        0x27 => InsnKind::Throw { reg: aa },
594        0x28 => {
595            return (
596                1,
597                InsnKind::Goto {
598                    target: (pc as i64 + aa as i8 as i64) as u32,
599                },
600            );
601        }
602        0x29 => {
603            return (
604                2,
605                InsnKind::Goto {
606                    target: (pc as i64 + u(pc + 1) as i16 as i64) as u32,
607                },
608            );
609        }
610        0x2a => {
611            return (
612                3,
613                InsnKind::Goto {
614                    target: (pc as i64 + i32of(pc + 1) as i64) as u32,
615                },
616            );
617        }
618        0x2b => {
619            let off = i32of(pc + 1);
620            return (
621                3,
622                InsnKind::PackedSwitch {
623                    reg: aa,
624                    payload_pc: (pc as i64 + off as i64) as u32,
625                },
626            );
627        }
628        0x2c => {
629            let off = i32of(pc + 1);
630            return (
631                3,
632                InsnKind::SparseSwitch {
633                    reg: aa,
634                    payload_pc: (pc as i64 + off as i64) as u32,
635                },
636            );
637        }
638        0x2d..=0x31 => {
639            let kind = match op {
640                0x2d => CmpKind::CmplF,
641                0x2e => CmpKind::CmpgF,
642                0x2f => CmpKind::CmplD,
643                0x30 => CmpKind::CmpgD,
644                _ => CmpKind::CmpJ,
645            };
646            let second = u(pc + 1);
647            return (
648                2,
649                InsnKind::Cmp {
650                    dst: aa,
651                    a: (second & 0xff),
652                    b: (second >> 8),
653                    kind,
654                },
655            );
656        }
657        0x32..=0x37 => {
658            let opkind = match op {
659                0x32 => CmpOp::Eq,
660                0x33 => CmpOp::Ne,
661                0x34 => CmpOp::Lt,
662                0x35 => CmpOp::Ge,
663                0x36 => CmpOp::Gt,
664                _ => CmpOp::Le,
665            };
666            let off = u(pc + 1) as i16 as i64 + pc as i64;
667            return (
668                2,
669                InsnKind::If {
670                    op: opkind,
671                    a: b4,
672                    b: a4,
673                    z: false,
674                    target: off as u32,
675                },
676            );
677        }
678        0x38..=0x3d => {
679            let opkind = match op {
680                0x38 => CmpOp::Eq,
681                0x39 => CmpOp::Ne,
682                0x3a => CmpOp::Lt,
683                0x3b => CmpOp::Ge,
684                0x3c => CmpOp::Gt,
685                _ => CmpOp::Le,
686            };
687            let off = u(pc + 1) as i16 as i64 + pc as i64;
688            return (
689                2,
690                InsnKind::If {
691                    op: opkind,
692                    a: aa,
693                    b: 0,
694                    z: true,
695                    target: off as u32,
696                },
697            );
698        }
699
700        // ---- array element access (0x44-0x51) ----
701        0x44..=0x51 => {
702            let second = u(pc + 1);
703            let r1 = aa;
704            let r2 = second & 0xff;
705            let r3 = second >> 8;
706            let is_load = op <= 0x4a;
707            let ty = match op {
708                0x44 | 0x4b => 'I',
709                0x45 | 0x4c => 'J',
710                0x46 | 0x4d => 'L',
711                0x47 | 0x4e => 'Z',
712                0x48 | 0x4f => 'B',
713                0x49 | 0x50 => 'C',
714                _ => 'S',
715            };
716            // 23x: TWO code units.
717            if is_load {
718                return (
719                    2,
720                    InsnKind::AGet {
721                        dst: r1,
722                        array: r2,
723                        index: r3,
724                        ty,
725                    },
726                );
727            } else {
728                return (
729                    2,
730                    InsnKind::APut {
731                        value: r1,
732                        array: r2,
733                        index: r3,
734                        ty,
735                    },
736                );
737            }
738        }
739
740        // ---- instance fields (0x52-0x5f) ----
741        0x52..=0x58 => {
742            return (
743                2,
744                InsnKind::IGet {
745                    dst: b4,
746                    obj: a4,
747                    field_idx: u(pc + 1) as u32,
748                },
749            );
750        }
751        0x59..=0x5f => {
752            return (
753                2,
754                InsnKind::IPut {
755                    value: b4,
756                    obj: a4,
757                    field_idx: u(pc + 1) as u32,
758                },
759            );
760        }
761
762        // ---- static fields (0x60-0x6d) ----
763        0x60..=0x66 => {
764            return (
765                2,
766                InsnKind::SGet {
767                    dst: aa,
768                    field_idx: u(pc + 1) as u32,
769                },
770            );
771        }
772        0x67..=0x6d => {
773            return (
774                2,
775                InsnKind::SPut {
776                    value: aa,
777                    field_idx: u(pc + 1) as u32,
778                },
779            );
780        }
781
782        // ---- invoke-custom (0xfc/0xfd): BBBB is a call site index ----
783        0xfc | 0xfd => {
784            let cs_idx = u(pc + 1) as u32;
785            let regs = if op == 0xfd {
786                let cnt = aa;
787                let first = u(pc + 2);
788                (first..first + cnt).collect()
789            } else {
790                let cnt = a4;
791                let g = b4;
792                let second = u(pc + 2);
793                let mut regs: Vec<u16> = Vec::with_capacity(cnt as usize);
794                for i in 0..cnt {
795                    let r = if i == 4 { g } else { (second >> (4 * i)) & 0xf };
796                    regs.push(r);
797                }
798                regs
799            };
800            return (
801                3,
802                InsnKind::InvokeCustom {
803                    call_site_idx: cs_idx,
804                    regs,
805                },
806            );
807        }
808
809        // ---- invokes ----
810        0x6e..=0x72 | 0x74..=0x78 => {
811            let kind = match op {
812                0x6e | 0x74 => InvokeKind::Virtual,
813                0x6f | 0x75 => InvokeKind::Super,
814                0x70 | 0x76 => InvokeKind::Direct,
815                0x71 | 0x77 => InvokeKind::Static,
816                _ => InvokeKind::Interface,
817            };
818            let is_range = matches!(op, 0x74..=0x78);
819            let idx = u(pc + 1) as u32;
820            let regs = if is_range {
821                let cnt = aa;
822                let first = u(pc + 2);
823                (first..first + cnt).collect()
824            } else {
825                let cnt = a4;
826                let g = b4;
827                let second = u(pc + 2);
828                let mut regs: Vec<u16> = Vec::with_capacity(cnt as usize);
829                for i in 0..cnt {
830                    let r = if i == 4 { g } else { (second >> (4 * i)) & 0xf };
831                    regs.push(r);
832                }
833                regs
834            };
835            return (
836                3,
837                InsnKind::Invoke {
838                    kind,
839                    regs,
840                    method_idx: idx,
841                },
842            );
843        }
844        0xfa | 0xfb => {
845            // invoke-polymorphic (45cc / 4rcc): treat as the base invoke,
846            // dropping the trailing proto operand.
847            let kind = InvokeKind::Polymorphic;
848            let idx = u(pc + 1) as u32;
849            let regs = if op == 0xfb {
850                let cnt = aa;
851                let first = u(pc + 3);
852                (first..first + cnt).collect()
853            } else {
854                let cnt = a4;
855                let g = b4;
856                let second = u(pc + 2);
857                let mut regs: Vec<u16> = Vec::with_capacity(cnt as usize);
858                for i in 0..cnt {
859                    let r = if i == 4 { g } else { (second >> (4 * i)) & 0xf };
860                    regs.push(r);
861                }
862                regs
863            };
864            return (
865                4,
866                InsnKind::Invoke {
867                    kind,
868                    regs,
869                    method_idx: idx,
870                },
871            );
872        }
873        0xfe => {
874            return (
875                2,
876                InsnKind::ConstMethodHandle {
877                    dst: aa,
878                    handle_idx: u(pc + 1) as u32,
879                },
880            );
881        }
882        0xff => {
883            return (
884                2,
885                InsnKind::ConstMethodType {
886                    dst: aa,
887                    proto_idx: u(pc + 1) as u32,
888                },
889            );
890        }
891
892        // ---- unary (0x7b-0x8f) ----
893        0x7b..=0x8f => {
894            let (opkind, from, to) = match op {
895                0x7b => (UnArith::Neg, 'I', 'I'),
896                0x7c => (UnArith::Not, 'I', 'I'),
897                0x7d => (UnArith::Neg, 'J', 'J'),
898                0x7e => (UnArith::Not, 'J', 'J'),
899                0x7f => (UnArith::Neg, 'F', 'F'),
900                0x80 => (UnArith::Neg, 'D', 'D'),
901                0x81 => (UnArith::Conv, 'I', 'J'),
902                0x82 => (UnArith::Conv, 'I', 'F'),
903                0x83 => (UnArith::Conv, 'I', 'D'),
904                0x84 => (UnArith::Conv, 'J', 'I'),
905                0x85 => (UnArith::Conv, 'J', 'F'),
906                0x86 => (UnArith::Conv, 'J', 'D'),
907                0x87 => (UnArith::Conv, 'F', 'I'),
908                0x88 => (UnArith::Conv, 'F', 'J'),
909                0x89 => (UnArith::Conv, 'F', 'D'),
910                0x8a => (UnArith::Conv, 'D', 'I'),
911                0x8b => (UnArith::Conv, 'D', 'J'),
912                0x8c => (UnArith::Conv, 'D', 'F'),
913                0x8d => (UnArith::Conv, 'I', 'B'),
914                0x8e => (UnArith::Conv, 'I', 'C'),
915                _ => (UnArith::Conv, 'I', 'S'),
916            };
917            InsnKind::Un {
918                dst: b4,
919                src: a4,
920                op: opkind,
921                from,
922                to,
923            }
924        }
925
926        // ---- three-register binops (0x90-0xaf, 23x) ----
927        0x90..=0xaf => {
928            let second = u(pc + 1);
929            let (aop, ty) = bin3(op - 0x90);
930            return (
931                2,
932                InsnKind::Bin {
933                    op: aop,
934                    dst: aa,
935                    a: (second & 0xff),
936                    b: (second >> 8),
937                    ty,
938                },
939            );
940        }
941
942        // ---- two-address binops (0xb0-0xcf, 12x) ----
943        0xb0..=0xcf => {
944            let (aop, ty) = bin3(op - 0xb0);
945            InsnKind::Bin {
946                op: aop,
947                dst: b4,
948                a: b4,
949                b: a4,
950                ty,
951            }
952        }
953
954        // ---- literal binops ----
955        0xd0..=0xd7 => {
956            let (aop, rsub) = binlit16(op - 0xd0);
957            return (
958                2,
959                InsnKind::BinLit {
960                    op: aop,
961                    dst: b4,
962                    a: a4,
963                    lit: u(pc + 1) as i16 as i32,
964                    rsub,
965                },
966            );
967        }
968        0xd8..=0xe2 => {
969            let (aop, rsub) = binlit8(op - 0xd8);
970            let second = u(pc + 1);
971            let cc = (second >> 8) as i8;
972            return (
973                2,
974                InsnKind::BinLit {
975                    op: aop,
976                    dst: aa,
977                    a: (second & 0xff),
978                    lit: cc as i32,
979                    rsub,
980                },
981            );
982        }
983
984        // ART-internal (return-void-no-barrier 0x73) / odex quickened / unused.
985        _ => InsnKind::Unknown,
986    };
987
988    // One-unit formats default; two/three-unit ones returned early.
989    (1, mk)
990}
991
992/// Maps a binop family offset to `(op, primitive)`.
993/// Layout: int(8) + int-shifts(3) + long(8) + long-shifts(3) + float(5) +
994/// double(5) — identical order for the 23x (0x90) and 2addr (0xb0) families.
995fn bin3(off: u8) -> (ArithOp, char) {
996    const OPS8: [ArithOp; 8] = [
997        ArithOp::Add,
998        ArithOp::Sub,
999        ArithOp::Mul,
1000        ArithOp::Div,
1001        ArithOp::Rem,
1002        ArithOp::And,
1003        ArithOp::Or,
1004        ArithOp::Xor,
1005    ];
1006    const OPS5: [ArithOp; 5] = [
1007        ArithOp::Add,
1008        ArithOp::Sub,
1009        ArithOp::Mul,
1010        ArithOp::Div,
1011        ArithOp::Rem,
1012    ];
1013    match off {
1014        0..=7 => (OPS8[off as usize], 'I'),
1015        8..=10 => (
1016            [ArithOp::Shl, ArithOp::Shr, ArithOp::Ushr][(off - 8) as usize],
1017            'I',
1018        ),
1019        11..=18 => (OPS8[(off - 11) as usize], 'J'),
1020        19..=21 => (
1021            [ArithOp::Shl, ArithOp::Shr, ArithOp::Ushr][(off - 19) as usize],
1022            'J',
1023        ),
1024        22..=26 => (OPS5[(off - 22) as usize], 'F'),
1025        _ => (OPS5[(off - 27) as usize], 'D'),
1026    }
1027}
1028
1029/// `binop/lit16` offset → op (+ rsub flag for reverse subtract).
1030fn binlit16(off: u8) -> (ArithOp, bool) {
1031    match off {
1032        0 => (ArithOp::Add, false),
1033        1 => (ArithOp::Sub, true), // rsub-int
1034        2 => (ArithOp::Mul, false),
1035        3 => (ArithOp::Div, false),
1036        4 => (ArithOp::Rem, false),
1037        5 => (ArithOp::And, false),
1038        6 => (ArithOp::Or, false),
1039        _ => (ArithOp::Xor, false),
1040    }
1041}
1042
1043/// `binop/lit8` offset → op (+ rsub flag).
1044fn binlit8(off: u8) -> (ArithOp, bool) {
1045    match off {
1046        0 => (ArithOp::Add, false),
1047        1 => (ArithOp::Sub, true),
1048        2 => (ArithOp::Mul, false),
1049        3 => (ArithOp::Div, false),
1050        4 => (ArithOp::Rem, false),
1051        5 => (ArithOp::And, false),
1052        6 => (ArithOp::Or, false),
1053        7 => (ArithOp::Xor, false),
1054        8 => (ArithOp::Shl, false),
1055        9 => (ArithOp::Shr, false),
1056        _ => (ArithOp::Ushr, false),
1057    }
1058}
1059
1060/// Instruction size (code units) by opcode — the full-format table from
1061/// the dalvik spec. Cross-checked against `decode_one`'s per-arm sizes by
1062/// the differential test below; the boundary walker (scan_instructions)
1063/// depends on it matching exactly.
1064pub fn opcode_units(op: u8) -> u32 {
1065    match op {
1066        0x00 => 1,
1067        0x01 | 0x04 | 0x07 => 1,
1068        0x02 | 0x05 | 0x08 => 2,
1069        0x03 | 0x06 | 0x09 => 3,
1070        0x0a..=0x12 => 1,
1071        0x13 | 0x15 | 0x16 | 0x19 => 2,
1072        0x14 | 0x17 => 3,
1073        0x18 => 5,
1074        0x1a | 0x1c => 2,
1075        0x1b => 3,
1076        0x1d | 0x1e | 0x21 | 0x27 | 0x28 => 1,
1077        0x1f | 0x20 | 0x22 | 0x23 => 2,
1078        0x24..=0x26 => 3,
1079        0x29 => 2,
1080        0x2a => 3,
1081        0x2b | 0x2c => 2,
1082        0x2d..=0x37 => 2,
1083        0x38..=0x43 => 2,
1084        0x44..=0x6d => 2,
1085        0x6e..=0x72 => 3,
1086        0x73 => 1,
1087        0x74..=0x78 => 3,
1088        0x79 | 0x7a => 1,
1089        0x7b..=0x8a => 1,
1090        0x8b..=0x8f => 2,
1091        0x90..=0xaf => 2,
1092        0xb0..=0xcf => 1,
1093        0xd0..=0xe2 => 2,
1094        // invoke-custom (0xfc/0xfd, 35c/3rc) and method handle/type
1095        // constants (0xfe/0xff, 21c) are real format-3/2 instructions.
1096        0xfc | 0xfd => 3,
1097        0xfe | 0xff => 2,
1098        // remaining odex/unused: safest one-unit default keeps the walk
1099        // aligned with decode_all's degenerate handling.
1100        _ => 1,
1101    }
1102}
1103
1104#[inline]
1105fn unit_at(bytes: &[u8], i: usize) -> u16 {
1106    if 2 * i + 1 < bytes.len() {
1107        u16::from_le_bytes([bytes[2 * i], bytes[2 * i + 1]])
1108    } else {
1109        0
1110    }
1111}
1112
1113#[inline]
1114fn units_len(bytes: &[u8]) -> usize {
1115    bytes.len() / 2
1116}
1117
1118/// Walk instruction BOUNDARIES only: `f(opcode, pc, insns_bytes)` fires for
1119/// every real instruction (payload pseudo-ops skipped), with the RAW insns
1120/// byte section for operand reads (`unit_at(bytes, pc + 1)` etc). No
1121/// InsnKind construction, no allocations — the reference-search scan path
1122/// (a full decode per instruction was the dominant cost).
1123pub fn scan_instructions<F: FnMut(u8, usize, &[u8])>(bytes: &[u8], f: &mut F) {
1124    let n = units_len(bytes);
1125    let mut pc = 0usize;
1126    while pc < n {
1127        let first = unit_at(bytes, pc);
1128        if first == 0x0100 || first == 0x0200 || first == 0x0300 {
1129            let size = payload_units_from_bytes(bytes, pc);
1130            pc += if size == 0 { 1 } else { size };
1131            continue;
1132        }
1133        let op = (first & 0xff) as u8;
1134        f(op, pc, bytes);
1135        pc += opcode_units(op).max(1) as usize;
1136    }
1137}
1138
1139fn payload_units_from_bytes(bytes: &[u8], pc: usize) -> usize {
1140    let unit = |i: usize| -> u16 { unit_at(bytes, i) };
1141    match unit(pc) {
1142        0x0100 => 4 + unit(pc + 1) as usize * 2,
1143        0x0200 => 2 + unit(pc + 1) as usize * 4,
1144        0x0300 => {
1145            let width = unit(pc + 1) as usize;
1146            let size = (unit(pc + 2) as usize) | ((unit(pc + 3) as usize) << 16);
1147            4 + (size * width).div_ceil(2)
1148        }
1149        _ => 0,
1150    }
1151}
1152
1153/// Decode a method body's unit stream: linear instructions + payloads map.
1154pub fn decode_all(bytes: &[u8]) -> (Vec<Insn>, HashMap<u32, Payload>) {
1155    let units = u16s(bytes);
1156    let mut insns = Vec::new();
1157    let mut payloads = HashMap::new();
1158    let mut pc = 0usize;
1159    while pc < units.len() {
1160        // Payload pseudo-instruction? (first unit is one of the idents.)
1161        let first = units[pc];
1162        if first == 0x0100 || first == 0x0200 || first == 0x0300 {
1163            let size = payload_units(bytes, pc);
1164            if size == 0 {
1165                // Malformed; treat as a one-unit nop so decoding continues.
1166                insns.push(Insn {
1167                    pc: pc as u32,
1168                    size: 1,
1169                    op: 0,
1170                    kind: InsnKind::Nop,
1171                });
1172                pc += 1;
1173                continue;
1174            }
1175            if let Some(p) = parse_payload(bytes, pc) {
1176                payloads.insert(pc as u32, p);
1177            }
1178            pc += size;
1179            continue;
1180        }
1181        let (size, kind) = decode_one(&units, pc);
1182        let size = size.max(1);
1183        insns.push(Insn {
1184            pc: pc as u32,
1185            size,
1186            op: (units[pc] & 0xff) as u8,
1187            kind,
1188        });
1189        pc += size as usize;
1190    }
1191    (insns, payloads)
1192}
1193
1194/// Best-effort mnemonic for diagnostics.
1195pub fn op_name(op: u8) -> &'static str {
1196    match op {
1197        0x00 => "nop",
1198        0x01 => "move",
1199        0x02 => "move/from16",
1200        0x03 => "move/16",
1201        0x04 => "move-wide",
1202        0x05 => "move-wide/from16",
1203        0x06 => "move-wide/16",
1204        0x07 => "move-object",
1205        0x08 => "move-object/from16",
1206        0x09 => "move-object/16",
1207        0x0a => "move-result",
1208        0x0b => "move-result-wide",
1209        0x0c => "move-result-object",
1210        0x0d => "move-exception",
1211        0x0e => "return-void",
1212        0x0f => "return",
1213        0x10 => "return-wide",
1214        0x11 => "return-object",
1215        0x12 => "const/4",
1216        0x13 => "const/16",
1217        0x14 => "const",
1218        0x15 => "const/high16",
1219        0x16 => "const-wide/16",
1220        0x17 => "const-wide/32",
1221        0x18 => "const-wide",
1222        0x19 => "const-wide/high16",
1223        0x1a => "const-string",
1224        0x1b => "const-string/jumbo",
1225        0x1c => "const-class",
1226        0x1d => "monitor-enter",
1227        0x1e => "monitor-exit",
1228        0x1f => "check-cast",
1229        0x20 => "instance-of",
1230        0x21 => "array-length",
1231        0x22 => "new-instance",
1232        0x23 => "new-array",
1233        0x24 => "filled-new-array",
1234        0x25 => "filled-new-array/range",
1235        0x26 => "fill-array-data",
1236        0x27 => "throw",
1237        0x28 => "goto",
1238        0x29 => "goto/16",
1239        0x2a => "goto/32",
1240        0x2b => "packed-switch",
1241        0x2c => "sparse-switch",
1242        0x2d..=0x31 => "cmp",
1243        0x32..=0x3d => "if",
1244        0x44..=0x4a => "aget",
1245        0x4b..=0x51 => "aput",
1246        0x52..=0x58 => "iget",
1247        0x59..=0x5f => "iput",
1248        0x60..=0x66 => "sget",
1249        0x67..=0x6d => "sput",
1250        0x6e..=0x72 => "invoke",
1251        0x74..=0x78 => "invoke/range",
1252        0x7b..=0x8f => "unop",
1253        0x90..=0xaf => "binop",
1254        0xb0..=0xcf => "binop/2addr",
1255        0xd0..=0xd7 => "binop/lit16",
1256        0xd8..=0xe2 => "binop/lit8",
1257        0xfa | 0xfb => "invoke-polymorphic",
1258        0xfc | 0xfd => "invoke-custom",
1259        0xfe => "const-method-handle",
1260        0xff => "const-method-type",
1261        _ => "unknown",
1262    }
1263}
1264
1265#[allow(dead_code)]
1266fn unused_format_units() {
1267    let _ = format_units(10);
1268    let _ = Cursor::new(&[]);
1269}