Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753680Ab1FSLtK (ORCPT ); Sun, 19 Jun 2011 07:49:10 -0400 Received: from mail.southpole.se ([193.12.106.18]:37743 "EHLO mail.southpole.se" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753452Ab1FSLpK (ORCPT ); Sun, 19 Jun 2011 07:45:10 -0400 From: Jonas Bonn To: linux-kernel@vger.kernel.org Cc: Jonas Bonn Subject: [PATCH 06/19] OpenRISC: PTrace Date: Sun, 19 Jun 2011 13:43:32 +0200 Message-Id: <1308483825-6023-7-git-send-email-jonas@southpole.se> X-Mailer: git-send-email 1.7.4.1 In-Reply-To: <1308483825-6023-1-git-send-email-jonas@southpole.se> References: <1308483825-6023-1-git-send-email-jonas@southpole.se> X-Assp-Client-SSL: yes Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 20978 Lines: 778 Signed-off-by: Jonas Bonn --- arch/openrisc/include/asm/ptrace.h | 124 ++++++++ arch/openrisc/kernel/ptrace.c | 580 ++++++++++++++++++++++++++++++++++++ arch/openrisc/kernel/ptrace.h | 37 +++ 3 files changed, 741 insertions(+), 0 deletions(-) create mode 100644 arch/openrisc/include/asm/ptrace.h create mode 100644 arch/openrisc/kernel/ptrace.c create mode 100644 arch/openrisc/kernel/ptrace.h diff --git a/arch/openrisc/include/asm/ptrace.h b/arch/openrisc/include/asm/ptrace.h new file mode 100644 index 0000000..d3f620e --- /dev/null +++ b/arch/openrisc/include/asm/ptrace.h @@ -0,0 +1,124 @@ +/* + * OpenRISC Linux + * + * Linux architectural port borrowing liberally from similar works of + * others. All original copyrights apply as per the original source + * declaration. + * + * OpenRISC implementation: + * Copyright (C) 2003 Matjaz Breskvar + * Copyright (C) 2010-2011 Jonas Bonn + * et al. + * + * 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. + */ + +#ifndef __ASM_OPENRISC_PTRACE_H +#define __ASM_OPENRISC_PTRACE_H + +#include +/* + * This struct defines the way the registers are stored on the + * kernel stack during a system call or other kernel entry. + * + * this should only contain volatile regs + * since we can keep non-volatile in the thread_struct + * should set this up when only volatiles are saved + * by intr code. + * + * Since this is going on the stack, *CARE MUST BE TAKEN* to insure + * that the overall structure is a multiple of 16 bytes in length. + * + * Note that the offsets of the fields in this struct correspond with + * the values below. + */ + +/* + * These are 'magic' values for PTRACE_PEEKUSR that return info about where a + * process is located in memory. + */ +#define PT_TEXT_ADDR 0x10000 +#define PT_DATA_ADDR 0x10004 +#define PT_TEXT_END_ADDR 0x10008 + +#ifndef __ASSEMBLY__ + +struct pt_regs { + union { + struct { + /* Named registers */ + long sr; /* Stored in place of r0 */ + long sp; /* r1 */ + }; + struct { + /* Old style */ + long offset[2]; + long gprs[30]; + }; + struct { + /* New style */ + long gpr[32]; + }; + }; + long pc; + long orig_gpr11; /* Used for restarting system calls */ + long syscallno; /* Syscall no. (used by strace) */ +}; +#endif /* __ASSEMBLY__ */ + +#ifdef __KERNEL__ +#define STACK_FRAME_OVERHEAD 128 /* size of minimum stack frame */ +//#define STACK_FRAME_OVERHEAD 0 /* size of minimum stack frame */ + +#define instruction_pointer(regs) ((regs)->pc) +#define user_mode(regs) (((regs)->sr & SPR_SR_SM) == 0) +#define user_stack_pointer(regs) ((unsigned long)(regs)->sp) +#define profile_pc(regs) instruction_pointer(regs) + +#define arch_has_single_step() (1) + +#endif /* __KERNEL__ */ + +/* + * Offsets used by 'ptrace' system call interface. + */ +#define PT_SR 0 +#define PT_SP 4 +#define PT_GPR2 8 +#define PT_GPR3 12 +#define PT_GPR4 16 +#define PT_GPR5 20 +#define PT_GPR6 24 +#define PT_GPR7 28 +#define PT_GPR8 32 +#define PT_GPR9 36 +#define PT_GPR10 40 +#define PT_GPR11 44 +#define PT_GPR12 48 +#define PT_GPR13 52 +#define PT_GPR14 56 +#define PT_GPR15 60 +#define PT_GPR16 64 +#define PT_GPR17 68 +#define PT_GPR18 72 +#define PT_GPR19 76 +#define PT_GPR20 80 +#define PT_GPR21 84 +#define PT_GPR22 88 +#define PT_GPR23 92 +#define PT_GPR24 96 +#define PT_GPR25 100 +#define PT_GPR26 104 +#define PT_GPR27 108 +#define PT_GPR28 112 +#define PT_GPR29 116 +#define PT_GPR30 120 +#define PT_GPR31 124 +#define PT_PC 128 +#define PT_ORIG_GPR11 132 +#define PT_SYSCALLNO 136 + +#endif /* __ASM_OPENRISC_PTRACE_H */ diff --git a/arch/openrisc/kernel/ptrace.c b/arch/openrisc/kernel/ptrace.c new file mode 100644 index 0000000..d0d1f44 --- /dev/null +++ b/arch/openrisc/kernel/ptrace.c @@ -0,0 +1,580 @@ +/* + * OpenRISC ptrace.c + * + * Linux architectural port borrowing liberally from similar works of + * others. All original copyrights apply as per the original source + * declaration. + * + * Modifications for the OpenRISC architecture: + * Copyright (C) 2003 Matjaz Breskvar + * Copyright (C) 2005 Gyorgy Jeney + * Copyright (C) 2010 Julius Baxter + * Copyright (C) 2010-2011 Jonas Bonn + * + * 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. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "ptrace.h" + +/* + * retrieve the contents of OpenRISC userspace general registers + */ +static int genregs_get(struct task_struct *target, + const struct user_regset *regset, + unsigned int pos, unsigned int count, + void *kbuf, void __user *ubuf) +{ + const struct pt_regs *regs = task_pt_regs(target); + int ret; + +#if 0 + /* r0 */ + ret = user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf, + 0, offsetof(struct pt_regs, regs)); +#endif + + ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, + regs, 0, sizeof(*regs)); + +/* put PPC here */ + +#if 0 + /* fill out rest of elf_gregset_t structure with zeroes */ + if (!ret) + ret = user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf, + sizeof(struct pt_regs), -1); +#endif + + return ret; +} + +/* + * update the contents of the OpenRISC userspace general registers + */ +static int genregs_set(struct task_struct *target, + const struct user_regset *regset, + unsigned int pos, unsigned int count, + const void *kbuf, const void __user *ubuf) +{ + struct pt_regs *regs = task_pt_regs(target); + int ret; + + /* PC */ + ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, + regs, + 0, offsetof(struct pt_regs, sr)); + + /* skip SR */ + ret = user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, + offsetof(struct pt_regs, sr), + offsetof(struct pt_regs, sp)); + + /* SP, r2 - r31 */ + ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, + regs, + offsetof(struct pt_regs, sp), + sizeof(struct pt_regs)); + +#if 0 + /* read out the rest of the elf_gregset_t structure */ + if (!ret) + ret = user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, + sizeof(struct pt_regs), -1); +#endif + + return ret; +} + +/* + * Define the register sets available on OpenRISC under Linux + */ +enum openrisc_regset { + REGSET_GENERAL, +}; + +static const struct user_regset openrisc_regsets[] = { + [REGSET_GENERAL] = { + .core_note_type = NT_PRSTATUS, + .n = ELF_NGREG, + .size = sizeof(long), + .align = sizeof(long), + .get = genregs_get, + .set = genregs_set, + }, +}; + +static const struct user_regset_view user_openrisc_native_view = { + .name = "OpenRISC", + .e_machine = EM_OPENRISC, + .regsets = openrisc_regsets, + .n = ARRAY_SIZE(openrisc_regsets), +}; + +const struct user_regset_view *task_user_regset_view(struct task_struct *task) +{ + return &user_openrisc_native_view; +} + +void user_enable_single_step(struct task_struct *child) +{ + set_tsk_thread_flag(child, TIF_SINGLESTEP); +} + +void user_disable_single_step(struct task_struct *child) +{ + clear_tsk_thread_flag(child, TIF_SINGLESTEP); +} + + +/* + * does not yet catch signals sent when the child dies. + * in exit.c or in signal.c. + */ + + + +#define OPC_MASK 0x3f +#define OPC_SHIFT 26 +#define OPC_J 0x00 +#define OPC_JAL 0x01 +#define OPC_BNF 0x03 +#define OPC_BF 0x04 +#define OPC_SYSC 0x08 +#define OPC_JR 0x11 +#define OPC_JALR 0x12 + +#define JUMP_IMM_MASK 0x03ffffff +#define JUMP_IMM_SIGN 0x02000000 + +/* Trap instruction on bit 15 of SR, fixed one, unconditional */ +#define OR1K_TRAP 0x2100000f + +/* Macro to extract branch offset with sign extension of immediate. */ +#define BRANCH_OFFSET(insn) ((insn & JUMP_IMM_SIGN) ? \ + ((insn & JUMP_IMM_MASK) << 2) | 0xf0000000 : \ + ((insn & JUMP_IMM_MASK) << 2)) + +/* Get address of next instruction we can insert a breakpoint on */ +/* pc passed to function is address of instruction we've now just trapped on. + It is yet/next to be executed. We determine next instruction after PC to + place a l.trap on. If we should take a branch, we must remember this. +*/ +static unsigned long +get_next_address(struct task_struct *tsk, unsigned long pc, + unsigned long insn) +{ + struct pt_regs *regs; + struct debug_info *dbg; + char opc; + unsigned long npc; + unsigned long rB; + + dbg = ¤t_thread_info()->debug; + + regs = task_pt_regs(tsk); + /* Extract opcode from instruction */ + opc = (insn >> OPC_SHIFT) & OPC_MASK; + + pr_debug(KERN_INFO "ptrace get_next_address: pc 0x%.8lx insn. 0x%.8lx opc. 0x%.2x\n", + pc, insn, opc); + + /* + Will always proceed to next instruction if this function was called + ie. we know we're not in a delay slot - the function calling this + checks dbg->branch_taken. + */ + npc = pc + 4; + + /* Check if we're to record a branch */ + switch (opc) { + case OPC_BNF: + /* Check flag - we're branching if !flag */ + if (!(((regs)->sr) & SPR_SR_F)) + { + dbg->bp.branch = 1; + dbg->bp.branch_target = pc + BRANCH_OFFSET(insn); + } + break; + case OPC_BF: + /* Check flag - we're branching if flag */ + if (((regs)->sr) & SPR_SR_F) + { + dbg->bp.branch = 1; + dbg->bp.branch_target = pc + BRANCH_OFFSET(insn); + } + break; + case OPC_J: + case OPC_JAL: + /* PC-relative branch target encoded in instruction. Extract + and add it. */ + dbg->bp.branch = 1; + dbg->bp.branch_target = pc + BRANCH_OFFSET(insn); + break; + case OPC_JR: + case OPC_JALR: + /* Register number holding branch target is encoded in rB slot + of instruction. Extract it. */ + rB = (insn >> 11) & 0x1f; + pr_debug(KERN_INFO "ptrace get_next_address: jump reg from r%ld=0x%08lx\n", + rB,/* *((unsigned long*)((char*)regs + (rB<<2) + 4))*/ regs->gprs[rB-2]); + if (rB < 2) + pr_debug(KERN_WARNING "ptrace get_next_address(): Warning, JR with GPR < 2"); + dbg->bp.branch = 1; + dbg->bp.branch_target = /* *((unsigned long*)((char*)regs + (rB<<2) + 4))*/regs->gprs[rB-2]; + break; +#if 0 + case OPC_SYSC: + /* Not sure we want to do this */ + /* Remember - l.sys has no delay slot. */ + npc = 0x900; + break; +#endif + default: + break; + } + + /* If setting a branch target, remember where branch was */ + if (dbg->bp.branch) + dbg->bp.branch_insn_address = pc; + + if (dbg->bp.branch) + pr_debug(KERN_INFO "ptrace get_next_address: branch detected to 0x%.8lx\n",dbg->bp.branch_target); + pr_debug(KERN_INFO "ptrace get_next_address: returning 0x%.8lx\n",npc); + + return npc; +} + +static inline int +read_instr(struct task_struct *tsk, unsigned long addr, u32 *res) +{ + int ret; + u32 val; + ret = access_process_vm(tsk, addr & ~3, &val, sizeof(val), 0); + ret = ret == sizeof(val) ? 0 : -EIO; + *res = val; + return ret; +} + + +static int +swap_insn(struct task_struct *tsk, unsigned long addr, + void *old_insn, void *new_insn, int size) +{ + int ret; + + ret = access_process_vm(tsk, addr, old_insn, size, 0); + if (ret == size) + ret = access_process_vm(tsk, addr, new_insn, size, 1); + return ret; +} + +static void +add_breakpoint(struct task_struct *tsk, struct debug_info *dbg, + unsigned long addr) +{ + u32 new_insn = OR1K_TRAP; + int res; + + res = swap_insn(tsk, addr, &dbg->bp.insn, &new_insn, 4); + + pr_debug(KERN_INFO "ptrace add_breakpoint: addr 0x%.8lx insn 0x%.8lx %d\n\n", addr, + dbg->bp.insn, res); + + if (res == 4) { + dbg->bp.address = addr; + dbg->bp.set = 1; + } +} + + +/* + * Clear the breakpoint in the user program. + */ +static void clear_breakpoint(struct task_struct *tsk, struct debug_entry *bp) +{ + u32 old_insn; + int ret; + struct pt_regs *regs; + unsigned long pc; + + unsigned long addr = bp->address; + + regs = task_pt_regs(tsk); + pc = instruction_pointer(regs); /* This is NPC */ + + // Either at the breakpoint, or in delay slot (pc will be at address of + // branch instruction - OR1K exception handlers do that, for now. + // Perhaps FIXME + if ((pc == addr) || + ((bp->branch_insn_address == pc) && bp->branch)) + ret = swap_insn(tsk, addr & ~3, &old_insn, + &bp->insn, 4); + else + { + // Trapped for some other reason. + pr_debug(KERN_INFO "ptrace clear_breakpoint: not correct, pc 0x%.8lx bp 0x%.8lx\n", + pc, addr); + pr_debug(KERN_INFO "ptrace clear_breakpoint: 0x%.8lx 0x%.8lx %ld\n", + bp->branch_insn_address, bp->branch_target, bp->branch); + return; + } + + pr_debug(KERN_INFO "ptrace clear_breakpoint: addr 0x%.8lx insn 0x%.8lx %d\n", addr, + bp->insn, ret); + + if (ret != 4 || old_insn != OR1K_TRAP) + pr_debug(KERN_ERR "%s:%d: corrupted breakpoint at 0x%08lx (0x%08x)\n", tsk->comm, + task_pid_nr(tsk), addr, old_insn); + + bp->set = 0; +} + + +void ptrace_set_bpt(struct task_struct *tsk) +{ + struct pt_regs *regs; + unsigned long pc; + u32 insn; + int res; + + regs = task_pt_regs(tsk); + pc = instruction_pointer(regs); /* This is NPC */ + + res = read_instr(tsk, pc, &insn); + if (!res) { + struct debug_info *dbg = ¤t_thread_info()->debug; + unsigned long npc; + + /* First check if breakpoint is still set, if so, probably + just let us run until we hit it. + This can occur because perhaps we caused an exception before + a jump or in a delay slot, and the OR1K exception handlers + will rewind us to the branch before the delay slot or jump + target. + This way we skip everything that goes on during an exception + and can hopefully just track the program's execution. + */ + if (dbg->bp.set) + return; + + /* Are we in delay slot of branch we should take? */ + if (dbg->bp.branch == 1) + { + /* Branch target is already determined. Set l.trap. */ + npc = dbg->bp.branch_target; + + // Reset NPC to branch instruction - should be one + // before this one. + instruction_pointer(regs) = dbg->bp.branch_insn_address; + + /* Now mark this as no longer needing to be taken */ + dbg->bp.branch = 0; + } + else + { + npc = get_next_address(tsk, pc, insn); + } + + add_breakpoint(tsk, dbg, npc); + } +} + + +/* + * Ensure no single-step breakpoint is pending. Returns non-zero + * value if child was being single-stepped. + */ +void ptrace_cancel_bpt(struct task_struct *tsk) +{ + clear_breakpoint(tsk, (struct debug_entry*) ¤t_thread_info()->debug); +} + + + +/* + * Called by kernel/ptrace.c when detaching.. + * + * Make sure the single step bit is not set. + */ +void ptrace_disable(struct task_struct *child) +{ + pr_debug(KERN_WARNING "ptrace_disable(): TODO\n"); + + user_disable_single_step(child); + clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); +} + + +/* + * Read the word at offset "off" into the "struct user". We + * actually access the pt_regs stored on the kernel stack. + */ +static int ptrace_read_user(struct task_struct *tsk, unsigned long off, + unsigned long __user *ret) +{ + struct pt_regs* regs; + unsigned long tmp; + + /* if (off & 3 || off >= sizeof(struct user)) + return -EIO;*/ + + regs = task_pt_regs(tsk); + + tmp = 0; + if (off == PT_TEXT_ADDR) + tmp = tsk->mm->start_code; + else if (off == PT_DATA_ADDR) + tmp = tsk->mm->start_data; + else if (off == PT_TEXT_END_ADDR) + tmp = tsk->mm->end_code; + else if (off < sizeof(struct pt_regs)) { + tmp = *((unsigned long*)((char*)regs + off)); + } + + return put_user(tmp, ret); +} + +/* + * Write the word at offset "off" into "struct user". We + * actually access the pt_regs stored on the kernel stack. + */ +static int ptrace_write_user(struct task_struct *tsk, unsigned long off, + unsigned long val) +{ + struct pt_regs* regs; + + /*if (off & 3 || off >= sizeof(struct user)) + return -EIO;*/ + + if (off >= sizeof(struct pt_regs)) + return 0; + + regs = task_pt_regs(tsk); + + if (off != offsetof(struct pt_regs, sr)) { + *((unsigned long*)((char*)regs + off)) = val; + } else { + /* Prevent any process from setting the SR flags and + * thus elevating privileges + */ + } + + return 0; +} + +long arch_ptrace(struct task_struct *child, long request, unsigned long addr, + unsigned long data) +{ + int ret; + unsigned long __user *datap = (unsigned long __user *)data; + + switch (request) { + /* read the word at location addr in the USER area. */ + case PTRACE_PEEKUSR: + ret = ptrace_read_user(child, addr, datap); + break; + case PTRACE_POKEUSR: + ret = ptrace_write_user(child, addr, data); + break; + default: + ret = ptrace_request(child, request, addr, data); + break; + } + + single_step_set(current); + + return ret; +} + +/* notification of system call entry/exit + * - triggered by current->work.syscall_trace + */ +asmlinkage long +do_syscall_trace_enter(struct pt_regs *regs) +{ + long ret = 0; + + if (test_thread_flag(TIF_SYSCALL_TRACE) && + tracehook_report_syscall_entry(regs)) + /* + * Tracing decided this syscall should not happen. + * We'll return a bogus call number to get an ENOSYS + * error, but leave the original number in . + */ + ret = -1L; + +/* if (unlikely(test_thread_flag(TIF_SYSCALL_TRACEPOINT))) + trace_sys_enter(regs, regs->syscallno); +*/ + + /* Are these regs right??? */ + if (unlikely(current->audit_context)) + audit_syscall_entry(audit_arch(), regs->syscallno, + regs->gpr[3], regs->gpr[4], + regs->gpr[5], regs->gpr[6]); + + return ret ?: regs->syscallno; + +#if 0 +/*FIXME : audit the rest of this */ + + + /* + * this isn't the same as continuing with a signal, but it will do + * for normal use. strace only continues with a signal if the + * stopping signal is not SIGTRAP. -brl + */ + if (current->exit_code) { + send_sig(current->exit_code, current, 1); + current->exit_code = 0; + } + out: + /*FIXME: audit_arch isn't even defined for openrisc */ + /*FIXME: What's with the register numbers here... makes no sense */ + if (unlikely(current->audit_context) && !entryexit) + audit_syscall_entry(audit_arch(), regs->regs[2], + regs->regs[4], regs->regs[5], + regs->regs[6], regs->regs[7]);/*RGD*/ + +#endif +} + +asmlinkage void +do_syscall_trace_leave(struct pt_regs* regs) +{ + int step; + + if (unlikely(current->audit_context)) + audit_syscall_exit(AUDITSC_RESULT(regs->gpr[11]), + regs->gpr[11]); + +/* if (unlikely(test_thread_flag(TIF_SYSCALL_TRACEPOINT))) + trace_sys_exit(regs, regs->gprs[9]); +*/ + + step = test_thread_flag(TIF_SINGLESTEP); + if (step || test_thread_flag(TIF_SYSCALL_TRACE)) + tracehook_report_syscall_exit(regs, step); +} diff --git a/arch/openrisc/kernel/ptrace.h b/arch/openrisc/kernel/ptrace.h new file mode 100644 index 0000000..71cc454 --- /dev/null +++ b/arch/openrisc/kernel/ptrace.h @@ -0,0 +1,37 @@ +/* + * linux/arch/openrisc/kernel/ptrace.h + * + * Copyright (C) 2000-2003 Russell King + * + * 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. + */ +#include + +extern void ptrace_cancel_bpt(struct task_struct *); +extern void ptrace_set_bpt(struct task_struct *); +extern void ptrace_break(struct task_struct *, struct pt_regs *); + +/* + * Send SIGTRAP if we're single-stepping + */ +static inline void single_step_trap(struct task_struct *task) +{ + if (test_tsk_thread_flag(task, TIF_SINGLESTEP)){ + ptrace_cancel_bpt(task); + send_sig(SIGTRAP, task, 1); + } +} + +static inline void single_step_clear(struct task_struct *task) +{ + if (test_tsk_thread_flag(task, TIF_SINGLESTEP)) + ptrace_cancel_bpt(task); +} + +static inline void single_step_set(struct task_struct *task) +{ + if (test_tsk_thread_flag(task, TIF_SINGLESTEP)) + ptrace_set_bpt(task); +} -- 1.7.4.1 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/