Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * Copyright (C) 2000-2003, Axis Communications AB. | |
3 | */ | |
4 | ||
5 | #include <linux/kernel.h> | |
6 | #include <linux/sched.h> | |
7 | #include <linux/mm.h> | |
8 | #include <linux/smp.h> | |
1da177e4 LT |
9 | #include <linux/errno.h> |
10 | #include <linux/ptrace.h> | |
11 | #include <linux/user.h> | |
7ed20e1a | 12 | #include <linux/signal.h> |
5d01e6ce | 13 | #include <linux/security.h> |
1da177e4 | 14 | |
7c0f6ba6 | 15 | #include <linux/uaccess.h> |
1da177e4 LT |
16 | #include <asm/page.h> |
17 | #include <asm/pgtable.h> | |
1da177e4 LT |
18 | #include <asm/processor.h> |
19 | ||
20 | /* | |
21 | * Determines which bits in DCCR the user has access to. | |
22 | * 1 = access, 0 = no access. | |
23 | */ | |
24 | #define DCCR_MASK 0x0000001f /* XNZVC */ | |
25 | ||
26 | /* | |
27 | * Get contents of register REGNO in task TASK. | |
28 | */ | |
29 | inline long get_reg(struct task_struct *task, unsigned int regno) | |
30 | { | |
31 | /* USP is a special case, it's not in the pt_regs struct but | |
32 | * in the tasks thread struct | |
33 | */ | |
34 | ||
35 | if (regno == PT_USP) | |
36 | return task->thread.usp; | |
37 | else if (regno < PT_MAX) | |
95ca0dc6 | 38 | return ((unsigned long *)task_pt_regs(task))[regno]; |
1da177e4 LT |
39 | else |
40 | return 0; | |
41 | } | |
42 | ||
43 | /* | |
44 | * Write contents of register REGNO in task TASK. | |
45 | */ | |
46 | inline int put_reg(struct task_struct *task, unsigned int regno, | |
47 | unsigned long data) | |
48 | { | |
49 | if (regno == PT_USP) | |
50 | task->thread.usp = data; | |
51 | else if (regno < PT_MAX) | |
95ca0dc6 | 52 | ((unsigned long *)task_pt_regs(task))[regno] = data; |
1da177e4 LT |
53 | else |
54 | return -1; | |
55 | return 0; | |
56 | } | |
57 | ||
58 | /* | |
59 | * Called by kernel/ptrace.c when detaching. | |
60 | * | |
61 | * Make sure the single step bit is not set. | |
62 | */ | |
63 | void | |
64 | ptrace_disable(struct task_struct *child) | |
65 | { | |
66 | /* Todo - pending singlesteps? */ | |
2afab729 | 67 | clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); |
1da177e4 LT |
68 | } |
69 | ||
70 | /* | |
71 | * Note that this implementation of ptrace behaves differently from vanilla | |
72 | * ptrace. Contrary to what the man page says, in the PTRACE_PEEKTEXT, | |
73 | * PTRACE_PEEKDATA, and PTRACE_PEEKUSER requests the data variable is not | |
74 | * ignored. Instead, the data variable is expected to point at a location | |
75 | * (in user space) where the result of the ptrace call is written (instead of | |
76 | * being returned). | |
77 | */ | |
9b05a69e NK |
78 | long arch_ptrace(struct task_struct *child, long request, |
79 | unsigned long addr, unsigned long data) | |
1da177e4 | 80 | { |
1da177e4 | 81 | int ret; |
475a4b81 | 82 | unsigned int regno = addr >> 2; |
1da177e4 LT |
83 | unsigned long __user *datap = (unsigned long __user *)data; |
84 | ||
1da177e4 LT |
85 | switch (request) { |
86 | /* Read word at location address. */ | |
87 | case PTRACE_PEEKTEXT: | |
76647323 AD |
88 | case PTRACE_PEEKDATA: |
89 | ret = generic_ptrace_peekdata(child, addr, data); | |
1da177e4 | 90 | break; |
1da177e4 LT |
91 | |
92 | /* Read the word at location address in the USER area. */ | |
93 | case PTRACE_PEEKUSR: { | |
94 | unsigned long tmp; | |
95 | ||
96 | ret = -EIO; | |
475a4b81 | 97 | if ((addr & 3) || regno > PT_MAX) |
1da177e4 LT |
98 | break; |
99 | ||
475a4b81 | 100 | tmp = get_reg(child, regno); |
1da177e4 LT |
101 | ret = put_user(tmp, datap); |
102 | break; | |
103 | } | |
104 | ||
105 | /* Write the word at location address. */ | |
106 | case PTRACE_POKETEXT: | |
107 | case PTRACE_POKEDATA: | |
f284ce72 | 108 | ret = generic_ptrace_pokedata(child, addr, data); |
1da177e4 LT |
109 | break; |
110 | ||
111 | /* Write the word at location address in the USER area. */ | |
112 | case PTRACE_POKEUSR: | |
113 | ret = -EIO; | |
475a4b81 | 114 | if ((addr & 3) || regno > PT_MAX) |
1da177e4 LT |
115 | break; |
116 | ||
475a4b81 | 117 | if (regno == PT_DCCR) { |
1da177e4 LT |
118 | /* don't allow the tracing process to change stuff like |
119 | * interrupt enable, kernel/user bit, dma enables etc. | |
120 | */ | |
121 | data &= DCCR_MASK; | |
122 | data |= get_reg(child, PT_DCCR) & ~DCCR_MASK; | |
123 | } | |
475a4b81 | 124 | if (put_reg(child, regno, data)) |
1da177e4 LT |
125 | break; |
126 | ret = 0; | |
127 | break; | |
128 | ||
1da177e4 LT |
129 | /* Get all GP registers from the child. */ |
130 | case PTRACE_GETREGS: { | |
131 | int i; | |
132 | unsigned long tmp; | |
133 | ||
c3508858 | 134 | ret = 0; |
1da177e4 LT |
135 | for (i = 0; i <= PT_MAX; i++) { |
136 | tmp = get_reg(child, i); | |
137 | ||
138 | if (put_user(tmp, datap)) { | |
139 | ret = -EFAULT; | |
c3508858 | 140 | break; |
1da177e4 LT |
141 | } |
142 | ||
475a4b81 | 143 | datap++; |
1da177e4 LT |
144 | } |
145 | ||
1da177e4 LT |
146 | break; |
147 | } | |
148 | ||
149 | /* Set all GP registers in the child. */ | |
150 | case PTRACE_SETREGS: { | |
151 | int i; | |
152 | unsigned long tmp; | |
153 | ||
c3508858 | 154 | ret = 0; |
1da177e4 LT |
155 | for (i = 0; i <= PT_MAX; i++) { |
156 | if (get_user(tmp, datap)) { | |
157 | ret = -EFAULT; | |
c3508858 | 158 | break; |
1da177e4 LT |
159 | } |
160 | ||
161 | if (i == PT_DCCR) { | |
162 | tmp &= DCCR_MASK; | |
163 | tmp |= get_reg(child, PT_DCCR) & ~DCCR_MASK; | |
164 | } | |
165 | ||
166 | put_reg(child, i, tmp); | |
475a4b81 | 167 | datap++; |
1da177e4 LT |
168 | } |
169 | ||
1da177e4 LT |
170 | break; |
171 | } | |
172 | ||
173 | default: | |
174 | ret = ptrace_request(child, request, addr, data); | |
175 | break; | |
176 | } | |
481bed45 | 177 | |
1da177e4 LT |
178 | return ret; |
179 | } | |
180 | ||
181 | void do_syscall_trace(void) | |
182 | { | |
183 | if (!test_thread_flag(TIF_SYSCALL_TRACE)) | |
184 | return; | |
185 | ||
186 | if (!(current->ptrace & PT_PTRACED)) | |
187 | return; | |
188 | ||
189 | /* the 0x80 provides a way for the tracing parent to distinguish | |
190 | between a syscall stop and SIGTRAP delivery */ | |
191 | ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD) | |
192 | ? 0x80 : 0)); | |
193 | ||
194 | /* | |
195 | * This isn't the same as continuing with a signal, but it will do for | |
196 | * normal use. | |
197 | */ | |
198 | if (current->exit_code) { | |
199 | send_sig(current->exit_code, current, 1); | |
200 | current->exit_code = 0; | |
201 | } | |
202 | } |