481 lines
13 KiB
C
481 lines
13 KiB
C
#include "capstone/cs_operand.h"
|
|
#include "capstone/riscv.h"
|
|
#include <stdint.h>
|
|
#include <float.h>
|
|
#include <math.h>
|
|
#ifdef CAPSTONE_HAS_RISCV
|
|
|
|
#include <string.h>
|
|
|
|
#include "../../Mapping.h"
|
|
#include "../../cs_simple_types.h"
|
|
#include "../../utils.h"
|
|
|
|
#include "RISCVMapping.h"
|
|
|
|
#define GET_INSTRINFO_ENUM
|
|
#include "RISCVGenInstrInfo.inc"
|
|
|
|
#define GET_REGINFO_ENUM
|
|
#define GET_REGINFO_MC_DESC
|
|
#include "RISCVGenRegisterInfo.inc"
|
|
|
|
#include "RISCVInstPrinter.h"
|
|
|
|
const char *RISCV_reg_name(csh handle, unsigned int reg)
|
|
{
|
|
int syntax_opt = ((cs_struct *)(uintptr_t)handle)->syntax;
|
|
|
|
if (syntax_opt & CS_OPT_SYNTAX_NOREGNAME) {
|
|
return RISCV_LLVM_getRegisterName(reg, RISCV_NoRegAltName);
|
|
}
|
|
return RISCV_LLVM_getRegisterName(reg, RISCV_ABIRegAltName);
|
|
}
|
|
|
|
static const insn_map insns[] = {
|
|
#include "RISCVGenCSMappingInsn.inc"
|
|
};
|
|
|
|
const insn_map *RISCV_insns = insns;
|
|
const unsigned int RISCV_insn_count = ARR_SIZE(insns);
|
|
|
|
#ifndef CAPSTONE_DIET
|
|
static const map_insn_ops insn_operands[] = {
|
|
#include "RISCVGenCSMappingInsnOp.inc"
|
|
};
|
|
|
|
static const name_map insn_alias_mnem_map[] = {
|
|
#include "RISCVGenCSAliasMnemMap.inc"
|
|
};
|
|
#endif
|
|
|
|
void RISCV_add_cs_detail_0(MCInst *MI, riscv_op_group opgroup, unsigned OpNum)
|
|
{
|
|
if (!detail_is_set(MI))
|
|
return;
|
|
// are not "true" arguments and has no Capstone equivalent
|
|
if (opgroup == RISCV_OP_GROUP_FRMArg ||
|
|
opgroup == RISCV_OP_GROUP_FRMArgLegacy)
|
|
return;
|
|
|
|
if (opgroup == RISCV_OP_GROUP_FPImmOperand) {
|
|
unsigned Imm = (unsigned)MCInst_getOperand(MI, OpNum)->ImmVal;
|
|
cs_riscv_op *op = RISCV_get_detail_op_at(MI, OpNum);
|
|
op->type = RISCV_OP_FP;
|
|
op->access = (cs_ac_type)map_get_op_access(MI, OpNum);
|
|
switch (Imm) {
|
|
case 1: // min
|
|
switch (MI->Opcode) {
|
|
case RISCV_FLI_S:
|
|
op->dimm = (double)FLT_MIN;
|
|
break;
|
|
case RISCV_FLI_D:
|
|
op->dimm = (double)DBL_MIN;
|
|
break;
|
|
case RISCV_FLI_H:
|
|
op->dimm = 6.103515625e-05;
|
|
break;
|
|
default:
|
|
op->dimm = 0.0;
|
|
break;
|
|
}
|
|
break;
|
|
case 30: // inf
|
|
op->dimm = INFINITY;
|
|
break;
|
|
case 31: // nan
|
|
op->dimm = NAN;
|
|
break;
|
|
default:
|
|
op->dimm = (double)getFPImm(Imm);
|
|
break;
|
|
}
|
|
RISCV_inc_op_count(MI);
|
|
return;
|
|
}
|
|
cs_riscv_op *op = RISCV_get_detail_op_at(MI, OpNum);
|
|
op->type = (riscv_op_type)map_get_op_type(MI, OpNum);
|
|
op->access = (cs_ac_type)map_get_op_access(MI, OpNum);
|
|
switch (map_get_op_type(MI, OpNum)) {
|
|
case CS_OP_REG:
|
|
op->reg = MCInst_getOperand(MI, OpNum)->RegVal;
|
|
break;
|
|
case CS_OP_MEM:
|
|
op->mem.base = 0;
|
|
op->mem.disp = MCInst_getOperand(MI, OpNum)->ImmVal;
|
|
break;
|
|
case CS_OP_IMM: {
|
|
uint64_t val = MCInst_getOperand(MI, OpNum)->ImmVal;
|
|
if (opgroup != RISCV_OP_GROUP_CSRSystemRegister) {
|
|
op->imm = val;
|
|
if (opgroup == RISCV_OP_GROUP_BranchOperand) {
|
|
op->imm += MI->address;
|
|
}
|
|
} else /* system register read-write */ {
|
|
op->type = RISCV_OP_CSR;
|
|
op->csr = val;
|
|
// CSR instruction always read-writes the system operand
|
|
op->access = CS_AC_READ_WRITE;
|
|
}
|
|
break;
|
|
}
|
|
case CS_OP_MEM_REG:
|
|
op->type = (riscv_op_type)CS_OP_MEM;
|
|
op->mem.base = MCInst_getOperand(MI, OpNum)->RegVal;
|
|
break;
|
|
case CS_OP_MEM_IMM:
|
|
// fill in the disp in the last operand
|
|
op = RISCV_get_detail_op_at(MI, OpNum - 1);
|
|
op->type = (riscv_op_type)CS_OP_MEM;
|
|
op->mem.disp = MCInst_getOperand(MI, OpNum)->ImmVal;
|
|
RISCV_dec_op_count(
|
|
MI); // don't increase the count, cancel the coming increment
|
|
break;
|
|
case CS_OP_INVALID:
|
|
break;
|
|
default: {
|
|
CS_ASSERT(0 && "unhandled operand type");
|
|
}
|
|
}
|
|
RISCV_inc_op_count(MI);
|
|
}
|
|
|
|
static inline void RISCV_add_adhoc_groups(MCInst *MI);
|
|
|
|
void RISCV_add_groups(MCInst *MI)
|
|
{
|
|
if (!detail_is_set(MI))
|
|
return;
|
|
|
|
get_detail(MI)->groups_count = 0;
|
|
|
|
#ifndef CAPSTONE_DIET
|
|
int i = 0;
|
|
while (insns[MI->Opcode].groups[i] != 0) {
|
|
add_group(MI, insns[MI->Opcode].groups[i]);
|
|
i++;
|
|
}
|
|
#endif
|
|
|
|
RISCV_add_adhoc_groups(MI);
|
|
}
|
|
|
|
enum {
|
|
#define GET_ENUM_VALUES_RISCVOpcode
|
|
#include "RISCVGenCSSystemOperandsEnum.inc"
|
|
};
|
|
|
|
static inline void RISCV_add_privileged_group(MCInst *MI)
|
|
{
|
|
const uint8_t *bytes = MI->flat_insn->bytes;
|
|
uint8_t opcode = bytes[0] & 0x80;
|
|
// no privileged instruction has a major opcode other than SYSTEM
|
|
if (opcode != RISCV_RISCVOPCODE_SYSTEM) {
|
|
return;
|
|
}
|
|
uint8_t func3 = (bytes[1] >> 4) & 0x7;
|
|
// no privileged instruction has a minor opcode other than PRIV or PRIVM
|
|
if (func3 != 0 && func3 != 0x4) {
|
|
return;
|
|
}
|
|
uint16_t func12 = readBytes16(MI, &(bytes[2])) >> 4;
|
|
// ecall and ebreak has SYSTEM and PRIV but aren't privileged
|
|
if (func12 == 0 || func12 == 1) {
|
|
return;
|
|
}
|
|
uint8_t func6 = func12 >> 6;
|
|
// a subspace under extension-defined custom SYSTEM instructions that is not privileged
|
|
if (func6 == 0x23 || func6 == 0x33) {
|
|
return;
|
|
}
|
|
add_group(MI, RISCV_GRP_PRIVILEGE);
|
|
}
|
|
|
|
static inline void RISCV_add_interrupt_group(MCInst *MI)
|
|
{
|
|
if (MI->Opcode == RISCV_ECALL || MI->Opcode == RISCV_EBREAK) {
|
|
add_group(MI, RISCV_GRP_INT);
|
|
}
|
|
}
|
|
|
|
static inline void RISCV_add_interrupt_ret_group(MCInst *MI)
|
|
{
|
|
if (MI->Opcode == RISCV_MRET || MI->Opcode == RISCV_SRET) {
|
|
add_group(MI, RISCV_GRP_IRET);
|
|
}
|
|
}
|
|
|
|
// calls are implemented in RISCV as plain jumps that happen to set a link register containing the return address
|
|
// but this link register could be given as the null register x0, discarding the return address and making them jumps
|
|
static inline void RISCV_add_call_group(MCInst *MI)
|
|
{
|
|
if (MI->Opcode == RISCV_JAL || MI->Opcode == RISCV_JALR) {
|
|
cs_riscv_op *op = RISCV_get_detail_op_at(MI, 0);
|
|
if ((op->type == (riscv_op_type)CS_OP_REG) &&
|
|
op->reg != RISCV_REG_X0 && (op->access & CS_AC_WRITE)) {
|
|
add_group(MI, RISCV_GRP_CALL);
|
|
}
|
|
if (MI->Opcode == RISCV_JAL) {
|
|
add_group(MI, RISCV_GRP_BRANCH_RELATIVE);
|
|
}
|
|
}
|
|
}
|
|
|
|
// returns are implemented in RISCV as a plain indirect jump that happen to reference the return address register ra == x1
|
|
static inline void RISCV_add_ret_group(MCInst *MI)
|
|
{
|
|
if (MI->Opcode == RISCV_C_JR) {
|
|
// indirect jumps whose source is ra
|
|
cs_riscv_op *op = RISCV_get_detail_op_at(MI, 0);
|
|
if ((op->type == (riscv_op_type)CS_OP_REG) &&
|
|
op->reg == RISCV_REG_X1) {
|
|
add_group(MI, RISCV_GRP_RET);
|
|
} else {
|
|
add_group(MI, RISCV_GRP_JUMP);
|
|
}
|
|
}
|
|
if (MI->Opcode == RISCV_JALR) {
|
|
// indirect jumps whose source is ra
|
|
cs_riscv_op *dstreg = RISCV_get_detail_op_at(MI, 0);
|
|
cs_riscv_op *op = RISCV_get_detail_op_at(MI, 1);
|
|
cs_riscv_op *op2 = RISCV_get_detail_op_at(MI, 2);
|
|
if ((op->type == (riscv_op_type)CS_OP_REG) &&
|
|
op->reg == RISCV_REG_X1 &&
|
|
op2->type == (riscv_op_type)CS_OP_IMM && op2->imm == 0 &&
|
|
dstreg->type == (riscv_op_type)CS_OP_REG &&
|
|
dstreg->reg == RISCV_REG_X0) {
|
|
add_group(MI, RISCV_GRP_RET);
|
|
} else {
|
|
if (!((dstreg->type == (riscv_op_type)CS_OP_REG) &&
|
|
dstreg->reg != RISCV_REG_X0 &&
|
|
(dstreg->access & CS_AC_WRITE))) {
|
|
add_group(MI, RISCV_GRP_JUMP);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void RISCV_add_adhoc_groups(MCInst *MI)
|
|
{
|
|
RISCV_add_privileged_group(MI);
|
|
RISCV_add_interrupt_group(MI);
|
|
RISCV_add_interrupt_ret_group(MI);
|
|
RISCV_add_call_group(MI);
|
|
RISCV_add_ret_group(MI);
|
|
}
|
|
|
|
// memset all stalled values in the detail struct to 0 before disassembling any next instruction
|
|
void RISCV_init_cs_detail(MCInst *MI)
|
|
{
|
|
if (detail_is_set(MI))
|
|
memset(get_detail(MI), 0,
|
|
offsetof(cs_detail, riscv) + sizeof(cs_riscv));
|
|
}
|
|
|
|
// for weird reasons some instructions end up with valid operands that are
|
|
// interspersed with invalid operands, i.e. the operands array is an "island"
|
|
// of valid operands with invalid gaps between them, this function will compactify
|
|
// all the valid operands and pad the rest of the array to invalid
|
|
void RISCV_compact_operands(MCInst *MI)
|
|
{
|
|
if (!detail_is_set(MI))
|
|
return;
|
|
cs_riscv_op *ops = RISCV_get_detail(MI)->operands;
|
|
unsigned int write_pos = 0;
|
|
|
|
// Move valid elements to front
|
|
for (unsigned int read_pos = 0; read_pos < NUM_RISCV_OPS; read_pos++) {
|
|
if (ops[read_pos].type != (riscv_op_type)CS_OP_INVALID) {
|
|
if (write_pos != read_pos) {
|
|
ops[write_pos] = ops[read_pos];
|
|
}
|
|
write_pos++;
|
|
}
|
|
}
|
|
// fill the rest, if any, with invalid
|
|
memset((void *)(&ops[write_pos]), CS_OP_INVALID,
|
|
(NUM_RISCV_OPS - write_pos) * sizeof(cs_riscv_op));
|
|
}
|
|
|
|
// some RISC-V instructions have only 2 apparent operands, one of them is read-write
|
|
// the actual operand information for those instruction should have 3 operands, the first and second are the same operand,
|
|
// but once with read and once write access
|
|
// when those instructions are disassembled only the operand entry with the read access is used,
|
|
// and therefore the read-write operand is wrongly classified as only-read
|
|
// this logic tries to correct that
|
|
void RISCV_add_missing_write_access(MCInst *MI)
|
|
{
|
|
if (!detail_is_set(MI))
|
|
return;
|
|
if (!isCompressed(MI))
|
|
return;
|
|
|
|
cs_riscv *riscv_details = RISCV_get_detail(MI);
|
|
cs_riscv_op *ops = riscv_details->operands;
|
|
// make the detection condition as specific as possible
|
|
// so it doesn't accidentally trigger for other cases
|
|
if (riscv_details->op_count == 2 && ops[0].type == RISCV_OP_INVALID &&
|
|
ops[1].type == RISCV_OP_REG && ops[1].access == CS_AC_READ) {
|
|
ops[1].access |= CS_AC_WRITE;
|
|
}
|
|
}
|
|
|
|
// given internal insn id, return public instruction info
|
|
void RISCV_get_insn_id(cs_struct *h, cs_insn *insn, unsigned int id)
|
|
{
|
|
insn_map const *insn_map = NULL;
|
|
|
|
if ((insn_map = lookup_insn_map(h, id))) {
|
|
insn->id = insn_map->mapid;
|
|
|
|
if (h->detail_opt) {
|
|
#ifndef CAPSTONE_DIET
|
|
memcpy(insn->detail->regs_read, insn_map->regs_use,
|
|
sizeof(insn_map->regs_use));
|
|
insn->detail->regs_read_count =
|
|
(uint8_t)count_positive(insn_map->regs_use);
|
|
|
|
memcpy(insn->detail->regs_write, insn_map->regs_mod,
|
|
sizeof(insn_map->regs_mod));
|
|
insn->detail->regs_write_count =
|
|
(uint8_t)count_positive(insn_map->regs_mod);
|
|
|
|
memcpy(insn->detail->groups, insn_map->groups,
|
|
sizeof(insn_map->groups));
|
|
insn->detail->groups_count =
|
|
(uint8_t)count_positive8(insn_map->groups);
|
|
|
|
if (insn_map->branch || insn_map->indirect_branch) {
|
|
// this insn also belongs to JUMP group. add JUMP group
|
|
insn->detail
|
|
->groups[insn->detail->groups_count] =
|
|
RISCV_GRP_JUMP;
|
|
insn->detail->groups_count++;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
static const char *const insn_name_maps[] = {
|
|
#include "RISCVGenCSMappingInsnName.inc"
|
|
};
|
|
|
|
// called from RISCV_LLVM_printInstruction() to avoid exporting
|
|
// insn_alias_mnem_map and its size via extern declarations
|
|
void RISCV_set_alias_id(MCInst *MI, SStream *O)
|
|
{
|
|
#ifndef CAPSTONE_DIET
|
|
map_set_alias_id(MI, O, insn_alias_mnem_map,
|
|
ARR_SIZE(insn_alias_mnem_map));
|
|
#endif
|
|
}
|
|
|
|
const char *RISCV_insn_name(csh handle, unsigned int id)
|
|
{
|
|
#ifndef CAPSTONE_DIET
|
|
if (id < RISCV_INS_ENDING)
|
|
return insn_name_maps[id];
|
|
|
|
if (id < RISCV_INS_ALIAS_END)
|
|
return insn_alias_mnem_map[id - RISCV_INS_ALIAS_BEGIN - 1].name;
|
|
#endif
|
|
return NULL;
|
|
}
|
|
|
|
#ifndef CAPSTONE_DIET
|
|
static const name_map group_name_maps[] = {
|
|
// generic groups
|
|
{ RISCV_GRP_INVALID, NULL },
|
|
{ RISCV_GRP_JUMP, "jump" },
|
|
{ RISCV_GRP_CALL, "call" },
|
|
{ RISCV_GRP_RET, "ret" },
|
|
{ RISCV_GRP_INT, "int" },
|
|
{ RISCV_GRP_IRET, "iret" },
|
|
{ RISCV_GRP_PRIVILEGE, "privileged" },
|
|
{ RISCV_GRP_BRANCH_RELATIVE, "branch_relative" },
|
|
|
|
// architecture specific
|
|
#include "RISCVGenCSFeatureName.inc"
|
|
|
|
{ RISCV_GRP_ENDING, NULL }
|
|
};
|
|
#endif
|
|
|
|
const char *RISCV_group_name(csh handle, unsigned int id)
|
|
{
|
|
#ifndef CAPSTONE_DIET
|
|
// verify group id
|
|
// if past the end
|
|
if (id >= RISCV_GRP_ENDING ||
|
|
// or in the encoding gap between generic groups and arch-specific groups
|
|
(id > RISCV_GRP_BRANCH_RELATIVE && id < RISCV_FEATURE_HASSTDEXTI))
|
|
return NULL;
|
|
return id2name(group_name_maps, ARR_SIZE(group_name_maps), id);
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
// map instruction name to public instruction ID
|
|
riscv_insn RISCV_map_insn(const char *name)
|
|
{
|
|
unsigned int i;
|
|
for (i = 1; i < ARR_SIZE(insn_name_maps); i++) {
|
|
if (!strcmp(name, insn_name_maps[i]))
|
|
return i;
|
|
}
|
|
#ifndef CAPSTONE_DIET
|
|
for (i = 0; i < ARR_SIZE(insn_alias_mnem_map); i++) {
|
|
if (!strcmp(name, insn_alias_mnem_map[i].name))
|
|
return insn_alias_mnem_map[i].id;
|
|
}
|
|
#endif
|
|
return RISCV_INS_INVALID;
|
|
}
|
|
|
|
void RISCV_reg_access(const cs_insn *insn, cs_regs regs_read,
|
|
uint8_t *regs_read_count, cs_regs regs_write,
|
|
uint8_t *regs_write_count)
|
|
{
|
|
const cs_riscv *riscv = &(insn->detail->riscv);
|
|
uint8_t read_count = 0;
|
|
uint8_t write_count = 0;
|
|
|
|
for (int j = 0; j < riscv->op_count; j++) {
|
|
const cs_riscv_op *op = &riscv->operands[j];
|
|
|
|
if (op->type == RISCV_OP_REG) {
|
|
if ((op->access & CS_AC_WRITE) &&
|
|
!arr_exist(regs_write, write_count, op->reg)) {
|
|
regs_write[write_count++] = (uint16_t)op->reg;
|
|
}
|
|
if ((op->access & CS_AC_READ) &&
|
|
!arr_exist(regs_read, read_count, op->reg)) {
|
|
regs_read[read_count++] = (uint16_t)op->reg;
|
|
}
|
|
} else if (op->type == RISCV_OP_MEM) {
|
|
if (op->mem.base != RISCV_REG_INVALID &&
|
|
!arr_exist(regs_read, read_count, op->mem.base)) {
|
|
regs_read[read_count++] =
|
|
(uint16_t)op->mem.base;
|
|
}
|
|
}
|
|
}
|
|
|
|
*regs_read_count = read_count;
|
|
*regs_write_count = write_count;
|
|
}
|
|
|
|
void RISCV_init(MCRegisterInfo *MRI)
|
|
{
|
|
MCRegisterInfo_InitMCRegisterInfo(MRI, RISCVRegDesc, RISCV_REG_ENDING,
|
|
0, 0, RISCVMCRegisterClasses,
|
|
ARR_SIZE(RISCVMCRegisterClasses), 0,
|
|
0, RISCVRegDiffLists, 0,
|
|
RISCVSubRegIdxLists,
|
|
ARR_SIZE(RISCVSubRegIdxLists), 0);
|
|
}
|
|
|
|
#endif
|