diff options
Diffstat (limited to 'arch/tile/kernel/ptrace.c')
-rw-r--r-- | arch/tile/kernel/ptrace.c | 203 |
1 files changed, 203 insertions, 0 deletions
diff --git a/arch/tile/kernel/ptrace.c b/arch/tile/kernel/ptrace.c new file mode 100644 index 000000000000..468054928e7d --- /dev/null +++ b/arch/tile/kernel/ptrace.c | |||
@@ -0,0 +1,203 @@ | |||
1 | /* | ||
2 | * Copyright 2010 Tilera Corporation. All Rights Reserved. | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or | ||
5 | * modify it under the terms of the GNU General Public License | ||
6 | * as published by the Free Software Foundation, version 2. | ||
7 | * | ||
8 | * This program is distributed in the hope that it will be useful, but | ||
9 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
10 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or | ||
11 | * NON INFRINGEMENT. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * Copied from i386: Ross Biro 1/23/92 | ||
15 | */ | ||
16 | |||
17 | #include <linux/kernel.h> | ||
18 | #include <linux/ptrace.h> | ||
19 | #include <linux/kprobes.h> | ||
20 | #include <linux/compat.h> | ||
21 | #include <linux/uaccess.h> | ||
22 | |||
23 | void user_enable_single_step(struct task_struct *child) | ||
24 | { | ||
25 | set_tsk_thread_flag(child, TIF_SINGLESTEP); | ||
26 | } | ||
27 | |||
28 | void user_disable_single_step(struct task_struct *child) | ||
29 | { | ||
30 | clear_tsk_thread_flag(child, TIF_SINGLESTEP); | ||
31 | } | ||
32 | |||
33 | /* | ||
34 | * This routine will put a word on the process's privileged stack. | ||
35 | */ | ||
36 | static void putreg(struct task_struct *task, | ||
37 | unsigned long addr, unsigned long value) | ||
38 | { | ||
39 | unsigned int regno = addr / sizeof(unsigned long); | ||
40 | struct pt_regs *childregs = task_pt_regs(task); | ||
41 | childregs->regs[regno] = value; | ||
42 | childregs->flags |= PT_FLAGS_RESTORE_REGS; | ||
43 | } | ||
44 | |||
45 | static unsigned long getreg(struct task_struct *task, unsigned long addr) | ||
46 | { | ||
47 | unsigned int regno = addr / sizeof(unsigned long); | ||
48 | struct pt_regs *childregs = task_pt_regs(task); | ||
49 | return childregs->regs[regno]; | ||
50 | } | ||
51 | |||
52 | /* | ||
53 | * Called by kernel/ptrace.c when detaching.. | ||
54 | */ | ||
55 | void ptrace_disable(struct task_struct *child) | ||
56 | { | ||
57 | clear_tsk_thread_flag(child, TIF_SINGLESTEP); | ||
58 | |||
59 | /* | ||
60 | * These two are currently unused, but will be set by arch_ptrace() | ||
61 | * and used in the syscall assembly when we do support them. | ||
62 | */ | ||
63 | clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | ||
64 | } | ||
65 | |||
66 | long arch_ptrace(struct task_struct *child, long request, long addr, long data) | ||
67 | { | ||
68 | unsigned long __user *datap; | ||
69 | unsigned long tmp; | ||
70 | int i; | ||
71 | long ret = -EIO; | ||
72 | |||
73 | #ifdef CONFIG_COMPAT | ||
74 | if (task_thread_info(current)->status & TS_COMPAT) | ||
75 | data = (u32)data; | ||
76 | if (task_thread_info(child)->status & TS_COMPAT) | ||
77 | addr = (u32)addr; | ||
78 | #endif | ||
79 | datap = (unsigned long __user *)data; | ||
80 | |||
81 | switch (request) { | ||
82 | |||
83 | case PTRACE_PEEKUSR: /* Read register from pt_regs. */ | ||
84 | if (addr & (sizeof(data)-1)) | ||
85 | break; | ||
86 | if (addr < 0 || addr >= PTREGS_SIZE) | ||
87 | break; | ||
88 | tmp = getreg(child, addr); /* Read register */ | ||
89 | ret = put_user(tmp, datap); | ||
90 | break; | ||
91 | |||
92 | case PTRACE_POKEUSR: /* Write register in pt_regs. */ | ||
93 | if (addr & (sizeof(data)-1)) | ||
94 | break; | ||
95 | if (addr < 0 || addr >= PTREGS_SIZE) | ||
96 | break; | ||
97 | putreg(child, addr, data); /* Write register */ | ||
98 | break; | ||
99 | |||
100 | case PTRACE_GETREGS: /* Get all registers from the child. */ | ||
101 | if (!access_ok(VERIFY_WRITE, datap, PTREGS_SIZE)) | ||
102 | break; | ||
103 | for (i = 0; i < PTREGS_SIZE; i += sizeof(long)) { | ||
104 | ret = __put_user(getreg(child, i), datap); | ||
105 | if (ret != 0) | ||
106 | break; | ||
107 | datap++; | ||
108 | } | ||
109 | break; | ||
110 | |||
111 | case PTRACE_SETREGS: /* Set all registers in the child. */ | ||
112 | if (!access_ok(VERIFY_READ, datap, PTREGS_SIZE)) | ||
113 | break; | ||
114 | for (i = 0; i < PTREGS_SIZE; i += sizeof(long)) { | ||
115 | ret = __get_user(tmp, datap); | ||
116 | if (ret != 0) | ||
117 | break; | ||
118 | putreg(child, i, tmp); | ||
119 | datap++; | ||
120 | } | ||
121 | break; | ||
122 | |||
123 | case PTRACE_GETFPREGS: /* Get the child FPU state. */ | ||
124 | case PTRACE_SETFPREGS: /* Set the child FPU state. */ | ||
125 | break; | ||
126 | |||
127 | case PTRACE_SETOPTIONS: | ||
128 | /* Support TILE-specific ptrace options. */ | ||
129 | child->ptrace &= ~PT_TRACE_MASK_TILE; | ||
130 | tmp = data & PTRACE_O_MASK_TILE; | ||
131 | data &= ~PTRACE_O_MASK_TILE; | ||
132 | ret = ptrace_request(child, request, addr, data); | ||
133 | if (tmp & PTRACE_O_TRACEMIGRATE) | ||
134 | child->ptrace |= PT_TRACE_MIGRATE; | ||
135 | break; | ||
136 | |||
137 | default: | ||
138 | #ifdef CONFIG_COMPAT | ||
139 | if (task_thread_info(current)->status & TS_COMPAT) { | ||
140 | ret = compat_ptrace_request(child, request, | ||
141 | addr, data); | ||
142 | break; | ||
143 | } | ||
144 | #endif | ||
145 | ret = ptrace_request(child, request, addr, data); | ||
146 | break; | ||
147 | } | ||
148 | |||
149 | return ret; | ||
150 | } | ||
151 | |||
152 | #ifdef CONFIG_COMPAT | ||
153 | /* Not used; we handle compat issues in arch_ptrace() directly. */ | ||
154 | long compat_arch_ptrace(struct task_struct *child, compat_long_t request, | ||
155 | compat_ulong_t addr, compat_ulong_t data) | ||
156 | { | ||
157 | BUG(); | ||
158 | } | ||
159 | #endif | ||
160 | |||
161 | void do_syscall_trace(void) | ||
162 | { | ||
163 | if (!test_thread_flag(TIF_SYSCALL_TRACE)) | ||
164 | return; | ||
165 | |||
166 | if (!(current->ptrace & PT_PTRACED)) | ||
167 | return; | ||
168 | |||
169 | /* | ||
170 | * The 0x80 provides a way for the tracing parent to distinguish | ||
171 | * between a syscall stop and SIGTRAP delivery | ||
172 | */ | ||
173 | ptrace_notify(SIGTRAP|((current->ptrace & PT_TRACESYSGOOD) ? 0x80 : 0)); | ||
174 | |||
175 | /* | ||
176 | * this isn't the same as continuing with a signal, but it will do | ||
177 | * for normal use. strace only continues with a signal if the | ||
178 | * stopping signal is not SIGTRAP. -brl | ||
179 | */ | ||
180 | if (current->exit_code) { | ||
181 | send_sig(current->exit_code, current, 1); | ||
182 | current->exit_code = 0; | ||
183 | } | ||
184 | } | ||
185 | |||
186 | void send_sigtrap(struct task_struct *tsk, struct pt_regs *regs, int error_code) | ||
187 | { | ||
188 | struct siginfo info; | ||
189 | |||
190 | memset(&info, 0, sizeof(info)); | ||
191 | info.si_signo = SIGTRAP; | ||
192 | info.si_code = TRAP_BRKPT; | ||
193 | info.si_addr = (void __user *) regs->pc; | ||
194 | |||
195 | /* Send us the fakey SIGTRAP */ | ||
196 | force_sig_info(SIGTRAP, &info, tsk); | ||
197 | } | ||
198 | |||
199 | /* Handle synthetic interrupt delivered only by the simulator. */ | ||
200 | void __kprobes do_breakpoint(struct pt_regs* regs, int fault_num) | ||
201 | { | ||
202 | send_sigtrap(current, regs, fault_num); | ||
203 | } | ||