[PATCH RFC v1 3/5] AArch64: Instruction simulation and decode support
Sandeepa Prabhu
sandeepa.prabhu at linaro.org
Tue Oct 1 11:57:58 EDT 2013
Support for v8 instruction decoding and simulation is implemented,
which are common for use by kprobes as well as uprobes.
Kprobes/uprobes on ARM64 is leveraged on single-stepping of
instruction from a out-of-line memory slot.
The instructions that use PC-relative access can not be stepped from
out-of-line memory slot, so are simulated in C code using the saved
copy of pt_regs.
This patch implements helper macros and data structures for
building instruction decode table, along with handlers for
instruction prepare and simulation.
Signed-off-by: Sandeepa Prabhu <sandeepa.prabhu at linaro.org>
---
arch/arm64/include/asm/probes.h | 48 ++++++++
arch/arm64/kernel/probes-aarch64.c | 235 +++++++++++++++++++++++++++++++++++++
arch/arm64/kernel/probes-aarch64.h | 127 ++++++++++++++++++++
arch/arm64/kernel/probes-common.c | 117 ++++++++++++++++++
4 files changed, 527 insertions(+)
create mode 100644 arch/arm64/include/asm/probes.h
create mode 100644 arch/arm64/kernel/probes-aarch64.c
create mode 100644 arch/arm64/kernel/probes-aarch64.h
create mode 100644 arch/arm64/kernel/probes-common.c
diff --git a/arch/arm64/include/asm/probes.h b/arch/arm64/include/asm/probes.h
new file mode 100644
index 0000000..8d4355e
--- /dev/null
+++ b/arch/arm64/include/asm/probes.h
@@ -0,0 +1,48 @@
+/*
+ * arch/arm64/include/asm/probes.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+#ifndef _ARM_PROBES_H
+#define _ARM_PROBES_H
+
+struct kprobe;
+struct arch_specific_insn;
+
+typedef u32 kprobe_opcode_t;
+typedef unsigned long (kprobes_pstate_check_t)(unsigned long);
+typedef unsigned long
+(kprobes_condition_check_t)(struct kprobe *, struct pt_regs *);
+typedef void
+(kprobes_prepare_t)(struct kprobe *, struct arch_specific_insn *);
+typedef void (kprobes_handler_t) (struct kprobe *, struct pt_regs *);
+
+typedef enum {
+ NO_RESTORE,
+ RESTORE_PC,
+} pc_restore_t;
+
+struct kprobe_pc_restore {
+ pc_restore_t type;
+ unsigned long addr;
+};
+
+/* architecture specific copy of original instruction */
+struct arch_specific_insn {
+ kprobe_opcode_t *insn;
+ kprobes_pstate_check_t *pstate_cc;
+ kprobes_condition_check_t *check_condn;
+ kprobes_prepare_t *prepare;
+ kprobes_handler_t *handler;
+ /* restore address after step xol */
+ struct kprobe_pc_restore restore;
+};
+
+#endif
diff --git a/arch/arm64/kernel/probes-aarch64.c b/arch/arm64/kernel/probes-aarch64.c
new file mode 100644
index 0000000..0163129
--- /dev/null
+++ b/arch/arm64/kernel/probes-aarch64.c
@@ -0,0 +1,235 @@
+/*
+ * arch/arm64/kernel/probes-aarch64.c
+ *
+ * Copyright (C) 2013 Linaro Limited.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/kprobes.h>
+#include <linux/module.h>
+
+#include "probes-aarch64.h"
+
+#define sign_extend(x, signbit) \
+ ((x) | (0 - ((x) & (1 << (signbit)))))
+
+#define bbl_displacement(insn) \
+ sign_extend(((insn) & 0x3ffffff) << 2, 27)
+
+#define bcond_displacement(insn) \
+ sign_extend(((insn >> 5) & 0xfffff) << 2, 21)
+
+#define cbz_displacement(insn) \
+ sign_extend(((insn >> 5) & 0xfffff) << 2, 21)
+
+#define tbz_displacement(insn) \
+ sign_extend(((insn >> 5) & 0x3fff) << 2, 15)
+
+#define ldr_displacement(insn) \
+ sign_extend(((insn >> 5) & 0xfffff) << 2, 21)
+
+/* conditional check functions */
+static unsigned long __kprobes
+__check_pstate(struct kprobe *p, struct pt_regs *regs)
+{
+ struct arch_specific_insn *asi = &p->ainsn;
+ unsigned long pstate = regs->pstate & 0xffffffff;
+
+ return asi->pstate_cc(pstate);
+}
+
+static unsigned long __kprobes
+__check_cbz(struct kprobe *p, struct pt_regs *regs)
+{
+ kprobe_opcode_t insn = p->opcode;
+ int xn = insn & 0x1f;
+
+ return (insn & (1 << 31)) ?
+ !(regs->regs[xn]) : !(regs->regs[xn] & 0xffffffff);
+}
+
+static unsigned long __kprobes
+__check_cbnz(struct kprobe *p, struct pt_regs *regs)
+{
+ kprobe_opcode_t insn = p->opcode;
+ int xn = insn & 0x1f;
+
+ return (insn & (1 << 31)) ?
+ (regs->regs[xn]) : (regs->regs[xn] & 0xffffffff);
+}
+
+static unsigned long __kprobes
+__check_tbz(struct kprobe *p, struct pt_regs *regs)
+{
+ kprobe_opcode_t insn = p->opcode;
+ int xn = insn & 0x1f;
+ int bit_pos = ((insn & (1 << 31)) >> 26) | ((insn >> 19) & 0x1f);
+
+ return ~((regs->regs[xn] >> bit_pos) & 0x1);
+}
+
+static unsigned long __kprobes
+__check_tbnz(struct kprobe *p, struct pt_regs *regs)
+{
+ kprobe_opcode_t insn = p->opcode;
+ int xn = insn & 0x1f;
+ int bit_pos = ((insn & (1 << 31)) >> 26) | ((insn >> 19) & 0x1f);
+
+ return (regs->regs[xn] >> bit_pos) & 0x1;
+}
+
+/* prepare functions */
+void __kprobes prepare_none(struct kprobe *p, struct arch_specific_insn *asi)
+{
+}
+
+void __kprobes prepare_bcond(struct kprobe *p, struct arch_specific_insn *asi)
+{
+ kprobe_opcode_t insn = p->opcode;
+
+ asi->check_condn = __check_pstate;
+ asi->pstate_cc = kprobe_condition_checks[insn & 0xf];
+}
+
+void __kprobes
+prepare_cbz_cbnz(struct kprobe *p, struct arch_specific_insn *asi)
+{
+ kprobe_opcode_t insn = p->opcode;
+
+ asi->check_condn = (insn & (1 << 24)) ? __check_cbnz : __check_cbz;
+}
+
+void __kprobes
+prepare_tbz_tbnz(struct kprobe *p, struct arch_specific_insn *asi)
+{
+ kprobe_opcode_t insn = p->opcode;
+
+ asi->check_condn = (insn & (1 << 24)) ? __check_tbnz : __check_tbz;
+}
+
+/* simulate functions */
+void __kprobes simulate_none(struct kprobe *p, struct pt_regs *regs)
+{
+}
+
+void __kprobes simulate_adr_adrp(struct kprobe *p, struct pt_regs *regs)
+{
+ kprobe_opcode_t insn = p->opcode;
+ long iaddr = (long)p->addr;
+ long res, imm, xn;
+
+ xn = insn & 0x1f;
+ imm = ((insn >> 3) & 0xffffc) | ((insn >> 29) & 0x3);
+ res = iaddr + 8 + sign_extend(imm, 20);
+
+ regs->regs[xn] = insn & 0x80000000 ? res & 0xfffffffffffff000 : res;
+ instruction_pointer(regs) += 4;
+
+ return;
+}
+
+void __kprobes simulate_b_bl(struct kprobe *p, struct pt_regs *regs)
+{
+ kprobe_opcode_t insn = p->opcode;
+ long iaddr = (long)p->addr;
+ int disp = bbl_displacement(insn);
+
+ /* Link register */
+ if (insn & (1 << 31))
+ regs->regs[30] = iaddr + 4;
+
+ instruction_pointer(regs) = iaddr + disp;
+
+ return;
+}
+
+void __kprobes simulate_b_cond(struct kprobe *p, struct pt_regs *regs)
+{
+ kprobe_opcode_t insn = p->opcode;
+ long iaddr = (long)p->addr;
+ int disp = bcond_displacement(insn);
+
+ instruction_pointer(regs) = iaddr + disp;
+
+ return;
+}
+
+void __kprobes simulate_br_blr_ret(struct kprobe *p, struct pt_regs *regs)
+{
+ kprobe_opcode_t insn = p->opcode;
+ long iaddr = (long)p->addr;
+ int xn = (insn >> 5) & 0x1f;
+
+ /* BLR */
+ if (((insn >> 21) & 0x3) == 1)
+ regs->regs[30] = iaddr + 4;
+
+ instruction_pointer(regs) = regs->regs[xn];
+
+ return;
+}
+
+void __kprobes simulate_cbz_cbnz(struct kprobe *p, struct pt_regs *regs)
+{
+ kprobe_opcode_t insn = p->opcode;
+ long iaddr = (long)p->addr;
+ int disp = cbz_displacement(insn);
+
+ instruction_pointer(regs) = iaddr + disp;
+
+ return;
+}
+
+void __kprobes simulate_tbz_tbnz(struct kprobe *p, struct pt_regs *regs)
+{
+ kprobe_opcode_t insn = p->opcode;
+ long iaddr = (long)p->addr;
+ int disp = tbz_displacement(insn);
+
+ instruction_pointer(regs) = iaddr + disp;
+
+ return;
+}
+
+void __kprobes simulate_ldr_literal(struct kprobe *p, struct pt_regs *regs)
+{
+ kprobe_opcode_t insn = p->opcode;
+ u64 *load_addr;
+ long iaddr = (long)p->addr;
+ int xn = insn & 0x1f;
+ int disp = ldr_displacement(insn);
+
+ load_addr = (u64 *) (iaddr + disp);
+
+ if (insn & (1 << 30)) /* x0-x31 */
+ regs->regs[xn] = *load_addr;
+ else /* w0-w31 */
+ *(u32 *) (®s->regs[xn]) = (*(u32 *) (load_addr));
+
+ return;
+}
+
+void __kprobes simulate_ldrsw_literal(struct kprobe *p, struct pt_regs *regs)
+{
+ kprobe_opcode_t insn = p->opcode;
+ u64 *load_addr;
+ long data, iaddr = (long)p->addr;
+ int xn = insn & 0x1f;
+ int disp = ldr_displacement(insn);
+
+ load_addr = (u64 *) (iaddr + disp);
+ data = *load_addr;
+
+ regs->regs[xn] = sign_extend(data, 63);
+
+ return;
+}
diff --git a/arch/arm64/kernel/probes-aarch64.h b/arch/arm64/kernel/probes-aarch64.h
new file mode 100644
index 0000000..fb7475c
--- /dev/null
+++ b/arch/arm64/kernel/probes-aarch64.h
@@ -0,0 +1,127 @@
+/*
+ * arch/arm64/kernel/probes-aarch64.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+#ifndef _ARM_KERNEL_PROBES_AARCH64_H
+#define _ARM_KERNEL_PROBES_AARCH64_H
+
+/*
+ * The following definitions and macros are used to build instruction
+ * decoding tables.
+ */
+enum decode_type {
+ DECODE_TYPE_END,
+ DECODE_TYPE_REJECT,
+ DECODE_TYPE_SINGLESTEP,
+ DECODE_TYPE_SIMULATE,
+ DECODE_TYPE_TABLE,
+ NUM_DECODE_TYPES, /* Must be last enum */
+};
+
+struct aarch64_decode_item;
+
+struct aarch64_decode_header {
+ enum decode_type type;
+ u32 mask;
+ u32 val;
+};
+
+struct aarch64_decode_actions {
+ kprobes_prepare_t *prepare;
+ kprobes_handler_t *handler;
+};
+
+struct aarch64_decode_table {
+ const struct aarch64_decode_item *tbl;
+};
+
+union aarch64_decode_handler {
+ struct aarch64_decode_actions actions;
+ struct aarch64_decode_table table;
+};
+
+struct aarch64_decode_item {
+ struct aarch64_decode_header header;
+ union aarch64_decode_handler decode;
+};
+
+#define decode_get_type(_entry) ((_entry).header.type)
+
+#define decode_table_end(_entry) \
+ ((_entry).header.type == DECODE_TYPE_END)
+
+#define decode_table_hit(_entry, insn) \
+ ((insn & (_entry).header.mask) == (_entry).header.val)
+
+#define decode_prepare_fn(_entry) ((_entry).decode.actions.prepare)
+#define decode_handler_fn(_entry) ((_entry).decode.actions.handler)
+#define decode_sub_table(_entry) ((_entry).decode.table.tbl)
+
+#define DECODE_ADD_HEADER(_type, _val, _mask) \
+ .header = { \
+ .type = _type, \
+ .mask = _mask, \
+ .val = _val, \
+ },
+
+#define DECODE_ADD_ACTION(_prepare, _handler) \
+ .decode = { \
+ .actions = { \
+ .prepare = _prepare, \
+ .handler = _handler, \
+ } \
+ },
+
+#define DECODE_ADD_TABLE(_table) \
+ .decode = { \
+ .table = {.tbl = _table} \
+ },
+
+#define DECODE_REJECT(_v, _m) \
+ { DECODE_ADD_HEADER(DECODE_TYPE_REJECT, _v, _m) }
+
+#define DECODE_SINGLESTEP(_v, _m) \
+ { DECODE_ADD_HEADER(DECODE_TYPE_SINGLESTEP, _v, _m) }
+
+#define DECODE_SIMULATE(_v, _m, _p, _h) \
+ { DECODE_ADD_HEADER(DECODE_TYPE_SIMULATE, _v, _m) \
+ DECODE_ADD_ACTION(_p, _h) }
+
+#define DECODE_TABLE(_v, _m, _table) \
+ { DECODE_ADD_HEADER(DECODE_TYPE_TABLE, _v, _m) \
+ DECODE_ADD_TABLE(_table) }
+
+#define DECODE_LITERAL(_v, _m, _p, _h) DECODE_SIMULATE(_v, _m, _p, _h)
+#define DECODE_BRANCH(_v, _m, _p, _h) DECODE_SIMULATE(_v, _m, _p, _h)
+
+/* should be the last element in decode structure */
+#define DECODE_END { .header = {.type = DECODE_TYPE_END, } }
+
+extern kprobes_pstate_check_t *const kprobe_condition_checks[16];
+
+void __kprobes prepare_none(struct kprobe *p, struct arch_specific_insn *asi);
+void __kprobes prepare_bcond(struct kprobe *p, struct arch_specific_insn *asi);
+void __kprobes prepare_cbz_cbnz(struct kprobe *p,
+ struct arch_specific_insn *asi);
+void __kprobes prepare_tbz_tbnz(struct kprobe *p,
+ struct arch_specific_insn *asi);
+void __kprobes simulate_none(struct kprobe *p, struct pt_regs *regs);
+void __kprobes simulate_adr_adrp(struct kprobe *p, struct pt_regs *regs);
+void __kprobes simulate_b_bl(struct kprobe *p, struct pt_regs *regs);
+void __kprobes simulate_b_cond(struct kprobe *p, struct pt_regs *regs);
+void __kprobes simulate_br_blr_ret(struct kprobe *p, struct pt_regs *regs);
+void __kprobes simulate_cbz_cbnz(struct kprobe *p, struct pt_regs *regs);
+void __kprobes simulate_tbz_tbnz(struct kprobe *p, struct pt_regs *regs);
+void __kprobes simulate_ldr_literal(struct kprobe *p, struct pt_regs *regs);
+void __kprobes simulate_ldrsw_literal(struct kprobe *p, struct pt_regs *regs);
+
+#endif /* _ARM_KERNEL_PROBES_AARCH64_H */
diff --git a/arch/arm64/kernel/probes-common.c b/arch/arm64/kernel/probes-common.c
new file mode 100644
index 0000000..4990940
--- /dev/null
+++ b/arch/arm64/kernel/probes-common.c
@@ -0,0 +1,117 @@
+/*
+ * arch/arm64/kernel/probes-common.c
+ *
+ * copied from arch/arm/kernel/kprobes-common.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * Description:
+ * This file is the place for common routines for AArch64 and
+ * AArch32 conditional checks, needed by kprobes-AArch64 and
+ * uprobes-AArch32/AArch64
+ *
+ * AArch64 and AArch32 instrcution decoding differ, and are implemented
+ * in respective probes-*** files, this file is for common code only.
+ */
+#include <linux/kernel.h>
+#include <linux/kprobes.h>
+#include <linux/module.h>
+
+static unsigned long __kprobes __check_eq(unsigned long pstate)
+{
+ return pstate & PSR_Z_BIT;
+}
+
+static unsigned long __kprobes __check_ne(unsigned long pstate)
+{
+ return (~pstate) & PSR_Z_BIT;
+}
+
+static unsigned long __kprobes __check_cs(unsigned long pstate)
+{
+ return pstate & PSR_C_BIT;
+}
+
+static unsigned long __kprobes __check_cc(unsigned long pstate)
+{
+ return (~pstate) & PSR_C_BIT;
+}
+
+static unsigned long __kprobes __check_mi(unsigned long pstate)
+{
+ return pstate & PSR_N_BIT;
+}
+
+static unsigned long __kprobes __check_pl(unsigned long pstate)
+{
+ return (~pstate) & PSR_N_BIT;
+}
+
+static unsigned long __kprobes __check_vs(unsigned long pstate)
+{
+ return pstate & PSR_V_BIT;
+}
+
+static unsigned long __kprobes __check_vc(unsigned long pstate)
+{
+ return (~pstate) & PSR_V_BIT;
+}
+
+static unsigned long __kprobes __check_hi(unsigned long pstate)
+{
+ pstate &= ~(pstate >> 1); /* PSR_C_BIT &= ~PSR_Z_BIT */
+ return pstate & PSR_C_BIT;
+}
+
+static unsigned long __kprobes __check_ls(unsigned long pstate)
+{
+ pstate &= ~(pstate >> 1); /* PSR_C_BIT &= ~PSR_Z_BIT */
+ return (~pstate) & PSR_C_BIT;
+}
+
+static unsigned long __kprobes __check_ge(unsigned long pstate)
+{
+ pstate ^= (pstate << 3); /* PSR_N_BIT ^= PSR_V_BIT */
+ return (~pstate) & PSR_N_BIT;
+}
+
+static unsigned long __kprobes __check_lt(unsigned long pstate)
+{
+ pstate ^= (pstate << 3); /* PSR_N_BIT ^= PSR_V_BIT */
+ return pstate & PSR_N_BIT;
+}
+
+static unsigned long __kprobes __check_gt(unsigned long pstate)
+{
+ /*PSR_N_BIT ^= PSR_V_BIT */
+ unsigned long temp = pstate ^ (pstate << 3);
+ temp |= (pstate << 1); /*PSR_N_BIT |= PSR_Z_BIT */
+ return (~temp) & PSR_N_BIT;
+}
+
+static unsigned long __kprobes __check_le(unsigned long pstate)
+{
+ /*PSR_N_BIT ^= PSR_V_BIT */
+ unsigned long temp = pstate ^ (pstate << 3);
+ temp |= (pstate << 1); /*PSR_N_BIT |= PSR_Z_BIT */
+ return temp & PSR_N_BIT;
+}
+
+static unsigned long __kprobes __check_al(unsigned long pstate)
+{
+ return true;
+}
+
+kprobes_pstate_check_t *const kprobe_condition_checks[16] = {
+ &__check_eq, &__check_ne, &__check_cs, &__check_cc,
+ &__check_mi, &__check_pl, &__check_vs, &__check_vc,
+ &__check_hi, &__check_ls, &__check_ge, &__check_lt,
+ &__check_gt, &__check_le, &__check_al, &__check_al
+};
--
1.8.1.2
More information about the linux-arm-kernel
mailing list