| 1 | /* |
| 2 | * Copyright (C) 2014-2020 Paul Cercueil <paul@crapouillou.net> |
| 3 | * |
| 4 | * This library is free software; you can redistribute it and/or |
| 5 | * modify it under the terms of the GNU Lesser General Public |
| 6 | * License as published by the Free Software Foundation; either |
| 7 | * version 2.1 of the License, or (at your option) any later version. |
| 8 | * |
| 9 | * This library is distributed in the hope that it will be useful, |
| 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 12 | * Lesser General Public License for more details. |
| 13 | */ |
| 14 | |
| 15 | #include "config.h" |
| 16 | |
| 17 | #if ENABLE_DISASSEMBLER |
| 18 | #include <dis-asm.h> |
| 19 | #endif |
| 20 | #include <stdbool.h> |
| 21 | #include <stdlib.h> |
| 22 | #include <string.h> |
| 23 | |
| 24 | #include "debug.h" |
| 25 | #include "disassembler.h" |
| 26 | #include "lightrec-private.h" |
| 27 | #include "memmanager.h" |
| 28 | |
| 29 | static bool is_unconditional_jump(const struct opcode *op) |
| 30 | { |
| 31 | switch (op->i.op) { |
| 32 | case OP_SPECIAL: |
| 33 | return op->r.op == OP_SPECIAL_JR || op->r.op == OP_SPECIAL_JALR; |
| 34 | case OP_J: |
| 35 | case OP_JAL: |
| 36 | return true; |
| 37 | case OP_BEQ: |
| 38 | case OP_BLEZ: |
| 39 | return op->i.rs == op->i.rt; |
| 40 | case OP_REGIMM: |
| 41 | return (op->r.rt == OP_REGIMM_BGEZ || |
| 42 | op->r.rt == OP_REGIMM_BGEZAL) && op->i.rs == 0; |
| 43 | default: |
| 44 | return false; |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | static bool is_syscall(const struct opcode *op) |
| 49 | { |
| 50 | return (op->i.op == OP_SPECIAL && (op->r.op == OP_SPECIAL_SYSCALL || |
| 51 | op->r.op == OP_SPECIAL_BREAK)) || |
| 52 | (op->i.op == OP_CP0 && (op->r.rs == OP_CP0_MTC0 || |
| 53 | op->r.rs == OP_CP0_CTC0) && |
| 54 | (op->r.rd == 12 || op->r.rd == 13)); |
| 55 | } |
| 56 | |
| 57 | void lightrec_free_opcode_list(struct lightrec_state *state, struct opcode *list) |
| 58 | { |
| 59 | struct opcode *next; |
| 60 | |
| 61 | while (list) { |
| 62 | next = list->next; |
| 63 | lightrec_free(state, MEM_FOR_IR, sizeof(*list), list); |
| 64 | list = next; |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | struct opcode * lightrec_disassemble(struct lightrec_state *state, |
| 69 | const u32 *src, unsigned int *len) |
| 70 | { |
| 71 | struct opcode *head = NULL; |
| 72 | bool stop_next = false; |
| 73 | struct opcode *curr, *last; |
| 74 | unsigned int i; |
| 75 | |
| 76 | for (i = 0, last = NULL; ; i++, last = curr) { |
| 77 | curr = lightrec_calloc(state, MEM_FOR_IR, sizeof(*curr)); |
| 78 | if (!curr) { |
| 79 | pr_err("Unable to allocate memory\n"); |
| 80 | lightrec_free_opcode_list(state, head); |
| 81 | return NULL; |
| 82 | } |
| 83 | |
| 84 | if (!last) |
| 85 | head = curr; |
| 86 | else |
| 87 | last->next = curr; |
| 88 | |
| 89 | /* TODO: Take care of endianness */ |
| 90 | curr->opcode = LE32TOH(*src++); |
| 91 | curr->offset = i; |
| 92 | |
| 93 | /* NOTE: The block disassembly ends after the opcode that |
| 94 | * follows an unconditional jump (delay slot) */ |
| 95 | if (stop_next || is_syscall(curr)) |
| 96 | break; |
| 97 | else if (is_unconditional_jump(curr)) |
| 98 | stop_next = true; |
| 99 | } |
| 100 | |
| 101 | if (len) |
| 102 | *len = (i + 1) * sizeof(u32); |
| 103 | |
| 104 | return head; |
| 105 | } |
| 106 | |
| 107 | unsigned int lightrec_cycles_of_opcode(union code code) |
| 108 | { |
| 109 | switch (code.i.op) { |
| 110 | case OP_META_REG_UNLOAD: |
| 111 | case OP_META_SYNC: |
| 112 | return 0; |
| 113 | default: |
| 114 | return 2; |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | #if ENABLE_DISASSEMBLER |
| 119 | void lightrec_print_disassembly(const struct block *block, |
| 120 | const u32 *code, unsigned int length) |
| 121 | { |
| 122 | struct disassemble_info info; |
| 123 | unsigned int i; |
| 124 | |
| 125 | memset(&info, 0, sizeof(info)); |
| 126 | init_disassemble_info(&info, stdout, (fprintf_ftype) fprintf); |
| 127 | |
| 128 | info.buffer = (bfd_byte *) code; |
| 129 | info.buffer_vma = (bfd_vma)(uintptr_t) code; |
| 130 | info.buffer_length = length; |
| 131 | info.flavour = bfd_target_unknown_flavour; |
| 132 | info.arch = bfd_arch_mips; |
| 133 | info.mach = bfd_mach_mips3000; |
| 134 | disassemble_init_for_target(&info); |
| 135 | |
| 136 | for (i = 0; i < length; i += 4) { |
| 137 | void print_insn_little_mips(bfd_vma, struct disassemble_info *); |
| 138 | putc('\t', stdout); |
| 139 | print_insn_little_mips((bfd_vma)(uintptr_t) code++, &info); |
| 140 | putc('\n', stdout); |
| 141 | } |
| 142 | } |
| 143 | #endif |