/*
* 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, see .
*
* 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
/* 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,
struct task_struct *tsk)
{
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;
}
/* Check if instruction is nop and return true. */
static int uprobe_emulate_insn(struct pt_regs *regs,
struct uprobe_probept *ppt)
{
unsigned int insn = *ppt->insn;
if (insn == 0x47000000)
/* ip already points to the insn after the nop/bkpt insn. */
return 1;
return 0;
}