Commit | Line | Data |
---|---|---|
98fa08a5 | 1 | // SPDX-License-Identifier: LGPL-2.1-or-later |
d16005f8 | 2 | /* |
98fa08a5 | 3 | * Copyright (C) 2014-2021 Paul Cercueil <paul@crapouillou.net> |
d16005f8 PC |
4 | */ |
5 | ||
d16005f8 | 6 | #include <stdbool.h> |
98fa08a5 | 7 | #include <stdio.h> |
d16005f8 PC |
8 | #include <stdlib.h> |
9 | #include <string.h> | |
10 | ||
d16005f8 | 11 | #include "lightrec-private.h" |
98fa08a5 | 12 | #include "regcache.h" |
d16005f8 | 13 | |
98fa08a5 PC |
14 | static const char *std_opcodes[] = { |
15 | [OP_J] = "j ", | |
16 | [OP_JAL] = "jal ", | |
17 | [OP_BEQ] = "beq ", | |
18 | [OP_BNE] = "bne ", | |
19 | [OP_BLEZ] = "blez ", | |
20 | [OP_BGTZ] = "bgtz ", | |
21 | [OP_ADDI] = "addi ", | |
22 | [OP_ADDIU] = "addiu ", | |
23 | [OP_SLTI] = "slti ", | |
24 | [OP_SLTIU] = "sltiu ", | |
25 | [OP_ANDI] = "andi ", | |
26 | [OP_ORI] = "ori ", | |
27 | [OP_XORI] = "xori ", | |
28 | [OP_LUI] = "lui ", | |
29 | [OP_LB] = "lb ", | |
30 | [OP_LH] = "lh ", | |
31 | [OP_LWL] = "lwl ", | |
32 | [OP_LW] = "lw ", | |
33 | [OP_LBU] = "lbu ", | |
34 | [OP_LHU] = "lhu ", | |
35 | [OP_LWR] = "lwr ", | |
36 | [OP_SB] = "sb ", | |
37 | [OP_SH] = "sh ", | |
38 | [OP_SWL] = "swl ", | |
39 | [OP_SW] = "sw ", | |
40 | [OP_SWR] = "swr ", | |
41 | [OP_LWC2] = "lwc2 ", | |
42 | [OP_SWC2] = "swc2 ", | |
43 | }; | |
d16005f8 | 44 | |
98fa08a5 PC |
45 | static const char *special_opcodes[] = { |
46 | [OP_SPECIAL_SLL] = "sll ", | |
47 | [OP_SPECIAL_SRL] = "srl ", | |
48 | [OP_SPECIAL_SRA] = "sra ", | |
49 | [OP_SPECIAL_SLLV] = "sllv ", | |
50 | [OP_SPECIAL_SRLV] = "srlv ", | |
51 | [OP_SPECIAL_SRAV] = "srav ", | |
52 | [OP_SPECIAL_JR] = "jr ", | |
53 | [OP_SPECIAL_JALR] = "jalr ", | |
54 | [OP_SPECIAL_SYSCALL] = "syscall ", | |
55 | [OP_SPECIAL_BREAK] = "break ", | |
56 | [OP_SPECIAL_MFHI] = "mfhi ", | |
57 | [OP_SPECIAL_MTHI] = "mthi ", | |
58 | [OP_SPECIAL_MFLO] = "mflo ", | |
59 | [OP_SPECIAL_MTLO] = "mtlo ", | |
60 | [OP_SPECIAL_MULT] = "mult ", | |
61 | [OP_SPECIAL_MULTU] = "multu ", | |
62 | [OP_SPECIAL_DIV] = "div ", | |
63 | [OP_SPECIAL_DIVU] = "divu ", | |
64 | [OP_SPECIAL_ADD] = "add ", | |
65 | [OP_SPECIAL_ADDU] = "addu ", | |
66 | [OP_SPECIAL_SUB] = "sub ", | |
67 | [OP_SPECIAL_SUBU] = "subu ", | |
68 | [OP_SPECIAL_AND] = "and ", | |
69 | [OP_SPECIAL_OR] = "or ", | |
70 | [OP_SPECIAL_XOR] = "xor ", | |
71 | [OP_SPECIAL_NOR] = "nor ", | |
72 | [OP_SPECIAL_SLT] = "slt ", | |
73 | [OP_SPECIAL_SLTU] = "sltu ", | |
74 | }; | |
d16005f8 | 75 | |
98fa08a5 PC |
76 | static const char *regimm_opcodes[] = { |
77 | [OP_REGIMM_BLTZ] = "bltz ", | |
78 | [OP_REGIMM_BGEZ] = "bgez ", | |
79 | [OP_REGIMM_BLTZAL] = "bltzal ", | |
80 | [OP_REGIMM_BGEZAL] = "bgezal ", | |
81 | }; | |
d16005f8 | 82 | |
98fa08a5 PC |
83 | static const char *cp0_opcodes[] = { |
84 | [OP_CP0_MFC0] = "mfc0 ", | |
85 | [OP_CP0_CFC0] = "cfc0 ", | |
86 | [OP_CP0_MTC0] = "mtc0 ", | |
87 | [OP_CP0_CTC0] = "ctc0 ", | |
88 | [OP_CP0_RFE] = "rfe", | |
89 | }; | |
90 | ||
91 | static const char *cp2_opcodes[] = { | |
92 | [OP_CP2_BASIC_MFC2] = "mfc2 ", | |
93 | [OP_CP2_BASIC_CFC2] = "cfc2 ", | |
94 | [OP_CP2_BASIC_MTC2] = "mtc2 ", | |
95 | [OP_CP2_BASIC_CTC2] = "ctc2 ", | |
96 | }; | |
97 | ||
98 | static const char *opcode_flags[] = { | |
99 | "switched branch/DS", | |
100 | "unload Rs", | |
101 | "unload Rt", | |
102 | "unload Rd", | |
103 | "sync point", | |
104 | }; | |
105 | ||
106 | static const char *opcode_io_flags[] = { | |
107 | "memory I/O", | |
108 | "hardware I/O", | |
109 | "self-modifying code", | |
110 | "no invalidation", | |
111 | }; | |
d16005f8 | 112 | |
98fa08a5 PC |
113 | static const char *opcode_branch_flags[] = { |
114 | "emulate branch", | |
115 | "local branch", | |
116 | }; | |
117 | ||
118 | static const char *opcode_multdiv_flags[] = { | |
119 | "No LO", | |
120 | "No HI", | |
121 | "No div check", | |
122 | }; | |
123 | ||
124 | static int print_flags(char *buf, size_t len, u16 flags, | |
125 | const char **array, size_t array_size) | |
d16005f8 | 126 | { |
98fa08a5 | 127 | const char *flag_name; |
d16005f8 | 128 | unsigned int i; |
98fa08a5 PC |
129 | size_t count = 0, bytes; |
130 | bool first = true; | |
d16005f8 | 131 | |
98fa08a5 PC |
132 | for (i = 0; i < array_size + ARRAY_SIZE(opcode_flags); i++) { |
133 | if (!(flags & BIT(i))) | |
134 | continue; | |
d16005f8 | 135 | |
98fa08a5 PC |
136 | if (i < ARRAY_SIZE(opcode_flags)) |
137 | flag_name = opcode_flags[i]; | |
d16005f8 | 138 | else |
98fa08a5 PC |
139 | flag_name = array[i - ARRAY_SIZE(opcode_flags)]; |
140 | ||
141 | if (first) | |
142 | bytes = snprintf(buf, len, "(%s", flag_name); | |
143 | else | |
144 | bytes = snprintf(buf, len, ", %s", flag_name); | |
145 | ||
146 | first = false; | |
147 | buf += bytes; | |
148 | len -= bytes; | |
149 | count += bytes; | |
d16005f8 PC |
150 | } |
151 | ||
98fa08a5 PC |
152 | if (!first) |
153 | count += snprintf(buf, len, ")"); | |
154 | else | |
155 | *buf = '\0'; | |
d16005f8 | 156 | |
98fa08a5 | 157 | return count; |
d16005f8 PC |
158 | } |
159 | ||
98fa08a5 PC |
160 | static int print_op_special(union code c, char *buf, size_t len, |
161 | const char ***flags_ptr, size_t *nb_flags) | |
d16005f8 | 162 | { |
98fa08a5 PC |
163 | switch (c.r.op) { |
164 | case OP_SPECIAL_SLL: | |
165 | case OP_SPECIAL_SRL: | |
166 | case OP_SPECIAL_SRA: | |
167 | return snprintf(buf, len, "%s%s,%s,%u", | |
168 | special_opcodes[c.r.op], | |
169 | lightrec_reg_name(c.r.rd), | |
170 | lightrec_reg_name(c.r.rt), | |
171 | c.r.imm); | |
172 | case OP_SPECIAL_SLLV: | |
173 | case OP_SPECIAL_SRLV: | |
174 | case OP_SPECIAL_SRAV: | |
175 | case OP_SPECIAL_ADD: | |
176 | case OP_SPECIAL_ADDU: | |
177 | case OP_SPECIAL_SUB: | |
178 | case OP_SPECIAL_SUBU: | |
179 | case OP_SPECIAL_AND: | |
180 | case OP_SPECIAL_OR: | |
181 | case OP_SPECIAL_XOR: | |
182 | case OP_SPECIAL_NOR: | |
183 | case OP_SPECIAL_SLT: | |
184 | case OP_SPECIAL_SLTU: | |
185 | return snprintf(buf, len, "%s%s,%s,%s", | |
186 | special_opcodes[c.r.op], | |
187 | lightrec_reg_name(c.r.rd), | |
188 | lightrec_reg_name(c.r.rt), | |
189 | lightrec_reg_name(c.r.rs)); | |
190 | case OP_SPECIAL_JR: | |
191 | case OP_SPECIAL_MTHI: | |
192 | case OP_SPECIAL_MTLO: | |
193 | return snprintf(buf, len, "%s%s", | |
194 | special_opcodes[c.r.op], | |
195 | lightrec_reg_name(c.r.rs)); | |
196 | case OP_SPECIAL_JALR: | |
197 | return snprintf(buf, len, "%s%s,%s", | |
198 | special_opcodes[c.r.op], | |
199 | lightrec_reg_name(c.r.rd), | |
200 | lightrec_reg_name(c.r.rt)); | |
201 | case OP_SPECIAL_SYSCALL: | |
202 | case OP_SPECIAL_BREAK: | |
203 | return snprintf(buf, len, "%s", special_opcodes[c.r.op]); | |
204 | case OP_SPECIAL_MFHI: | |
205 | case OP_SPECIAL_MFLO: | |
206 | return snprintf(buf, len, "%s%s", | |
207 | special_opcodes[c.r.op], | |
208 | lightrec_reg_name(c.r.rd)); | |
209 | case OP_SPECIAL_MULT: | |
210 | case OP_SPECIAL_MULTU: | |
211 | case OP_SPECIAL_DIV: | |
212 | case OP_SPECIAL_DIVU: | |
213 | *flags_ptr = opcode_multdiv_flags; | |
214 | *nb_flags = ARRAY_SIZE(opcode_multdiv_flags); | |
215 | return snprintf(buf, len, "%s%s,%s,%s,%s", | |
216 | special_opcodes[c.r.op], | |
217 | lightrec_reg_name(get_mult_div_hi(c)), | |
218 | lightrec_reg_name(get_mult_div_lo(c)), | |
219 | lightrec_reg_name(c.r.rs), | |
220 | lightrec_reg_name(c.r.rt)); | |
d16005f8 | 221 | default: |
98fa08a5 | 222 | return snprintf(buf, len, "unknown (0x%08x)", c.opcode); |
d16005f8 PC |
223 | } |
224 | } | |
225 | ||
98fa08a5 | 226 | static int print_op_cp(union code c, char *buf, size_t len, unsigned int cp) |
d16005f8 | 227 | { |
98fa08a5 PC |
228 | if (cp == 2) { |
229 | switch (c.i.rs) { | |
230 | case OP_CP0_MFC0: | |
231 | case OP_CP0_CFC0: | |
232 | case OP_CP0_MTC0: | |
233 | case OP_CP0_CTC0: | |
234 | return snprintf(buf, len, "%s%s,%u", | |
235 | cp2_opcodes[c.i.rs], | |
236 | lightrec_reg_name(c.i.rt), | |
237 | c.r.rd); | |
238 | default: | |
239 | return snprintf(buf, len, "cp2 (0x%08x)", c.opcode); | |
240 | } | |
241 | } else { | |
242 | switch (c.i.rs) { | |
243 | case OP_CP0_MFC0: | |
244 | case OP_CP0_CFC0: | |
245 | case OP_CP0_MTC0: | |
246 | case OP_CP0_CTC0: | |
247 | return snprintf(buf, len, "%s%s,%u", | |
248 | cp0_opcodes[c.i.rs], | |
249 | lightrec_reg_name(c.i.rt), | |
250 | c.r.rd); | |
251 | case OP_CP0_RFE: | |
252 | return snprintf(buf, len, "rfe "); | |
253 | default: | |
254 | return snprintf(buf, len, "unknown (0x%08x)", c.opcode); | |
255 | } | |
256 | } | |
257 | } | |
258 | ||
259 | static int print_op(union code c, u32 pc, char *buf, size_t len, | |
260 | const char ***flags_ptr, size_t *nb_flags) | |
261 | { | |
262 | if (c.opcode == 0) | |
263 | return snprintf(buf, len, "nop "); | |
264 | ||
265 | switch (c.i.op) { | |
266 | case OP_SPECIAL: | |
267 | return print_op_special(c, buf, len, flags_ptr, nb_flags); | |
268 | case OP_REGIMM: | |
269 | *flags_ptr = opcode_branch_flags; | |
270 | *nb_flags = ARRAY_SIZE(opcode_branch_flags); | |
271 | return snprintf(buf, len, "%s%s,0x%x", | |
272 | regimm_opcodes[c.i.rt], | |
273 | lightrec_reg_name(c.i.rs), | |
274 | pc + 4 + ((s16)c.i.imm << 2)); | |
275 | case OP_J: | |
276 | case OP_JAL: | |
277 | return snprintf(buf, len, "%s0x%x", | |
278 | std_opcodes[c.i.op], | |
279 | (pc & 0xf0000000) | (c.j.imm << 2)); | |
280 | case OP_BEQ: | |
281 | case OP_BNE: | |
282 | case OP_BLEZ: | |
283 | case OP_BGTZ: | |
284 | *flags_ptr = opcode_branch_flags; | |
285 | *nb_flags = ARRAY_SIZE(opcode_branch_flags); | |
286 | return snprintf(buf, len, "%s%s,%s,0x%x", | |
287 | std_opcodes[c.i.op], | |
288 | lightrec_reg_name(c.i.rs), | |
289 | lightrec_reg_name(c.i.rt), | |
290 | pc + 4 + ((s16)c.i.imm << 2)); | |
291 | case OP_ADDI: | |
292 | case OP_ADDIU: | |
293 | case OP_SLTI: | |
294 | case OP_SLTIU: | |
295 | case OP_ANDI: | |
296 | case OP_ORI: | |
297 | case OP_XORI: | |
298 | return snprintf(buf, len, "%s%s,%s,0x%04hx", | |
299 | std_opcodes[c.i.op], | |
300 | lightrec_reg_name(c.i.rt), | |
301 | lightrec_reg_name(c.i.rs), | |
302 | (u16)c.i.imm); | |
303 | ||
304 | case OP_LUI: | |
305 | return snprintf(buf, len, "%s%s,0x%04hx", | |
306 | std_opcodes[c.i.op], | |
307 | lightrec_reg_name(c.i.rt), | |
308 | (u16)c.i.imm); | |
309 | case OP_CP0: | |
310 | return print_op_cp(c, buf, len, 0); | |
311 | case OP_CP2: | |
312 | return print_op_cp(c, buf, len, 2); | |
313 | case OP_LB: | |
314 | case OP_LH: | |
315 | case OP_LWL: | |
316 | case OP_LW: | |
317 | case OP_LBU: | |
318 | case OP_LHU: | |
319 | case OP_LWR: | |
320 | case OP_SB: | |
321 | case OP_SH: | |
322 | case OP_SWL: | |
323 | case OP_SW: | |
324 | case OP_SWR: | |
325 | *flags_ptr = opcode_io_flags; | |
326 | *nb_flags = ARRAY_SIZE(opcode_io_flags); | |
327 | return snprintf(buf, len, "%s%s,%hd(%s)", | |
328 | std_opcodes[c.i.op], | |
329 | lightrec_reg_name(c.i.rt), | |
330 | (s16)c.i.imm, | |
331 | lightrec_reg_name(c.i.rs)); | |
332 | case OP_LWC2: | |
333 | case OP_SWC2: | |
334 | *flags_ptr = opcode_io_flags; | |
335 | *nb_flags = ARRAY_SIZE(opcode_io_flags); | |
336 | return snprintf(buf, len, "%s%s,%hd(%s)", | |
337 | std_opcodes[c.i.op], | |
338 | lightrec_reg_name(c.i.rt), | |
339 | (s16)c.i.imm, | |
340 | lightrec_reg_name(c.i.rs)); | |
341 | case OP_META_MOV: | |
342 | return snprintf(buf, len, "move %s,%s", | |
343 | lightrec_reg_name(c.r.rd), | |
344 | lightrec_reg_name(c.r.rs)); | |
345 | case OP_META_EXTC: | |
346 | return snprintf(buf, len, "extc %s,%s", | |
347 | lightrec_reg_name(c.i.rt), | |
348 | lightrec_reg_name(c.i.rs)); | |
349 | case OP_META_EXTS: | |
350 | return snprintf(buf, len, "exts %s,%s", | |
351 | lightrec_reg_name(c.i.rt), | |
352 | lightrec_reg_name(c.i.rs)); | |
353 | default: | |
354 | return snprintf(buf, len, "unknown (0x%08x)", c.opcode); | |
355 | } | |
356 | } | |
357 | ||
358 | void lightrec_print_disassembly(const struct block *block, const u32 *code) | |
359 | { | |
360 | const struct opcode *op; | |
361 | const char **flags_ptr; | |
362 | size_t nb_flags, count, count2; | |
363 | char buf[256], buf2[256], buf3[256]; | |
d16005f8 | 364 | unsigned int i; |
98fa08a5 PC |
365 | u32 pc, branch_pc; |
366 | ||
367 | for (i = 0; i < block->nb_ops; i++) { | |
368 | op = &block->opcode_list[i]; | |
369 | branch_pc = get_branch_pc(block, i, 0); | |
370 | pc = block->pc + (i << 2); | |
371 | ||
372 | count = print_op((union code)code[i], pc, buf, sizeof(buf), | |
373 | &flags_ptr, &nb_flags); | |
374 | ||
375 | flags_ptr = NULL; | |
376 | nb_flags = 0; | |
377 | count2 = print_op(op->c, branch_pc, buf2, sizeof(buf2), | |
378 | &flags_ptr, &nb_flags); | |
379 | ||
380 | if (code[i] == op->c.opcode) { | |
381 | *buf2 = '\0'; | |
382 | count2 = 0; | |
383 | } | |
384 | ||
385 | print_flags(buf3, sizeof(buf3), op->flags, flags_ptr, nb_flags); | |
d16005f8 | 386 | |
98fa08a5 PC |
387 | printf("0x%08x (0x%x)\t%s%*c%s%*c%s\n", pc, i << 2, |
388 | buf, 30 - (int)count, ' ', buf2, 30 - (int)count2, ' ', buf3); | |
d16005f8 PC |
389 | } |
390 | } |