Path: blob/main/sys/cddl/dev/kinst/riscv/kinst_isa.c
48378 views
/*1* SPDX-License-Identifier: CDDL 1.02*3* Copyright (c) 2023 The FreeBSD Foundation4*5* This software was developed by Christos Margiolis <[email protected]>6* under sponsorship from the FreeBSD Foundation.7*/89#include <sys/param.h>1011#include <sys/dtrace.h>12#include <cddl/dev/dtrace/dtrace_cddl.h>1314#include "kinst.h"1516DPCPU_DEFINE_STATIC(struct kinst_cpu_state, kinst_state);1718#define _MATCH_REG(reg) \19(offsetof(struct trapframe, tf_ ## reg) / sizeof(register_t))2021static int22kinst_regoff(struct trapframe *frame, int n)23{24switch (n) {25case 0:26/* There is no zero register in the trapframe structure. */27return (-1);28case 1:29return (_MATCH_REG(ra));30case 2:31return (_MATCH_REG(sp));32case 3:33return (_MATCH_REG(gp));34case 4:35return (_MATCH_REG(tp));36case 5 ... 7:37return (_MATCH_REG(t[n - 5]));38case 8 ... 9:39return (_MATCH_REG(s[n - 8]));40case 10 ... 17:41return (_MATCH_REG(a[n - 10]));42case 18 ... 27:43return (_MATCH_REG(s[n - 18 + 2]));44case 28 ... 31:45return (_MATCH_REG(t[n - 28 + 3]));46default:47panic("%s: unhandled register index %d", __func__, n);48}49}5051static int52kinst_c_regoff(struct trapframe *frame, int n)53{54switch (n) {55case 0 ... 1:56return (_MATCH_REG(s[n]));57case 2 ... 7:58return (_MATCH_REG(a[n - 2]));59default:60panic("%s: unhandled register index %d", __func__, n);61}62}6364#undef _MATCH_REG6566static int67kinst_emulate(struct trapframe *frame, const struct kinst_probe *kp)68{69kinst_patchval_t instr = kp->kp_savedval;70register_t prevpc;71uint64_t imm;72uint16_t off;73uint8_t funct;7475if (kp->kp_md.instlen == INSN_SIZE) {76#define rs1_index ((instr & RS1_MASK) >> RS1_SHIFT)77#define rs2_index ((instr & RS2_MASK) >> RS2_SHIFT)78#define rd_index ((instr & RD_MASK) >> RD_SHIFT)79#define rs1 ((register_t *)frame)[kinst_regoff(frame, rs1_index)]80#define rs2 ((register_t *)frame)[kinst_regoff(frame, rs2_index)]81#define rd ((register_t *)frame)[kinst_regoff(frame, rd_index)]82#define rs1_lval (rs1_index != 0 ? rs1 : 0)83#define rs2_lval (rs2_index != 0 ? rs2 : 0)84switch (instr & 0x7f) {85case 0b1101111: /* jal */86imm = 0;87imm |= ((instr >> 21) & 0x03ff) << 1;88imm |= ((instr >> 20) & 0x0001) << 11;89imm |= ((instr >> 12) & 0x00ff) << 12;90imm |= ((instr >> 31) & 0x0001) << 20;91if (imm & 0x0000000000100000)92imm |= 0xfffffffffff00000;93if (rd_index != 0)94rd = frame->tf_sepc + INSN_SIZE;95frame->tf_sepc += imm;96break;97case 0b1100111: /* jalr */98prevpc = frame->tf_sepc;99imm = (instr & IMM_MASK) >> IMM_SHIFT;100if (imm & 0x0000000000000800)101imm |= 0xfffffffffffff000;102frame->tf_sepc = (rs1_lval + imm) & ~1;103if (rd_index != 0)104rd = prevpc + INSN_SIZE;105break;106case 0b1100011: /* branch */107imm = 0;108imm |= ((instr >> 8) & 0x000f) << 1;109imm |= ((instr >> 25) & 0x003f) << 5;110imm |= ((instr >> 7) & 0x0001) << 11;111imm |= ((instr >> 31) & 0x0001) << 12;112if (imm & 0x0000000000001000)113imm |= 0xfffffffffffff000;114funct = (instr >> 12) & 0x07;115switch (funct) {116case 0b000: /* beq */117if (rs1_lval == rs2_lval)118frame->tf_sepc += imm;119else120frame->tf_sepc += INSN_SIZE;121break;122case 0b001: /* bne */123if (rs1_lval != rs2_lval)124frame->tf_sepc += imm;125else126frame->tf_sepc += INSN_SIZE;127break;128case 0b100: /* blt */129if ((int64_t)rs1_lval < (int64_t)rs2_lval)130frame->tf_sepc += imm;131else132frame->tf_sepc += INSN_SIZE;133break;134case 0b110: /* bltu */135if ((uint64_t)rs1_lval < (uint64_t)rs2_lval)136frame->tf_sepc += imm;137else138frame->tf_sepc += INSN_SIZE;139break;140case 0b101: /* bge */141if ((int64_t)rs1_lval >= (int64_t)rs2_lval)142frame->tf_sepc += imm;143else144frame->tf_sepc += INSN_SIZE;145break;146case 0b111: /* bgeu */147if ((uint64_t)rs1_lval >= (uint64_t)rs2_lval)148frame->tf_sepc += imm;149else150frame->tf_sepc += INSN_SIZE;151break;152}153break;154case 0b0010111: /* auipc */155imm = instr & 0xfffff000;156rd = frame->tf_sepc +157(imm & 0x0000000080000000 ?158imm | 0xffffffff80000000 : imm);159frame->tf_sepc += INSN_SIZE;160break;161}162#undef rs1_lval163#undef rs2_lval164#undef rs1165#undef rs2166#undef rd167#undef rs1_index168#undef rs2_index169#undef rd_index170} else {171switch (instr & 0x03) {172#define rs1 \173((register_t *)frame)[kinst_c_regoff(frame, (instr >> 7) & 0x07)]174case 0b01:175funct = (instr >> 13) & 0x07;176switch (funct) {177case 0b101: /* c.j */178off = (instr >> 2) & 0x07ff;179imm = 0;180imm |= ((off >> 1) & 0x07) << 1;181imm |= ((off >> 9) & 0x01) << 4;182imm |= ((off >> 0) & 0x01) << 5;183imm |= ((off >> 5) & 0x01) << 6;184imm |= ((off >> 4) & 0x01) << 7;185imm |= ((off >> 7) & 0x03) << 8;186imm |= ((off >> 6) & 0x01) << 10;187imm |= ((off >> 10) & 0x01) << 11;188if (imm & 0x0000000000000800)189imm |= 0xfffffffffffff000;190frame->tf_sepc += imm;191break;192case 0b110: /* c.beqz */193case 0b111: /* c.bnez */194imm = 0;195imm |= ((instr >> 3) & 0x03) << 1;196imm |= ((instr >> 10) & 0x03) << 3;197imm |= ((instr >> 2) & 0x01) << 5;198imm |= ((instr >> 5) & 0x03) << 6;199imm |= ((instr >> 12) & 0x01) << 8;200if (imm & 0x0000000000000100)201imm |= 0xffffffffffffff00;202if (funct == 0b110 && rs1 == 0)203frame->tf_sepc += imm;204else if (funct == 0b111 && rs1 != 0)205frame->tf_sepc += imm;206else207frame->tf_sepc += INSN_C_SIZE;208break;209}210break;211#undef rs1212#define rs1_index ((instr & RD_MASK) >> RD_SHIFT)213#define rs1 ((register_t *)frame)[kinst_regoff(frame, rs1_index)]214case 0b10:215funct = (instr >> 13) & 0x07;216if (funct == 0b100 && rs1_index != 0) {217/* c.jr/c.jalr */218prevpc = frame->tf_sepc;219frame->tf_sepc = rs1;220if (((instr >> 12) & 0x01) != 0)221frame->tf_ra = prevpc + INSN_C_SIZE;222}223break;224#undef rs1225#undef rs1_index226}227}228229return (MATCH_C_NOP);230}231232static int233kinst_jump_next_instr(struct trapframe *frame, const struct kinst_probe *kp)234{235frame->tf_sepc = (register_t)((const uint8_t *)kp->kp_patchpoint +236kp->kp_md.instlen);237238return (MATCH_C_NOP);239}240241static void242kinst_trampoline_populate(struct kinst_probe *kp)243{244static uint16_t nop = MATCH_C_NOP;245static uint32_t ebreak = MATCH_EBREAK;246int ilen;247248ilen = kp->kp_md.instlen;249kinst_memcpy(kp->kp_tramp, &kp->kp_savedval, ilen);250251/*252* Since we cannot encode large displacements in a single instruction253* in order to encode a far-jump back to the next instruction, and we254* also cannot clobber a register inside the trampoline, we execute a255* breakpoint after the copied instruction. kinst_invop() is256* responsible for detecting this special case and performing the257* "jump" manually.258*259* Add a NOP after a compressed instruction for padding.260*/261if (ilen == INSN_C_SIZE)262kinst_memcpy(&kp->kp_tramp[ilen], &nop, INSN_C_SIZE);263264kinst_memcpy(&kp->kp_tramp[INSN_SIZE], &ebreak, INSN_SIZE);265266fence_i();267}268269/*270* There are two ways by which an instruction is traced:271*272* - By using the trampoline.273* - By emulating it in software (see kinst_emulate()).274*275* The trampoline is used for instructions that can be copied and executed276* as-is without additional modification. However, instructions that use277* PC-relative addressing have to be emulated, because RISC-V doesn't allow278* encoding of large displacements in a single instruction, and since we cannot279* clobber a register in order to encode the two-instruction sequence needed to280* create large displacements, we cannot use the trampoline at all.281* Fortunately, the instructions are simple enough to be emulated in just a few282* lines of code.283*284* The problem discussed above also means that, unlike amd64, we cannot encode285* a far-jump back from the trampoline to the next instruction. The mechanism286* employed to achieve this functionality, is to use a breakpoint instead of a287* jump after the copied instruction. This breakpoint is detected and handled288* by kinst_invop(), which performs the jump back to the next instruction289* manually (see kinst_jump_next_instr()).290*/291int292kinst_invop(uintptr_t addr, struct trapframe *frame, uintptr_t scratch)293{294solaris_cpu_t *cpu;295struct kinst_cpu_state *ks;296const struct kinst_probe *kp;297298ks = DPCPU_PTR(kinst_state);299300/*301* Detect if the breakpoint was triggered by the trampoline, and302* manually set the PC to the next instruction.303*/304if (ks->state == KINST_PROBE_FIRED &&305addr == (uintptr_t)(ks->kp->kp_tramp + INSN_SIZE)) {306/*307* Restore interrupts if they were enabled prior to the first308* breakpoint.309*/310if ((ks->status & SSTATUS_SPIE) != 0)311frame->tf_sstatus |= SSTATUS_SPIE;312ks->state = KINST_PROBE_ARMED;313return (kinst_jump_next_instr(frame, ks->kp));314}315316LIST_FOREACH(kp, KINST_GETPROBE(addr), kp_hashnext) {317if ((uintptr_t)kp->kp_patchpoint == addr)318break;319}320if (kp == NULL)321return (0);322323cpu = &solaris_cpu[curcpu];324cpu->cpu_dtrace_caller = addr;325dtrace_probe(kp->kp_id, 0, 0, 0, 0, 0);326cpu->cpu_dtrace_caller = 0;327328if (kp->kp_md.emulate)329return (kinst_emulate(frame, kp));330331ks->state = KINST_PROBE_FIRED;332ks->kp = kp;333334/*335* Cache the current SSTATUS and clear interrupts for the336* duration of the double breakpoint.337*/338ks->status = frame->tf_sstatus;339frame->tf_sstatus &= ~SSTATUS_SPIE;340frame->tf_sepc = (register_t)kp->kp_tramp;341342return (MATCH_C_NOP);343}344345void346kinst_patch_tracepoint(struct kinst_probe *kp, kinst_patchval_t val)347{348switch (kp->kp_patchval) {349case KINST_C_PATCHVAL:350*(uint16_t *)kp->kp_patchpoint = (uint16_t)val;351fence_i();352break;353case KINST_PATCHVAL:354*kp->kp_patchpoint = val;355fence_i();356break;357}358}359360static void361kinst_instr_dissect(struct kinst_probe *kp, int instrsize)362{363struct kinst_probe_md *kpmd;364kinst_patchval_t instr = kp->kp_savedval;365uint8_t funct;366367kpmd = &kp->kp_md;368kpmd->instlen = instrsize;369kpmd->emulate = false;370371/*372* The following instructions use PC-relative addressing and need to be373* emulated in software.374*/375if (kpmd->instlen == INSN_SIZE) {376switch (instr & 0x7f) {377case 0b1101111: /* jal */378case 0b1100111: /* jalr */379case 0b1100011: /* branch */380case 0b0010111: /* auipc */381kpmd->emulate = true;382break;383}384} else {385switch (instr & 0x03) {386case 0b01:387funct = (instr >> 13) & 0x07;388switch (funct) {389case 0b101: /* c.j */390case 0b110: /* c.beqz */391case 0b111: /* c.bnez */392kpmd->emulate = true;393break;394}395break;396case 0b10:397funct = (instr >> 13) & 0x07;398if (funct == 0b100 &&399((instr >> 7) & 0x1f) != 0 &&400((instr >> 2) & 0x1f) == 0)401kpmd->emulate = true; /* c.jr/c.jalr */402break;403}404}405406if (!kpmd->emulate)407kinst_trampoline_populate(kp);408}409410static bool411kinst_instr_system(kinst_patchval_t instr)412{413if (dtrace_match_opcode(instr, MATCH_C_EBREAK, MASK_C_EBREAK) ||414(instr & 0x7f) == 0b1110011)415return (true);416417return (false);418}419420static bool421kinst_instr_lr(kinst_patchval_t instr)422{423if (dtrace_match_opcode(instr, MATCH_LR_W, MASK_LR_W) ||424dtrace_match_opcode(instr, MATCH_LR_D, MASK_LR_D))425return (true);426427return (false);428}429430static bool431kinst_instr_sc(kinst_patchval_t instr)432{433if (dtrace_match_opcode(instr, MATCH_SC_W, MASK_SC_W) ||434dtrace_match_opcode(instr, MATCH_SC_D, MASK_SC_D))435return (true);436437return (false);438}439440int441kinst_make_probe(linker_file_t lf, int symindx, linker_symval_t *symval,442void *opaque)443{444struct kinst_probe *kp;445dtrace_kinst_probedesc_t *pd;446const char *func;447kinst_patchval_t *insn, v;448uint8_t *instr, *limit;449int instrsize, n, off;450bool lrsc_block, store_found;451452pd = opaque;453func = symval->name;454455if (kinst_excluded(func))456return (0);457if (strcmp(func, pd->kpd_func) != 0)458return (0);459460instr = (uint8_t *)(symval->value);461limit = (uint8_t *)(symval->value + symval->size);462if (instr >= limit)463return (0);464465/* Check for the usual function prologue. */466store_found = false;467for (insn = (kinst_patchval_t *)instr;468insn < (kinst_patchval_t *)limit; insn++) {469if (dtrace_instr_sdsp(&insn) || dtrace_instr_c_sdsp(&insn)) {470store_found = true;471break;472}473}474if (!store_found)475return (0);476477n = 0;478lrsc_block = false;479while (instr < limit) {480instrsize = dtrace_instr_size(instr);481off = (int)(instr - (uint8_t *)symval->value);482483/*484* Avoid undefined behavior (i.e simply casting `*instr` to485* `kinst_patchval_t`) in case the pointer is unaligned.486* memcpy() can safely operate on unaligned pointers.487*/488memcpy(&v, instr, sizeof(kinst_patchval_t));489490/* Skip SYSTEM instructions. */491if (kinst_instr_system(v))492goto cont;493494/*495* Skip LR/SC blocks used to build atomic operations. If a496* breakpoint is placed in a LR/SC block, the loop becomes497* unconstrained. In this case we violate the operation and the498* loop might fail on some implementations (see section 8.3 of499* the RISC-V unprivileged spec).500*/501if (kinst_instr_lr(v))502lrsc_block = true;503else if (kinst_instr_sc(v)) {504lrsc_block = false;505goto cont;506}507if (lrsc_block)508goto cont;509510if (pd->kpd_off != -1 && off != pd->kpd_off)511goto cont;512513/*514* Prevent separate dtrace(1) instances from creating copies of515* the same probe.516*/517LIST_FOREACH(kp, KINST_GETPROBE(instr), kp_hashnext) {518if (strcmp(kp->kp_func, func) == 0 &&519strtol(kp->kp_name, NULL, 10) == off)520return (0);521}522if (++n > KINST_PROBETAB_MAX) {523KINST_LOG("probe list full: %d entries", n);524return (ENOMEM);525}526kp = malloc(sizeof(struct kinst_probe), M_KINST,527M_WAITOK | M_ZERO);528kp->kp_func = func;529snprintf(kp->kp_name, sizeof(kp->kp_name), "%d", off);530kp->kp_patchpoint = (kinst_patchval_t *)instr;531kp->kp_savedval = v;532if (instrsize == INSN_SIZE)533kp->kp_patchval = KINST_PATCHVAL;534else535kp->kp_patchval = KINST_C_PATCHVAL;536if ((kp->kp_tramp = kinst_trampoline_alloc(M_WAITOK)) == NULL) {537KINST_LOG("cannot allocate trampoline for %p", instr);538return (ENOMEM);539}540541kinst_instr_dissect(kp, instrsize);542kinst_probe_create(kp, lf);543cont:544instr += instrsize;545}546if (lrsc_block)547KINST_LOG("warning: unterminated LR/SC block");548549return (0);550}551552int553kinst_md_init(void)554{555struct kinst_cpu_state *ks;556int cpu;557558CPU_FOREACH(cpu) {559ks = DPCPU_PTR(kinst_state);560ks->state = KINST_PROBE_ARMED;561}562563return (0);564}565566void567kinst_md_deinit(void)568{569}570571/*572* Exclude machine-dependent functions that are not safe-to-trace.573*/574bool575kinst_md_excluded(const char *name)576{577if (strcmp(name, "cpu_exception_handler") == 0 ||578strcmp(name, "cpu_exception_handler_supervisor") == 0 ||579strcmp(name, "cpu_exception_handler_user") == 0 ||580strcmp(name, "do_trap_supervisor") == 0 ||581strcmp(name, "do_trap_user") == 0)582return (true);583584return (false);585}586587588