diff options
author | Roland McGrath <roland@redhat.com> | 2008-01-30 07:31:01 -0500 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2008-01-30 07:31:01 -0500 |
commit | 2047b08be67b70875d8765fc81d34ce28041bec3 (patch) | |
tree | 04fc804f8530d924283a0c21f4aaafba68070bf7 /arch/x86/kernel/ptrace.c | |
parent | 06ee1b687ac91698ccd47fa652d5b3cf1bfcd806 (diff) |
x86: x86 ptrace getreg/putreg merge
This merges 64-bit support into the low-level register access
functions in arch/x86/kernel/ptrace.c, paving the way to share
this file between 32-bit and 64-bit builds.
Signed-off-by: Roland McGrath <roland@redhat.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Diffstat (limited to 'arch/x86/kernel/ptrace.c')
-rw-r--r-- | arch/x86/kernel/ptrace.c | 217 |
1 files changed, 213 insertions, 4 deletions
diff --git a/arch/x86/kernel/ptrace.c b/arch/x86/kernel/ptrace.c index eaec75a4094b..c709868d28a5 100644 --- a/arch/x86/kernel/ptrace.c +++ b/arch/x86/kernel/ptrace.c | |||
@@ -24,6 +24,8 @@ | |||
24 | #include <asm/debugreg.h> | 24 | #include <asm/debugreg.h> |
25 | #include <asm/ldt.h> | 25 | #include <asm/ldt.h> |
26 | #include <asm/desc.h> | 26 | #include <asm/desc.h> |
27 | #include <asm/prctl.h> | ||
28 | #include <asm/proto.h> | ||
27 | 29 | ||
28 | /* | 30 | /* |
29 | * does not yet catch signals sent when the child dies. | 31 | * does not yet catch signals sent when the child dies. |
@@ -40,6 +42,16 @@ | |||
40 | X86_EFLAGS_DF | X86_EFLAGS_OF | \ | 42 | X86_EFLAGS_DF | X86_EFLAGS_OF | \ |
41 | X86_EFLAGS_RF | X86_EFLAGS_AC)) | 43 | X86_EFLAGS_RF | X86_EFLAGS_AC)) |
42 | 44 | ||
45 | /* | ||
46 | * Determines whether a value may be installed in a segment register. | ||
47 | */ | ||
48 | static inline bool invalid_selector(u16 value) | ||
49 | { | ||
50 | return unlikely(value != 0 && (value & SEGMENT_RPL_MASK) != USER_RPL); | ||
51 | } | ||
52 | |||
53 | #ifdef CONFIG_X86_32 | ||
54 | |||
43 | #define FLAG_MASK FLAG_MASK_32 | 55 | #define FLAG_MASK FLAG_MASK_32 |
44 | 56 | ||
45 | static long *pt_regs_access(struct pt_regs *regs, unsigned long regno) | 57 | static long *pt_regs_access(struct pt_regs *regs, unsigned long regno) |
@@ -73,7 +85,7 @@ static int set_segment_reg(struct task_struct *task, | |||
73 | /* | 85 | /* |
74 | * The value argument was already truncated to 16 bits. | 86 | * The value argument was already truncated to 16 bits. |
75 | */ | 87 | */ |
76 | if (value && (value & 3) != 3) | 88 | if (invalid_selector(value)) |
77 | return -EIO; | 89 | return -EIO; |
78 | 90 | ||
79 | if (offset != offsetof(struct user_regs_struct, gs)) | 91 | if (offset != offsetof(struct user_regs_struct, gs)) |
@@ -91,6 +103,142 @@ static int set_segment_reg(struct task_struct *task, | |||
91 | return 0; | 103 | return 0; |
92 | } | 104 | } |
93 | 105 | ||
106 | static unsigned long debugreg_addr_limit(struct task_struct *task) | ||
107 | { | ||
108 | return TASK_SIZE - 3; | ||
109 | } | ||
110 | |||
111 | #else /* CONFIG_X86_64 */ | ||
112 | |||
113 | #define FLAG_MASK (FLAG_MASK_32 | X86_EFLAGS_NT) | ||
114 | |||
115 | static unsigned long *pt_regs_access(struct pt_regs *regs, unsigned long offset) | ||
116 | { | ||
117 | BUILD_BUG_ON(offsetof(struct pt_regs, r15) != 0); | ||
118 | return ®s->r15 + (offset / sizeof(regs->r15)); | ||
119 | } | ||
120 | |||
121 | static u16 get_segment_reg(struct task_struct *task, unsigned long offset) | ||
122 | { | ||
123 | /* | ||
124 | * Returning the value truncates it to 16 bits. | ||
125 | */ | ||
126 | unsigned int seg; | ||
127 | |||
128 | switch (offset) { | ||
129 | case offsetof(struct user_regs_struct, fs): | ||
130 | if (task == current) { | ||
131 | /* Older gas can't assemble movq %?s,%r?? */ | ||
132 | asm("movl %%fs,%0" : "=r" (seg)); | ||
133 | return seg; | ||
134 | } | ||
135 | return task->thread.fsindex; | ||
136 | case offsetof(struct user_regs_struct, gs): | ||
137 | if (task == current) { | ||
138 | asm("movl %%gs,%0" : "=r" (seg)); | ||
139 | return seg; | ||
140 | } | ||
141 | return task->thread.gsindex; | ||
142 | case offsetof(struct user_regs_struct, ds): | ||
143 | if (task == current) { | ||
144 | asm("movl %%ds,%0" : "=r" (seg)); | ||
145 | return seg; | ||
146 | } | ||
147 | return task->thread.ds; | ||
148 | case offsetof(struct user_regs_struct, es): | ||
149 | if (task == current) { | ||
150 | asm("movl %%es,%0" : "=r" (seg)); | ||
151 | return seg; | ||
152 | } | ||
153 | return task->thread.es; | ||
154 | |||
155 | case offsetof(struct user_regs_struct, cs): | ||
156 | case offsetof(struct user_regs_struct, ss): | ||
157 | break; | ||
158 | } | ||
159 | return *pt_regs_access(task_pt_regs(task), offset); | ||
160 | } | ||
161 | |||
162 | static int set_segment_reg(struct task_struct *task, | ||
163 | unsigned long offset, u16 value) | ||
164 | { | ||
165 | /* | ||
166 | * The value argument was already truncated to 16 bits. | ||
167 | */ | ||
168 | if (invalid_selector(value)) | ||
169 | return -EIO; | ||
170 | |||
171 | switch (offset) { | ||
172 | case offsetof(struct user_regs_struct,fs): | ||
173 | /* | ||
174 | * If this is setting fs as for normal 64-bit use but | ||
175 | * setting fs_base has implicitly changed it, leave it. | ||
176 | */ | ||
177 | if ((value == FS_TLS_SEL && task->thread.fsindex == 0 && | ||
178 | task->thread.fs != 0) || | ||
179 | (value == 0 && task->thread.fsindex == FS_TLS_SEL && | ||
180 | task->thread.fs == 0)) | ||
181 | break; | ||
182 | task->thread.fsindex = value; | ||
183 | if (task == current) | ||
184 | loadsegment(fs, task->thread.fsindex); | ||
185 | break; | ||
186 | case offsetof(struct user_regs_struct,gs): | ||
187 | /* | ||
188 | * If this is setting gs as for normal 64-bit use but | ||
189 | * setting gs_base has implicitly changed it, leave it. | ||
190 | */ | ||
191 | if ((value == GS_TLS_SEL && task->thread.gsindex == 0 && | ||
192 | task->thread.gs != 0) || | ||
193 | (value == 0 && task->thread.gsindex == GS_TLS_SEL && | ||
194 | task->thread.gs == 0)) | ||
195 | break; | ||
196 | task->thread.gsindex = value; | ||
197 | if (task == current) | ||
198 | load_gs_index(task->thread.gsindex); | ||
199 | break; | ||
200 | case offsetof(struct user_regs_struct,ds): | ||
201 | task->thread.ds = value; | ||
202 | if (task == current) | ||
203 | loadsegment(ds, task->thread.ds); | ||
204 | break; | ||
205 | case offsetof(struct user_regs_struct,es): | ||
206 | task->thread.es = value; | ||
207 | if (task == current) | ||
208 | loadsegment(es, task->thread.es); | ||
209 | break; | ||
210 | |||
211 | /* | ||
212 | * Can't actually change these in 64-bit mode. | ||
213 | */ | ||
214 | case offsetof(struct user_regs_struct,cs): | ||
215 | #ifdef CONFIG_IA32_EMULATION | ||
216 | if (test_tsk_thread_flag(task, TIF_IA32)) | ||
217 | task_pt_regs(task)->cs = value; | ||
218 | break; | ||
219 | #endif | ||
220 | case offsetof(struct user_regs_struct,ss): | ||
221 | #ifdef CONFIG_IA32_EMULATION | ||
222 | if (test_tsk_thread_flag(task, TIF_IA32)) | ||
223 | task_pt_regs(task)->ss = value; | ||
224 | break; | ||
225 | #endif | ||
226 | } | ||
227 | |||
228 | return 0; | ||
229 | } | ||
230 | |||
231 | static unsigned long debugreg_addr_limit(struct task_struct *task) | ||
232 | { | ||
233 | #ifdef CONFIG_IA32_EMULATION | ||
234 | if (test_tsk_thread_flag(task, TIF_IA32)) | ||
235 | return IA32_PAGE_OFFSET - 3; | ||
236 | #endif | ||
237 | return TASK_SIZE64 - 7; | ||
238 | } | ||
239 | |||
240 | #endif /* CONFIG_X86_32 */ | ||
241 | |||
94 | static unsigned long get_flags(struct task_struct *task) | 242 | static unsigned long get_flags(struct task_struct *task) |
95 | { | 243 | { |
96 | unsigned long retval = task_pt_regs(task)->flags; | 244 | unsigned long retval = task_pt_regs(task)->flags; |
@@ -137,6 +285,29 @@ static int putreg(struct task_struct *child, | |||
137 | 285 | ||
138 | case offsetof(struct user_regs_struct, flags): | 286 | case offsetof(struct user_regs_struct, flags): |
139 | return set_flags(child, value); | 287 | return set_flags(child, value); |
288 | |||
289 | #ifdef CONFIG_X86_64 | ||
290 | case offsetof(struct user_regs_struct,fs_base): | ||
291 | if (value >= TASK_SIZE_OF(child)) | ||
292 | return -EIO; | ||
293 | /* | ||
294 | * When changing the segment base, use do_arch_prctl | ||
295 | * to set either thread.fs or thread.fsindex and the | ||
296 | * corresponding GDT slot. | ||
297 | */ | ||
298 | if (child->thread.fs != value) | ||
299 | return do_arch_prctl(child, ARCH_SET_FS, value); | ||
300 | return 0; | ||
301 | case offsetof(struct user_regs_struct,gs_base): | ||
302 | /* | ||
303 | * Exactly the same here as the %fs handling above. | ||
304 | */ | ||
305 | if (value >= TASK_SIZE_OF(child)) | ||
306 | return -EIO; | ||
307 | if (child->thread.gs != value) | ||
308 | return do_arch_prctl(child, ARCH_SET_GS, value); | ||
309 | return 0; | ||
310 | #endif | ||
140 | } | 311 | } |
141 | 312 | ||
142 | *pt_regs_access(task_pt_regs(child), offset) = value; | 313 | *pt_regs_access(task_pt_regs(child), offset) = value; |
@@ -156,6 +327,37 @@ static unsigned long getreg(struct task_struct *task, unsigned long offset) | |||
156 | 327 | ||
157 | case offsetof(struct user_regs_struct, flags): | 328 | case offsetof(struct user_regs_struct, flags): |
158 | return get_flags(task); | 329 | return get_flags(task); |
330 | |||
331 | #ifdef CONFIG_X86_64 | ||
332 | case offsetof(struct user_regs_struct, fs_base): { | ||
333 | /* | ||
334 | * do_arch_prctl may have used a GDT slot instead of | ||
335 | * the MSR. To userland, it appears the same either | ||
336 | * way, except the %fs segment selector might not be 0. | ||
337 | */ | ||
338 | unsigned int seg = task->thread.fsindex; | ||
339 | if (task->thread.fs != 0) | ||
340 | return task->thread.fs; | ||
341 | if (task == current) | ||
342 | asm("movl %%fs,%0" : "=r" (seg)); | ||
343 | if (seg != FS_TLS_SEL) | ||
344 | return 0; | ||
345 | return get_desc_base(&task->thread.tls_array[FS_TLS]); | ||
346 | } | ||
347 | case offsetof(struct user_regs_struct, gs_base): { | ||
348 | /* | ||
349 | * Exactly the same here as the %fs handling above. | ||
350 | */ | ||
351 | unsigned int seg = task->thread.gsindex; | ||
352 | if (task->thread.gs != 0) | ||
353 | return task->thread.gs; | ||
354 | if (task == current) | ||
355 | asm("movl %%gs,%0" : "=r" (seg)); | ||
356 | if (seg != GS_TLS_SEL) | ||
357 | return 0; | ||
358 | return get_desc_base(&task->thread.tls_array[GS_TLS]); | ||
359 | } | ||
360 | #endif | ||
159 | } | 361 | } |
160 | 362 | ||
161 | return *pt_regs_access(task_pt_regs(task), offset); | 363 | return *pt_regs_access(task_pt_regs(task), offset); |
@@ -187,7 +389,7 @@ static int ptrace_set_debugreg(struct task_struct *child, | |||
187 | if (unlikely(n == 4 || n == 5)) | 389 | if (unlikely(n == 4 || n == 5)) |
188 | return -EIO; | 390 | return -EIO; |
189 | 391 | ||
190 | if (n < 4 && unlikely(data >= TASK_SIZE - 3)) | 392 | if (n < 4 && unlikely(data >= debugreg_addr_limit(child))) |
191 | return -EIO; | 393 | return -EIO; |
192 | 394 | ||
193 | switch (n) { | 395 | switch (n) { |
@@ -197,6 +399,8 @@ static int ptrace_set_debugreg(struct task_struct *child, | |||
197 | case 3: child->thread.debugreg3 = data; break; | 399 | case 3: child->thread.debugreg3 = data; break; |
198 | 400 | ||
199 | case 6: | 401 | case 6: |
402 | if ((data & ~0xffffffffUL) != 0) | ||
403 | return -EIO; | ||
200 | child->thread.debugreg6 = data; | 404 | child->thread.debugreg6 = data; |
201 | break; | 405 | break; |
202 | 406 | ||
@@ -215,7 +419,7 @@ static int ptrace_set_debugreg(struct task_struct *child, | |||
215 | * data in the watchpoint case. | 419 | * data in the watchpoint case. |
216 | * | 420 | * |
217 | * The invalid values are: | 421 | * The invalid values are: |
218 | * - LENi == 0x10 (undefined), so mask |= 0x0f00. | 422 | * - LENi == 0x10 (undefined), so mask |= 0x0f00. [32-bit] |
219 | * - R/Wi == 0x10 (break on I/O reads or writes), so | 423 | * - R/Wi == 0x10 (break on I/O reads or writes), so |
220 | * mask |= 0x4444. | 424 | * mask |= 0x4444. |
221 | * - R/Wi == 0x00 && LENi != 0x00, so we have mask |= | 425 | * - R/Wi == 0x00 && LENi != 0x00, so we have mask |= |
@@ -231,9 +435,14 @@ static int ptrace_set_debugreg(struct task_struct *child, | |||
231 | * 64-bit kernel), so the x86_64 mask value is 0x5454. | 435 | * 64-bit kernel), so the x86_64 mask value is 0x5454. |
232 | * See the AMD manual no. 24593 (AMD64 System Programming) | 436 | * See the AMD manual no. 24593 (AMD64 System Programming) |
233 | */ | 437 | */ |
438 | #ifdef CONFIG_X86_32 | ||
439 | #define DR7_MASK 0x5f54 | ||
440 | #else | ||
441 | #define DR7_MASK 0x5554 | ||
442 | #endif | ||
234 | data &= ~DR_CONTROL_RESERVED; | 443 | data &= ~DR_CONTROL_RESERVED; |
235 | for (i = 0; i < 4; i++) | 444 | for (i = 0; i < 4; i++) |
236 | if ((0x5f54 >> ((data >> (16 + 4*i)) & 0xf)) & 1) | 445 | if ((DR7_MASK >> ((data >> (16 + 4*i)) & 0xf)) & 1) |
237 | return -EIO; | 446 | return -EIO; |
238 | child->thread.debugreg7 = data; | 447 | child->thread.debugreg7 = data; |
239 | if (data) | 448 | if (data) |