1183 lines
30 KiB
C
1183 lines
30 KiB
C
/*
|
|
* Copyright (c) 2007-2022 Apple Inc. All rights reserved.
|
|
*/
|
|
/*
|
|
* CDDL HEADER START
|
|
*
|
|
* The contents of this file are subject to the terms of the
|
|
* Common Development and Distribution License, Version 1.0 only
|
|
* (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
|
|
*/
|
|
/*
|
|
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
|
|
* Use is subject to license terms.
|
|
*/
|
|
|
|
#include <sys/fasttrap_isa.h>
|
|
#include <sys/fasttrap_impl.h>
|
|
#include <sys/dtrace.h>
|
|
#include <sys/dtrace_impl.h>
|
|
#include <kern/task.h>
|
|
#include <arm/thread.h>
|
|
|
|
#include <sys/dtrace_ptss.h>
|
|
|
|
#if __has_include(<ptrauth.h>)
|
|
#include <ptrauth.h>
|
|
#endif
|
|
|
|
extern dtrace_id_t dtrace_probeid_error;
|
|
|
|
/* Solaris proc_t is the struct. Darwin's proc_t is a pointer to it. */
|
|
#define proc_t struct proc /* Steer clear of the Darwin typedef for proc_t */
|
|
|
|
extern uint8_t dtrace_decode_arm64(uint32_t instr);
|
|
|
|
#define IS_ARM64_NOP(x) ((x) == 0xD503201F)
|
|
/* Marker for is-enabled probes */
|
|
#define IS_ARM64_IS_ENABLED(x) ((x) == 0xD2800000)
|
|
|
|
int
|
|
fasttrap_tracepoint_init(proc_t *p, fasttrap_tracepoint_t *tp,
|
|
user_addr_t pc, fasttrap_probe_type_t type)
|
|
{
|
|
#pragma unused(type)
|
|
uint32_t instr = 0;
|
|
|
|
/*
|
|
* Read the instruction at the given address out of the process's
|
|
* address space. We don't have to worry about a debugger
|
|
* changing this instruction before we overwrite it with our trap
|
|
* instruction since P_PR_LOCK is set. Since instructions can span
|
|
* pages, we potentially read the instruction in two parts. If the
|
|
* second part fails, we just zero out that part of the instruction.
|
|
*/
|
|
/*
|
|
* APPLE NOTE: Of course, we do not have a P_PR_LOCK, so this is racey...
|
|
*/
|
|
|
|
if (uread(p, &instr, 4, pc) != 0) {
|
|
return -1;
|
|
}
|
|
|
|
tp->ftt_instr = instr;
|
|
|
|
if (tp->ftt_fntype != FASTTRAP_FN_DONE_INIT) {
|
|
switch (tp->ftt_fntype) {
|
|
case FASTTRAP_FN_UNKNOWN:
|
|
case FASTTRAP_FN_ARM64:
|
|
case FASTTRAP_FN_ARM64_32:
|
|
/*
|
|
* On arm64 there is no distinction between
|
|
* arm vs. thumb mode instruction types.
|
|
*/
|
|
tp->ftt_fntype = FASTTRAP_FN_DONE_INIT;
|
|
break;
|
|
|
|
case FASTTRAP_FN_USDT:
|
|
if (IS_ARM64_NOP(instr) || IS_ARM64_IS_ENABLED(instr)) {
|
|
tp->ftt_fntype = FASTTRAP_FN_DONE_INIT;
|
|
} else {
|
|
/*
|
|
* Shouldn't reach here - this means we don't
|
|
* recognize the instruction at one of the
|
|
* USDT probe locations
|
|
*/
|
|
return -1;
|
|
}
|
|
|
|
break;
|
|
|
|
case FASTTRAP_FN_ARM:
|
|
case FASTTRAP_FN_THUMB:
|
|
default:
|
|
/*
|
|
* If we get an arm or thumb mode type
|
|
* then we are clearly in the wrong path.
|
|
*/
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
tp->ftt_type = dtrace_decode_arm64(instr);
|
|
|
|
if (tp->ftt_type == FASTTRAP_T_ARM64_EXCLUSIVE_MEM) {
|
|
kprintf("Detected attempt to place DTrace probe on exclusive memory instruction (pc = 0x%llx); refusing to trace (or exclusive operation could never succeed).\n", pc);
|
|
tp->ftt_type = FASTTRAP_T_INV;
|
|
return -1;
|
|
}
|
|
|
|
if (tp->ftt_type == FASTTRAP_T_INV) {
|
|
/* This is an instruction we either don't recognize or can't instrument */
|
|
printf("dtrace: fasttrap init64: Unrecognized instruction: %08x at %08llx\n", instr, pc);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
fasttrap_tracepoint_install(proc_t *p, fasttrap_tracepoint_t *tp)
|
|
{
|
|
uint32_t instr;
|
|
int size;
|
|
|
|
if (proc_is64bit_data(p)) {
|
|
size = 4;
|
|
instr = FASTTRAP_ARM64_INSTR;
|
|
} else {
|
|
return -1;
|
|
}
|
|
|
|
if (uwrite(p, &instr, size, tp->ftt_pc) != 0) {
|
|
return -1;
|
|
}
|
|
|
|
tp->ftt_installed = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
fasttrap_tracepoint_remove(proc_t *p, fasttrap_tracepoint_t *tp)
|
|
{
|
|
uint32_t instr;
|
|
int size = 4;
|
|
|
|
if (proc_is64bit_data(p)) {
|
|
/*
|
|
* Distinguish between read or write failures and a changed
|
|
* instruction.
|
|
*/
|
|
if (uread(p, &instr, size, tp->ftt_pc) != 0) {
|
|
goto end;
|
|
}
|
|
|
|
if (instr != FASTTRAP_ARM64_INSTR) {
|
|
goto end;
|
|
}
|
|
} else {
|
|
return -1;
|
|
}
|
|
|
|
if (uwrite(p, &tp->ftt_instr, size, tp->ftt_pc) != 0) {
|
|
return -1;
|
|
}
|
|
|
|
end:
|
|
tp->ftt_installed = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
fasttrap_return_common(proc_t *p, arm_saved_state_t *regs, user_addr_t pc, user_addr_t new_pc)
|
|
{
|
|
pid_t pid = proc_getpid(p);
|
|
fasttrap_tracepoint_t *tp;
|
|
fasttrap_bucket_t *bucket;
|
|
fasttrap_id_t *id;
|
|
lck_mtx_t *pid_mtx;
|
|
int retire_tp = 1;
|
|
pid_mtx = &cpu_core[CPU->cpu_id].cpuc_pid_lock;
|
|
lck_mtx_lock(pid_mtx);
|
|
bucket = &fasttrap_tpoints.fth_table[FASTTRAP_TPOINTS_INDEX(pid, pc)];
|
|
|
|
for (tp = bucket->ftb_data; tp != NULL; tp = tp->ftt_next) {
|
|
if (pid == tp->ftt_pid && pc == tp->ftt_pc &&
|
|
tp->ftt_proc->ftpc_acount != 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Don't sweat it if we can't find the tracepoint again; unlike
|
|
* when we're in fasttrap_pid_probe(), finding the tracepoint here
|
|
* is not essential to the correct execution of the process.
|
|
*/
|
|
if (tp == NULL) {
|
|
lck_mtx_unlock(pid_mtx);
|
|
return;
|
|
}
|
|
|
|
for (id = tp->ftt_retids; id != NULL; id = id->fti_next) {
|
|
fasttrap_probe_t *probe = id->fti_probe;
|
|
/* ARM64_TODO - check for FASTTRAP_T_RET */
|
|
if ((tp->ftt_type != FASTTRAP_T_ARM64_RET || tp->ftt_type != FASTTRAP_T_ARM64_RETAB) &&
|
|
new_pc - probe->ftp_faddr < probe->ftp_fsize) {
|
|
continue;
|
|
}
|
|
if (probe->ftp_prov->ftp_provider_type == DTFTP_PROVIDER_ONESHOT) {
|
|
if (os_atomic_xchg(&probe->ftp_triggered, 1, relaxed)) {
|
|
/* already triggered */
|
|
continue;
|
|
}
|
|
}
|
|
/*
|
|
* If we have at least one probe associated that
|
|
* is not a oneshot probe, don't remove the
|
|
* tracepoint
|
|
*/
|
|
else {
|
|
retire_tp = 0;
|
|
}
|
|
|
|
#if defined(XNU_TARGET_OS_OSX)
|
|
if (ISSET(current_proc()->p_lflag, P_LNOATTACH)) {
|
|
dtrace_probe(dtrace_probeid_error, 0 /* state */, id->fti_probe->ftp_id,
|
|
1 /* ndx */, -1 /* offset */, DTRACEFLT_UPRIV);
|
|
#else
|
|
if (FALSE) {
|
|
#endif /* defined(XNU_TARGET_OS_OSX) */
|
|
} else {
|
|
dtrace_probe(probe->ftp_id,
|
|
pc - id->fti_probe->ftp_faddr,
|
|
saved_state64(regs)->x[0], 0, 0, 0);
|
|
}
|
|
}
|
|
if (retire_tp) {
|
|
fasttrap_tracepoint_retire(p, tp);
|
|
}
|
|
|
|
lck_mtx_unlock(pid_mtx);
|
|
}
|
|
|
|
#if DEBUG
|
|
__dead2
|
|
#endif
|
|
static void
|
|
fasttrap_sigsegv(proc_t *p, uthread_t t, user_addr_t addr, arm_saved_state_t *regs)
|
|
{
|
|
/* TODO: This function isn't implemented yet. In debug mode, panic the system to
|
|
* find out why we're hitting this point. In other modes, kill the process.
|
|
*/
|
|
#if DEBUG
|
|
#pragma unused(p,t,addr,arm_saved_state)
|
|
panic("fasttrap: sigsegv not yet implemented");
|
|
#else
|
|
#pragma unused(p,t,addr)
|
|
/* Kill the process */
|
|
set_saved_state_pc(regs, 0);
|
|
#endif
|
|
|
|
#if 0
|
|
proc_lock(p);
|
|
|
|
/* Set fault address and mark signal */
|
|
t->uu_code = addr;
|
|
t->uu_siglist |= sigmask(SIGSEGV);
|
|
|
|
/*
|
|
* XXX These two line may be redundant; if not, then we need
|
|
* XXX to potentially set the data address in the machine
|
|
* XXX specific thread state structure to indicate the address.
|
|
*/
|
|
t->uu_exception = KERN_INVALID_ADDRESS; /* SIGSEGV */
|
|
t->uu_subcode = 0; /* XXX pad */
|
|
|
|
proc_unlock(p);
|
|
|
|
/* raise signal */
|
|
signal_setast(get_machthread(t));
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
fasttrap_usdt_args64(fasttrap_probe_t *probe, arm_saved_state64_t *regs64, int argc,
|
|
uint64_t *argv)
|
|
{
|
|
int i, x, cap = MIN(argc, probe->ftp_nargs);
|
|
|
|
for (i = 0; i < cap; i++) {
|
|
x = probe->ftp_argmap[i];
|
|
|
|
/* Up to 8 args are passed in registers on arm64 */
|
|
if (x < 8) {
|
|
argv[i] = regs64->x[x];
|
|
} else {
|
|
fasttrap_fuword64_noerr(regs64->sp + (x - 8) * sizeof(uint64_t), &argv[i]);
|
|
}
|
|
}
|
|
|
|
for (; i < argc; i++) {
|
|
argv[i] = 0;
|
|
}
|
|
}
|
|
|
|
static int
|
|
condition_true(int cond, int cpsr)
|
|
{
|
|
int taken = 0;
|
|
int zf = (cpsr & PSR64_Z) ? 1 : 0,
|
|
nf = (cpsr & PSR64_N) ? 1 : 0,
|
|
cf = (cpsr & PSR64_C) ? 1 : 0,
|
|
vf = (cpsr & PSR64_V) ? 1 : 0;
|
|
|
|
switch (cond) {
|
|
case 0: taken = zf; break;
|
|
case 1: taken = !zf; break;
|
|
case 2: taken = cf; break;
|
|
case 3: taken = !cf; break;
|
|
case 4: taken = nf; break;
|
|
case 5: taken = !nf; break;
|
|
case 6: taken = vf; break;
|
|
case 7: taken = !vf; break;
|
|
case 8: taken = (cf && !zf); break;
|
|
case 9: taken = (!cf || zf); break;
|
|
case 10: taken = (nf == vf); break;
|
|
case 11: taken = (nf != vf); break;
|
|
case 12: taken = (!zf && (nf == vf)); break;
|
|
case 13: taken = (zf || (nf != vf)); break;
|
|
case 14: taken = 1; break;
|
|
case 15: taken = 1; break; /* always "true" for ARM, unpredictable for THUMB. */
|
|
}
|
|
|
|
return taken;
|
|
}
|
|
|
|
/*
|
|
* Copy out an instruction for execution in userland.
|
|
* Trap back to kernel to handle return to original flow of execution, because
|
|
* direct branches don't have sufficient range (+/- 128MB) and we
|
|
* cannot clobber a GPR. Note that we have to specially handle PC-rel loads/stores
|
|
* as well, which have range +/- 1MB (convert to an indirect load). Instruction buffer
|
|
* layout:
|
|
*
|
|
* [ Thunked instruction sequence ]
|
|
* [ Trap for return to original code and return probe handling ]
|
|
*
|
|
* This *does* make it impossible for an ldxr/stxr pair to succeed if we trace on or between
|
|
* them... may need to get fancy at some point.
|
|
*/
|
|
static void
|
|
fasttrap_pid_probe_thunk_instr64(arm_saved_state_t *state, fasttrap_tracepoint_t *tp, proc_t *p, uthread_t uthread,
|
|
const uint32_t *instructions, uint32_t num_instrs, user_addr_t *pc_out)
|
|
{
|
|
uint32_t local_scratch[8];
|
|
user_addr_t pc = get_saved_state_pc(state);
|
|
user_addr_t user_scratch_area;
|
|
|
|
assert(num_instrs < 8);
|
|
|
|
bcopy(instructions, local_scratch, num_instrs * sizeof(uint32_t));
|
|
local_scratch[num_instrs] = FASTTRAP_ARM64_RET_INSTR;
|
|
|
|
uthread->t_dtrace_astpc = uthread->t_dtrace_scrpc = uthread->t_dtrace_scratch->addr;
|
|
user_scratch_area = uthread->t_dtrace_scratch->write_addr;
|
|
|
|
if (user_scratch_area == (user_addr_t)0) {
|
|
fasttrap_sigtrap(p, uthread, pc); // Should be killing target proc
|
|
*pc_out = pc;
|
|
return;
|
|
}
|
|
|
|
if (uwrite(p, local_scratch, (num_instrs + 1) * sizeof(uint32_t), user_scratch_area) != KERN_SUCCESS) {
|
|
fasttrap_sigtrap(p, uthread, pc);
|
|
*pc_out = pc;
|
|
return;
|
|
}
|
|
|
|
/* We're stepping (come back to kernel to adjust PC for return to regular code). */
|
|
uthread->t_dtrace_step = 1;
|
|
|
|
/* We may or may not be about to run a return probe (but we wouldn't thunk ret lr)*/
|
|
uthread->t_dtrace_ret = (tp->ftt_retids != NULL);
|
|
assert(tp->ftt_type != FASTTRAP_T_ARM64_RET);
|
|
assert(tp->ftt_type != FASTTRAP_T_ARM64_RETAB);
|
|
|
|
/* Set address of instruction we've patched */
|
|
uthread->t_dtrace_pc = pc;
|
|
|
|
/* Any branch would be emulated, next instruction should be one ahead */
|
|
uthread->t_dtrace_npc = pc + 4;
|
|
|
|
/* We are certainly handling a probe */
|
|
uthread->t_dtrace_on = 1;
|
|
|
|
/* Let's jump to the scratch area */
|
|
*pc_out = uthread->t_dtrace_scratch->addr;
|
|
}
|
|
|
|
/*
|
|
* Sign-extend bit "sign_bit_index" out to bit 64.
|
|
*/
|
|
static int64_t
|
|
sign_extend(int64_t input, uint32_t sign_bit_index)
|
|
{
|
|
assert(sign_bit_index < 63);
|
|
if (input & (1ULL << sign_bit_index)) {
|
|
/* All 1's & ~[1's from 0 to sign bit] */
|
|
input |= ((~0ULL) & ~((1ULL << (sign_bit_index + 1)) - 1ULL));
|
|
}
|
|
|
|
return input;
|
|
}
|
|
|
|
/*
|
|
* Handle xzr vs. sp, fp, lr, etc. Will *not* read the SP.
|
|
*/
|
|
static uint64_t
|
|
get_saved_state64_regno(arm_saved_state64_t *regs64, uint32_t regno, int use_xzr)
|
|
{
|
|
/* Set PC to register value */
|
|
switch (regno) {
|
|
case 29:
|
|
return regs64->fp;
|
|
case 30:
|
|
return regs64->lr;
|
|
case 31:
|
|
/* xzr */
|
|
if (use_xzr) {
|
|
return 0;
|
|
} else {
|
|
return regs64->sp;
|
|
}
|
|
default:
|
|
return regs64->x[regno];
|
|
}
|
|
}
|
|
|
|
static void
|
|
set_saved_state_regno(arm_saved_state_t *state, uint32_t regno, int use_xzr, register_t value)
|
|
{
|
|
/* Set PC to register value */
|
|
switch (regno) {
|
|
case 29:
|
|
set_saved_state_fp(state, value);
|
|
break;
|
|
case 30:
|
|
set_saved_state_lr(state, value);
|
|
break;
|
|
case 31:
|
|
if (!use_xzr) {
|
|
set_saved_state_sp(state, value);
|
|
}
|
|
break;
|
|
default:
|
|
set_saved_state_reg(state, regno, value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Common operation: extract sign-extended PC offset from instruction
|
|
* Left-shifts result by two bits.
|
|
*/
|
|
static uint64_t
|
|
extract_address_literal_sign_extended(uint32_t instr, uint32_t base, uint32_t numbits)
|
|
{
|
|
uint64_t offset;
|
|
|
|
offset = (instr >> base) & ((1 << numbits) - 1);
|
|
offset = sign_extend(offset, numbits - 1);
|
|
offset = offset << 2;
|
|
|
|
return offset;
|
|
}
|
|
|
|
static void
|
|
do_cbz_cnbz(arm_saved_state64_t *regs64, uint32_t regwidth, uint32_t instr, int is_cbz, user_addr_t *pc_out)
|
|
{
|
|
uint32_t regno;
|
|
uint64_t regval;
|
|
uint64_t offset;
|
|
|
|
/* Extract register */
|
|
regno = (instr & 0x1f);
|
|
assert(regno <= 31);
|
|
regval = get_saved_state64_regno(regs64, regno, 1);
|
|
|
|
/* Control for size */
|
|
if (regwidth == 32) {
|
|
regval &= 0xFFFFFFFFULL;
|
|
}
|
|
|
|
/* Extract offset */
|
|
offset = extract_address_literal_sign_extended(instr, 5, 19);
|
|
|
|
/* Do test */
|
|
if ((is_cbz && regval == 0) || ((!is_cbz) && regval != 0)) {
|
|
/* Set PC from label */
|
|
*pc_out = regs64->pc + offset;
|
|
} else {
|
|
/* Advance PC */
|
|
*pc_out = regs64->pc + 4;
|
|
}
|
|
}
|
|
|
|
static void
|
|
do_tbz_tbnz(arm_saved_state64_t *regs64, uint32_t instr, int is_tbz, user_addr_t *pc_out)
|
|
{
|
|
uint64_t offset, regval;
|
|
uint32_t bit_index, b5, b40, regno, bit_set;
|
|
|
|
/* Compute offset */
|
|
offset = extract_address_literal_sign_extended(instr, 5, 14);
|
|
|
|
/* Extract bit index */
|
|
b5 = (instr >> 31);
|
|
b40 = ((instr >> 19) & 0x1f);
|
|
bit_index = (b5 << 5) | b40;
|
|
assert(bit_index <= 63);
|
|
|
|
/* Extract register */
|
|
regno = (instr & 0x1f);
|
|
assert(regno <= 31);
|
|
regval = get_saved_state64_regno(regs64, regno, 1);
|
|
|
|
/* Test bit */
|
|
bit_set = ((regval & (1 << bit_index)) != 0);
|
|
|
|
if ((is_tbz && (!bit_set)) || ((!is_tbz) && bit_set)) {
|
|
/* Branch: unsigned addition so overflow defined */
|
|
*pc_out = regs64->pc + offset;
|
|
} else {
|
|
/* Advance PC */
|
|
*pc_out = regs64->pc + 4;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
fasttrap_pid_probe_handle_patched_instr64(arm_saved_state_t *state, fasttrap_tracepoint_t *tp __unused, uthread_t uthread,
|
|
proc_t *p, uint_t is_enabled, int *was_simulated)
|
|
{
|
|
thread_t th = get_machthread(uthread);
|
|
int res1, res2;
|
|
arm_saved_state64_t *regs64 = saved_state64(state);
|
|
uint32_t instr = tp->ftt_instr;
|
|
user_addr_t new_pc = 0;
|
|
|
|
/* Neon state should be threaded throw, but hack it until we have better arm/arm64 integration */
|
|
arm_neon_saved_state64_t *ns64 = &(get_user_neon_regs(th)->ns_64);
|
|
|
|
/* is-enabled probe: set x0 to 1 and step forwards */
|
|
if (is_enabled) {
|
|
regs64->x[0] = 1;
|
|
add_saved_state_pc(state, 4);
|
|
return;
|
|
}
|
|
|
|
/* For USDT probes, bypass all the emulation logic for the nop instruction */
|
|
if (IS_ARM64_NOP(tp->ftt_instr)) {
|
|
add_saved_state_pc(state, 4);
|
|
return;
|
|
}
|
|
|
|
|
|
/* Only one of many cases in the switch doesn't simulate */
|
|
switch (tp->ftt_type) {
|
|
/*
|
|
* Function entry: emulate for speed.
|
|
* stp fp, lr, [sp, #-16]!
|
|
*/
|
|
case FASTTRAP_T_ARM64_STANDARD_FUNCTION_ENTRY:
|
|
{
|
|
/* Store values to stack */
|
|
res1 = fasttrap_suword64(regs64->sp - 16, regs64->fp);
|
|
res2 = fasttrap_suword64(regs64->sp - 8, regs64->lr);
|
|
if (res1 != 0 || res2 != 0) {
|
|
fasttrap_sigsegv(p, uthread, regs64->sp - (res1 ? 16 : 8), state);
|
|
#ifndef DEBUG
|
|
new_pc = regs64->pc; /* Bit of a hack */
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
/* Move stack pointer */
|
|
regs64->sp -= 16;
|
|
|
|
/* Move PC forward */
|
|
new_pc = regs64->pc + 4;
|
|
*was_simulated = 1;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* PC-relative loads/stores: emulate for correctness.
|
|
* All loads are 32bits or greater (no need to handle byte or halfword accesses).
|
|
* LDR Wt, addr
|
|
* LDR Xt, addr
|
|
* LDRSW Xt, addr
|
|
*
|
|
* LDR St, addr
|
|
* LDR Dt, addr
|
|
* LDR Qt, addr
|
|
* PRFM label -> becomes a NOP
|
|
*/
|
|
case FASTTRAP_T_ARM64_LDR_S_PC_REL:
|
|
case FASTTRAP_T_ARM64_LDR_W_PC_REL:
|
|
case FASTTRAP_T_ARM64_LDR_D_PC_REL:
|
|
case FASTTRAP_T_ARM64_LDR_X_PC_REL:
|
|
case FASTTRAP_T_ARM64_LDR_Q_PC_REL:
|
|
case FASTTRAP_T_ARM64_LDRSW_PC_REL:
|
|
{
|
|
uint64_t offset;
|
|
uint32_t valsize, regno;
|
|
user_addr_t address;
|
|
union {
|
|
uint32_t val32;
|
|
uint64_t val64;
|
|
uint128_t val128;
|
|
} value;
|
|
|
|
/* Extract 19-bit offset, add to pc */
|
|
offset = extract_address_literal_sign_extended(instr, 5, 19);
|
|
address = regs64->pc + offset;
|
|
|
|
/* Extract destination register */
|
|
regno = (instr & 0x1f);
|
|
assert(regno <= 31);
|
|
|
|
/* Read value of desired size from memory */
|
|
switch (tp->ftt_type) {
|
|
case FASTTRAP_T_ARM64_LDR_S_PC_REL:
|
|
case FASTTRAP_T_ARM64_LDR_W_PC_REL:
|
|
case FASTTRAP_T_ARM64_LDRSW_PC_REL:
|
|
valsize = 4;
|
|
break;
|
|
case FASTTRAP_T_ARM64_LDR_D_PC_REL:
|
|
case FASTTRAP_T_ARM64_LDR_X_PC_REL:
|
|
valsize = 8;
|
|
break;
|
|
case FASTTRAP_T_ARM64_LDR_Q_PC_REL:
|
|
valsize = 16;
|
|
break;
|
|
default:
|
|
panic("Should never get here!");
|
|
valsize = -1;
|
|
break;
|
|
}
|
|
|
|
if (copyin(address, &value, valsize) != 0) {
|
|
fasttrap_sigsegv(p, uthread, address, state);
|
|
#ifndef DEBUG
|
|
new_pc = regs64->pc; /* Bit of a hack, we know about update in fasttrap_sigsegv() */
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
/* Stash in correct register slot */
|
|
switch (tp->ftt_type) {
|
|
case FASTTRAP_T_ARM64_LDR_W_PC_REL:
|
|
set_saved_state_regno(state, regno, 1, value.val32);
|
|
break;
|
|
case FASTTRAP_T_ARM64_LDRSW_PC_REL:
|
|
set_saved_state_regno(state, regno, 1, sign_extend(value.val32, 31));
|
|
break;
|
|
case FASTTRAP_T_ARM64_LDR_X_PC_REL:
|
|
set_saved_state_regno(state, regno, 1, value.val64);
|
|
break;
|
|
case FASTTRAP_T_ARM64_LDR_S_PC_REL:
|
|
ns64->v.s[regno][0] = value.val32;
|
|
break;
|
|
case FASTTRAP_T_ARM64_LDR_D_PC_REL:
|
|
ns64->v.d[regno][0] = value.val64;
|
|
break;
|
|
case FASTTRAP_T_ARM64_LDR_Q_PC_REL:
|
|
ns64->v.q[regno] = value.val128;
|
|
break;
|
|
default:
|
|
panic("Should never get here!");
|
|
}
|
|
|
|
|
|
/* Move PC forward */
|
|
new_pc = regs64->pc + 4;
|
|
*was_simulated = 1;
|
|
break;
|
|
}
|
|
|
|
case FASTTRAP_T_ARM64_PRFM:
|
|
{
|
|
/* Becomes a NOP (architecturally permitted). Just move PC forward */
|
|
new_pc = regs64->pc + 4;
|
|
*was_simulated = 1;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* End explicit memory accesses.
|
|
*/
|
|
|
|
/*
|
|
* Branches: parse condition codes if needed, emulate for correctness and
|
|
* in the case of the indirect branches, convenience
|
|
* B.cond
|
|
* CBNZ Wn, label
|
|
* CBNZ Xn, label
|
|
* CBZ Wn, label
|
|
* CBZ Xn, label
|
|
* TBNZ, Xn|Wn, #uimm16, label
|
|
* TBZ, Xn|Wn, #uimm16, label
|
|
*
|
|
* B label
|
|
* BL label
|
|
*
|
|
* BLR Xm
|
|
* BR Xm
|
|
* RET Xm
|
|
*/
|
|
case FASTTRAP_T_ARM64_B_COND:
|
|
{
|
|
int cond;
|
|
|
|
/* Extract condition code */
|
|
cond = (instr & 0xf);
|
|
|
|
/* Determine if it passes */
|
|
if (condition_true(cond, regs64->cpsr)) {
|
|
uint64_t offset;
|
|
|
|
/* Extract 19-bit target offset, add to PC */
|
|
offset = extract_address_literal_sign_extended(instr, 5, 19);
|
|
new_pc = regs64->pc + offset;
|
|
} else {
|
|
/* Move forwards */
|
|
new_pc = regs64->pc + 4;
|
|
}
|
|
|
|
*was_simulated = 1;
|
|
break;
|
|
}
|
|
|
|
case FASTTRAP_T_ARM64_CBNZ_W:
|
|
{
|
|
do_cbz_cnbz(regs64, 32, instr, 0, &new_pc);
|
|
*was_simulated = 1;
|
|
break;
|
|
}
|
|
case FASTTRAP_T_ARM64_CBNZ_X:
|
|
{
|
|
do_cbz_cnbz(regs64, 64, instr, 0, &new_pc);
|
|
*was_simulated = 1;
|
|
break;
|
|
}
|
|
case FASTTRAP_T_ARM64_CBZ_W:
|
|
{
|
|
do_cbz_cnbz(regs64, 32, instr, 1, &new_pc);
|
|
*was_simulated = 1;
|
|
break;
|
|
}
|
|
case FASTTRAP_T_ARM64_CBZ_X:
|
|
{
|
|
do_cbz_cnbz(regs64, 64, instr, 1, &new_pc);
|
|
*was_simulated = 1;
|
|
break;
|
|
}
|
|
|
|
case FASTTRAP_T_ARM64_TBNZ:
|
|
{
|
|
do_tbz_tbnz(regs64, instr, 0, &new_pc);
|
|
*was_simulated = 1;
|
|
break;
|
|
}
|
|
case FASTTRAP_T_ARM64_TBZ:
|
|
{
|
|
do_tbz_tbnz(regs64, instr, 1, &new_pc);
|
|
*was_simulated = 1;
|
|
break;
|
|
}
|
|
case FASTTRAP_T_ARM64_B:
|
|
case FASTTRAP_T_ARM64_BL:
|
|
{
|
|
uint64_t offset;
|
|
|
|
/* Extract offset from instruction */
|
|
offset = extract_address_literal_sign_extended(instr, 0, 26);
|
|
|
|
/* Update LR if appropriate */
|
|
if (tp->ftt_type == FASTTRAP_T_ARM64_BL) {
|
|
set_saved_state_lr(state, regs64->pc + 4);
|
|
}
|
|
|
|
/* Compute PC (unsigned addition for defined overflow) */
|
|
new_pc = regs64->pc + offset;
|
|
*was_simulated = 1;
|
|
break;
|
|
}
|
|
|
|
case FASTTRAP_T_ARM64_BLR:
|
|
case FASTTRAP_T_ARM64_BR:
|
|
{
|
|
uint32_t regno;
|
|
|
|
/* Extract register from instruction */
|
|
regno = ((instr >> 5) & 0x1f);
|
|
assert(regno <= 31);
|
|
|
|
/* Update LR if appropriate */
|
|
if (tp->ftt_type == FASTTRAP_T_ARM64_BLR) {
|
|
set_saved_state_lr(state, regs64->pc + 4);
|
|
}
|
|
|
|
/* Update PC in saved state */
|
|
new_pc = get_saved_state64_regno(regs64, regno, 1);
|
|
*was_simulated = 1;
|
|
break;
|
|
}
|
|
|
|
case FASTTRAP_T_ARM64_RET:
|
|
{
|
|
/* Extract register */
|
|
unsigned regno = ((instr >> 5) & 0x1f);
|
|
assert(regno <= 31);
|
|
|
|
/* Set PC to register value (xzr, not sp) */
|
|
new_pc = get_saved_state64_regno(regs64, regno, 1);
|
|
|
|
*was_simulated = 1;
|
|
break;
|
|
}
|
|
case FASTTRAP_T_ARM64_RETAB:
|
|
{
|
|
/* Set PC to register value (xzr, not sp) */
|
|
new_pc = get_saved_state64_regno(regs64, 30, 1);
|
|
#if __has_feature(ptrauth_calls)
|
|
new_pc = (user_addr_t) ptrauth_strip((void *)new_pc, ptrauth_key_return_address);
|
|
#endif
|
|
|
|
*was_simulated = 1;
|
|
break;
|
|
}
|
|
/*
|
|
* End branches.
|
|
*/
|
|
|
|
/*
|
|
* Address calculations: emulate for correctness.
|
|
*
|
|
* ADRP Xd, label
|
|
* ADR Xd, label
|
|
*/
|
|
case FASTTRAP_T_ARM64_ADRP:
|
|
case FASTTRAP_T_ARM64_ADR:
|
|
{
|
|
uint64_t immhi, immlo, offset, result;
|
|
uint32_t regno;
|
|
|
|
/* Extract destination register */
|
|
regno = (instr & 0x1f);
|
|
assert(regno <= 31);
|
|
|
|
/* Extract offset */
|
|
immhi = ((instr & 0x00ffffe0) >> 5); /* bits [23,5]: 19 bits */
|
|
immlo = ((instr & 0x60000000) >> 29); /* bits [30,29]: 2 bits */
|
|
|
|
/* Add to PC. Use unsigned addition so that overflow wraps (rather than being undefined). */
|
|
if (tp->ftt_type == FASTTRAP_T_ARM64_ADRP) {
|
|
offset = (immhi << 14) | (immlo << 12); /* Concatenate bits into [32,12]*/
|
|
offset = sign_extend(offset, 32); /* Sign extend from bit 32 */
|
|
result = (regs64->pc & ~0xfffULL) + offset; /* And add to page of current pc */
|
|
} else {
|
|
assert(tp->ftt_type == FASTTRAP_T_ARM64_ADR);
|
|
offset = (immhi << 2) | immlo; /* Concatenate bits into [20,0] */
|
|
offset = sign_extend(offset, 20); /* Sign-extend */
|
|
result = regs64->pc + offset; /* And add to page of current pc */
|
|
}
|
|
|
|
/* xzr, not sp */
|
|
set_saved_state_regno(state, regno, 1, result);
|
|
|
|
/* Move PC forward */
|
|
new_pc = regs64->pc + 4;
|
|
*was_simulated = 1;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* End address calculations.
|
|
*/
|
|
|
|
/*
|
|
* Everything else: thunk to userland
|
|
*/
|
|
case FASTTRAP_T_COMMON:
|
|
{
|
|
fasttrap_pid_probe_thunk_instr64(state, tp, p, uthread, &tp->ftt_instr, 1, &new_pc);
|
|
*was_simulated = 0;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
panic("An instruction DTrace doesn't expect: %d", tp->ftt_type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
set_saved_state_pc(state, new_pc);
|
|
return;
|
|
}
|
|
|
|
int
|
|
fasttrap_pid_probe(arm_saved_state_t *state)
|
|
{
|
|
proc_t *p = current_proc();
|
|
fasttrap_bucket_t *bucket;
|
|
lck_mtx_t *pid_mtx;
|
|
fasttrap_tracepoint_t *tp, tp_local;
|
|
pid_t pid;
|
|
dtrace_icookie_t cookie;
|
|
uint_t is_enabled = 0;
|
|
int was_simulated, retire_tp = 1;
|
|
|
|
uint64_t pc = get_saved_state_pc(state);
|
|
|
|
assert(is_saved_state64(state));
|
|
|
|
uthread_t uthread = current_uthread();
|
|
|
|
/*
|
|
* It's possible that a user (in a veritable orgy of bad planning)
|
|
* could redirect this thread's flow of control before it reached the
|
|
* return probe fasttrap. In this case we need to kill the process
|
|
* since it's in a unrecoverable state.
|
|
*/
|
|
if (uthread->t_dtrace_step) {
|
|
ASSERT(uthread->t_dtrace_on);
|
|
fasttrap_sigtrap(p, uthread, (user_addr_t)pc);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Clear all user tracing flags.
|
|
*/
|
|
uthread->t_dtrace_ft = 0;
|
|
uthread->t_dtrace_pc = 0;
|
|
uthread->t_dtrace_npc = 0;
|
|
uthread->t_dtrace_scrpc = 0;
|
|
uthread->t_dtrace_astpc = 0;
|
|
uthread->t_dtrace_reg = 0;
|
|
|
|
|
|
pid = proc_getpid(p);
|
|
pid_mtx = &cpu_core[CPU->cpu_id].cpuc_pid_lock;
|
|
lck_mtx_lock(pid_mtx);
|
|
bucket = &fasttrap_tpoints.fth_table[FASTTRAP_TPOINTS_INDEX(pid, pc)];
|
|
|
|
/*
|
|
* Lookup the tracepoint that the process just hit.
|
|
*/
|
|
for (tp = bucket->ftb_data; tp != NULL; tp = tp->ftt_next) {
|
|
if (pid == tp->ftt_pid && pc == tp->ftt_pc &&
|
|
tp->ftt_proc->ftpc_acount != 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If we couldn't find a matching tracepoint, either a tracepoint has
|
|
* been inserted without using the pid<pid> ioctl interface (see
|
|
* fasttrap_ioctl), or somehow we have mislaid this tracepoint.
|
|
*/
|
|
if (tp == NULL) {
|
|
lck_mtx_unlock(pid_mtx);
|
|
return -1;
|
|
}
|
|
|
|
/* Execute the actual probe */
|
|
if (tp->ftt_ids != NULL) {
|
|
fasttrap_id_t *id;
|
|
uint64_t arg4;
|
|
|
|
if (is_saved_state64(state)) {
|
|
arg4 = get_saved_state_reg(state, 4);
|
|
} else {
|
|
return -1;
|
|
}
|
|
|
|
|
|
/* First four parameters are passed in registers */
|
|
|
|
for (id = tp->ftt_ids; id != NULL; id = id->fti_next) {
|
|
fasttrap_probe_t *probe = id->fti_probe;
|
|
|
|
#if defined(XNU_TARGET_OS_OSX)
|
|
if (ISSET(current_proc()->p_lflag, P_LNOATTACH)) {
|
|
dtrace_probe(dtrace_probeid_error, 0 /* state */, probe->ftp_id,
|
|
1 /* ndx */, -1 /* offset */, DTRACEFLT_UPRIV);
|
|
#else
|
|
if (FALSE) {
|
|
#endif /* defined(XNU_TARGET_OS_OSX) */
|
|
} else {
|
|
if (probe->ftp_prov->ftp_provider_type == DTFTP_PROVIDER_ONESHOT) {
|
|
if (os_atomic_xchg(&probe->ftp_triggered, 1, relaxed)) {
|
|
/* already triggered */
|
|
continue;
|
|
}
|
|
}
|
|
/*
|
|
* If we have at least one probe associated that
|
|
* is not a oneshot probe, don't remove the
|
|
* tracepoint
|
|
*/
|
|
else {
|
|
retire_tp = 0;
|
|
}
|
|
if (id->fti_ptype == DTFTP_ENTRY) {
|
|
/*
|
|
* We note that this was an entry
|
|
* probe to help ustack() find the
|
|
* first caller.
|
|
*/
|
|
cookie = dtrace_interrupt_disable();
|
|
DTRACE_CPUFLAG_SET(CPU_DTRACE_ENTRY);
|
|
dtrace_probe(probe->ftp_id,
|
|
get_saved_state_reg(state, 0),
|
|
get_saved_state_reg(state, 1),
|
|
get_saved_state_reg(state, 2),
|
|
get_saved_state_reg(state, 3),
|
|
arg4);
|
|
DTRACE_CPUFLAG_CLEAR(CPU_DTRACE_ENTRY);
|
|
dtrace_interrupt_enable(cookie);
|
|
} else if (id->fti_ptype == DTFTP_IS_ENABLED) {
|
|
/*
|
|
* Note that in this case, we don't
|
|
* call dtrace_probe() since it's only
|
|
* an artificial probe meant to change
|
|
* the flow of control so that it
|
|
* encounters the true probe.
|
|
*/
|
|
is_enabled = 1;
|
|
} else if (probe->ftp_argmap == NULL) {
|
|
dtrace_probe(probe->ftp_id,
|
|
get_saved_state_reg(state, 0),
|
|
get_saved_state_reg(state, 1),
|
|
get_saved_state_reg(state, 2),
|
|
get_saved_state_reg(state, 3),
|
|
arg4);
|
|
} else {
|
|
uint64_t t[5];
|
|
|
|
fasttrap_usdt_args64(probe, saved_state64(state), 5, t);
|
|
dtrace_probe(probe->ftp_id, t[0], t[1], t[2], t[3], t[4]);
|
|
}
|
|
}
|
|
}
|
|
if (retire_tp) {
|
|
fasttrap_tracepoint_retire(p, tp);
|
|
}
|
|
}
|
|
/*
|
|
* We're about to do a bunch of work so we cache a local copy of
|
|
* the tracepoint to emulate the instruction, and then find the
|
|
* tracepoint again later if we need to light up any return probes.
|
|
*/
|
|
tp_local = *tp;
|
|
lck_mtx_unlock(pid_mtx);
|
|
tp = &tp_local;
|
|
|
|
/*
|
|
* APPLE NOTE:
|
|
*
|
|
* Subroutines should update PC.
|
|
* We're setting this earlier than Solaris does, to get a "correct"
|
|
* ustack() output. In the Sun code, a() -> b() -> c() -> d() is
|
|
* reported at: d, b, a. The new way gives c, b, a, which is closer
|
|
* to correct, as the return instruction has already exectued.
|
|
*/
|
|
fasttrap_pid_probe_handle_patched_instr64(state, tp, uthread, p, is_enabled, &was_simulated);
|
|
|
|
/*
|
|
* If there were no return probes when we first found the tracepoint,
|
|
* we should feel no obligation to honor any return probes that were
|
|
* subsequently enabled -- they'll just have to wait until the next
|
|
* time around.
|
|
*/
|
|
if (tp->ftt_retids != NULL) {
|
|
/*
|
|
* We need to wait until the results of the instruction are
|
|
* apparent before invoking any return probes. If this
|
|
* instruction was emulated we can just call
|
|
* fasttrap_return_common(); if it needs to be executed, we
|
|
* need to wait until the user thread returns to the kernel.
|
|
*/
|
|
/*
|
|
* It used to be that only common instructions were simulated.
|
|
* For performance reasons, we now simulate some instructions
|
|
* when safe and go back to userland otherwise. The was_simulated
|
|
* flag means we don't need to go back to userland.
|
|
*/
|
|
if (was_simulated) {
|
|
fasttrap_return_common(p, state, (user_addr_t)pc, (user_addr_t)get_saved_state_pc(state));
|
|
} else {
|
|
ASSERT(uthread->t_dtrace_ret != 0);
|
|
ASSERT(uthread->t_dtrace_pc == pc);
|
|
ASSERT(uthread->t_dtrace_scrpc != 0);
|
|
ASSERT(((user_addr_t)get_saved_state_pc(state)) == uthread->t_dtrace_astpc);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
fasttrap_return_probe(arm_saved_state_t *regs)
|
|
{
|
|
proc_t *p = current_proc();
|
|
uthread_t uthread = current_uthread();
|
|
user_addr_t pc = uthread->t_dtrace_pc;
|
|
user_addr_t npc = uthread->t_dtrace_npc;
|
|
|
|
uthread->t_dtrace_pc = 0;
|
|
uthread->t_dtrace_npc = 0;
|
|
uthread->t_dtrace_scrpc = 0;
|
|
uthread->t_dtrace_astpc = 0;
|
|
|
|
|
|
/*
|
|
* We set rp->r_pc to the address of the traced instruction so
|
|
* that it appears to dtrace_probe() that we're on the original
|
|
* instruction, and so that the user can't easily detect our
|
|
* complex web of lies. dtrace_return_probe() (our caller)
|
|
* will correctly set %pc after we return.
|
|
*/
|
|
set_saved_state_pc(regs, pc);
|
|
|
|
fasttrap_return_common(p, regs, pc, npc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint64_t
|
|
fasttrap_pid_getarg(void *arg, dtrace_id_t id, void *parg, int argno,
|
|
int aframes)
|
|
{
|
|
#pragma unused(arg, id, parg, aframes)
|
|
arm_saved_state_t* regs = find_user_regs(current_thread());
|
|
|
|
/* First eight arguments are in registers */
|
|
if (argno < 8) {
|
|
return saved_state64(regs)->x[argno];
|
|
}
|
|
|
|
/* Look on the stack for the rest */
|
|
uint64_t value;
|
|
uint64_t* sp = (uint64_t*) saved_state64(regs)->sp;
|
|
DTRACE_CPUFLAG_SET(CPU_DTRACE_NOFAULT);
|
|
value = dtrace_fuword64((user_addr_t) (sp + argno - 8));
|
|
DTRACE_CPUFLAG_CLEAR(CPU_DTRACE_NOFAULT | CPU_DTRACE_BADADDR);
|
|
|
|
return value;
|
|
}
|
|
|
|
uint64_t
|
|
fasttrap_usdt_getarg(void *arg, dtrace_id_t id, void *parg, int argno, int aframes)
|
|
{
|
|
#pragma unused(arg, id, parg, argno, aframes)
|
|
return 0;
|
|
}
|