rizin/librz/arch/p/analysis/analysis_riscv_cs.c
2024-09-15 19:12:07 +08:00

638 lines
17 KiB
C

// SPDX-FileCopyrightText: 2013-2020 pancake <pancake@nopcode.org>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_asm.h>
#include <rz_lib.h>
#include <capstone/capstone.h>
#include <capstone/riscv.h>
// http://www.mrc.uidaho.edu/mrc/people/jff/digital/RISCVir.html
#define OPERAND(x) insn->detail->riscv.operands[x]
#define REGID(x) insn->detail->riscv.operands[x].reg
#define REG(x) cs_reg_name(*handle, insn->detail->riscv.operands[x].reg)
#define IMM(x) insn->detail->riscv.operands[x].imm
#define MEMBASE(x) cs_reg_name(*handle, insn->detail->riscv.operands[x].mem.base)
#define MEMINDEX(x) insn->detail->riscv.operands[x].mem.index
#define MEMDISP(x) insn->detail->riscv.operands[x].mem.disp
#define OPCOUNT() insn->detail->riscv.op_count
// TODO scale and disp
#define SET_VAL(op, i) \
if ((i) < OPCOUNT() && OPERAND(i).type == RISCV_OP_IMM) { \
(op)->val = OPERAND(i).imm; \
}
#define CREATE_SRC_DST_3(op) \
(op)->src[0] = rz_analysis_value_new(); \
(op)->src[1] = rz_analysis_value_new(); \
(op)->dst = rz_analysis_value_new();
#define CREATE_SRC_DST_2(op) \
(op)->src[0] = rz_analysis_value_new(); \
(op)->dst = rz_analysis_value_new();
#define SET_SRC_DST_3_REGS(op) \
CREATE_SRC_DST_3(op); \
(op)->dst->reg = rz_reg_get(analysis->reg, REG(0), RZ_REG_TYPE_GPR); \
(op)->src[0]->reg = rz_reg_get(analysis->reg, REG(1), RZ_REG_TYPE_GPR); \
(op)->src[1]->reg = rz_reg_get(analysis->reg, REG(2), RZ_REG_TYPE_GPR);
#define SET_SRC_DST_3_IMM(op) \
CREATE_SRC_DST_3(op); \
(op)->dst->reg = rz_reg_get(analysis->reg, REG(0), RZ_REG_TYPE_GPR); \
(op)->src[0]->reg = rz_reg_get(analysis->reg, REG(1), RZ_REG_TYPE_GPR); \
(op)->src[1]->imm = IMM(2);
#define SET_SRC_DST_2_REGS(op) \
CREATE_SRC_DST_2(op); \
(op)->dst->reg = rz_reg_get(analysis->reg, REG(0), RZ_REG_TYPE_GPR); \
(op)->src[0]->reg = rz_reg_get(analysis->reg, REG(1), RZ_REG_TYPE_GPR);
#define SET_SRC_DST_3_REG_OR_IMM(op) \
if (OPERAND(2).type == RISCV_OP_IMM) { \
SET_SRC_DST_3_IMM(op); \
} else if (OPERAND(2).type == RISCV_OP_REG) { \
SET_SRC_DST_3_REGS(op); \
}
// ESIL macros:
// put the sign bit on the stack
#define ES_IS_NEGATIVE(arg) "1," arg ",<<<,1,&"
// call with delay slot
#define ES_CALL_DR(ra, addr) "pc,4,+," ra ",=," ES_J(addr)
#define ES_CALL_D(addr) ES_CALL_DR("ra", addr)
// call without delay slot
#define ES_CALL_NDR(ra, addr) "pc," ra ",=," ES_J(addr)
#define ES_CALL_ND(addr) ES_CALL_NDR("ra", addr)
#define USE_DS 0
#if USE_DS
// emit ERR trap if executed in a delay slot
#define ES_TRAP_DS() "$ds,!,!,?{,$$,1,TRAP,BREAK,},"
// jump to address
#define ES_J(addr) addr ",SETJT,1,SETD"
#else
#define ES_TRAP_DS() ""
#define ES_J(addr) addr ",pc,="
#endif
// sign extend 32 -> 64
#define ES_SIGN_EXT64(arg) \
arg ",0x80000000,&,0,<,?{," \
"0xffffffff00000000," arg ",|=," \
"}"
#define PROTECT_ZERO() \
if (REG(0)[0] == 'z') { \
rz_strbuf_appendf(&op->esil, ","); \
} else
#define ESIL_LOAD(size) \
PROTECT_ZERO() { \
rz_strbuf_appendf(&op->esil, "%s,[" size "],%s,=", \
ARG(1), REG(0)); \
}
static void opex(RzStrBuf *buf, csh handle, cs_insn *insn) {
int i;
PJ *pj = pj_new();
if (!pj) {
return;
}
pj_o(pj);
pj_ka(pj, "operands");
cs_riscv *x = &insn->detail->riscv;
for (i = 0; i < x->op_count; i++) {
cs_riscv_op *op = x->operands + i;
pj_o(pj);
switch (op->type) {
case RISCV_OP_REG:
pj_ks(pj, "type", "reg");
pj_ks(pj, "value", cs_reg_name(handle, op->reg));
break;
case RISCV_OP_IMM:
pj_ks(pj, "type", "imm");
pj_kN(pj, "value", op->imm);
break;
case RISCV_OP_MEM:
pj_ks(pj, "type", "mem");
if (op->mem.base != RISCV_REG_INVALID) {
pj_ks(pj, "base", cs_reg_name(handle, op->mem.base));
}
pj_kN(pj, "disp", op->mem.disp);
break;
default:
pj_ks(pj, "type", "invalid");
break;
}
pj_end(pj); /* o operand */
}
pj_end(pj); /* a operands */
pj_end(pj);
rz_strbuf_init(buf);
rz_strbuf_append(buf, pj_string(pj));
pj_free(pj);
}
static const char *arg(csh *handle, cs_insn *insn, char *buf, int n) {
*buf = 0;
switch (insn->detail->riscv.operands[n].type) {
case RISCV_OP_INVALID:
break;
case RISCV_OP_REG:
sprintf(buf, "%s",
cs_reg_name(*handle,
insn->detail->riscv.operands[n].reg));
break;
case RISCV_OP_IMM: {
st64 x = (st64)insn->detail->riscv.operands[n].imm;
sprintf(buf, "%" PFMT64d, x);
break;
}
case RISCV_OP_MEM: {
st64 disp = insn->detail->riscv.operands[n].mem.disp;
if (disp < 0) {
sprintf(buf, "%" PFMT64d ",%s,-",
(ut64)-insn->detail->riscv.operands[n].mem.disp,
cs_reg_name(*handle,
insn->detail->riscv.operands[n].mem.base));
} else {
sprintf(buf, "0x%" PFMT64x ",%s,+",
(ut64)insn->detail->riscv.operands[n].mem.disp,
cs_reg_name(*handle,
insn->detail->riscv.operands[n].mem.base));
}
break;
}
}
return buf;
}
#define ARG(x) (*str[x] != 0) ? str[x] : arg(handle, insn, str[x], x)
static int analyze_op_esil(RzAnalysis *a, RzAnalysisOp *op, ut64 addr, const ut8 *buf, int len, csh *handle, cs_insn *insn) {
char str[8][32] = { { 0 } };
int i;
rz_strbuf_init(&op->esil);
rz_strbuf_set(&op->esil, "");
if (insn) {
// caching operands
for (i = 0; i < insn->detail->riscv.op_count && i < 8; i++) {
*str[i] = 0;
ARG(i);
}
}
switch (insn->id) {
// case RISCV_INS_NOP:
// rz_strbuf_setf (&op->esil, ",");
// break;
}
return 0;
}
static int parse_reg_name(RzRegItem *reg, csh handle, cs_insn *insn, int reg_num) {
if (!reg) {
return -1;
}
switch (OPERAND(reg_num).type) {
case RISCV_OP_REG:
reg->name = (char *)cs_reg_name(handle, OPERAND(reg_num).reg);
break;
case RISCV_OP_MEM:
if (OPERAND(reg_num).mem.base != RISCV_REG_INVALID) {
reg->name = (char *)cs_reg_name(handle, OPERAND(reg_num).mem.base);
}
default:
break;
}
return 0;
}
typedef struct {
RzRegItem reg;
csh hndl;
int omode;
int obits;
} RiscvContext;
static bool riscv_init(void **user) {
RiscvContext *ctx = RZ_NEW0(RiscvContext);
rz_return_val_if_fail(ctx, false);
ctx->hndl = 0;
ctx->omode = -1;
ctx->obits = 32;
*user = ctx;
return true;
}
static void op_fillval(RzAnalysis *analysis, RzAnalysisOp *op, csh *handle, cs_insn *insn) {
RiscvContext *ctx = (RiscvContext *)analysis->plugin_data;
switch (op->type & RZ_ANALYSIS_OP_TYPE_MASK) {
case RZ_ANALYSIS_OP_TYPE_LOAD:
if (OPERAND(1).type == RISCV_OP_MEM) {
ZERO_FILL(ctx->reg);
op->src[0] = rz_analysis_value_new();
op->src[0]->reg = &ctx->reg;
parse_reg_name(op->src[0]->reg, *handle, insn, 1);
op->src[0]->delta = OPERAND(1).mem.disp;
}
break;
case RZ_ANALYSIS_OP_TYPE_STORE:
if (OPERAND(1).type == RISCV_OP_MEM) {
ZERO_FILL(ctx->reg);
op->dst = rz_analysis_value_new();
op->dst->reg = &ctx->reg;
parse_reg_name(op->dst->reg, *handle, insn, 1);
op->dst->delta = OPERAND(1).mem.disp;
}
break;
case RZ_ANALYSIS_OP_TYPE_SHL:
case RZ_ANALYSIS_OP_TYPE_SHR:
case RZ_ANALYSIS_OP_TYPE_SAR:
case RZ_ANALYSIS_OP_TYPE_XOR:
case RZ_ANALYSIS_OP_TYPE_SUB:
case RZ_ANALYSIS_OP_TYPE_AND:
case RZ_ANALYSIS_OP_TYPE_ADD:
case RZ_ANALYSIS_OP_TYPE_OR:
SET_SRC_DST_3_REG_OR_IMM(op);
break;
case RZ_ANALYSIS_OP_TYPE_MOV:
SET_SRC_DST_3_REG_OR_IMM(op);
break;
case RZ_ANALYSIS_OP_TYPE_DIV: // UDIV
#if 0
capstone bug
------------
$ rizin -a riscv -e cfg.bigendian=1 -c "wx 0083001b" -
// should be 3 regs, right?
[0x00000000]> aoj~{}
[
{
"opcode": "divu zero, a0, v1",
"disasm": "divu zero, a0, v1",
"mnemonic": "divu",
"sign": false,
"prefix": 0,
"id": 192,
"opex": {
"operands": [
{
"type": "reg",
"value": "a0"
},
{
"type": "reg",
"value": "v1"
}
]
},
#endif
if (OPERAND(0).type == RISCV_OP_REG && OPERAND(1).type == RISCV_OP_REG && OPERAND(2).type == RISCV_OP_REG) {
SET_SRC_DST_3_REGS(op);
} else if (OPERAND(0).type == RISCV_OP_REG && OPERAND(1).type == RISCV_OP_REG) {
SET_SRC_DST_2_REGS(op);
} else {
RZ_LOG_ERROR("riscv: unknown div opcode at 0x%08" PFMT64x "\n", op->addr);
}
break;
}
if (insn && (insn->id == RISCV_INS_SLTI || insn->id == RISCV_INS_SLTIU)) {
SET_SRC_DST_3_IMM(op);
}
}
static void set_opdir(RzAnalysisOp *op) {
switch (op->type & RZ_ANALYSIS_OP_TYPE_MASK) {
case RZ_ANALYSIS_OP_TYPE_LOAD:
op->direction = RZ_ANALYSIS_OP_DIR_READ;
break;
case RZ_ANALYSIS_OP_TYPE_STORE:
op->direction = RZ_ANALYSIS_OP_DIR_WRITE;
break;
case RZ_ANALYSIS_OP_TYPE_LEA:
op->direction = RZ_ANALYSIS_OP_DIR_REF;
break;
case RZ_ANALYSIS_OP_TYPE_CALL:
case RZ_ANALYSIS_OP_TYPE_JMP:
case RZ_ANALYSIS_OP_TYPE_UJMP:
case RZ_ANALYSIS_OP_TYPE_UCALL:
op->direction = RZ_ANALYSIS_OP_DIR_EXEC;
break;
default:
break;
}
}
static int analyze_op(RzAnalysis *analysis, RzAnalysisOp *op, ut64 addr, const ut8 *buf, int len, RzAnalysisOpMask mask) {
RiscvContext *ctx = (RiscvContext *)analysis->plugin_data;
int n, ret, opsize = -1;
cs_insn *insn;
int mode = (analysis->bits == 64) ? CS_MODE_RISCV64 : CS_MODE_RISCV32;
if (mode != ctx->omode || analysis->bits != ctx->obits) {
cs_close(&ctx->hndl);
ctx->hndl = 0;
ctx->omode = mode;
ctx->obits = analysis->bits;
}
// XXX no arch->cpu ?!?! CS_MODE_MICRO, N64
op->addr = addr;
if (len < 4) {
return -1;
}
op->size = 4;
if (ctx->hndl == 0) {
ret = cs_open(CS_ARCH_RISCV, mode, &ctx->hndl);
if (ret != CS_ERR_OK) {
goto fin;
}
cs_option(ctx->hndl, CS_OPT_DETAIL, CS_OPT_ON);
}
n = cs_disasm(ctx->hndl, (ut8 *)buf, len, addr, 1, &insn);
if (n < 1 || insn->size < 1) {
goto beach;
}
op->id = insn->id;
opsize = op->size = insn->size;
switch (insn->id) {
case RISCV_INS_C_NOP:
op->type = RZ_ANALYSIS_OP_TYPE_NOP;
break;
case RISCV_INS_INVALID:
op->type = RZ_ANALYSIS_OP_TYPE_ILL;
break;
case RISCV_INS_C_JALR:
op->type = RZ_ANALYSIS_OP_TYPE_UCALL;
break;
case RISCV_INS_C_JR:
op->type = RZ_ANALYSIS_OP_TYPE_UJMP;
break;
case RISCV_INS_C_MV:
op->type = RZ_ANALYSIS_OP_TYPE_MOV;
break;
case RISCV_INS_JAL:
op->type = RZ_ANALYSIS_OP_TYPE_CALL;
op->jump = IMM(0);
op->fail = op->addr + op->size;
break;
case RISCV_INS_MRET:
case RISCV_INS_SRET:
case RISCV_INS_URET:
op->type = RZ_ANALYSIS_OP_TYPE_RET;
break;
}
beach:
set_opdir(op);
if (insn && mask & RZ_ANALYSIS_OP_MASK_OPEX) {
opex(&op->opex, ctx->hndl, insn);
}
if (mask & RZ_ANALYSIS_OP_MASK_ESIL) {
if (analyze_op_esil(analysis, op, addr, buf, len, &ctx->hndl, insn) != 0) {
rz_strbuf_fini(&op->esil);
}
}
if (mask & RZ_ANALYSIS_OP_MASK_VAL) {
op_fillval(analysis, op, &ctx->hndl, insn);
}
cs_free(insn, n);
// cs_close (&handle);
fin:
return opsize;
}
static char *get_reg_profile(RzAnalysis *analysis) {
const char *p = NULL;
switch (analysis->bits) {
case 32:
p =
"=PC pc\n"
"=SP sp\n" // ABI: stack pointer
"=LR ra\n" // ABI: return address
"=BP s0\n" // ABI: frame pointer
"=A0 a0\n"
"=A1 a1\n"
"gpr pc .32 0 0\n"
// RV32I regs (ABI names)
// From user-Level ISA Specification, section 2.1
// "zero" has been left out as it ignores writes and always reads as zero
"gpr ra .32 4 0\n" // =x1
"gpr sp .32 8 0\n" // =x2
"gpr gp .32 12 0\n" // =x3
"gpr tp .32 16 0\n" // =x4
"gpr t0 .32 20 0\n" // =x5
"gpr t1 .32 24 0\n" // =x6
"gpr t2 .32 28 0\n" // =x7
"gpr s0 .32 32 0\n" // =x8
"gpr s1 .32 36 0\n" // =x9
"gpr a0 .32 40 0\n" // =x10
"gpr a1 .32 44 0\n" // =x11
"gpr a2 .32 48 0\n" // =x12
"gpr a3 .32 52 0\n" // =x13
"gpr a4 .32 56 0\n" // =x14
"gpr a5 .32 60 0\n" // =x15
"gpr a6 .32 64 0\n" // =x16
"gpr a7 .32 68 0\n" // =x17
"gpr s2 .32 72 0\n" // =x18
"gpr s3 .32 76 0\n" // =x19
"gpr s4 .32 80 0\n" // =x20
"gpr s5 .32 84 0\n" // =x21
"gpr s6 .32 88 0\n" // =x22
"gpr s7 .32 92 0\n" // =x23
"gpr s8 .32 96 0\n" // =x24
"gpr s9 .32 100 0\n" // =x25
"gpr s10 .32 104 0\n" // =x26
"gpr s11 .32 108 0\n" // =x27
"gpr t3 .32 112 0\n" // =x28
"gpr t4 .32 116 0\n" // =x29
"gpr t5 .32 120 0\n" // =x30
"gpr t6 .32 124 0\n" // =x31
// RV32F/D regs (ABI names)
// From user-Level ISA Specification, section 8.1 and 9.1
"gpr ft0 .64 128 0\n" // =f0
"gpr ft1 .64 136 0\n" // =f1
"gpr ft2 .64 144 0\n" // =f2
"gpr ft3 .64 152 0\n" // =f3
"gpr ft4 .64 160 0\n" // =f4
"gpr ft5 .64 168 0\n" // =f5
"gpr ft6 .64 176 0\n" // =f6
"gpr ft7 .64 184 0\n" // =f7
"gpr fs0 .64 192 0\n" // =f8
"gpr fs1 .64 200 0\n" // =f9
"gpr fa0 .64 208 0\n" // =f10
"gpr fa1 .64 216 0\n" // =f11
"gpr fa2 .64 224 0\n" // =f12
"gpr fa3 .64 232 0\n" // =f13
"gpr fa4 .64 240 0\n" // =f14
"gpr fa5 .64 248 0\n" // =f15
"gpr fa6 .64 256 0\n" // =f16
"gpr fa7 .64 264 0\n" // =f17
"gpr fs2 .64 272 0\n" // =f18
"gpr fs3 .64 280 0\n" // =f19
"gpr fs4 .64 288 0\n" // =f20
"gpr fs5 .64 296 0\n" // =f21
"gpr fs6 .64 304 0\n" // =f22
"gpr fs7 .64 312 0\n" // =f23
"gpr fs8 .64 320 0\n" // =f24
"gpr fs9 .64 328 0\n" // =f25
"gpr fs10 .64 336 0\n" // =f26
"gpr fs11 .64 344 0\n" // =f27
"gpr ft8 .64 352 0\n" // =f28
"gpr ft9 .64 360 0\n" // =f29
"gpr ft10 .64 368 0\n" // =f30
"gpr ft11 .64 376 0\n" // =f31
"gpr fcsr .32 384 0\n"
"flg nx .1 3072 0\n"
"flg uf .1 3073 0\n"
"flg of .1 3074 0\n"
"flg dz .1 3075 0\n"
"flg nv .1 3076 0\n"
"flg frm .3 3077 0\n";
break;
case 64:
p =
"=PC pc\n"
"=SP sp\n" // ABI: stack pointer
"=LR ra\n" // ABI: return address
"=BP s0\n" // ABI: frame pointer
"=A0 a0\n"
"=A1 a1\n"
"gpr pc .64 0 0\n"
// RV64I regs (ABI names)
// From user-Level ISA Specification, section 2.1 and 4.1
// "zero" has been left out as it ignores writes and always reads as zero
"gpr ra .64 8 0\n" // =x1
"gpr sp .64 16 0\n" // =x2
"gpr gp .64 24 0\n" // =x3
"gpr tp .64 32 0\n" // =x4
"gpr t0 .64 40 0\n" // =x5
"gpr t1 .64 48 0\n" // =x6
"gpr t2 .64 56 0\n" // =x7
"gpr s0 .64 64 0\n" // =x8
"gpr s1 .64 72 0\n" // =x9
"gpr a0 .64 80 0\n" // =x10
"gpr a1 .64 88 0\n" // =x11
"gpr a2 .64 96 0\n" // =x12
"gpr a3 .64 104 0\n" // =x13
"gpr a4 .64 112 0\n" // =x14
"gpr a5 .64 120 0\n" // =x15
"gpr a6 .64 128 0\n" // =x16
"gpr a7 .64 136 0\n" // =x17
"gpr s2 .64 144 0\n" // =x18
"gpr s3 .64 152 0\n" // =x19
"gpr s4 .64 160 0\n" // =x20
"gpr s5 .64 168 0\n" // =x21
"gpr s6 .64 176 0\n" // =x22
"gpr s7 .64 184 0\n" // =x23
"gpr s8 .64 192 0\n" // =x24
"gpr s9 .64 200 0\n" // =x25
"gpr s10 .64 208 0\n" // =x26
"gpr s11 .64 216 0\n" // =x27
"gpr t3 .64 224 0\n" // =x28
"gpr t4 .64 232 0\n" // =x29
"gpr t5 .64 240 0\n" // =x30
"gpr t6 .64 248 0\n" // =x31
// RV64F/D regs (ABI names)
"gpr ft0 .64 256 0\n" // =f0
"gpr ft1 .64 264 0\n" // =f1
"gpr ft2 .64 272 0\n" // =f2
"gpr ft3 .64 280 0\n" // =f3
"gpr ft4 .64 288 0\n" // =f4
"gpr ft5 .64 296 0\n" // =f5
"gpr ft6 .64 304 0\n" // =f6
"gpr ft7 .64 312 0\n" // =f7
"gpr fs0 .64 320 0\n" // =f8
"gpr fs1 .64 328 0\n" // =f9
"gpr fa0 .64 336 0\n" // =f10
"gpr fa1 .64 344 0\n" // =f11
"gpr fa2 .64 352 0\n" // =f12
"gpr fa3 .64 360 0\n" // =f13
"gpr fa4 .64 368 0\n" // =f14
"gpr fa5 .64 376 0\n" // =f15
"gpr fa6 .64 384 0\n" // =f16
"gpr fa7 .64 392 0\n" // =f17
"gpr fs2 .64 400 0\n" // =f18
"gpr fs3 .64 408 0\n" // =f19
"gpr fs4 .64 416 0\n" // =f20
"gpr fs5 .64 424 0\n" // =f21
"gpr fs6 .64 432 0\n" // =f22
"gpr fs7 .64 440 0\n" // =f23
"gpr fs8 .64 448 0\n" // =f24
"gpr fs9 .64 456 0\n" // =f25
"gpr fs10 .64 464 0\n" // =f26
"gpr fs11 .64 472 0\n" // =f27
"gpr ft8 .64 480 0\n" // =f28
"gpr ft9 .64 488 0\n" // =f29
"gpr ft10 .64 496 0\n" // =f30
"gpr ft11 .64 504 0\n" // =f31
"gpr fcsr .32 512 0\n"
"flg nx .1 4096 0\n"
"flg uf .1 4097 0\n"
"flg of .1 4098 0\n"
"flg dz .1 4099 0\n"
"flg nv .1 4100 0\n"
"flg frm .3 4101 0\n";
break;
}
return rz_str_dup(p);
}
static int archinfo(RzAnalysis *a, RzAnalysisInfoType query) {
bool is_64_bits = a && a->bits == 64;
switch (query) {
case RZ_ANALYSIS_ARCHINFO_MIN_OP_SIZE:
return is_64_bits ? 4 : 2;
case RZ_ANALYSIS_ARCHINFO_MAX_OP_SIZE:
return 6;
case RZ_ANALYSIS_ARCHINFO_TEXT_ALIGN:
/* fall-thru */
case RZ_ANALYSIS_ARCHINFO_DATA_ALIGN:
return 4;
case RZ_ANALYSIS_ARCHINFO_CAN_USE_POINTERS:
return true;
default:
return -1;
}
}
static bool riscv_fini(void *user) {
RiscvContext *ctx = (RiscvContext *)user;
if (ctx) {
RZ_FREE(ctx);
}
return true;
}
RzAnalysisPlugin rz_analysis_plugin_riscv_cs = {
.name = "riscv.cs",
.desc = "Capstone RISCV analyzer",
.license = "BSD",
.esil = true,
.arch = "riscv",
.get_reg_profile = get_reg_profile,
.archinfo = archinfo,
.bits = 32 | 64,
.op = &analyze_op,
.init = riscv_init,
.fini = riscv_fini,
};
#ifndef RZ_PLUGIN_INCORE
RZ_API RzLibStruct rizin_plugin = {
.type = RZ_LIB_TYPE_ANALYSIS,
.data = &rz_analysis_plugin_riscv_cs,
.version = RZ_VERSION
};
#endif