rizin/librz/arch/arch-asm/sh/disassembler.c
2024-03-07 18:38:49 +08:00

242 lines
6.1 KiB
C

// SPDX-FileCopyrightText: 2022 Dhruv Maroo <dhruvmaru007@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include "disassembler.h"
#include "common.h"
#include "regs.h"
extern const SHOpRaw sh_op_lookup[];
extern const ut32 OPCODE_NUM;
/**
* \brief Get SHParam from opcode
* Make sure the opcode is passed in little-endian form
*
* \param opcode instruction opcode (assumed to be in little-endian)
* \param shb SHParamBuilder instance which contains the necessary info to find the param
*/
static SHParam sh_op_get_param(ut16 opcode, SHParamBuilder shb) {
if (shb.is_param) {
return shb.param;
}
if (shb.addr.mode == SH_ADDR_INVALID) {
SHParam invalid;
invalid.mode = SH_ADDR_INVALID;
return invalid;
}
ut16 nibble = opcode >> shb.addr.start;
ut8 len = 0;
if (shb.addr.bits != -1) {
// do not infer the bit length from mode
len = shb.addr.bits;
goto extract;
}
switch (shb.addr.mode) {
case SH_REG_DIRECT:
case SH_REG_INDIRECT:
case SH_REG_INDIRECT_I:
case SH_REG_INDIRECT_D:
case SH_REG_INDIRECT_INDEXED:
case SH_PC_RELATIVE_REG:
len = 4;
break;
case SH_REG_INDIRECT_DISP:
case SH_GBR_INDIRECT_DISP:
case SH_PC_RELATIVE_DISP:
case SH_PC_RELATIVE8:
case SH_IMM_S:
case SH_IMM_U:
len = 8;
break;
case SH_PC_RELATIVE12:
len = 12;
break;
default:
break;
}
extract:
nibble &= 0xffff >> (16 - len);
SHParam ret_param;
ret_param.mode = shb.addr.mode;
switch (shb.addr.mode) {
case SH_REG_DIRECT:
case SH_REG_INDIRECT:
case SH_REG_INDIRECT_I:
case SH_REG_INDIRECT_D:
case SH_REG_INDIRECT_INDEXED:
case SH_GBR_INDIRECT_DISP:
case SH_PC_RELATIVE_DISP:
case SH_PC_RELATIVE8:
case SH_PC_RELATIVE12:
case SH_PC_RELATIVE_REG:
case SH_IMM_S:
case SH_IMM_U:
ret_param.param[0] = nibble;
break;
case SH_REG_INDIRECT_DISP:
ret_param.param[0] = nibble >> 4;
ret_param.param[1] = nibble & 0xf;
break;
case SH_GBR_INDIRECT_INDEXED:
break;
default:
break;
}
/* For the special case of banked registers in `LDC` and `STC`, the bit length is 3
In such case, we can modify the param found to correspond to its banked counterpart */
if (len == 3) {
ret_param.param[0] += SH_REG_IND_R0B;
}
return ret_param;
}
/**
* \brief Get params for mov.l instruction (0001NMD)
* A special function is required because the nibbles for the second param (@(disp:Rn)) (i.e. N and D)
* are separated by the nibble for the first param (Rm) (i.e. M), so sh_op_get_param cannot be used
*
* \param opcode opcode
* \param m if true, get Rm ; otherwise get @(disp:Rn)
* \return SHParam return appropriate param
*/
static SHParam sh_op_get_param_movl(ut16 opcode, bool m) {
if (m) {
ut16 reg = (opcode >> 4) & 0xf;
return (SHParam){ .param = {
reg,
},
.mode = SH_REG_DIRECT };
} else {
ut16 d = opcode & 0xf;
ut16 n = (opcode >> 8) & 0xf;
return (SHParam){ .param = { n, d }, .mode = SH_REG_INDIRECT_DISP };
}
}
/**
* \brief Disassemble \p opcode and return a SHOp
*
* \param opcode 16 bit wide opcode
* \return SHOp object corresponding to the opcode
*/
RZ_IPI RZ_OWN SHOp *sh_disassembler(ut16 opcode) {
for (ut16 i = 0; i < OPCODE_NUM; i++) {
if ((opcode | sh_op_lookup[i].mask) != sh_op_lookup[i].opcode) {
continue;
}
SHOpRaw raw = sh_op_lookup[i];
SHOp *op = RZ_NEW(SHOp);
op->opcode = opcode;
op->mnemonic = raw.mnemonic;
op->scaling = raw.scaling;
op->str_mnem = raw.str_mnem;
// check for "weird" mov.l
if (raw.opcode == MOVL) {
op->param[0] = sh_op_get_param_movl(opcode, true);
op->param[1] = sh_op_get_param_movl(opcode, false);
return op;
}
op->param[0] = sh_op_get_param(opcode, raw.param_builder[0]);
op->param[1] = sh_op_get_param(opcode, raw.param_builder[1]);
return op;
}
RZ_LOG_DEBUG("SuperH: Invalid opcode encountered by disassembler: 0x%06x\n", opcode);
return NULL;
}
#undef MOVL
/**
* \brief Return string representation of disassembled \p param
*
* \param SHParam to be disassembled
* \param SHScaling of the instruction associated with the param
* \return char *, owned by the caller
*/
RZ_IPI RZ_OWN char *sh_op_param_to_str(SHParam param, SHScaling scaling, ut64 pc) {
if (param.mode == SH_ADDR_INVALID) {
return NULL;
}
RzStrBuf *buf = rz_strbuf_new(NULL);
switch (param.mode) {
case SH_REG_DIRECT:
rz_strbuf_appendf(buf, "%s", sh_registers[param.param[0]]);
break;
case SH_REG_INDIRECT:
rz_strbuf_appendf(buf, "@%s", sh_registers[param.param[0]]);
break;
case SH_REG_INDIRECT_I:
rz_strbuf_appendf(buf, "@%s+", sh_registers[param.param[0]]);
break;
case SH_REG_INDIRECT_D:
rz_strbuf_appendf(buf, "@-%s", sh_registers[param.param[0]]);
break;
case SH_REG_INDIRECT_DISP:
rz_strbuf_appendf(buf, "@(0x%02x,%s)", param.param[1] * sh_scaling_size[scaling], sh_registers[param.param[0]]);
break;
case SH_REG_INDIRECT_INDEXED:
rz_strbuf_appendf(buf, "@(r0,%s)", sh_registers[param.param[0]]);
break;
case SH_GBR_INDIRECT_DISP:
rz_strbuf_appendf(buf, "@(0x%03x,gbr)", param.param[0] * sh_scaling_size[scaling]);
break;
case SH_GBR_INDIRECT_INDEXED:
rz_strbuf_append(buf, "@(r0,gbr)");
break;
case SH_PC_RELATIVE_DISP:
rz_strbuf_appendf(buf, "@(0x%03x,pc)", param.param[0] * sh_scaling_size[scaling]);
break;
case SH_PC_RELATIVE8:
case SH_PC_RELATIVE12:
rz_strbuf_appendf(buf, "0x%08x", (ut32)pc + 4 + (st32)((st8)param.param[0]) * 2);
break;
case SH_PC_RELATIVE_REG:
rz_strbuf_appendf(buf, "%s", sh_registers[param.param[0]]);
break;
case SH_IMM_U:
case SH_IMM_S:
rz_strbuf_appendf(buf, "0x%02x", param.param[0]);
break;
default:
rz_warn_if_reached();
}
return rz_strbuf_drain(buf);
}
/**
* \brief Return string representation of disassembled \p op
*
* \param SHOp to be disassembled
* \return char *, owned by the caller
*/
RZ_IPI RZ_OWN char *sh_op_to_str(RZ_NONNULL const SHOp *op, ut64 pc) {
rz_return_val_if_fail(op, NULL);
if (!op->str_mnem) {
return NULL;
}
RzStrBuf *buf = rz_strbuf_new(op->str_mnem);
char *param = NULL;
if ((param = sh_op_param_to_str(op->param[0], op->scaling, pc))) {
rz_strbuf_appendf(buf, " %s", param);
free(param);
if ((param = sh_op_param_to_str(op->param[1], op->scaling, pc))) {
rz_strbuf_appendf(buf, ", %s", param);
free(param);
}
}
return rz_strbuf_drain(buf);
}