]>
Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * linux/arch/m68k/kernel/ptrace.c | |
3 | * | |
4 | * Copyright (C) 1994 by Hamish Macdonald | |
5 | * Taken from linux/kernel/ptrace.c and modified for M680x0. | |
6 | * linux/kernel/ptrace.c is by Ross Biro 1/23/92, edited by Linus Torvalds | |
7 | * | |
8 | * This file is subject to the terms and conditions of the GNU General | |
9 | * Public License. See the file COPYING in the main directory of | |
10 | * this archive for more details. | |
11 | */ | |
12 | ||
13 | #include <linux/kernel.h> | |
14 | #include <linux/sched.h> | |
15 | #include <linux/mm.h> | |
16 | #include <linux/smp.h> | |
1da177e4 LT |
17 | #include <linux/errno.h> |
18 | #include <linux/ptrace.h> | |
19 | #include <linux/user.h> | |
7ed20e1a | 20 | #include <linux/signal.h> |
1da177e4 LT |
21 | |
22 | #include <asm/uaccess.h> | |
23 | #include <asm/page.h> | |
24 | #include <asm/pgtable.h> | |
25 | #include <asm/system.h> | |
26 | #include <asm/processor.h> | |
27 | ||
28 | /* | |
29 | * does not yet catch signals sent when the child dies. | |
30 | * in exit.c or in signal.c. | |
31 | */ | |
32 | ||
33 | /* determines which bits in the SR the user has access to. */ | |
34 | /* 1 = access 0 = no access */ | |
35 | #define SR_MASK 0x001f | |
36 | ||
37 | /* sets the trace bits. */ | |
faa47b46 AS |
38 | #define TRACE_BITS 0xC000 |
39 | #define T1_BIT 0x8000 | |
40 | #define T0_BIT 0x4000 | |
1da177e4 LT |
41 | |
42 | /* Find the stack offset for a register, relative to thread.esp0. */ | |
43 | #define PT_REG(reg) ((long)&((struct pt_regs *)0)->reg) | |
44 | #define SW_REG(reg) ((long)&((struct switch_stack *)0)->reg \ | |
45 | - sizeof(struct switch_stack)) | |
46 | /* Mapping from PT_xxx to the stack offset at which the register is | |
47 | saved. Notice that usp has no stack-slot and needs to be treated | |
48 | specially (see get_reg/put_reg below). */ | |
f195e2bf | 49 | static const int regoff[] = { |
1da177e4 LT |
50 | [0] = PT_REG(d1), |
51 | [1] = PT_REG(d2), | |
52 | [2] = PT_REG(d3), | |
53 | [3] = PT_REG(d4), | |
54 | [4] = PT_REG(d5), | |
55 | [5] = SW_REG(d6), | |
56 | [6] = SW_REG(d7), | |
57 | [7] = PT_REG(a0), | |
58 | [8] = PT_REG(a1), | |
59 | [9] = PT_REG(a2), | |
60 | [10] = SW_REG(a3), | |
61 | [11] = SW_REG(a4), | |
62 | [12] = SW_REG(a5), | |
63 | [13] = SW_REG(a6), | |
64 | [14] = PT_REG(d0), | |
65 | [15] = -1, | |
66 | [16] = PT_REG(orig_d0), | |
67 | [17] = PT_REG(sr), | |
68 | [18] = PT_REG(pc), | |
69 | }; | |
70 | ||
71 | /* | |
72 | * Get contents of register REGNO in task TASK. | |
73 | */ | |
74 | static inline long get_reg(struct task_struct *task, int regno) | |
75 | { | |
76 | unsigned long *addr; | |
77 | ||
78 | if (regno == PT_USP) | |
79 | addr = &task->thread.usp; | |
ea5e1a82 | 80 | else if (regno < ARRAY_SIZE(regoff)) |
1da177e4 LT |
81 | addr = (unsigned long *)(task->thread.esp0 + regoff[regno]); |
82 | else | |
83 | return 0; | |
f195e2bf AS |
84 | /* Need to take stkadj into account. */ |
85 | if (regno == PT_SR || regno == PT_PC) { | |
86 | long stkadj = *(long *)(task->thread.esp0 + PT_REG(stkadj)); | |
87 | addr = (unsigned long *) ((unsigned long)addr + stkadj); | |
88 | /* The sr is actually a 16 bit register. */ | |
89 | if (regno == PT_SR) | |
90 | return *(unsigned short *)addr; | |
91 | } | |
1da177e4 LT |
92 | return *addr; |
93 | } | |
94 | ||
95 | /* | |
96 | * Write contents of register REGNO in task TASK. | |
97 | */ | |
98 | static inline int put_reg(struct task_struct *task, int regno, | |
99 | unsigned long data) | |
100 | { | |
101 | unsigned long *addr; | |
102 | ||
103 | if (regno == PT_USP) | |
104 | addr = &task->thread.usp; | |
ea5e1a82 | 105 | else if (regno < ARRAY_SIZE(regoff)) |
b3319f50 | 106 | addr = (unsigned long *)(task->thread.esp0 + regoff[regno]); |
1da177e4 LT |
107 | else |
108 | return -1; | |
f195e2bf AS |
109 | /* Need to take stkadj into account. */ |
110 | if (regno == PT_SR || regno == PT_PC) { | |
111 | long stkadj = *(long *)(task->thread.esp0 + PT_REG(stkadj)); | |
112 | addr = (unsigned long *) ((unsigned long)addr + stkadj); | |
113 | /* The sr is actually a 16 bit register. */ | |
114 | if (regno == PT_SR) { | |
115 | *(unsigned short *)addr = data; | |
116 | return 0; | |
117 | } | |
118 | } | |
1da177e4 LT |
119 | *addr = data; |
120 | return 0; | |
121 | } | |
122 | ||
123 | /* | |
1da177e4 LT |
124 | * Make sure the single step bit is not set. |
125 | */ | |
69f447cf | 126 | static inline void singlestep_disable(struct task_struct *child) |
1da177e4 | 127 | { |
f195e2bf | 128 | unsigned long tmp = get_reg(child, PT_SR) & ~TRACE_BITS; |
1da177e4 | 129 | put_reg(child, PT_SR, tmp); |
3b66a1ed | 130 | clear_tsk_thread_flag(child, TIF_DELAYED_TRACE); |
69f447cf RZ |
131 | } |
132 | ||
133 | /* | |
134 | * Called by kernel/ptrace.c when detaching.. | |
135 | */ | |
136 | void ptrace_disable(struct task_struct *child) | |
137 | { | |
138 | singlestep_disable(child); | |
1da177e4 LT |
139 | } |
140 | ||
faa47b46 AS |
141 | void user_enable_single_step(struct task_struct *child) |
142 | { | |
f195e2bf AS |
143 | unsigned long tmp = get_reg(child, PT_SR) & ~TRACE_BITS; |
144 | put_reg(child, PT_SR, tmp | T1_BIT); | |
faa47b46 AS |
145 | set_tsk_thread_flag(child, TIF_DELAYED_TRACE); |
146 | } | |
147 | ||
148 | void user_enable_block_step(struct task_struct *child) | |
149 | { | |
f195e2bf AS |
150 | unsigned long tmp = get_reg(child, PT_SR) & ~TRACE_BITS; |
151 | put_reg(child, PT_SR, tmp | T0_BIT); | |
faa47b46 AS |
152 | } |
153 | ||
154 | void user_disable_single_step(struct task_struct *child) | |
155 | { | |
156 | singlestep_disable(child); | |
157 | } | |
158 | ||
9b05a69e NK |
159 | long arch_ptrace(struct task_struct *child, long request, |
160 | unsigned long addr, unsigned long data) | |
1da177e4 | 161 | { |
69f447cf RZ |
162 | unsigned long tmp; |
163 | int i, ret = 0; | |
1da177e4 | 164 | |
1da177e4 | 165 | switch (request) { |
1da177e4 | 166 | /* read the word at location addr in the USER area. */ |
69f447cf RZ |
167 | case PTRACE_PEEKUSR: |
168 | if (addr & 3) | |
169 | goto out_eio; | |
170 | addr >>= 2; /* temporary hack. */ | |
1da177e4 | 171 | |
69f447cf | 172 | if (addr >= 0 && addr < 19) { |
b3319f50 | 173 | tmp = get_reg(child, addr); |
b3319f50 RZ |
174 | } else if (addr >= 21 && addr < 49) { |
175 | tmp = child->thread.fp[addr - 21]; | |
b3319f50 RZ |
176 | /* Convert internal fpu reg representation |
177 | * into long double format | |
178 | */ | |
179 | if (FPU_IS_EMU && (addr < 45) && !(addr % 3)) | |
180 | tmp = ((tmp & 0xffff0000) << 15) | | |
181 | ((tmp & 0x0000ffff) << 16); | |
b3319f50 | 182 | } else |
f195e2bf | 183 | goto out_eio; |
b3319f50 RZ |
184 | ret = put_user(tmp, (unsigned long *)data); |
185 | break; | |
1da177e4 | 186 | |
b3319f50 | 187 | case PTRACE_POKEUSR: /* write the word at location addr in the USER area */ |
69f447cf RZ |
188 | if (addr & 3) |
189 | goto out_eio; | |
190 | addr >>= 2; /* temporary hack. */ | |
1da177e4 | 191 | |
b3319f50 RZ |
192 | if (addr == PT_SR) { |
193 | data &= SR_MASK; | |
f195e2bf AS |
194 | data |= get_reg(child, PT_SR) & ~SR_MASK; |
195 | } | |
196 | if (addr >= 0 && addr < 19) { | |
b3319f50 | 197 | if (put_reg(child, addr, data)) |
69f447cf RZ |
198 | goto out_eio; |
199 | } else if (addr >= 21 && addr < 48) { | |
b3319f50 RZ |
200 | /* Convert long double format |
201 | * into internal fpu reg representation | |
202 | */ | |
203 | if (FPU_IS_EMU && (addr < 45) && !(addr % 3)) { | |
9b05a69e | 204 | data <<= 15; |
b3319f50 RZ |
205 | data = (data & 0xffff0000) | |
206 | ((data & 0x0000ffff) >> 1); | |
207 | } | |
b3319f50 | 208 | child->thread.fp[addr - 21] = data; |
69f447cf RZ |
209 | } else |
210 | goto out_eio; | |
b3319f50 | 211 | break; |
1da177e4 | 212 | |
69f447cf | 213 | case PTRACE_GETREGS: /* Get all gp regs from the child. */ |
b3319f50 RZ |
214 | for (i = 0; i < 19; i++) { |
215 | tmp = get_reg(child, i); | |
69f447cf RZ |
216 | ret = put_user(tmp, (unsigned long *)data); |
217 | if (ret) | |
1da177e4 | 218 | break; |
9b05a69e | 219 | data += sizeof(unsigned long); |
1da177e4 | 220 | } |
b3319f50 | 221 | break; |
1da177e4 | 222 | |
69f447cf | 223 | case PTRACE_SETREGS: /* Set all gp regs in the child. */ |
b3319f50 | 224 | for (i = 0; i < 19; i++) { |
69f447cf RZ |
225 | ret = get_user(tmp, (unsigned long *)data); |
226 | if (ret) | |
1da177e4 | 227 | break; |
b3319f50 | 228 | if (i == PT_SR) { |
1da177e4 | 229 | tmp &= SR_MASK; |
f195e2bf | 230 | tmp |= get_reg(child, PT_SR) & ~SR_MASK; |
1da177e4 | 231 | } |
b3319f50 | 232 | put_reg(child, i, tmp); |
9b05a69e | 233 | data += sizeof(unsigned long); |
1da177e4 | 234 | } |
b3319f50 | 235 | break; |
1da177e4 | 236 | |
69f447cf | 237 | case PTRACE_GETFPREGS: /* Get the child FPU state. */ |
b3319f50 RZ |
238 | if (copy_to_user((void *)data, &child->thread.fp, |
239 | sizeof(struct user_m68kfp_struct))) | |
240 | ret = -EFAULT; | |
241 | break; | |
1da177e4 | 242 | |
69f447cf | 243 | case PTRACE_SETFPREGS: /* Set the child FPU state. */ |
b3319f50 RZ |
244 | if (copy_from_user(&child->thread.fp, (void *)data, |
245 | sizeof(struct user_m68kfp_struct))) | |
246 | ret = -EFAULT; | |
247 | break; | |
1da177e4 | 248 | |
9674cdc7 MK |
249 | case PTRACE_GET_THREAD_AREA: |
250 | ret = put_user(task_thread_info(child)->tp_value, | |
251 | (unsigned long __user *)data); | |
252 | break; | |
253 | ||
b3319f50 RZ |
254 | default: |
255 | ret = ptrace_request(child, request, addr, data); | |
256 | break; | |
1da177e4 | 257 | } |
481bed45 | 258 | |
1da177e4 | 259 | return ret; |
69f447cf | 260 | out_eio: |
481bed45 | 261 | return -EIO; |
1da177e4 LT |
262 | } |
263 | ||
264 | asmlinkage void syscall_trace(void) | |
265 | { | |
1da177e4 LT |
266 | ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD) |
267 | ? 0x80 : 0)); | |
268 | /* | |
269 | * this isn't the same as continuing with a signal, but it will do | |
270 | * for normal use. strace only continues with a signal if the | |
271 | * stopping signal is not SIGTRAP. -brl | |
272 | */ | |
273 | if (current->exit_code) { | |
274 | send_sig(current->exit_code, current, 1); | |
275 | current->exit_code = 0; | |
276 | } | |
277 | } |