neo_solidity/cli/bytecode/bytecode_disasm/
disassemble.rs

1pub(crate) fn disassemble_neovm_bytecode(bytecode: &[u8]) -> String {
2    let width = if bytecode.len() <= 0xFFFF { 4 } else { 6 };
3    let mut out = String::new();
4    let mut pc: usize = 0;
5
6    while pc < bytecode.len() {
7        let offset = pc;
8        let opcode = bytecode[pc];
9        pc += 1;
10
11        let opname = neo_solidity::runtime::spec::opcode_name(opcode)
12            .map(str::to_string)
13            .unwrap_or_else(|| format!("OP_{opcode:02X}"));
14
15        out.push_str(&format!("{offset:0width$X}: {opname}", width = width));
16
17        match opcode {
18            // Push immediates
19            0x00 => {
20                // PUSHINT8
21                if let Some(v) = take_i8(bytecode, &mut pc) {
22                    out.push_str(&format!(" {v}"));
23                } else {
24                    out.push_str(" <unexpected EOF>");
25                    break;
26                }
27            }
28            0x01 => {
29                // PUSHINT16
30                if let Some(v) = take_i16(bytecode, &mut pc) {
31                    out.push_str(&format!(" {v}"));
32                } else {
33                    out.push_str(" <unexpected EOF>");
34                    break;
35                }
36            }
37            0x02 => {
38                // PUSHINT32
39                if let Some(v) = take_i32(bytecode, &mut pc) {
40                    out.push_str(&format!(" {v}"));
41                } else {
42                    out.push_str(" <unexpected EOF>");
43                    break;
44                }
45            }
46            0x03 => {
47                // PUSHINT64
48                if let Some(v) = take_i64(bytecode, &mut pc) {
49                    out.push_str(&format!(" {v}"));
50                } else {
51                    out.push_str(" <unexpected EOF>");
52                    break;
53                }
54            }
55            0x04 => {
56                // PUSHINT128 (raw LE bytes)
57                if let Some(bytes) = take(bytecode, &mut pc, 16) {
58                    out.push_str(&format!(" 0x{}", hex::encode(bytes)));
59                } else {
60                    out.push_str(" <unexpected EOF>");
61                    break;
62                }
63            }
64            0x05 => {
65                // PUSHINT256 (raw LE bytes)
66                if let Some(bytes) = take(bytecode, &mut pc, 32) {
67                    out.push_str(&format!(" 0x{}", hex::encode(bytes)));
68                } else {
69                    out.push_str(" <unexpected EOF>");
70                    break;
71                }
72            }
73            0x0A => {
74                // PUSHA (absolute address)
75                if let Some(target) = take_u32(bytecode, &mut pc) {
76                    out.push_str(&format!(" 0x{target:06X}"));
77                } else {
78                    out.push_str(" <unexpected EOF>");
79                    break;
80                }
81            }
82            0x0C => {
83                // PUSHDATA1
84                let Some(len) = take_u8(bytecode, &mut pc) else {
85                    out.push_str(" <unexpected EOF>");
86                    break;
87                };
88                let Some(data) = take(bytecode, &mut pc, len as usize) else {
89                    out.push_str(" <unexpected EOF>");
90                    break;
91                };
92                out.push_str(&format!(" len={len} 0x{}", hex::encode(data)));
93            }
94            0x0D => {
95                // PUSHDATA2
96                let Some(len) = take_u16(bytecode, &mut pc) else {
97                    out.push_str(" <unexpected EOF>");
98                    break;
99                };
100                let Some(data) = take(bytecode, &mut pc, len as usize) else {
101                    out.push_str(" <unexpected EOF>");
102                    break;
103                };
104                out.push_str(&format!(" len={len} 0x{}", hex::encode(data)));
105            }
106            0x0E => {
107                // PUSHDATA4
108                let Some(len) = take_u32(bytecode, &mut pc) else {
109                    out.push_str(" <unexpected EOF>");
110                    break;
111                };
112                let Some(data) = take(bytecode, &mut pc, len as usize) else {
113                    out.push_str(" <unexpected EOF>");
114                    break;
115                };
116                out.push_str(&format!(" len={len} 0x{}", hex::encode(data)));
117            }
118
119            // Flow control (wide forms used by compiler)
120            0x23 // JMP_L
121            | 0x25 // JMPIF_L
122            | 0x27 // JMPIFNOT_L
123            | 0x29 // JMPEQ_L
124            | 0x2B // JMPNE_L
125            | 0x2D // JMPGT_L
126            | 0x2F // JMPGE_L
127            | 0x31 // JMPLT_L
128            | 0x33 // JMPLE_L
129            | 0x35 // CALL_L
130            | 0x3E // ENDTRY_L
131            => {
132                let Some(rel) = take_i32(bytecode, &mut pc) else {
133                    out.push_str(" <unexpected EOF>");
134                    break;
135                };
136                out.push_str(&format!(" -> {}", fmt_target(bytecode.len(), offset, rel)));
137            }
138            0x3C => {
139                // TRY_L CatchOffset(int) FinallyOffset(int)
140                let Some(catch_rel) = take_i32(bytecode, &mut pc) else {
141                    out.push_str(" <unexpected EOF>");
142                    break;
143                };
144                let Some(finally_rel) = take_i32(bytecode, &mut pc) else {
145                    out.push_str(" <unexpected EOF>");
146                    break;
147                };
148                if catch_rel != 0 {
149                    out.push_str(&format!(
150                        " catch={}",
151                        fmt_target(bytecode.len(), offset, catch_rel)
152                    ));
153                }
154                if finally_rel != 0 {
155                    out.push_str(&format!(
156                        " finally={}",
157                        fmt_target(bytecode.len(), offset, finally_rel)
158                    ));
159                }
160            }
161            0x3B => {
162                // TRY CatchOffset(sbyte) FinallyOffset(sbyte)
163                let Some(catch_rel) = take_i8(bytecode, &mut pc) else {
164                    out.push_str(" <unexpected EOF>");
165                    break;
166                };
167                let Some(finally_rel) = take_i8(bytecode, &mut pc) else {
168                    out.push_str(" <unexpected EOF>");
169                    break;
170                };
171                if catch_rel != 0 {
172                    out.push_str(&format!(
173                        " catch={}",
174                        fmt_target(bytecode.len(), offset, catch_rel as i32)
175                    ));
176                }
177                if finally_rel != 0 {
178                    out.push_str(&format!(
179                        " finally={}",
180                        fmt_target(bytecode.len(), offset, finally_rel as i32)
181                    ));
182                }
183            }
184            0x22 // JMP
185            | 0x24 // JMPIF
186            | 0x26 // JMPIFNOT
187            | 0x28 // JMPEQ
188            | 0x2A // JMPNE
189            | 0x2C // JMPGT
190            | 0x2E // JMPGE
191            | 0x30 // JMPLT
192            | 0x32 // JMPLE
193            | 0x34 // CALL
194            | 0x3D // ENDTRY
195            => {
196                let Some(rel) = take_i8(bytecode, &mut pc) else {
197                    out.push_str(" <unexpected EOF>");
198                    break;
199                };
200                out.push_str(&format!(
201                    " -> {}",
202                    fmt_target(bytecode.len(), offset, rel as i32)
203                ));
204            }
205
206            // Slots / indices
207            0x56 => {
208                // INITSSLOT static_slots
209                let Some(count) = take_u8(bytecode, &mut pc) else {
210                    out.push_str(" <unexpected EOF>");
211                    break;
212                };
213                out.push_str(&format!(" {count}"));
214            }
215            0x57 => {
216                // INITSLOT locals,args
217                let Some(locals) = take_u8(bytecode, &mut pc) else {
218                    out.push_str(" <unexpected EOF>");
219                    break;
220                };
221                let Some(args) = take_u8(bytecode, &mut pc) else {
222                    out.push_str(" <unexpected EOF>");
223                    break;
224                };
225                out.push_str(&format!(" locals={locals} args={args}"));
226            }
227            0x5F | 0x67 | 0x6F | 0x77 | 0x7F | 0x87 => {
228                // LDSFLD/STSFLD/LDLOC/STLOC/LDARG/STARG (generic index form)
229                let Some(index) = take_u8(bytecode, &mut pc) else {
230                    out.push_str(" <unexpected EOF>");
231                    break;
232                };
233                out.push_str(&format!(" {index}"));
234            }
235
236            // Syscall
237            0x41 => {
238                let Some(id_bytes) = take(bytecode, &mut pc, 4) else {
239                    out.push_str(" <unexpected EOF>");
240                    break;
241                };
242                let id = [id_bytes[0], id_bytes[1], id_bytes[2], id_bytes[3]];
243                if let Some(name) = neo_solidity::runtime::spec::syscall_name(&id) {
244                    out.push_str(&format!(" {name}"));
245                } else {
246                    out.push_str(&format!(" 0x{}", hex::encode(id)));
247                }
248            }
249
250            _ => {}
251        }
252
253        out.push('\n');
254    }
255
256    out
257}