Commit | Line | Data |
---|---|---|
5a0015d6 CZ |
1 | // TODO some minor issues |
2 | /* | |
3 | * This file is subject to the terms and conditions of the GNU General Public | |
4 | * License. See the file "COPYING" in the main directory of this archive | |
5 | * for more details. | |
6 | * | |
c658eac6 | 7 | * Copyright (C) 2001 - 2007 Tensilica Inc. |
5a0015d6 CZ |
8 | * |
9 | * Joe Taylor <joe@tensilica.com, joetylr@yahoo.com> | |
10 | * Chris Zankel <chris@zankel.net> | |
11 | * Scott Foehner<sfoehner@yahoo.com>, | |
12 | * Kevin Chea | |
13 | * Marc Gauthier<marc@tensilica.com> <marc@alumni.uwaterloo.ca> | |
14 | */ | |
15 | ||
5a0015d6 CZ |
16 | #include <linux/kernel.h> |
17 | #include <linux/sched.h> | |
18 | #include <linux/mm.h> | |
19 | #include <linux/errno.h> | |
20 | #include <linux/ptrace.h> | |
21 | #include <linux/smp.h> | |
5a0015d6 | 22 | #include <linux/security.h> |
0ee23b50 | 23 | #include <linux/signal.h> |
5a0015d6 CZ |
24 | |
25 | #include <asm/pgtable.h> | |
26 | #include <asm/page.h> | |
27 | #include <asm/system.h> | |
28 | #include <asm/uaccess.h> | |
29 | #include <asm/ptrace.h> | |
30 | #include <asm/elf.h> | |
c658eac6 | 31 | #include <asm/coprocessor.h> |
5a0015d6 | 32 | |
6d75ca10 CH |
33 | |
34 | void user_enable_single_step(struct task_struct *child) | |
35 | { | |
36 | child->ptrace |= PT_SINGLESTEP; | |
37 | } | |
38 | ||
39 | void user_disable_single_step(struct task_struct *child) | |
40 | { | |
41 | child->ptrace &= ~PT_SINGLESTEP; | |
42 | } | |
43 | ||
5a0015d6 | 44 | /* |
c658eac6 | 45 | * Called by kernel/ptrace.c when detaching to disable single stepping. |
5a0015d6 CZ |
46 | */ |
47 | ||
48 | void ptrace_disable(struct task_struct *child) | |
49 | { | |
50 | /* Nothing to do.. */ | |
51 | } | |
52 | ||
c658eac6 | 53 | int ptrace_getregs(struct task_struct *child, void __user *uregs) |
5a0015d6 | 54 | { |
c658eac6 CZ |
55 | struct pt_regs *regs = task_pt_regs(child); |
56 | xtensa_gregset_t __user *gregset = uregs; | |
c658eac6 | 57 | unsigned long wm = regs->wmask; |
42086cec CZ |
58 | unsigned long wb = regs->windowbase; |
59 | int live, i; | |
c658eac6 CZ |
60 | |
61 | if (!access_ok(VERIFY_WRITE, uregs, sizeof(xtensa_gregset_t))) | |
62 | return -EIO; | |
63 | ||
42086cec CZ |
64 | __put_user(regs->pc, &gregset->pc); |
65 | __put_user(regs->ps & ~(1 << PS_EXCM_BIT), &gregset->ps); | |
66 | __put_user(regs->lbeg, &gregset->lbeg); | |
67 | __put_user(regs->lend, &gregset->lend); | |
68 | __put_user(regs->lcount, &gregset->lcount); | |
69 | __put_user(regs->windowstart, &gregset->windowstart); | |
70 | __put_user(regs->windowbase, &gregset->windowbase); | |
c658eac6 CZ |
71 | |
72 | live = (wm & 2) ? 4 : (wm & 4) ? 8 : (wm & 8) ? 12 : 16; | |
c658eac6 | 73 | |
42086cec CZ |
74 | for (i = 0; i < live; i++) |
75 | __put_user(regs->areg[i],gregset->a+((wb*4+i)%XCHAL_NUM_AREGS)); | |
76 | for (i = XCHAL_NUM_AREGS - (wm >> 4) * 4; i < XCHAL_NUM_AREGS; i++) | |
77 | __put_user(regs->areg[i],gregset->a+((wb*4+i)%XCHAL_NUM_AREGS)); | |
78 | ||
79 | return 0; | |
c658eac6 | 80 | } |
5a0015d6 | 81 | |
c658eac6 CZ |
82 | int ptrace_setregs(struct task_struct *child, void __user *uregs) |
83 | { | |
84 | struct pt_regs *regs = task_pt_regs(child); | |
85 | xtensa_gregset_t *gregset = uregs; | |
86 | const unsigned long ps_mask = PS_CALLINC_MASK | PS_OWB_MASK; | |
c658eac6 | 87 | unsigned long ps; |
42086cec | 88 | unsigned long wb; |
c658eac6 CZ |
89 | |
90 | if (!access_ok(VERIFY_WRITE, uregs, sizeof(xtensa_gregset_t))) | |
91 | return -EIO; | |
92 | ||
42086cec CZ |
93 | __get_user(regs->pc, &gregset->pc); |
94 | __get_user(ps, &gregset->ps); | |
95 | __get_user(regs->lbeg, &gregset->lbeg); | |
96 | __get_user(regs->lend, &gregset->lend); | |
97 | __get_user(regs->lcount, &gregset->lcount); | |
98 | __get_user(regs->windowstart, &gregset->windowstart); | |
99 | __get_user(wb, &gregset->windowbase); | |
c658eac6 CZ |
100 | |
101 | regs->ps = (regs->ps & ~ps_mask) | (ps & ps_mask) | (1 << PS_EXCM_BIT); | |
102 | ||
42086cec CZ |
103 | if (wb >= XCHAL_NUM_AREGS / 4) |
104 | return -EFAULT; | |
c658eac6 | 105 | |
42086cec CZ |
106 | regs->windowbase = wb; |
107 | ||
108 | if (wb != 0 && __copy_from_user(regs->areg + XCHAL_NUM_AREGS - wb * 4, | |
109 | gregset->a, wb * 16)) | |
110 | return -EFAULT; | |
111 | ||
112 | if (__copy_from_user(regs->areg, gregset->a + wb*4, (WSBITS-wb) * 16)) | |
113 | return -EFAULT; | |
114 | ||
115 | return 0; | |
c658eac6 | 116 | } |
5a0015d6 | 117 | |
5a0015d6 | 118 | |
c658eac6 CZ |
119 | int ptrace_getxregs(struct task_struct *child, void __user *uregs) |
120 | { | |
121 | struct pt_regs *regs = task_pt_regs(child); | |
122 | struct thread_info *ti = task_thread_info(child); | |
123 | elf_xtregs_t __user *xtregs = uregs; | |
124 | int ret = 0; | |
125 | ||
126 | if (!access_ok(VERIFY_WRITE, uregs, sizeof(elf_xtregs_t))) | |
127 | return -EIO; | |
128 | ||
129 | #if XTENSA_HAVE_COPROCESSORS | |
130 | /* Flush all coprocessor registers to memory. */ | |
131 | coprocessor_flush_all(ti); | |
132 | ret |= __copy_to_user(&xtregs->cp0, &ti->xtregs_cp, | |
133 | sizeof(xtregs_coprocessor_t)); | |
134 | #endif | |
135 | ret |= __copy_to_user(&xtregs->opt, ®s->xtregs_opt, | |
136 | sizeof(xtregs->opt)); | |
137 | ret |= __copy_to_user(&xtregs->user,&ti->xtregs_user, | |
138 | sizeof(xtregs->user)); | |
5a0015d6 | 139 | |
c658eac6 CZ |
140 | return ret ? -EFAULT : 0; |
141 | } | |
142 | ||
143 | int ptrace_setxregs(struct task_struct *child, void __user *uregs) | |
144 | { | |
145 | struct thread_info *ti = task_thread_info(child); | |
146 | struct pt_regs *regs = task_pt_regs(child); | |
147 | elf_xtregs_t *xtregs = uregs; | |
148 | int ret = 0; | |
149 | ||
0d0138eb DR |
150 | if (!access_ok(VERIFY_READ, uregs, sizeof(elf_xtregs_t))) |
151 | return -EFAULT; | |
152 | ||
c658eac6 CZ |
153 | #if XTENSA_HAVE_COPROCESSORS |
154 | /* Flush all coprocessors before we overwrite them. */ | |
155 | coprocessor_flush_all(ti); | |
156 | coprocessor_release_all(ti); | |
157 | ||
158 | ret |= __copy_from_user(&ti->xtregs_cp, &xtregs->cp0, | |
159 | sizeof(xtregs_coprocessor_t)); | |
160 | #endif | |
161 | ret |= __copy_from_user(®s->xtregs_opt, &xtregs->opt, | |
162 | sizeof(xtregs->opt)); | |
163 | ret |= __copy_from_user(&ti->xtregs_user, &xtregs->user, | |
164 | sizeof(xtregs->user)); | |
165 | ||
166 | return ret ? -EFAULT : 0; | |
167 | } | |
168 | ||
169 | int ptrace_peekusr(struct task_struct *child, long regno, long __user *ret) | |
170 | { | |
171 | struct pt_regs *regs; | |
172 | unsigned long tmp; | |
173 | ||
174 | regs = task_pt_regs(child); | |
175 | tmp = 0; /* Default return value. */ | |
5a0015d6 | 176 | |
c658eac6 | 177 | switch(regno) { |
5a0015d6 CZ |
178 | |
179 | case REG_AR_BASE ... REG_AR_BASE + XCHAL_NUM_AREGS - 1: | |
c658eac6 | 180 | tmp = regs->areg[regno - REG_AR_BASE]; |
5a0015d6 | 181 | break; |
c658eac6 | 182 | |
5a0015d6 | 183 | case REG_A_BASE ... REG_A_BASE + 15: |
c658eac6 | 184 | tmp = regs->areg[regno - REG_A_BASE]; |
5a0015d6 | 185 | break; |
c658eac6 | 186 | |
5a0015d6 CZ |
187 | case REG_PC: |
188 | tmp = regs->pc; | |
189 | break; | |
c658eac6 | 190 | |
5a0015d6 CZ |
191 | case REG_PS: |
192 | /* Note: PS.EXCM is not set while user task is running; | |
193 | * its being set in regs is for exception handling | |
194 | * convenience. */ | |
173d6681 | 195 | tmp = (regs->ps & ~(1 << PS_EXCM_BIT)); |
5a0015d6 | 196 | break; |
c658eac6 | 197 | |
5a0015d6 | 198 | case REG_WB: |
c658eac6 CZ |
199 | break; /* tmp = 0 */ |
200 | ||
5a0015d6 | 201 | case REG_WS: |
c658eac6 CZ |
202 | { |
203 | unsigned long wb = regs->windowbase; | |
204 | unsigned long ws = regs->windowstart; | |
205 | tmp = ((ws>>wb) | (ws<<(WSBITS-wb))) & ((1<<WSBITS)-1); | |
5a0015d6 | 206 | break; |
c658eac6 | 207 | } |
5a0015d6 CZ |
208 | case REG_LBEG: |
209 | tmp = regs->lbeg; | |
210 | break; | |
c658eac6 | 211 | |
5a0015d6 CZ |
212 | case REG_LEND: |
213 | tmp = regs->lend; | |
214 | break; | |
c658eac6 | 215 | |
5a0015d6 CZ |
216 | case REG_LCOUNT: |
217 | tmp = regs->lcount; | |
218 | break; | |
c658eac6 | 219 | |
5a0015d6 CZ |
220 | case REG_SAR: |
221 | tmp = regs->sar; | |
222 | break; | |
c658eac6 | 223 | |
5a0015d6 CZ |
224 | case SYSCALL_NR: |
225 | tmp = regs->syscall; | |
226 | break; | |
5a0015d6 | 227 | |
c658eac6 CZ |
228 | default: |
229 | return -EIO; | |
230 | } | |
231 | return put_user(tmp, ret); | |
232 | } | |
5a0015d6 | 233 | |
c658eac6 CZ |
234 | int ptrace_pokeusr(struct task_struct *child, long regno, long val) |
235 | { | |
236 | struct pt_regs *regs; | |
237 | regs = task_pt_regs(child); | |
5a0015d6 | 238 | |
c658eac6 | 239 | switch (regno) { |
5a0015d6 | 240 | case REG_AR_BASE ... REG_AR_BASE + XCHAL_NUM_AREGS - 1: |
c658eac6 | 241 | regs->areg[regno - REG_AR_BASE] = val; |
5a0015d6 | 242 | break; |
c658eac6 | 243 | |
5a0015d6 | 244 | case REG_A_BASE ... REG_A_BASE + 15: |
c658eac6 | 245 | regs->areg[regno - REG_A_BASE] = val; |
5a0015d6 | 246 | break; |
c658eac6 | 247 | |
5a0015d6 | 248 | case REG_PC: |
c658eac6 | 249 | regs->pc = val; |
5a0015d6 | 250 | break; |
c658eac6 | 251 | |
5a0015d6 | 252 | case SYSCALL_NR: |
c658eac6 | 253 | regs->syscall = val; |
5a0015d6 | 254 | break; |
5a0015d6 CZ |
255 | |
256 | default: | |
c658eac6 CZ |
257 | return -EIO; |
258 | } | |
259 | return 0; | |
260 | } | |
261 | ||
9b05a69e NK |
262 | long arch_ptrace(struct task_struct *child, long request, |
263 | unsigned long addr, unsigned long data) | |
c658eac6 CZ |
264 | { |
265 | int ret = -EPERM; | |
5ef45079 | 266 | void __user *datap = (void __user *) data; |
c658eac6 CZ |
267 | |
268 | switch (request) { | |
269 | case PTRACE_PEEKTEXT: /* read word at location addr. */ | |
270 | case PTRACE_PEEKDATA: | |
271 | ret = generic_ptrace_peekdata(child, addr, data); | |
272 | break; | |
273 | ||
274 | case PTRACE_PEEKUSR: /* read register specified by addr. */ | |
5ef45079 | 275 | ret = ptrace_peekusr(child, addr, datap); |
c658eac6 CZ |
276 | break; |
277 | ||
278 | case PTRACE_POKETEXT: /* write the word at location addr. */ | |
279 | case PTRACE_POKEDATA: | |
280 | ret = generic_ptrace_pokedata(child, addr, data); | |
281 | break; | |
282 | ||
283 | case PTRACE_POKEUSR: /* write register specified by addr. */ | |
284 | ret = ptrace_pokeusr(child, addr, data); | |
5a0015d6 | 285 | break; |
5a0015d6 | 286 | |
5a0015d6 | 287 | case PTRACE_GETREGS: |
5ef45079 | 288 | ret = ptrace_getregs(child, datap); |
5a0015d6 | 289 | break; |
5a0015d6 CZ |
290 | |
291 | case PTRACE_SETREGS: | |
5ef45079 | 292 | ret = ptrace_setregs(child, datap); |
5a0015d6 | 293 | break; |
5a0015d6 | 294 | |
c658eac6 | 295 | case PTRACE_GETXTREGS: |
5ef45079 | 296 | ret = ptrace_getxregs(child, datap); |
5a0015d6 | 297 | break; |
5a0015d6 | 298 | |
c658eac6 | 299 | case PTRACE_SETXTREGS: |
5ef45079 | 300 | ret = ptrace_setxregs(child, datap); |
5a0015d6 CZ |
301 | break; |
302 | ||
5a0015d6 CZ |
303 | default: |
304 | ret = ptrace_request(child, request, addr, data); | |
c658eac6 | 305 | break; |
5a0015d6 | 306 | } |
c658eac6 | 307 | |
5a0015d6 CZ |
308 | return ret; |
309 | } | |
310 | ||
311 | void do_syscall_trace(void) | |
312 | { | |
5a0015d6 CZ |
313 | /* |
314 | * The 0x80 provides a way for the tracing parent to distinguish | |
315 | * between a syscall stop and SIGTRAP delivery | |
316 | */ | |
317 | ptrace_notify(SIGTRAP|((current->ptrace & PT_TRACESYSGOOD) ? 0x80 : 0)); | |
318 | ||
319 | /* | |
320 | * this isn't the same as continuing with a signal, but it will do | |
321 | * for normal use. strace only continues with a signal if the | |
322 | * stopping signal is not SIGTRAP. -brl | |
323 | */ | |
324 | if (current->exit_code) { | |
325 | send_sig(current->exit_code, current, 1); | |
326 | current->exit_code = 0; | |
327 | } | |
328 | } | |
fc4fb2ad CZ |
329 | |
330 | void do_syscall_trace_enter(struct pt_regs *regs) | |
331 | { | |
332 | if (test_thread_flag(TIF_SYSCALL_TRACE) | |
333 | && (current->ptrace & PT_PTRACED)) | |
334 | do_syscall_trace(); | |
335 | ||
336 | #if 0 | |
337 | if (unlikely(current->audit_context)) | |
338 | audit_syscall_entry(current, AUDIT_ARCH_XTENSA..); | |
339 | #endif | |
340 | } | |
341 | ||
342 | void do_syscall_trace_leave(struct pt_regs *regs) | |
343 | { | |
344 | if ((test_thread_flag(TIF_SYSCALL_TRACE)) | |
345 | && (current->ptrace & PT_PTRACED)) | |
346 | do_syscall_trace(); | |
347 | } | |
348 |