Path: blob/main/sys/cddl/dev/kinst/aarch64/kinst_isa.c
48375 views
/*1* SPDX-License-Identifier: CDDL 1.02*3* Copyright (c) 2022 Christos Margiolis <[email protected]>4* Copyright (c) 2022 Mark Johnston <[email protected]>5* Copyright (c) 2023 The FreeBSD Foundation6*7* Portions of this software were developed by Christos Margiolis8* <[email protected]> under sponsorship from the FreeBSD Foundation.9*/1011#include <sys/param.h>1213#include <sys/dtrace.h>14#include <cddl/dev/dtrace/dtrace_cddl.h>1516#include "kinst.h"1718DPCPU_DEFINE_STATIC(struct kinst_cpu_state, kinst_state);1920static int21kinst_emulate(struct trapframe *frame, const struct kinst_probe *kp)22{23kinst_patchval_t instr = kp->kp_savedval;24uint64_t imm;25uint8_t cond, reg, bitpos;26bool res;2728if (((instr >> 24) & 0x1f) == 0b10000) {29/* adr/adrp */30reg = instr & 0x1f;31imm = (instr >> 29) & 0x3;32imm |= ((instr >> 5) & 0x0007ffff) << 2;33if (((instr >> 31) & 0x1) == 0) {34/* adr */35if (imm & 0x0000000000100000)36imm |= 0xfffffffffff00000;37frame->tf_x[reg] = frame->tf_elr + imm;38} else {39/* adrp */40imm <<= 12;41if (imm & 0x0000000100000000)42imm |= 0xffffffff00000000;43frame->tf_x[reg] = (frame->tf_elr & ~0xfff) + imm;44}45frame->tf_elr += INSN_SIZE;46} else if (((instr >> 26) & 0x3f) == 0b000101) {47/* b */48imm = instr & 0x03ffffff;49if (imm & 0x0000000002000000)50imm |= 0xfffffffffe000000;51frame->tf_elr += imm << 2;52} else if (((instr >> 24) & 0xff) == 0b01010100) {53/* b.cond */54imm = (instr >> 5) & 0x0007ffff;55if (imm & 0x0000000000040000)56imm |= 0xfffffffffffc0000;57cond = instr & 0xf;58switch ((cond >> 1) & 0x7) {59case 0b000: /* eq/ne */60res = (frame->tf_spsr & PSR_Z) != 0;61break;62case 0b001: /* cs/cc */63res = (frame->tf_spsr & PSR_C) != 0;64break;65case 0b010: /* mi/pl */66res = (frame->tf_spsr & PSR_N) != 0;67break;68case 0b011: /* vs/vc */69res = (frame->tf_spsr & PSR_V) != 0;70break;71case 0b100: /* hi/ls */72res = ((frame->tf_spsr & PSR_C) != 0) &&73((frame->tf_spsr & PSR_Z) == 0);74break;75case 0b101: /* ge/lt */76res = ((frame->tf_spsr & PSR_N) != 0) ==77((frame->tf_spsr & PSR_V) != 0);78break;79case 0b110: /* gt/le */80res = ((frame->tf_spsr & PSR_Z) == 0) &&81(((frame->tf_spsr & PSR_N) != 0) ==82((frame->tf_spsr & PSR_V) != 0));83break;84case 0b111: /* al */85res = 1;86break;87}88if ((cond & 0x1) && cond != 0b1111)89res = !res;90if (res)91frame->tf_elr += imm << 2;92else93frame->tf_elr += INSN_SIZE;94} else if (((instr >> 26) & 0x3f) == 0b100101) {95/* bl */96imm = instr & 0x03ffffff;97if (imm & 0x0000000002000000)98imm |= 0xfffffffffe000000;99frame->tf_lr = frame->tf_elr + INSN_SIZE;100frame->tf_elr += imm << 2;101} else if (((instr >> 25) & 0x3f) == 0b011010) {102/* cbnz/cbz */103cond = (instr >> 24) & 0x1;104reg = instr & 0x1f;105imm = (instr >> 5) & 0x0007ffff;106if (imm & 0x0000000000040000)107imm |= 0xfffffffffffc0000;108if (cond == 1 && frame->tf_x[reg] != 0)109/* cbnz */110frame->tf_elr += imm << 2;111else if (cond == 0 && frame->tf_x[reg] == 0)112/* cbz */113frame->tf_elr += imm << 2;114else115frame->tf_elr += INSN_SIZE;116} else if (((instr >> 25) & 0x3f) == 0b011011) {117/* tbnz/tbz */118cond = (instr >> 24) & 0x1;119reg = instr & 0x1f;120bitpos = (instr >> 19) & 0x1f;121bitpos |= ((instr >> 31) & 0x1) << 5;122imm = (instr >> 5) & 0x3fff;123if (imm & 0x0000000000002000)124imm |= 0xffffffffffffe000;125if (cond == 1 && (frame->tf_x[reg] & (1 << bitpos)) != 0)126/* tbnz */127frame->tf_elr += imm << 2;128else if (cond == 0 && (frame->tf_x[reg] & (1 << bitpos)) == 0)129/* tbz */130frame->tf_elr += imm << 2;131else132frame->tf_elr += INSN_SIZE;133}134135return (0);136}137138static int139kinst_jump_next_instr(struct trapframe *frame, const struct kinst_probe *kp)140{141frame->tf_elr = (register_t)((const uint8_t *)kp->kp_patchpoint +142INSN_SIZE);143144return (0);145}146147static void148kinst_trampoline_populate(struct kinst_probe *kp)149{150static uint32_t bpt = KINST_PATCHVAL;151152kinst_memcpy(kp->kp_tramp, &kp->kp_savedval, INSN_SIZE);153kinst_memcpy(&kp->kp_tramp[INSN_SIZE], &bpt, INSN_SIZE);154155cpu_icache_sync_range(kp->kp_tramp, KINST_TRAMP_SIZE);156}157158/*159* There are two ways by which an instruction is traced:160*161* - By using the trampoline.162* - By emulating it in software (see kinst_emulate()).163*164* The trampoline is used for instructions that can be copied and executed165* as-is without additional modification. However, instructions that use166* PC-relative addressing have to be emulated, because ARM64 doesn't allow167* encoding of large displacements in a single instruction, and since we cannot168* clobber a register in order to encode the two-instruction sequence needed to169* create large displacements, we cannot use the trampoline at all.170* Fortunately, the instructions are simple enough to be emulated in just a few171* lines of code.172*173* The problem discussed above also means that, unlike amd64, we cannot encode174* a far-jump back from the trampoline to the next instruction. The mechanism175* employed to achieve this functionality, is to use a breakpoint instead of a176* jump after the copied instruction. This breakpoint is detected and handled177* by kinst_invop(), which performs the jump back to the next instruction178* manually (see kinst_jump_next_instr()).179*/180int181kinst_invop(uintptr_t addr, struct trapframe *frame, uintptr_t scratch)182{183solaris_cpu_t *cpu;184struct kinst_cpu_state *ks;185const struct kinst_probe *kp;186187ks = DPCPU_PTR(kinst_state);188189/*190* Detect if the breakpoint was triggered by the trampoline, and191* manually set the PC to the next instruction.192*/193if (ks->state == KINST_PROBE_FIRED &&194addr == (uintptr_t)(ks->kp->kp_tramp + INSN_SIZE)) {195/*196* Restore interrupts if they were enabled prior to the first197* breakpoint.198*/199if ((ks->status & PSR_I) == 0)200frame->tf_spsr &= ~PSR_I;201ks->state = KINST_PROBE_ARMED;202return (kinst_jump_next_instr(frame, ks->kp));203}204205LIST_FOREACH(kp, KINST_GETPROBE(addr), kp_hashnext) {206if ((uintptr_t)kp->kp_patchpoint == addr)207break;208}209if (kp == NULL)210return (0);211212cpu = &solaris_cpu[curcpu];213cpu->cpu_dtrace_caller = addr;214dtrace_probe(kp->kp_id, 0, 0, 0, 0, 0);215cpu->cpu_dtrace_caller = 0;216217if (kp->kp_md.emulate)218return (kinst_emulate(frame, kp));219220ks->state = KINST_PROBE_FIRED;221ks->kp = kp;222223/*224* Cache the current SPSR and clear interrupts for the duration225* of the double breakpoint.226*/227ks->status = frame->tf_spsr;228frame->tf_spsr |= PSR_I;229frame->tf_elr = (register_t)kp->kp_tramp;230231return (0);232}233234void235kinst_patch_tracepoint(struct kinst_probe *kp, kinst_patchval_t val)236{237void *addr;238239if (!arm64_get_writable_addr(kp->kp_patchpoint, &addr))240panic("%s: Unable to write new instruction", __func__);241*(kinst_patchval_t *)addr = val;242cpu_icache_sync_range(kp->kp_patchpoint, INSN_SIZE);243}244245static void246kinst_instr_dissect(struct kinst_probe *kp)247{248struct kinst_probe_md *kpmd;249kinst_patchval_t instr = kp->kp_savedval;250251kpmd = &kp->kp_md;252kpmd->emulate = false;253254if (((instr >> 24) & 0x1f) == 0b10000)255kpmd->emulate = true; /* adr/adrp */256else if (((instr >> 26) & 0x3f) == 0b000101)257kpmd->emulate = true; /* b */258else if (((instr >> 24) & 0xff) == 0b01010100)259kpmd->emulate = true; /* b.cond */260else if (((instr >> 26) & 0x3f) == 0b100101)261kpmd->emulate = true; /* bl */262else if (((instr >> 25) & 0x3f) == 0b011010)263kpmd->emulate = true; /* cbnz/cbz */264else if (((instr >> 25) & 0x3f) == 0b011011)265kpmd->emulate = true; /* tbnz/tbz */266267if (!kpmd->emulate)268kinst_trampoline_populate(kp);269}270271static bool272kinst_instr_ldx(kinst_patchval_t instr)273{274if (((instr >> 22) & 0xff) == 0b00100001)275return (true);276277return (false);278}279280static bool281kinst_instr_stx(kinst_patchval_t instr)282{283if (((instr >> 22) & 0xff) == 0b00100000)284return (true);285286return (false);287}288289int290kinst_make_probe(linker_file_t lf, int symindx, linker_symval_t *symval,291void *opaque)292{293struct kinst_probe *kp;294dtrace_kinst_probedesc_t *pd;295const char *func;296kinst_patchval_t *instr, *limit, *tmp;297int n, off;298bool ldxstx_block, found;299300pd = opaque;301func = symval->name;302303if (kinst_excluded(func))304return (0);305if (strcmp(func, pd->kpd_func) != 0)306return (0);307308instr = (kinst_patchval_t *)(symval->value);309limit = (kinst_patchval_t *)(symval->value + symval->size);310if (instr >= limit)311return (0);312313tmp = instr;314315/*316* Ignore any bti instruction at the start of the function317* we need to keep it there for any indirect branches calling318* the function on Armv8.5+319*/320if ((*tmp & BTI_MASK) == BTI_INSTR)321tmp++;322323/* Look for stp (pre-indexed) operation */324found = false;325326/*327* If the first instruction is a nop it's a specially marked328* asm function. We only support a nop first as it's not a normal329* part of the function prologue.330*/331if (*tmp == NOP_INSTR)332found = true;333for (; !found && tmp < limit; tmp++) {334/*335* Functions start with "stp xt1, xt2, [xn, <const>]!" or336* "sub sp, sp, <const>".337*338* Sometimes the compiler will have a sub instruction that is339* not of the above type so don't stop if we see one.340*/341if ((*tmp & LDP_STP_MASK) == STP_64) {342/*343* Assume any other store of this type means we are344* past the function prolog.345*/346if (((*tmp >> ADDR_SHIFT) & ADDR_MASK) == 31)347found = true;348} else if ((*tmp & SUB_MASK) == SUB_INSTR &&349((*tmp >> SUB_RD_SHIFT) & SUB_R_MASK) == 31 &&350((*tmp >> SUB_RN_SHIFT) & SUB_R_MASK) == 31)351found = true;352}353354if (!found)355return (0);356357ldxstx_block = false;358for (n = 0; instr < limit; instr++) {359off = (int)((uint8_t *)instr - (uint8_t *)symval->value);360361/*362* Skip LDX/STX blocks that contain atomic operations. If a363* breakpoint is placed in a LDX/STX block, we violate the364* operation and the loop might fail.365*/366if (kinst_instr_ldx(*instr))367ldxstx_block = true;368else if (kinst_instr_stx(*instr)) {369ldxstx_block = false;370continue;371}372if (ldxstx_block)373continue;374375/*376* XXX: Skip ADR and ADRP instructions. The arm64 exception377* handler has a micro-optimization where it doesn't restore378* callee-saved registers when returning from exceptions in379* EL1. This results in a panic when the kinst emulation code380* modifies one of those registers.381*/382if (((*instr >> 24) & 0x1f) == 0b10000)383continue;384385if (pd->kpd_off != -1 && off != pd->kpd_off)386continue;387388/*389* Prevent separate dtrace(1) instances from creating copies of390* the same probe.391*/392LIST_FOREACH(kp, KINST_GETPROBE(instr), kp_hashnext) {393if (strcmp(kp->kp_func, func) == 0 &&394strtol(kp->kp_name, NULL, 10) == off)395return (0);396}397if (++n > KINST_PROBETAB_MAX) {398KINST_LOG("probe list full: %d entries", n);399return (ENOMEM);400}401kp = malloc(sizeof(struct kinst_probe), M_KINST,402M_WAITOK | M_ZERO);403kp->kp_func = func;404snprintf(kp->kp_name, sizeof(kp->kp_name), "%d", off);405kp->kp_patchpoint = instr;406kp->kp_savedval = *instr;407kp->kp_patchval = KINST_PATCHVAL;408if ((kp->kp_tramp = kinst_trampoline_alloc(M_WAITOK)) == NULL) {409KINST_LOG("cannot allocate trampoline for %p", instr);410return (ENOMEM);411}412413kinst_instr_dissect(kp);414kinst_probe_create(kp, lf);415}416if (ldxstx_block)417KINST_LOG("warning: unterminated LDX/STX block");418419return (0);420}421422int423kinst_md_init(void)424{425struct kinst_cpu_state *ks;426int cpu;427428CPU_FOREACH(cpu) {429ks = DPCPU_PTR(kinst_state);430ks->state = KINST_PROBE_ARMED;431}432433return (0);434}435436void437kinst_md_deinit(void)438{439}440441/*442* Exclude machine-dependent functions that are not safe-to-trace.443*/444bool445kinst_md_excluded(const char *name)446{447if (strcmp(name, "handle_el1h_sync") == 0 ||448strcmp(name, "do_el1h_sync") == 0)449return (true);450451return (false);452}453454455