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