/* $NetBSD: fbt_isa.c,v 1.2 2023/05/22 15:12:54 riastradh Exp $ */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * Portions Copyright 2006-2008 John Birrell jb@freebsd.org * Portions Copyright 2013 Justin Hibbits jhibbits@freebsd.org * Portions Copyright 2013 Howard Su howardsu@freebsd.org * * $FreeBSD: head/sys/cddl/dev/fbt/arm/fbt_isa.c 312378 2017-01-18 13:27:24Z andrew $ * */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include "fbt.h" #define FBT_PUSHM 0xe92d0000 #define FBT_POPM 0xe8bd0000 #define FBT_JUMP 0xea000000 #define FBT_SUBSP 0xe24dd000 #define FBT_ENTRY "entry" #define FBT_RETURN "return" static uint32_t ldinstr(const uint32_t *instr) { #ifdef _ARM_ARCH_BE8 /* big-endian data, big-endian instructions */ return *instr; #else /* little-endian instructions */ return le32toh(*instr); #endif } static void stinstr(uint32_t *instr, uint32_t val) { #ifdef _ARM_ARCH_BE8 /* big-endian data, big-endian instructions */ val = bswap32(val); #endif ktext_write(instr, &val, sizeof(val)); /* write little-endian */ } int fbt_invop(uintptr_t addr, struct trapframe *frame, uintptr_t rval) { solaris_cpu_t *cpu = &solaris_cpu[cpu_number()]; fbt_probe_t *fbt = fbt_probetab[FBT_ADDR2NDX(addr)]; register_t fifthparam; for (; fbt != NULL; fbt = fbt->fbtp_hashnext) { if ((uintptr_t)fbt->fbtp_patchpoint == addr) { if (fbt->fbtp_roffset == 0) { /* Get 5th parameter from stack */ DTRACE_CPUFLAG_SET(CPU_DTRACE_NOFAULT); fifthparam = *(register_t *)frame->tf_svc_sp; DTRACE_CPUFLAG_CLEAR(CPU_DTRACE_NOFAULT | CPU_DTRACE_BADADDR); cpu->cpu_dtrace_caller = frame->tf_svc_lr; dtrace_probe(fbt->fbtp_id, frame->tf_r0, frame->tf_r1, frame->tf_r2, frame->tf_r3, fifthparam); } else { /* XXX set caller */ cpu->cpu_dtrace_caller = 0; dtrace_probe(fbt->fbtp_id, fbt->fbtp_roffset, rval, 0, 0, 0); } cpu->cpu_dtrace_caller = 0; return (fbt->fbtp_rval); } } return (0); } void fbt_patch_tracepoint(fbt_probe_t *fbt, fbt_patchval_t val) { dtrace_icookie_t c; c = dtrace_interrupt_disable(); stinstr(fbt->fbtp_patchpoint, val); dtrace_interrupt_enable(c); } #ifdef __FreeBSD__ int fbt_provide_module_function(linker_file_t lf, int symindx, linker_symval_t *symval, void *opaque) { char *modname = opaque; const char *name = symval->name; fbt_probe_t *fbt, *retfbt; uint32_t *instr, *limit; int popm; if (fbt_excluded(name)) return (0); instr = (uint32_t *)symval->value; limit = (uint32_t *)(symval->value + symval->size); /* * va_arg functions has first instruction of * sub sp, sp, #? */ if ((ldinstr(instr) & 0xfffff000) == FBT_SUBSP) instr++; /* * check if insn is a pushm with LR */ if ((ldinstr(instr) & 0xffff0000) != FBT_PUSHM || (ldinstr(instr) & (1 << LR)) == 0) return (0); fbt = kmem_zalloc(sizeof (fbt_probe_t), KM_SLEEP); fbt->fbtp_name = name; fbt->fbtp_id = dtrace_probe_create(fbt_id, modname, name, FBT_ENTRY, 5, fbt); fbt->fbtp_patchpoint = instr; fbt->fbtp_ctl = lf; fbt->fbtp_loadcnt = lf->loadcnt; fbt->fbtp_savedval = ldinstr(instr); fbt->fbtp_patchval = FBT_BREAKPOINT; fbt->fbtp_rval = DTRACE_INVOP_PUSHM; fbt->fbtp_symindx = symindx; fbt->fbtp_hashnext = fbt_probetab[FBT_ADDR2NDX(instr)]; fbt_probetab[FBT_ADDR2NDX(instr)] = fbt; lf->fbt_nentries++; popm = FBT_POPM | (ldinstr(instr) & 0x3FFF) | 0x8000; retfbt = NULL; again: for (; instr < limit; instr++) { if (ldinstr(instr) == popm) break; else if ((ldinstr(instr) & 0xff000000) == FBT_JUMP) { uint32_t *target, *start; int offset; offset = (ldinstr(instr) & 0xffffff); offset <<= 8; offset /= 64; target = instr + (2 + offset); start = (uint32_t *)symval->value; if (target >= limit || target < start) break; } } if (instr >= limit) return (0); /* * We have a winner! */ fbt = kmem_zalloc(sizeof (fbt_probe_t), KM_SLEEP); fbt->fbtp_name = name; if (retfbt == NULL) { fbt->fbtp_id = dtrace_probe_create(fbt_id, modname, name, FBT_RETURN, 5, fbt); } else { retfbt->fbtp_next = fbt; fbt->fbtp_id = retfbt->fbtp_id; } retfbt = fbt; fbt->fbtp_patchpoint = instr; fbt->fbtp_ctl = lf; fbt->fbtp_loadcnt = lf->loadcnt; fbt->fbtp_symindx = symindx; if ((ldinstr(instr) & 0xff000000) == FBT_JUMP) fbt->fbtp_rval = DTRACE_INVOP_B; else fbt->fbtp_rval = DTRACE_INVOP_POPM; fbt->fbtp_savedval = ldinstr(instr); fbt->fbtp_patchval = FBT_BREAKPOINT; fbt->fbtp_hashnext = fbt_probetab[FBT_ADDR2NDX(instr)]; fbt_probetab[FBT_ADDR2NDX(instr)] = fbt; lf->fbt_nentries++; instr++; goto again; } #endif /* __FreeBSD_ */ #ifdef __NetBSD__ #define FBT_PATCHVAL DTRACE_BREAKPOINT /* entry and return */ #define FBT_BX_LR_P(insn) (((insn) & ~INSN_COND_MASK) == 0x012fff1e) #define FBT_B_LABEL_P(insn) (((insn) & 0xff000000) == 0xea000000) /* entry */ #define FBT_MOV_IP_SP_P(insn) ((insn) == 0xe1a0c00d) /* index=1, add=1, wback=0 */ #define FBT_LDR_IMM_P(insn) (((insn) & 0xfff00000) == 0xe5900000) #define FBT_MOVW_P(insn) (((insn) & 0xfff00000) == 0xe3000000) #define FBT_MOV_IMM_P(insn) (((insn) & 0xffff0000) == 0xe3a00000) #define FBT_CMP_IMM_P(insn) (((insn) & 0xfff00000) == 0xe3500000) #define FBT_PUSH_P(insn) (((insn) & 0xffff0000) == 0xe92d0000) /* return */ /* cond=always, writeback=no, rn=sp and register_list includes pc */ #define FBT_LDM_P(insn) (((insn) & 0x0fff8000) == 0x089d8000) #define FBT_LDMIB_P(insn) (((insn) & 0x0fff8000) == 0x099d8000) #define FBT_MOV_PC_LR_P(insn) (((insn) & ~INSN_COND_MASK) == 0x01a0f00e) /* cond=always, writeback=no, rn=sp and register_list includes lr, but not pc */ #define FBT_LDM_LR_P(insn) (((insn) & 0xffffc000) == 0xe89d4000) #define FBT_LDMIB_LR_P(insn) (((insn) & 0xffffc000) == 0xe99d4000) /* rval = insn | invop_id (overwriting cond with invop ID) */ #define BUILD_RVAL(insn, id) (((insn) & ~INSN_COND_MASK) | __SHIFTIN((id), INSN_COND_MASK)) /* encode cond in the first byte */ #define PATCHVAL_ENCODE_COND(insn) (FBT_PATCHVAL | __SHIFTOUT((insn), INSN_COND_MASK)) int fbt_provide_module_cb(const char *name, int symindx, void *value, uint32_t symsize, int type, void *opaque) { fbt_probe_t *fbt, *retfbt; uint32_t *instr, *limit; bool was_ldm_lr = false; int size; struct fbt_ksyms_arg *fka = opaque; modctl_t *mod = fka->fka_mod; const char *modname = module_name(mod); /* got a function? */ if (ELF_ST_TYPE(type) != STT_FUNC) return 0; if (fbt_excluded(name)) return (0); /* * Exclude some more symbols which can be called from probe context. */ if (strncmp(name, "_spl", 4) == 0 || strcmp(name, "binuptime") == 0 || strcmp(name, "nanouptime") == 0 || strcmp(name, "dosoftints") == 0 || strcmp(name, "fbt_emulate") == 0 || strcmp(name, "undefinedinstruction") == 0 || strncmp(name, "dmt_", 4) == 0 /* omap */ || strncmp(name, "mvsoctmr_", 9) == 0 /* marvell */ ) { return 0; } instr = (uint32_t *) value; limit = (uint32_t *)((uintptr_t)value + symsize); if (!FBT_MOV_IP_SP_P(ldinstr(instr)) && !FBT_BX_LR_P(ldinstr(instr)) && !FBT_MOVW_P(ldinstr(instr)) && !FBT_MOV_IMM_P(ldinstr(instr)) && !FBT_B_LABEL_P(ldinstr(instr)) && !FBT_LDR_IMM_P(ldinstr(instr)) && !FBT_CMP_IMM_P(ldinstr(instr)) && !FBT_PUSH_P(ldinstr(instr)) ) { return 0; } fbt = kmem_zalloc(sizeof (fbt_probe_t), KM_SLEEP); fbt->fbtp_name = name; fbt->fbtp_id = dtrace_probe_create(fbt_id, modname, name, FBT_ENTRY, 5, fbt); fbt->fbtp_patchpoint = instr; fbt->fbtp_ctl = mod; /* fbt->fbtp_loadcnt = lf->loadcnt; */ if (FBT_MOV_IP_SP_P(ldinstr(instr))) { fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr), DTRACE_INVOP_MOV_IP_SP); } else if (FBT_LDR_IMM_P(ldinstr(instr))) { fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr), DTRACE_INVOP_LDR_IMM); } else if (FBT_MOVW_P(ldinstr(instr))) { fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr), DTRACE_INVOP_MOVW); } else if (FBT_MOV_IMM_P(ldinstr(instr))) { fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr), DTRACE_INVOP_MOV_IMM); } else if (FBT_CMP_IMM_P(ldinstr(instr))) { fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr), DTRACE_INVOP_CMP_IMM); } else if (FBT_BX_LR_P(ldinstr(instr))) { fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr), DTRACE_INVOP_BX_LR); } else if (FBT_PUSH_P(ldinstr(instr))) { fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr), DTRACE_INVOP_PUSHM); } else if (FBT_B_LABEL_P(ldinstr(instr))) { fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr), DTRACE_INVOP_B); } else { KASSERT(0); } KASSERTMSG((fbt->fbtp_rval >> 28) != 0, "fbt %p insn 0x%x name %s rval 0x%08x", fbt, ldinstr(instr), name, fbt->fbtp_rval); fbt->fbtp_patchval = PATCHVAL_ENCODE_COND(ldinstr(instr)); fbt->fbtp_savedval = ldinstr(instr); fbt->fbtp_symindx = symindx; fbt->fbtp_hashnext = fbt_probetab[FBT_ADDR2NDX(instr)]; fbt_probetab[FBT_ADDR2NDX(instr)] = fbt; retfbt = NULL; while (instr < limit) { if (instr >= limit) return (0); size = 1; if (!FBT_BX_LR_P(ldinstr(instr)) && !FBT_MOV_PC_LR_P(ldinstr(instr)) && !FBT_LDM_P(ldinstr(instr)) && !FBT_LDMIB_P(ldinstr(instr)) && !(was_ldm_lr && FBT_B_LABEL_P(ldinstr(instr))) ) { if (FBT_LDM_LR_P(ldinstr(instr)) || FBT_LDMIB_LR_P(ldinstr(instr))) was_ldm_lr = true; else was_ldm_lr = false; instr += size; continue; } /* * We have a winner! */ fbt = kmem_zalloc(sizeof (fbt_probe_t), KM_SLEEP); fbt->fbtp_name = name; if (retfbt == NULL) { fbt->fbtp_id = dtrace_probe_create(fbt_id, modname, name, FBT_RETURN, 5, fbt); } else { retfbt->fbtp_next = fbt; fbt->fbtp_id = retfbt->fbtp_id; } retfbt = fbt; fbt->fbtp_patchpoint = instr; fbt->fbtp_ctl = mod; /* fbt->fbtp_loadcnt = lf->loadcnt; */ fbt->fbtp_symindx = symindx; if (FBT_BX_LR_P(ldinstr(instr))) { fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr), DTRACE_INVOP_BX_LR); } else if (FBT_MOV_PC_LR_P(ldinstr(instr))) { fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr), DTRACE_INVOP_MOV_PC_LR); } else if (FBT_LDM_P(ldinstr(instr))) { fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr), DTRACE_INVOP_LDM); } else if (FBT_LDMIB_P(ldinstr(instr))) { fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr), DTRACE_INVOP_POPM); } else if (FBT_B_LABEL_P(ldinstr(instr))) { fbt->fbtp_rval = BUILD_RVAL(ldinstr(instr), DTRACE_INVOP_B); } else { KASSERT(0); } KASSERTMSG((fbt->fbtp_rval >> 28) != 0, "fbt %p name %s rval 0x%08x", fbt, name, fbt->fbtp_rval); fbt->fbtp_roffset = (uintptr_t)(instr - (uint32_t *) value); fbt->fbtp_patchval = PATCHVAL_ENCODE_COND(ldinstr(instr)); fbt->fbtp_savedval = ldinstr(instr); fbt->fbtp_hashnext = fbt_probetab[FBT_ADDR2NDX(instr)]; fbt_probetab[FBT_ADDR2NDX(instr)] = fbt; instr += size; was_ldm_lr = false; } return 0; } #endif /* __NetBSD__ */