From nobody Wed Jul 19 14:59:54 2023 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4R5fB23tQ0z4nThW; Wed, 19 Jul 2023 14:59:54 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4R5fB22fTQz3mkd; Wed, 19 Jul 2023 14:59:54 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1689778794; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=qFKVei4bXd+wBNcybx2Gwbm4iwkJMeP351Y8qeFUKYs=; b=jL7lQBTkmFSQjXahxW+W0g8zy7W3HMtAgxQw69v42FBMygCXuopvpjpX5SwhGqqYicXN3Q A08nlZpZi+g2ClD6FeNAh3PoI2XSWcvr5Zq6u+ooIO0Q8A4m5BdCjjGHcjnSv3ptF0MQof otCQ8qKbZNfFi0aZkBb8t1mnEwGzrUFiPonAHgAzBwexwwNweMYU5OWt1TwhjFM6BaXM+7 iELAp4PqwpzgCNvt/QE7LvjOeLTNV94M8eI4YzkPbV4pzrWVm5FYnoioHgLN+bLi4PJTtP tQFaTXMoZf6nXZnX3PImE/89k9rrnaDLcp2XZwhh20PuJHUey2htWIFnGmcX/g== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1689778794; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=qFKVei4bXd+wBNcybx2Gwbm4iwkJMeP351Y8qeFUKYs=; b=XceWer4iAV1xY103m8pDFvpInV1J0y0Ji+YrWnXcM11a7P1ekVl7yuE39r7GZC6Y+eW9rZ 7B/y6jaUVXHpcJwjdfxJ0g2UdVFqnh2/ihQT8VaAPoOH/oBTYuTdrvdjphKphUkRt9rXEB 9ApLRXZi+nLfzJTbwWc1IY38a5Nthytb8q+z4LGbmGnQ4xkgz3fpGvY5eeli6enKb+Y/PU 39XE9xW5US9ZyiR7HaYbIpfH56Vl+wbfgSf0LE+xvL2qeMtCggoxDg39UmR5mP2MsuYN6r WbbwS2IArZR9x3UUQ+F/1DJa2sxghUG67cUBIVJz5wG3uFF24D+daYrzzitB1A== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1689778794; a=rsa-sha256; cv=none; b=XD7iMlqMIDD9yjrtViD6tvZCRhrJpm6jZwt0ghaRa6zmsmKZZ9GEUVkXfrCgtwISNBPeec jpzkiMpwPE6BhjFbOYRMi1X8QlkeShvd/Qgg1vhCu+pillVTTGJO4OOepMy8RsTLMTBS47 R1y35xLo25bI6u/mHGmoqPBPwAMve83xRYrFp5MtT1l9G7pQFh40HVZQbw1s1OiJ6XxyR8 N7v/l44n5T/Rx/iPfKgHdSqIH8jXtbZ0jLg7ZR6wNKuPtf6tn41imJCrcR3++acYLHQUY/ LMcNOgFO4man7942OzhKYINqeemUwAzdD7Tt6frDmJ/YZx07ZDWKQwr5CCh2Qg== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 4R5fB21lYtzrh7; Wed, 19 Jul 2023 14:59:54 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.17.1/8.17.1) with ESMTP id 36JExstZ067624; Wed, 19 Jul 2023 14:59:54 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.17.1/8.17.1/Submit) id 36JExsm8067623; Wed, 19 Jul 2023 14:59:54 GMT (envelope-from git) Date: Wed, 19 Jul 2023 14:59:54 GMT Message-Id: <202307191459.36JExsm8067623@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Christos Margiolis Subject: git: 07864a8a2466 - main - kinst: port to arm64 List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: Sender: owner-dev-commits-src-all@freebsd.org X-BeenThere: dev-commits-src-all@freebsd.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: christos X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 07864a8a2466b8ab3d6fa6623a7eaaf0834c0b51 Auto-Submitted: auto-generated The branch main has been updated by christos: URL: https://cgit.FreeBSD.org/src/commit/?id=07864a8a2466b8ab3d6fa6623a7eaaf0834c0b51 commit 07864a8a2466b8ab3d6fa6623a7eaaf0834c0b51 Author: Christos Margiolis AuthorDate: 2023-07-19 14:58:18 +0000 Commit: Christos Margiolis CommitDate: 2023-07-19 14:58:18 +0000 kinst: port to arm64 Reviewed by: markj Approved by: markj (mentor) Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D40337 --- sys/cddl/dev/kinst/aarch64/kinst_isa.c | 455 +++++++++++++++++++++++++++++++++ sys/cddl/dev/kinst/aarch64/kinst_isa.h | 26 ++ sys/modules/dtrace/Makefile | 2 +- 3 files changed, 482 insertions(+), 1 deletion(-) diff --git a/sys/cddl/dev/kinst/aarch64/kinst_isa.c b/sys/cddl/dev/kinst/aarch64/kinst_isa.c new file mode 100644 index 000000000000..bf3ab1d35de3 --- /dev/null +++ b/sys/cddl/dev/kinst/aarch64/kinst_isa.c @@ -0,0 +1,455 @@ +/* + * SPDX-License-Identifier: CDDL 1.0 + * + * Copyright (c) 2022 Christos Margiolis + * Copyright (c) 2022 Mark Johnston + * Copyright (c) 2023 The FreeBSD Foundation + * + * Portions of this software were developed by Christos Margiolis + * under sponsorship from the FreeBSD Foundation. + */ + +#include + +#include +#include + +#include "kinst.h" + +DPCPU_DEFINE_STATIC(struct kinst_cpu_state, kinst_state); + +static int +kinst_emulate(struct trapframe *frame, const struct kinst_probe *kp) +{ + kinst_patchval_t instr = kp->kp_savedval; + uint64_t imm; + uint8_t cond, reg, bitpos; + bool res; + + if (((instr >> 24) & 0x1f) == 0b10000) { + /* adr/adrp */ + reg = instr & 0x1f; + imm = (instr >> 29) & 0x3; + imm |= ((instr >> 5) & 0x0007ffff) << 2; + if (((instr >> 31) & 0x1) == 0) { + /* adr */ + if (imm & 0x0000000000100000) + imm |= 0xfffffffffff00000; + frame->tf_x[reg] = frame->tf_elr + imm; + } else { + /* adrp */ + imm <<= 12; + if (imm & 0x0000000100000000) + imm |= 0xffffffff00000000; + frame->tf_x[reg] = (frame->tf_elr & ~0xfff) + imm; + } + frame->tf_elr += INSN_SIZE; + } else if (((instr >> 26) & 0x3f) == 0b000101) { + /* b */ + imm = instr & 0x03ffffff; + if (imm & 0x0000000002000000) + imm |= 0xfffffffffe000000; + frame->tf_elr += imm << 2; + } else if (((instr >> 24) & 0xff) == 0b01010100) { + /* b.cond */ + imm = (instr >> 5) & 0x0007ffff; + if (imm & 0x0000000000040000) + imm |= 0xfffffffffffc0000; + cond = instr & 0xf; + switch ((cond >> 1) & 0x7) { + case 0b000: /* eq/ne */ + res = (frame->tf_spsr & PSR_Z) != 0; + break; + case 0b001: /* cs/cc */ + res = (frame->tf_spsr & PSR_C) != 0; + break; + case 0b010: /* mi/pl */ + res = (frame->tf_spsr & PSR_N) != 0; + break; + case 0b011: /* vs/vc */ + res = (frame->tf_spsr & PSR_V) != 0; + break; + case 0b100: /* hi/ls */ + res = ((frame->tf_spsr & PSR_C) != 0) && + ((frame->tf_spsr & PSR_Z) == 0); + break; + case 0b101: /* ge/lt */ + res = ((frame->tf_spsr & PSR_N) != 0) == + ((frame->tf_spsr & PSR_V) != 0); + break; + case 0b110: /* gt/le */ + res = ((frame->tf_spsr & PSR_Z) == 0) && + (((frame->tf_spsr & PSR_N) != 0) == + ((frame->tf_spsr & PSR_V) != 0)); + break; + case 0b111: /* al */ + res = 1; + break; + } + if ((cond & 0x1) && cond != 0b1111) + res = !res; + if (res) + frame->tf_elr += imm << 2; + else + frame->tf_elr += INSN_SIZE; + } else if (((instr >> 26) & 0x3f) == 0b100101) { + /* bl */ + imm = instr & 0x03ffffff; + if (imm & 0x0000000002000000) + imm |= 0xfffffffffe000000; + frame->tf_lr = frame->tf_elr + INSN_SIZE; + frame->tf_elr += imm << 2; + } else if (((instr >> 25) & 0x3f) == 0b011010) { + /* cbnz/cbz */ + cond = (instr >> 24) & 0x1; + reg = instr & 0x1f; + imm = (instr >> 5) & 0x0007ffff; + if (imm & 0x0000000000040000) + imm |= 0xfffffffffffc0000; + if (cond == 1 && frame->tf_x[reg] != 0) + /* cbnz */ + frame->tf_elr += imm << 2; + else if (cond == 0 && frame->tf_x[reg] == 0) + /* cbz */ + frame->tf_elr += imm << 2; + else + frame->tf_elr += INSN_SIZE; + } else if (((instr >> 25) & 0x3f) == 0b011011) { + /* tbnz/tbz */ + cond = (instr >> 24) & 0x1; + reg = instr & 0x1f; + bitpos = (instr >> 19) & 0x1f; + bitpos |= ((instr >> 31) & 0x1) << 5; + imm = (instr >> 5) & 0x3fff; + if (imm & 0x0000000000002000) + imm |= 0xffffffffffffe000; + if (cond == 1 && (frame->tf_x[reg] & (1 << bitpos)) != 0) + /* tbnz */ + frame->tf_elr += imm << 2; + else if (cond == 0 && (frame->tf_x[reg] & (1 << bitpos)) == 0) + /* tbz */ + frame->tf_elr += imm << 2; + else + frame->tf_elr += INSN_SIZE; + } + + return (0); +} + +static int +kinst_jump_next_instr(struct trapframe *frame, const struct kinst_probe *kp) +{ + frame->tf_elr = (register_t)((const uint8_t *)kp->kp_patchpoint + + INSN_SIZE); + + return (0); +} + +static void +kinst_trampoline_populate(struct kinst_probe *kp) +{ + static uint32_t bpt = KINST_PATCHVAL; + + kinst_memcpy(kp->kp_tramp, &kp->kp_savedval, INSN_SIZE); + kinst_memcpy(&kp->kp_tramp[INSN_SIZE], &bpt, INSN_SIZE); + + cpu_icache_sync_range((vm_offset_t)kp->kp_tramp, + (vm_size_t)KINST_TRAMP_SIZE); +} + +/* + * There are two ways by which an instruction is traced: + * + * - By using the trampoline. + * - By emulating it in software (see kinst_emulate()). + * + * The trampoline is used for instructions that can be copied and executed + * as-is without additional modification. However, instructions that use + * PC-relative addressing have to be emulated, because ARM64 doesn't allow + * encoding of large displacements in a single instruction, and since we cannot + * clobber a register in order to encode the two-instruction sequence needed to + * create large displacements, we cannot use the trampoline at all. + * Fortunately, the instructions are simple enough to be emulated in just a few + * lines of code. + * + * The problem discussed above also means that, unlike amd64, we cannot encode + * a far-jump back from the trampoline to the next instruction. The mechanism + * employed to achieve this functionality, is to use a breakpoint instead of a + * jump after the copied instruction. This breakpoint is detected and handled + * by kinst_invop(), which performs the jump back to the next instruction + * manually (see kinst_jump_next_instr()). + */ +int +kinst_invop(uintptr_t addr, struct trapframe *frame, uintptr_t scratch) +{ + solaris_cpu_t *cpu; + struct kinst_cpu_state *ks; + const struct kinst_probe *kp; + + ks = DPCPU_PTR(kinst_state); + + /* + * Detect if the breakpoint was triggered by the trampoline, and + * manually set the PC to the next instruction. + */ + if (ks->state == KINST_PROBE_FIRED && + addr == (uintptr_t)(ks->kp->kp_tramp + INSN_SIZE)) { + /* + * Restore interrupts if they were enabled prior to the first + * breakpoint. + */ + if ((ks->status & PSR_I) == 0) + frame->tf_spsr &= ~PSR_I; + ks->state = KINST_PROBE_ARMED; + return (kinst_jump_next_instr(frame, ks->kp)); + } + + LIST_FOREACH(kp, KINST_GETPROBE(addr), kp_hashnext) { + if ((uintptr_t)kp->kp_patchpoint == addr) + break; + } + if (kp == NULL) + return (0); + + cpu = &solaris_cpu[curcpu]; + cpu->cpu_dtrace_caller = addr; + dtrace_probe(kp->kp_id, 0, 0, 0, 0, 0); + cpu->cpu_dtrace_caller = 0; + + if (kp->kp_md.emulate) + return (kinst_emulate(frame, kp)); + + ks->state = KINST_PROBE_FIRED; + ks->kp = kp; + + /* + * Cache the current SPSR and clear interrupts for the duration + * of the double breakpoint. + */ + ks->status = frame->tf_spsr; + frame->tf_spsr |= PSR_I; + frame->tf_elr = (register_t)kp->kp_tramp; + + return (0); +} + +void +kinst_patch_tracepoint(struct kinst_probe *kp, kinst_patchval_t val) +{ + vm_offset_t addr; + + if (!arm64_get_writable_addr((vm_offset_t)kp->kp_patchpoint, &addr)) + panic("%s: Unable to write new instruction", __func__); + *(kinst_patchval_t *)addr = val; + cpu_icache_sync_range((vm_offset_t)kp->kp_patchpoint, + (vm_size_t)INSN_SIZE); +} + +static void +kinst_instr_dissect(struct kinst_probe *kp) +{ + struct kinst_probe_md *kpmd; + kinst_patchval_t instr = kp->kp_savedval; + + kpmd = &kp->kp_md; + kpmd->emulate = false; + + if (((instr >> 24) & 0x1f) == 0b10000) + kpmd->emulate = true; /* adr/adrp */ + else if (((instr >> 26) & 0x3f) == 0b000101) + kpmd->emulate = true; /* b */ + else if (((instr >> 24) & 0xff) == 0b01010100) + kpmd->emulate = true; /* b.cond */ + else if (((instr >> 26) & 0x3f) == 0b100101) + kpmd->emulate = true; /* bl */ + else if (((instr >> 25) & 0x3f) == 0b011010) + kpmd->emulate = true; /* cbnz/cbz */ + else if (((instr >> 25) & 0x3f) == 0b011011) + kpmd->emulate = true; /* tbnz/tbz */ + + if (!kpmd->emulate) + kinst_trampoline_populate(kp); +} + +static bool +kinst_instr_ldx(kinst_patchval_t instr) +{ + if (((instr >> 22) & 0xff) == 0b00100001) + return (true); + + return (false); +} + +static bool +kinst_instr_stx(kinst_patchval_t instr) +{ + if (((instr >> 22) & 0xff) == 0b00100000) + return (true); + + return (false); +} + +int +kinst_make_probe(linker_file_t lf, int symindx, linker_symval_t *symval, + void *opaque) +{ + struct kinst_probe *kp; + dtrace_kinst_probedesc_t *pd; + const char *func; + kinst_patchval_t *instr, *limit, *tmp; + int n, off; + bool ldxstx_block, found; + + pd = opaque; + func = symval->name; + + if (kinst_excluded(func)) + return (0); + if (strcmp(func, pd->kpd_func) != 0) + return (0); + + instr = (kinst_patchval_t *)(symval->value); + limit = (kinst_patchval_t *)(symval->value + symval->size); + if (instr >= limit) + return (0); + + tmp = instr; + + /* + * Ignore any bti instruction at the start of the function + * we need to keep it there for any indirect branches calling + * the function on Armv8.5+ + */ + if ((*tmp & BTI_MASK) == BTI_INSTR) + tmp++; + + /* Look for stp (pre-indexed) operation */ + found = false; + + /* + * If the first instruction is a nop it's a specially marked + * asm function. We only support a nop first as it's not a normal + * part of the function prologue. + */ + if (*tmp == NOP_INSTR) + found = true; + for (; !found && tmp < limit; tmp++) { + /* + * Functions start with "stp xt1, xt2, [xn, ]!" or + * "sub sp, sp, ". + * + * Sometimes the compiler will have a sub instruction that is + * not of the above type so don't stop if we see one. + */ + if ((*tmp & LDP_STP_MASK) == STP_64) { + /* + * Assume any other store of this type means we are + * past the function prolog. + */ + if (((*tmp >> ADDR_SHIFT) & ADDR_MASK) == 31) + found = true; + } else if ((*tmp & SUB_MASK) == SUB_INSTR && + ((*tmp >> SUB_RD_SHIFT) & SUB_R_MASK) == 31 && + ((*tmp >> SUB_RN_SHIFT) & SUB_R_MASK) == 31) + found = true; + } + + if (!found) + return (0); + + ldxstx_block = false; + for (n = 0; instr < limit; instr++) { + off = (int)((uint8_t *)instr - (uint8_t *)symval->value); + + /* + * Skip LDX/STX blocks that contain atomic operations. If a + * breakpoint is placed in a LDX/STX block, we violate the + * operation and the loop might fail. + */ + if (kinst_instr_ldx(*instr)) + ldxstx_block = true; + else if (kinst_instr_stx(*instr)) { + ldxstx_block = false; + continue; + } + if (ldxstx_block) + continue; + + /* + * XXX: Skip ADR and ADRP instructions. The arm64 exception + * handler has a micro-optimization where it doesn't restore + * callee-saved registers when returning from exceptions in + * EL1. This results in a panic when the kinst emulation code + * modifies one of those registers. + */ + if (((*instr >> 24) & 0x1f) == 0b10000) + continue; + + if (pd->kpd_off != -1 && off != pd->kpd_off) + continue; + + /* + * Prevent separate dtrace(1) instances from creating copies of + * the same probe. + */ + LIST_FOREACH(kp, KINST_GETPROBE(instr), kp_hashnext) { + if (strcmp(kp->kp_func, func) == 0 && + strtol(kp->kp_name, NULL, 10) == off) + return (0); + } + if (++n > KINST_PROBETAB_MAX) { + KINST_LOG("probe list full: %d entries", n); + return (ENOMEM); + } + kp = malloc(sizeof(struct kinst_probe), M_KINST, + M_WAITOK | M_ZERO); + kp->kp_func = func; + snprintf(kp->kp_name, sizeof(kp->kp_name), "%d", off); + kp->kp_patchpoint = instr; + kp->kp_savedval = *instr; + kp->kp_patchval = KINST_PATCHVAL; + if ((kp->kp_tramp = kinst_trampoline_alloc(M_WAITOK)) == NULL) { + KINST_LOG("cannot allocate trampoline for %p", instr); + return (ENOMEM); + } + + kinst_instr_dissect(kp); + kinst_probe_create(kp, lf); + } + if (ldxstx_block) + KINST_LOG("warning: unterminated LDX/STX block"); + + return (0); +} + +int +kinst_md_init(void) +{ + struct kinst_cpu_state *ks; + int cpu; + + CPU_FOREACH(cpu) { + ks = DPCPU_PTR(kinst_state); + ks->state = KINST_PROBE_ARMED; + } + + return (0); +} + +void +kinst_md_deinit(void) +{ +} + +/* + * Exclude machine-dependent functions that are not safe-to-trace. + */ +bool +kinst_md_excluded(const char *name) +{ + if (strcmp(name, "handle_el1h_sync") == 0 || + strcmp(name, "do_el1h_sync") == 0) + return (true); + + return (false); +} diff --git a/sys/cddl/dev/kinst/aarch64/kinst_isa.h b/sys/cddl/dev/kinst/aarch64/kinst_isa.h new file mode 100644 index 000000000000..7e1fd8d123e9 --- /dev/null +++ b/sys/cddl/dev/kinst/aarch64/kinst_isa.h @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: CDDL 1.0 + * + * Copyright (c) 2023 The FreeBSD Foundation + * + * This software was developed by Christos Margiolis + * under sponsorship from the FreeBSD Foundation. + */ + +#ifndef _KINST_ISA_H_ +#define _KINST_ISA_H_ + +#define KINST_PATCHVAL DTRACE_PATCHVAL + +/* + * The trampoline contains [instruction, brk]. + */ +#define KINST_TRAMP_SIZE 8 + +typedef uint32_t kinst_patchval_t; + +struct kinst_probe_md { + bool emulate; /* emulate in sw */ +}; + +#endif /* _KINST_ISA_H_ */ diff --git a/sys/modules/dtrace/Makefile b/sys/modules/dtrace/Makefile index e38e2687e8a7..73bbbff80640 100644 --- a/sys/modules/dtrace/Makefile +++ b/sys/modules/dtrace/Makefile @@ -21,7 +21,7 @@ SUBDIR+= fasttrap systrace_linux SUBDIR+= systrace_linux32 SUBDIR+= kinst .endif -.if ${MACHINE_CPUARCH} == "riscv" +.if ${MACHINE_CPUARCH} == "riscv" || ${MACHINE_CPUARCH} == "aarch64" SUBDIR+= kinst .endif .if ${MACHINE_CPUARCH} == "powerpc"