summaryrefslogtreecommitdiffstats
path: root/runtime/uprobes/uprobes_s390.c
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/uprobes/uprobes_s390.c')
-rw-r--r--runtime/uprobes/uprobes_s390.c208
1 files changed, 208 insertions, 0 deletions
diff --git a/runtime/uprobes/uprobes_s390.c b/runtime/uprobes/uprobes_s390.c
new file mode 100644
index 00000000..f97dd408
--- /dev/null
+++ b/runtime/uprobes/uprobes_s390.c
@@ -0,0 +1,208 @@
+/*
+ * Userspace Probes (UProbes)
+ * arch/s390/uprobes/uprobes_s390.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) IBM Corporation, 2006
+ */
+/*
+ * In versions of uprobes built in the SystemTap runtime, this file
+ * is #included at the end of uprobes.c.
+ */
+#include <linux/uaccess.h>
+
+/* adapted from s390/kernel/kprobes.c is_prohibited_opcode() */
+/* TODO More instructions?? Should floating point inst be added?? */
+static int prohibited_opcode(uprobe_opcode_t *instruction)
+{
+ switch (*(__u8 *) instruction) {
+ case 0x0c: /* bassm */
+ case 0x0b: /* bsm */
+ case 0x83: /* diag */
+ case 0x44: /* ex */
+ return -EINVAL;
+ }
+ switch (*(__u16 *) instruction) {
+ case 0x0101: /* pr */
+ case 0xb25a: /* bsa */
+ case 0xb240: /* bakr */
+ case 0xb258: /* bsg */
+ case 0xb218: /* pc */
+ case 0xb228: /* pt */
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static
+int arch_validate_probed_insn(struct uprobe_probept *ppt)
+{
+ if (ppt->vaddr & 0x01) {
+ printk("Attempt to register uprobe at an unaligned address\n");
+ return -EPERM;
+ }
+
+ /* Make sure the probe isn't going on a difficult instruction */
+ if (prohibited_opcode((uprobe_opcode_t *) ppt->insn))
+ return -EPERM;
+
+ return 0;
+}
+
+/*
+ * Get an instruction slot from the process's SSOL area, containing the
+ * instruction at ppt's probepoint. Point the psw.addr at that slot, in
+ * preparation for single-stepping out of line.
+ */
+static
+void uprobe_pre_ssout(struct uprobe_task *utask, struct uprobe_probept *ppt,
+ struct pt_regs *regs)
+{
+ struct uprobe_ssol_slot *slot;
+
+ slot = uprobe_get_insn_slot(ppt);
+ if (!slot) {
+ utask->doomed = 1;
+ return;
+ }
+ regs->psw.addr = (long)slot->insn;
+ utask->singlestep_addr = regs->psw.addr;
+}
+
+static
+void uprobe_post_ssout(struct uprobe_task *utask, struct uprobe_probept *ppt,
+ struct pt_regs *regs)
+{
+ int ilen, fixup, reg;
+ unsigned long copy_ins_addr = utask->singlestep_addr;
+ unsigned long orig_ins_addr = ppt->vaddr;
+
+ up_read(&ppt->slot->rwsem);
+
+ /* default fixup method */
+ fixup = FIXUP_PSW_NORMAL;;
+
+ /* get r1 operand */
+ reg = (*ppt->insn & 0xf0) >> 4;
+
+ /* save the instruction length (pop 5-5) in bytes */
+ switch (*(__u8 *) (ppt->insn) >> 6) {
+ case 0:
+ ilen = 2;
+ break;
+ case 1:
+ case 2:
+ ilen = 4;
+ break;
+ case 3:
+ ilen = 6;
+ break;
+ default:
+ ilen = 0;
+ BUG();
+ }
+
+
+ switch (*(__u8 *) ppt->insn) {
+ case 0x05: /* balr */
+ case 0x0d: /* basr */
+ fixup = FIXUP_RETURN_REGISTER;
+ /* if r2 = 0, no branch will be taken */
+ if ((*ppt->insn & 0x0f) == 0)
+ fixup |= FIXUP_BRANCH_NOT_TAKEN;
+ break;
+ case 0x06: /* bctr */
+ case 0x07: /* bcr */
+ fixup = FIXUP_BRANCH_NOT_TAKEN;
+ break;
+ case 0x45: /* bal */
+ case 0x4d: /* bas */
+ fixup = FIXUP_RETURN_REGISTER;
+ break;
+ case 0x47: /* bc */
+ case 0x46: /* bct */
+ case 0x86: /* bxh */
+ case 0x87: /* bxle */
+ fixup = FIXUP_BRANCH_NOT_TAKEN;
+ break;
+ case 0x82: /* lpsw */
+ fixup = FIXUP_NOT_REQUIRED;
+ break;
+ case 0xb2: /* lpswe */
+ if (*(((__u8 *) ppt->insn) + 1) == 0xb2) {
+ fixup = FIXUP_NOT_REQUIRED;
+ }
+ break;
+ case 0xa7: /* bras */
+ if ((*ppt->insn & 0x0f) == 0x05) {
+ fixup |= FIXUP_RETURN_REGISTER;
+ }
+ break;
+ case 0xc0:
+ if ((*ppt->insn & 0x0f) == 0x00 /* larl */
+ || (*ppt->insn & 0x0f) == 0x05) /* brasl */
+ fixup |= FIXUP_RETURN_REGISTER;
+ break;
+ case 0xeb:
+ if (*(((__u8 *) ppt->insn) + 5 ) == 0x44 || /* bxhg */
+ *(((__u8 *) ppt->insn) + 5) == 0x45) {/* bxleg */
+ fixup = FIXUP_BRANCH_NOT_TAKEN;
+ }
+ break;
+ case 0xe3: /* bctg */
+ if (*(((__u8 *) ppt->insn) + 5) == 0x46) {
+ fixup = FIXUP_BRANCH_NOT_TAKEN;
+ }
+ break;
+ }
+
+ /* do the fixup and adjust psw as needed */
+ regs->psw.addr &= PSW_ADDR_INSN;
+
+ if (fixup & FIXUP_PSW_NORMAL)
+ regs->psw.addr = orig_ins_addr + regs->psw.addr -
+ copy_ins_addr;
+
+ if (fixup & FIXUP_BRANCH_NOT_TAKEN)
+ if (regs->psw.addr - copy_ins_addr == ilen)
+ regs->psw.addr = orig_ins_addr + ilen;
+
+ if (fixup & FIXUP_RETURN_REGISTER)
+ regs->gprs[reg] = (orig_ins_addr + (regs->gprs[reg] -
+ copy_ins_addr)) | PSW_ADDR_AMODE;
+
+ regs->psw.addr |= PSW_ADDR_AMODE;
+}
+
+
+/*
+ * Replace the return address with the trampoline address. Returns
+ * the original return address.
+ */
+static
+unsigned long arch_hijack_uret_addr(unsigned long trampoline_address,
+ struct pt_regs *regs, struct uprobe_task *utask)
+{
+ unsigned long orig_ret_addr;
+#ifdef CONFIG_COMPAT
+ if (test_tsk_thread_flag(utask->tsk, TIF_31BIT))
+ orig_ret_addr = regs->gprs[14]&0x7FFFFFFFUL;
+ else
+#endif
+ orig_ret_addr = regs->gprs[14];
+ regs->gprs[14] = trampoline_address;
+ return orig_ret_addr;
+}