+// SPDX-License-Identifier: LGPL-2.1-or-later
/*
- * Copyright (C) 2014-2020 Paul Cercueil <paul@crapouillou.net>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
+ * Copyright (C) 2014-2021 Paul Cercueil <paul@crapouillou.net>
*/
-#include "config.h"
-
-#if ENABLE_DISASSEMBLER
-#include <dis-asm.h>
-#endif
#include <stdbool.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "debug.h"
-#include "disassembler.h"
#include "lightrec-private.h"
-#include "memmanager.h"
+#include "regcache.h"
-static bool is_unconditional_jump(const struct opcode *op)
-{
- switch (op->i.op) {
- case OP_SPECIAL:
- return op->r.op == OP_SPECIAL_JR || op->r.op == OP_SPECIAL_JALR;
- case OP_J:
- case OP_JAL:
- return true;
- case OP_BEQ:
- case OP_BLEZ:
- return op->i.rs == op->i.rt;
- case OP_REGIMM:
- return (op->r.rt == OP_REGIMM_BGEZ ||
- op->r.rt == OP_REGIMM_BGEZAL) && op->i.rs == 0;
- default:
- return false;
- }
-}
+static const char *std_opcodes[] = {
+ [OP_J] = "j ",
+ [OP_JAL] = "jal ",
+ [OP_BEQ] = "beq ",
+ [OP_BNE] = "bne ",
+ [OP_BLEZ] = "blez ",
+ [OP_BGTZ] = "bgtz ",
+ [OP_ADDI] = "addi ",
+ [OP_ADDIU] = "addiu ",
+ [OP_SLTI] = "slti ",
+ [OP_SLTIU] = "sltiu ",
+ [OP_ANDI] = "andi ",
+ [OP_ORI] = "ori ",
+ [OP_XORI] = "xori ",
+ [OP_LUI] = "lui ",
+ [OP_LB] = "lb ",
+ [OP_LH] = "lh ",
+ [OP_LWL] = "lwl ",
+ [OP_LW] = "lw ",
+ [OP_LBU] = "lbu ",
+ [OP_LHU] = "lhu ",
+ [OP_LWR] = "lwr ",
+ [OP_SB] = "sb ",
+ [OP_SH] = "sh ",
+ [OP_SWL] = "swl ",
+ [OP_SW] = "sw ",
+ [OP_SWR] = "swr ",
+ [OP_LWC2] = "lwc2 ",
+ [OP_SWC2] = "swc2 ",
+};
+
+static const char *special_opcodes[] = {
+ [OP_SPECIAL_SLL] = "sll ",
+ [OP_SPECIAL_SRL] = "srl ",
+ [OP_SPECIAL_SRA] = "sra ",
+ [OP_SPECIAL_SLLV] = "sllv ",
+ [OP_SPECIAL_SRLV] = "srlv ",
+ [OP_SPECIAL_SRAV] = "srav ",
+ [OP_SPECIAL_JR] = "jr ",
+ [OP_SPECIAL_JALR] = "jalr ",
+ [OP_SPECIAL_SYSCALL] = "syscall ",
+ [OP_SPECIAL_BREAK] = "break ",
+ [OP_SPECIAL_MFHI] = "mfhi ",
+ [OP_SPECIAL_MTHI] = "mthi ",
+ [OP_SPECIAL_MFLO] = "mflo ",
+ [OP_SPECIAL_MTLO] = "mtlo ",
+ [OP_SPECIAL_MULT] = "mult ",
+ [OP_SPECIAL_MULTU] = "multu ",
+ [OP_SPECIAL_DIV] = "div ",
+ [OP_SPECIAL_DIVU] = "divu ",
+ [OP_SPECIAL_ADD] = "add ",
+ [OP_SPECIAL_ADDU] = "addu ",
+ [OP_SPECIAL_SUB] = "sub ",
+ [OP_SPECIAL_SUBU] = "subu ",
+ [OP_SPECIAL_AND] = "and ",
+ [OP_SPECIAL_OR] = "or ",
+ [OP_SPECIAL_XOR] = "xor ",
+ [OP_SPECIAL_NOR] = "nor ",
+ [OP_SPECIAL_SLT] = "slt ",
+ [OP_SPECIAL_SLTU] = "sltu ",
+};
+
+static const char *regimm_opcodes[] = {
+ [OP_REGIMM_BLTZ] = "bltz ",
+ [OP_REGIMM_BGEZ] = "bgez ",
+ [OP_REGIMM_BLTZAL] = "bltzal ",
+ [OP_REGIMM_BGEZAL] = "bgezal ",
+};
+
+static const char *cp0_opcodes[] = {
+ [OP_CP0_MFC0] = "mfc0 ",
+ [OP_CP0_CFC0] = "cfc0 ",
+ [OP_CP0_MTC0] = "mtc0 ",
+ [OP_CP0_CTC0] = "ctc0 ",
+ [OP_CP0_RFE] = "rfe",
+};
+
+static const char *cp2_opcodes[] = {
+ [OP_CP2_BASIC_MFC2] = "mfc2 ",
+ [OP_CP2_BASIC_CFC2] = "cfc2 ",
+ [OP_CP2_BASIC_MTC2] = "mtc2 ",
+ [OP_CP2_BASIC_CTC2] = "ctc2 ",
+};
+
+static const char *opcode_flags[] = {
+ "switched branch/DS",
+ "sync point",
+};
-static bool is_syscall(const struct opcode *op)
+static const char *opcode_io_flags[] = {
+ "self-modifying code",
+ "no invalidation",
+ "no mask",
+};
+
+static const char *opcode_io_modes[] = {
+ "Memory access",
+ "I/O access",
+ "RAM access",
+ "BIOS access",
+ "Scratchpad access",
+};
+
+static const char *opcode_branch_flags[] = {
+ "emulate branch",
+ "local branch",
+};
+
+static const char *opcode_multdiv_flags[] = {
+ "No LO",
+ "No HI",
+ "No div check",
+};
+
+static size_t do_snprintf(char *buf, size_t len, bool *first,
+ const char *arg1, const char *arg2)
{
- return (op->i.op == OP_SPECIAL && (op->r.op == OP_SPECIAL_SYSCALL ||
- op->r.op == OP_SPECIAL_BREAK)) ||
- (op->i.op == OP_CP0 && (op->r.rs == OP_CP0_MTC0 ||
- op->r.rs == OP_CP0_CTC0) &&
- (op->r.rd == 12 || op->r.rd == 13));
+ size_t bytes;
+
+ if (*first)
+ bytes = snprintf(buf, len, "(%s%s", arg1, arg2);
+ else
+ bytes = snprintf(buf, len, ", %s%s", arg1, arg2);
+
+ *first = false;
+
+ return bytes;
}
-void lightrec_free_opcode_list(struct lightrec_state *state, struct opcode *list)
+static const char * const reg_op_token[3] = {
+ "-", "*", "~",
+};
+
+static int print_flags(char *buf, size_t len, const struct opcode *op,
+ const char **array, size_t array_size,
+ bool is_io)
{
- struct opcode *next;
+ const char *flag_name, *io_mode_name;
+ unsigned int i, io_mode;
+ size_t count = 0, bytes;
+ bool first = true;
+ u32 flags = op->flags;
+ unsigned int reg_op;
- while (list) {
- next = list->next;
- lightrec_free(state, MEM_FOR_IR, sizeof(*list), list);
- list = next;
+ for (i = 0; i < array_size + ARRAY_SIZE(opcode_flags); i++) {
+ if (!(flags & BIT(i)))
+ continue;
+
+ if (i < ARRAY_SIZE(opcode_flags))
+ flag_name = opcode_flags[i];
+ else
+ flag_name = array[i - ARRAY_SIZE(opcode_flags)];
+
+ bytes = do_snprintf(buf, len, &first, "", flag_name);
+ buf += bytes;
+ len -= bytes;
+ count += bytes;
}
-}
-struct opcode * lightrec_disassemble(struct lightrec_state *state,
- const u32 *src, unsigned int *len)
-{
- struct opcode *head = NULL;
- bool stop_next = false;
- struct opcode *curr, *last;
- unsigned int i;
+ if (is_io) {
+ io_mode = LIGHTREC_FLAGS_GET_IO_MODE(flags);
+ if (io_mode > 0) {
+ io_mode_name = opcode_io_modes[io_mode - 1];
- for (i = 0, last = NULL; ; i++, last = curr) {
- curr = lightrec_calloc(state, MEM_FOR_IR, sizeof(*curr));
- if (!curr) {
- pr_err("Unable to allocate memory\n");
- lightrec_free_opcode_list(state, head);
- return NULL;
+ bytes = do_snprintf(buf, len, &first, "", io_mode_name);
+ buf += bytes;
+ len -= bytes;
+ count += bytes;
}
+ }
- if (!last)
- head = curr;
- else
- last->next = curr;
-
- /* TODO: Take care of endianness */
- curr->opcode = LE32TOH(*src++);
- curr->offset = i;
-
- /* NOTE: The block disassembly ends after the opcode that
- * follows an unconditional jump (delay slot) */
- if (stop_next || is_syscall(curr))
- break;
- else if (is_unconditional_jump(curr))
- stop_next = true;
+ if (OPT_EARLY_UNLOAD) {
+ reg_op = LIGHTREC_FLAGS_GET_RS(flags);
+ if (reg_op) {
+ bytes = do_snprintf(buf, len, &first,
+ reg_op_token[reg_op - 1],
+ lightrec_reg_name(op->i.rs));
+ buf += bytes;
+ len -= bytes;
+ count += bytes;
+ }
+
+ reg_op = LIGHTREC_FLAGS_GET_RT(flags);
+ if (reg_op) {
+ bytes = do_snprintf(buf, len, &first,
+ reg_op_token[reg_op - 1],
+ lightrec_reg_name(op->i.rt));
+ buf += bytes;
+ len -= bytes;
+ count += bytes;
+ }
+
+ reg_op = LIGHTREC_FLAGS_GET_RD(flags);
+ if (reg_op) {
+ bytes = do_snprintf(buf, len, &first,
+ reg_op_token[reg_op - 1],
+ lightrec_reg_name(op->r.rd));
+ buf += bytes;
+ len -= bytes;
+ count += bytes;
+ }
}
- if (len)
- *len = (i + 1) * sizeof(u32);
+ if (!first)
+ count += snprintf(buf, len, ")");
+ else
+ *buf = '\0';
- return head;
+ return count;
}
-unsigned int lightrec_cycles_of_opcode(union code code)
+static int print_op_special(union code c, char *buf, size_t len,
+ const char ***flags_ptr, size_t *nb_flags)
{
- switch (code.i.op) {
- case OP_META_REG_UNLOAD:
- case OP_META_SYNC:
- return 0;
+ switch (c.r.op) {
+ case OP_SPECIAL_SLL:
+ case OP_SPECIAL_SRL:
+ case OP_SPECIAL_SRA:
+ return snprintf(buf, len, "%s%s,%s,%u",
+ special_opcodes[c.r.op],
+ lightrec_reg_name(c.r.rd),
+ lightrec_reg_name(c.r.rt),
+ c.r.imm);
+ case OP_SPECIAL_SLLV:
+ case OP_SPECIAL_SRLV:
+ case OP_SPECIAL_SRAV:
+ case OP_SPECIAL_ADD:
+ case OP_SPECIAL_ADDU:
+ case OP_SPECIAL_SUB:
+ case OP_SPECIAL_SUBU:
+ case OP_SPECIAL_AND:
+ case OP_SPECIAL_OR:
+ case OP_SPECIAL_XOR:
+ case OP_SPECIAL_NOR:
+ case OP_SPECIAL_SLT:
+ case OP_SPECIAL_SLTU:
+ return snprintf(buf, len, "%s%s,%s,%s",
+ special_opcodes[c.r.op],
+ lightrec_reg_name(c.r.rd),
+ lightrec_reg_name(c.r.rt),
+ lightrec_reg_name(c.r.rs));
+ case OP_SPECIAL_JR:
+ *flags_ptr = opcode_branch_flags;
+ *nb_flags = ARRAY_SIZE(opcode_branch_flags);
+ fallthrough;
+ case OP_SPECIAL_MTHI:
+ case OP_SPECIAL_MTLO:
+ return snprintf(buf, len, "%s%s",
+ special_opcodes[c.r.op],
+ lightrec_reg_name(c.r.rs));
+ case OP_SPECIAL_JALR:
+ return snprintf(buf, len, "%s%s,%s",
+ special_opcodes[c.r.op],
+ lightrec_reg_name(c.r.rd),
+ lightrec_reg_name(c.r.rt));
+ case OP_SPECIAL_SYSCALL:
+ case OP_SPECIAL_BREAK:
+ return snprintf(buf, len, "%s", special_opcodes[c.r.op]);
+ case OP_SPECIAL_MFHI:
+ case OP_SPECIAL_MFLO:
+ return snprintf(buf, len, "%s%s",
+ special_opcodes[c.r.op],
+ lightrec_reg_name(c.r.rd));
+ case OP_SPECIAL_MULT:
+ case OP_SPECIAL_MULTU:
+ case OP_SPECIAL_DIV:
+ case OP_SPECIAL_DIVU:
+ *flags_ptr = opcode_multdiv_flags;
+ *nb_flags = ARRAY_SIZE(opcode_multdiv_flags);
+ return snprintf(buf, len, "%s%s,%s,%s,%s",
+ special_opcodes[c.r.op],
+ lightrec_reg_name(get_mult_div_hi(c)),
+ lightrec_reg_name(get_mult_div_lo(c)),
+ lightrec_reg_name(c.r.rs),
+ lightrec_reg_name(c.r.rt));
default:
- return 2;
+ return snprintf(buf, len, "unknown (0x%08x)", c.opcode);
+ }
+}
+
+static int print_op_cp(union code c, char *buf, size_t len, unsigned int cp)
+{
+ if (cp == 2) {
+ switch (c.i.rs) {
+ case OP_CP0_MFC0:
+ case OP_CP0_CFC0:
+ case OP_CP0_MTC0:
+ case OP_CP0_CTC0:
+ return snprintf(buf, len, "%s%s,%u",
+ cp2_opcodes[c.i.rs],
+ lightrec_reg_name(c.i.rt),
+ c.r.rd);
+ default:
+ return snprintf(buf, len, "cp2 (0x%08x)", c.opcode);
+ }
+ } else {
+ switch (c.i.rs) {
+ case OP_CP0_MFC0:
+ case OP_CP0_CFC0:
+ case OP_CP0_MTC0:
+ case OP_CP0_CTC0:
+ return snprintf(buf, len, "%s%s,%u",
+ cp0_opcodes[c.i.rs],
+ lightrec_reg_name(c.i.rt),
+ c.r.rd);
+ case OP_CP0_RFE:
+ return snprintf(buf, len, "rfe ");
+ default:
+ return snprintf(buf, len, "unknown (0x%08x)", c.opcode);
+ }
}
}
-#if ENABLE_DISASSEMBLER
-void lightrec_print_disassembly(const struct block *block,
- const u32 *code, unsigned int length)
+static int print_op(union code c, u32 pc, char *buf, size_t len,
+ const char ***flags_ptr, size_t *nb_flags,
+ bool *is_io)
{
- struct disassemble_info info;
+ if (c.opcode == 0)
+ return snprintf(buf, len, "nop ");
+
+ switch (c.i.op) {
+ case OP_SPECIAL:
+ return print_op_special(c, buf, len, flags_ptr, nb_flags);
+ case OP_REGIMM:
+ *flags_ptr = opcode_branch_flags;
+ *nb_flags = ARRAY_SIZE(opcode_branch_flags);
+ return snprintf(buf, len, "%s%s,0x%x",
+ regimm_opcodes[c.i.rt],
+ lightrec_reg_name(c.i.rs),
+ pc + 4 + ((s16)c.i.imm << 2));
+ case OP_J:
+ case OP_JAL:
+ *flags_ptr = opcode_branch_flags;
+ *nb_flags = ARRAY_SIZE(opcode_branch_flags);
+ return snprintf(buf, len, "%s0x%x",
+ std_opcodes[c.i.op],
+ (pc & 0xf0000000) | (c.j.imm << 2));
+ case OP_BEQ:
+ if (c.i.rs == c.i.rt) {
+ *flags_ptr = opcode_branch_flags;
+ *nb_flags = ARRAY_SIZE(opcode_branch_flags);
+ return snprintf(buf, len, "b 0x%x",
+ pc + 4 + ((s16)c.i.imm << 2));
+ }
+ fallthrough;
+ case OP_BNE:
+ case OP_BLEZ:
+ case OP_BGTZ:
+ *flags_ptr = opcode_branch_flags;
+ *nb_flags = ARRAY_SIZE(opcode_branch_flags);
+ return snprintf(buf, len, "%s%s,%s,0x%x",
+ std_opcodes[c.i.op],
+ lightrec_reg_name(c.i.rs),
+ lightrec_reg_name(c.i.rt),
+ pc + 4 + ((s16)c.i.imm << 2));
+ case OP_ADDI:
+ case OP_ADDIU:
+ case OP_SLTI:
+ case OP_SLTIU:
+ case OP_ANDI:
+ case OP_ORI:
+ case OP_XORI:
+ return snprintf(buf, len, "%s%s,%s,0x%04hx",
+ std_opcodes[c.i.op],
+ lightrec_reg_name(c.i.rt),
+ lightrec_reg_name(c.i.rs),
+ (u16)c.i.imm);
+
+ case OP_LUI:
+ return snprintf(buf, len, "%s%s,0x%04hx",
+ std_opcodes[c.i.op],
+ lightrec_reg_name(c.i.rt),
+ (u16)c.i.imm);
+ case OP_CP0:
+ return print_op_cp(c, buf, len, 0);
+ case OP_CP2:
+ return print_op_cp(c, buf, len, 2);
+ case OP_LB:
+ case OP_LH:
+ case OP_LWL:
+ case OP_LW:
+ case OP_LBU:
+ case OP_LHU:
+ case OP_LWR:
+ case OP_SB:
+ case OP_SH:
+ case OP_SWL:
+ case OP_SW:
+ case OP_SWR:
+ *flags_ptr = opcode_io_flags;
+ *nb_flags = ARRAY_SIZE(opcode_io_flags);
+ *is_io = true;
+ return snprintf(buf, len, "%s%s,%hd(%s)",
+ std_opcodes[c.i.op],
+ lightrec_reg_name(c.i.rt),
+ (s16)c.i.imm,
+ lightrec_reg_name(c.i.rs));
+ case OP_LWC2:
+ case OP_SWC2:
+ *flags_ptr = opcode_io_flags;
+ *nb_flags = ARRAY_SIZE(opcode_io_flags);
+ return snprintf(buf, len, "%s%s,%hd(%s)",
+ std_opcodes[c.i.op],
+ lightrec_reg_name(c.i.rt),
+ (s16)c.i.imm,
+ lightrec_reg_name(c.i.rs));
+ case OP_META_MOV:
+ return snprintf(buf, len, "move %s,%s",
+ lightrec_reg_name(c.r.rd),
+ lightrec_reg_name(c.r.rs));
+ case OP_META_EXTC:
+ return snprintf(buf, len, "extc %s,%s",
+ lightrec_reg_name(c.i.rt),
+ lightrec_reg_name(c.i.rs));
+ case OP_META_EXTS:
+ return snprintf(buf, len, "exts %s,%s",
+ lightrec_reg_name(c.i.rt),
+ lightrec_reg_name(c.i.rs));
+ default:
+ return snprintf(buf, len, "unknown (0x%08x)", c.opcode);
+ }
+}
+
+void lightrec_print_disassembly(const struct block *block, const u32 *code_ptr)
+{
+ const struct opcode *op;
+ const char **flags_ptr;
+ size_t nb_flags, count, count2;
+ char buf[256], buf2[256], buf3[256];
unsigned int i;
+ u32 pc, branch_pc, code;
+ bool is_io;
+
+ for (i = 0; i < block->nb_ops; i++) {
+ op = &block->opcode_list[i];
+ branch_pc = get_branch_pc(block, i, 0);
+ pc = block->pc + (i << 2);
+ code = LE32TOH(code_ptr[i]);
+
+ count = print_op((union code)code, pc, buf, sizeof(buf),
+ &flags_ptr, &nb_flags, &is_io);
+
+ flags_ptr = NULL;
+ nb_flags = 0;
+ is_io = false;
+ count2 = print_op(op->c, branch_pc, buf2, sizeof(buf2),
+ &flags_ptr, &nb_flags, &is_io);
+
+ if (code == op->c.opcode) {
+ *buf2 = '\0';
+ count2 = 0;
+ }
+
+ print_flags(buf3, sizeof(buf3), op, flags_ptr, nb_flags, is_io);
- memset(&info, 0, sizeof(info));
- init_disassemble_info(&info, stdout, (fprintf_ftype) fprintf);
-
- info.buffer = (bfd_byte *) code;
- info.buffer_vma = (bfd_vma)(uintptr_t) code;
- info.buffer_length = length;
- info.flavour = bfd_target_unknown_flavour;
- info.arch = bfd_arch_mips;
- info.mach = bfd_mach_mips3000;
- disassemble_init_for_target(&info);
-
- for (i = 0; i < length; i += 4) {
- void print_insn_little_mips(bfd_vma, struct disassemble_info *);
- putc('\t', stdout);
- print_insn_little_mips((bfd_vma)(uintptr_t) code++, &info);
- putc('\n', stdout);
+ printf("0x%08x (0x%x)\t%s%*c%s%*c%s\n", pc, i << 2,
+ buf, 30 - (int)count, ' ', buf2, 30 - (int)count2, ' ', buf3);
}
}
-#endif