| // SPDX-License-Identifier: GPL-2.0 |
| /* ptrace.c: Sparc process tracing support. |
| * |
| * Copyright (C) 1996, 2008 David S. Miller (davem@davemloft.net) |
| * |
| * Based upon code written by Ross Biro, Linus Torvalds, Bob Manson, |
| * and David Mosberger. |
| * |
| * Added Linux support -miguel (weird, eh?, the original code was meant |
| * to emulate SunOS). |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/sched.h> |
| #include <linux/mm.h> |
| #include <linux/errno.h> |
| #include <linux/ptrace.h> |
| #include <linux/user.h> |
| #include <linux/smp.h> |
| #include <linux/security.h> |
| #include <linux/signal.h> |
| #include <linux/regset.h> |
| #include <linux/elf.h> |
| |
| #include <linux/uaccess.h> |
| #include <asm/cacheflush.h> |
| |
| #include "kernel.h" |
| |
| /* #define ALLOW_INIT_TRACING */ |
| |
| /* |
| * Called by kernel/ptrace.c when detaching.. |
| * |
| * Make sure single step bits etc are not set. |
| */ |
| void ptrace_disable(struct task_struct *child) |
| { |
| /* nothing to do */ |
| } |
| |
| enum sparc_regset { |
| REGSET_GENERAL, |
| REGSET_FP, |
| }; |
| |
| static int regwindow32_get(struct task_struct *target, |
| const struct pt_regs *regs, |
| u32 *uregs) |
| { |
| unsigned long reg_window = regs->u_regs[UREG_I6]; |
| int size = 16 * sizeof(u32); |
| |
| if (target == current) { |
| if (copy_from_user(uregs, (void __user *)reg_window, size)) |
| return -EFAULT; |
| } else { |
| if (access_process_vm(target, reg_window, uregs, size, |
| FOLL_FORCE) != size) |
| return -EFAULT; |
| } |
| return 0; |
| } |
| |
| static int regwindow32_set(struct task_struct *target, |
| const struct pt_regs *regs, |
| u32 *uregs) |
| { |
| unsigned long reg_window = regs->u_regs[UREG_I6]; |
| int size = 16 * sizeof(u32); |
| |
| if (target == current) { |
| if (copy_to_user((void __user *)reg_window, uregs, size)) |
| return -EFAULT; |
| } else { |
| if (access_process_vm(target, reg_window, uregs, size, |
| FOLL_FORCE | FOLL_WRITE) != size) |
| return -EFAULT; |
| } |
| return 0; |
| } |
| |
| static int genregs32_get(struct task_struct *target, |
| const struct user_regset *regset, |
| struct membuf to) |
| { |
| const struct pt_regs *regs = target->thread.kregs; |
| u32 uregs[16]; |
| |
| if (target == current) |
| flush_user_windows(); |
| |
| membuf_write(&to, regs->u_regs, 16 * sizeof(u32)); |
| if (!to.left) |
| return 0; |
| if (regwindow32_get(target, regs, uregs)) |
| return -EFAULT; |
| membuf_write(&to, uregs, 16 * sizeof(u32)); |
| membuf_store(&to, regs->psr); |
| membuf_store(&to, regs->pc); |
| membuf_store(&to, regs->npc); |
| membuf_store(&to, regs->y); |
| return membuf_zero(&to, 2 * sizeof(u32)); |
| } |
| |
| static int genregs32_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 = target->thread.kregs; |
| u32 uregs[16]; |
| u32 psr; |
| int ret; |
| |
| if (target == current) |
| flush_user_windows(); |
| |
| ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, |
| regs->u_regs, |
| 0, 16 * sizeof(u32)); |
| if (ret || !count) |
| return ret; |
| |
| if (regwindow32_get(target, regs, uregs)) |
| return -EFAULT; |
| ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, |
| uregs, |
| 16 * sizeof(u32), 32 * sizeof(u32)); |
| if (ret) |
| return ret; |
| if (regwindow32_set(target, regs, uregs)) |
| return -EFAULT; |
| if (!count) |
| return 0; |
| |
| ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, |
| &psr, |
| 32 * sizeof(u32), 33 * sizeof(u32)); |
| if (ret) |
| return ret; |
| regs->psr = (regs->psr & ~(PSR_ICC | PSR_SYSCALL)) | |
| (psr & (PSR_ICC | PSR_SYSCALL)); |
| if (!count) |
| return 0; |
| ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, |
| ®s->pc, |
| 33 * sizeof(u32), 34 * sizeof(u32)); |
| if (ret || !count) |
| return ret; |
| ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, |
| ®s->npc, |
| 34 * sizeof(u32), 35 * sizeof(u32)); |
| if (ret || !count) |
| return ret; |
| ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, |
| ®s->y, |
| 35 * sizeof(u32), 36 * sizeof(u32)); |
| if (ret || !count) |
| return ret; |
| return user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, |
| 36 * sizeof(u32), 38 * sizeof(u32)); |
| } |
| |
| static int fpregs32_get(struct task_struct *target, |
| const struct user_regset *regset, |
| struct membuf to) |
| { |
| #if 0 |
| if (target == current) |
| save_and_clear_fpu(); |
| #endif |
| |
| membuf_write(&to, target->thread.float_regs, 32 * sizeof(u32)); |
| membuf_zero(&to, sizeof(u32)); |
| membuf_write(&to, &target->thread.fsr, sizeof(u32)); |
| membuf_store(&to, (u32)((1 << 8) | (8 << 16))); |
| return membuf_zero(&to, 64 * sizeof(u32)); |
| } |
| |
| static int fpregs32_set(struct task_struct *target, |
| const struct user_regset *regset, |
| unsigned int pos, unsigned int count, |
| const void *kbuf, const void __user *ubuf) |
| { |
| unsigned long *fpregs = target->thread.float_regs; |
| int ret; |
| |
| #if 0 |
| if (target == current) |
| save_and_clear_fpu(); |
| #endif |
| ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, |
| fpregs, |
| 0, 32 * sizeof(u32)); |
| if (!ret) |
| user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, |
| 32 * sizeof(u32), |
| 33 * sizeof(u32)); |
| if (!ret) |
| ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, |
| &target->thread.fsr, |
| 33 * sizeof(u32), |
| 34 * sizeof(u32)); |
| if (!ret) |
| ret = user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, |
| 34 * sizeof(u32), -1); |
| return ret; |
| } |
| |
| static const struct user_regset sparc32_regsets[] = { |
| /* Format is: |
| * G0 --> G7 |
| * O0 --> O7 |
| * L0 --> L7 |
| * I0 --> I7 |
| * PSR, PC, nPC, Y, WIM, TBR |
| */ |
| [REGSET_GENERAL] = { |
| .core_note_type = NT_PRSTATUS, |
| .n = 38, |
| .size = sizeof(u32), .align = sizeof(u32), |
| .regset_get = genregs32_get, .set = genregs32_set |
| }, |
| /* Format is: |
| * F0 --> F31 |
| * empty 32-bit word |
| * FSR (32--bit word) |
| * FPU QUEUE COUNT (8-bit char) |
| * FPU QUEUE ENTRYSIZE (8-bit char) |
| * FPU ENABLED (8-bit char) |
| * empty 8-bit char |
| * FPU QUEUE (64 32-bit ints) |
| */ |
| [REGSET_FP] = { |
| .core_note_type = NT_PRFPREG, |
| .n = 99, |
| .size = sizeof(u32), .align = sizeof(u32), |
| .regset_get = fpregs32_get, .set = fpregs32_set |
| }, |
| }; |
| |
| static int getregs_get(struct task_struct *target, |
| const struct user_regset *regset, |
| struct membuf to) |
| { |
| const struct pt_regs *regs = target->thread.kregs; |
| |
| if (target == current) |
| flush_user_windows(); |
| |
| membuf_store(&to, regs->psr); |
| membuf_store(&to, regs->pc); |
| membuf_store(&to, regs->npc); |
| membuf_store(&to, regs->y); |
| return membuf_write(&to, regs->u_regs + 1, 15 * sizeof(u32)); |
| } |
| |
| static int setregs_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 = target->thread.kregs; |
| u32 v[4]; |
| int ret; |
| |
| if (target == current) |
| flush_user_windows(); |
| |
| ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, |
| v, |
| 0, 4 * sizeof(u32)); |
| if (ret) |
| return ret; |
| regs->psr = (regs->psr & ~(PSR_ICC | PSR_SYSCALL)) | |
| (v[0] & (PSR_ICC | PSR_SYSCALL)); |
| regs->pc = v[1]; |
| regs->npc = v[2]; |
| regs->y = v[3]; |
| return user_regset_copyin(&pos, &count, &kbuf, &ubuf, |
| regs->u_regs + 1, |
| 4 * sizeof(u32) , 19 * sizeof(u32)); |
| } |
| |
| static int getfpregs_get(struct task_struct *target, |
| const struct user_regset *regset, |
| struct membuf to) |
| { |
| #if 0 |
| if (target == current) |
| save_and_clear_fpu(); |
| #endif |
| membuf_write(&to, &target->thread.float_regs, 32 * sizeof(u32)); |
| membuf_write(&to, &target->thread.fsr, sizeof(u32)); |
| return membuf_zero(&to, 35 * sizeof(u32)); |
| } |
| |
| static int setfpregs_set(struct task_struct *target, |
| const struct user_regset *regset, |
| unsigned int pos, unsigned int count, |
| const void *kbuf, const void __user *ubuf) |
| { |
| unsigned long *fpregs = target->thread.float_regs; |
| int ret; |
| |
| #if 0 |
| if (target == current) |
| save_and_clear_fpu(); |
| #endif |
| ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, |
| fpregs, |
| 0, 32 * sizeof(u32)); |
| if (ret) |
| return ret; |
| return user_regset_copyin(&pos, &count, &kbuf, &ubuf, |
| &target->thread.fsr, |
| 32 * sizeof(u32), |
| 33 * sizeof(u32)); |
| } |
| |
| static const struct user_regset ptrace32_regsets[] = { |
| [REGSET_GENERAL] = { |
| .n = 19, .size = sizeof(u32), |
| .regset_get = getregs_get, .set = setregs_set, |
| }, |
| [REGSET_FP] = { |
| .n = 68, .size = sizeof(u32), |
| .regset_get = getfpregs_get, .set = setfpregs_set, |
| }, |
| }; |
| |
| static const struct user_regset_view ptrace32_view = { |
| .regsets = ptrace32_regsets, .n = ARRAY_SIZE(ptrace32_regsets) |
| }; |
| |
| static const struct user_regset_view user_sparc32_view = { |
| .name = "sparc", .e_machine = EM_SPARC, |
| .regsets = sparc32_regsets, .n = ARRAY_SIZE(sparc32_regsets) |
| }; |
| |
| const struct user_regset_view *task_user_regset_view(struct task_struct *task) |
| { |
| return &user_sparc32_view; |
| } |
| |
| struct fps { |
| unsigned long regs[32]; |
| unsigned long fsr; |
| unsigned long flags; |
| unsigned long extra; |
| unsigned long fpqd; |
| struct fq { |
| unsigned long *insnaddr; |
| unsigned long insn; |
| } fpq[16]; |
| }; |
| |
| long arch_ptrace(struct task_struct *child, long request, |
| unsigned long addr, unsigned long data) |
| { |
| unsigned long addr2 = current->thread.kregs->u_regs[UREG_I4]; |
| void __user *addr2p; |
| struct pt_regs __user *pregs; |
| struct fps __user *fps; |
| int ret; |
| |
| addr2p = (void __user *) addr2; |
| pregs = (struct pt_regs __user *) addr; |
| fps = (struct fps __user *) addr; |
| |
| switch(request) { |
| case PTRACE_GETREGS: { |
| ret = copy_regset_to_user(child, &ptrace32_view, |
| REGSET_GENERAL, 0, |
| 19 * sizeof(u32), |
| pregs); |
| break; |
| } |
| |
| case PTRACE_SETREGS: { |
| ret = copy_regset_from_user(child, &ptrace32_view, |
| REGSET_GENERAL, 0, |
| 19 * sizeof(u32), |
| pregs); |
| break; |
| } |
| |
| case PTRACE_GETFPREGS: { |
| ret = copy_regset_to_user(child, &ptrace32_view, |
| REGSET_FP, 0, |
| 68 * sizeof(u32), |
| fps); |
| break; |
| } |
| |
| case PTRACE_SETFPREGS: { |
| ret = copy_regset_from_user(child, &ptrace32_view, |
| REGSET_FP, 0, |
| 33 * sizeof(u32), |
| fps); |
| break; |
| } |
| |
| case PTRACE_READTEXT: |
| case PTRACE_READDATA: |
| ret = ptrace_readdata(child, addr, addr2p, data); |
| |
| if (ret == data) |
| ret = 0; |
| else if (ret >= 0) |
| ret = -EIO; |
| break; |
| |
| case PTRACE_WRITETEXT: |
| case PTRACE_WRITEDATA: |
| ret = ptrace_writedata(child, addr2p, addr, data); |
| |
| if (ret == data) |
| ret = 0; |
| else if (ret >= 0) |
| ret = -EIO; |
| break; |
| |
| default: |
| if (request == PTRACE_SPARC_DETACH) |
| request = PTRACE_DETACH; |
| ret = ptrace_request(child, request, addr, data); |
| break; |
| } |
| |
| return ret; |
| } |
| |
| asmlinkage int syscall_trace(struct pt_regs *regs, int syscall_exit_p) |
| { |
| int ret = 0; |
| |
| if (test_thread_flag(TIF_SYSCALL_TRACE)) { |
| if (syscall_exit_p) |
| ptrace_report_syscall_exit(regs, 0); |
| else |
| ret = ptrace_report_syscall_entry(regs); |
| } |
| |
| return ret; |
| } |