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