Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* ptrace.c: Sparc process tracing support. |
2 | * | |
3 | * Copyright (C) 1996 David S. Miller (davem@caipfs.rutgers.edu) | |
4 | * | |
5 | * Based upon code written by Ross Biro, Linus Torvalds, Bob Manson, | |
6 | * and David Mosberger. | |
7 | * | |
8 | * Added Linux support -miguel (weird, eh?, the orignal code was meant | |
9 | * to emulate SunOS). | |
10 | */ | |
11 | ||
12 | #include <linux/kernel.h> | |
13 | #include <linux/sched.h> | |
14 | #include <linux/mm.h> | |
15 | #include <linux/errno.h> | |
16 | #include <linux/ptrace.h> | |
17 | #include <linux/user.h> | |
18 | #include <linux/smp.h> | |
19 | #include <linux/smp_lock.h> | |
20 | #include <linux/security.h> | |
7ed20e1a | 21 | #include <linux/signal.h> |
1da177e4 LT |
22 | |
23 | #include <asm/pgtable.h> | |
24 | #include <asm/system.h> | |
25 | #include <asm/uaccess.h> | |
26 | ||
27 | #define MAGIC_CONSTANT 0x80000000 | |
28 | ||
29 | ||
30 | /* Returning from ptrace is a bit tricky because the syscall return | |
31 | * low level code assumes any value returned which is negative and | |
32 | * is a valid errno will mean setting the condition codes to indicate | |
33 | * an error return. This doesn't work, so we have this hook. | |
34 | */ | |
35 | static inline void pt_error_return(struct pt_regs *regs, unsigned long error) | |
36 | { | |
37 | regs->u_regs[UREG_I0] = error; | |
38 | regs->psr |= PSR_C; | |
39 | regs->pc = regs->npc; | |
40 | regs->npc += 4; | |
41 | } | |
42 | ||
43 | static inline void pt_succ_return(struct pt_regs *regs, unsigned long value) | |
44 | { | |
45 | regs->u_regs[UREG_I0] = value; | |
46 | regs->psr &= ~PSR_C; | |
47 | regs->pc = regs->npc; | |
48 | regs->npc += 4; | |
49 | } | |
50 | ||
51 | static void | |
52 | pt_succ_return_linux(struct pt_regs *regs, unsigned long value, long __user *addr) | |
53 | { | |
54 | if (put_user(value, addr)) { | |
55 | pt_error_return(regs, EFAULT); | |
56 | return; | |
57 | } | |
58 | regs->u_regs[UREG_I0] = 0; | |
59 | regs->psr &= ~PSR_C; | |
60 | regs->pc = regs->npc; | |
61 | regs->npc += 4; | |
62 | } | |
63 | ||
64 | static void | |
65 | pt_os_succ_return (struct pt_regs *regs, unsigned long val, long __user *addr) | |
66 | { | |
67 | if (current->personality == PER_SUNOS) | |
68 | pt_succ_return (regs, val); | |
69 | else | |
70 | pt_succ_return_linux (regs, val, addr); | |
71 | } | |
72 | ||
73 | /* Fuck me gently with a chainsaw... */ | |
74 | static inline void read_sunos_user(struct pt_regs *regs, unsigned long offset, | |
75 | struct task_struct *tsk, long __user *addr) | |
76 | { | |
77 | struct pt_regs *cregs = tsk->thread.kregs; | |
78 | struct thread_info *t = tsk->thread_info; | |
79 | int v; | |
80 | ||
81 | if(offset >= 1024) | |
82 | offset -= 1024; /* whee... */ | |
83 | if(offset & ((sizeof(unsigned long) - 1))) { | |
84 | pt_error_return(regs, EIO); | |
85 | return; | |
86 | } | |
87 | if(offset >= 16 && offset < 784) { | |
88 | offset -= 16; offset >>= 2; | |
89 | pt_os_succ_return(regs, *(((unsigned long *)(&t->reg_window[0]))+offset), addr); | |
90 | return; | |
91 | } | |
92 | if(offset >= 784 && offset < 832) { | |
93 | offset -= 784; offset >>= 2; | |
94 | pt_os_succ_return(regs, *(((unsigned long *)(&t->rwbuf_stkptrs[0]))+offset), addr); | |
95 | return; | |
96 | } | |
97 | switch(offset) { | |
98 | case 0: | |
99 | v = t->ksp; | |
100 | break; | |
101 | case 4: | |
102 | v = t->kpc; | |
103 | break; | |
104 | case 8: | |
105 | v = t->kpsr; | |
106 | break; | |
107 | case 12: | |
108 | v = t->uwinmask; | |
109 | break; | |
110 | case 832: | |
111 | v = t->w_saved; | |
112 | break; | |
113 | case 896: | |
114 | v = cregs->u_regs[UREG_I0]; | |
115 | break; | |
116 | case 900: | |
117 | v = cregs->u_regs[UREG_I1]; | |
118 | break; | |
119 | case 904: | |
120 | v = cregs->u_regs[UREG_I2]; | |
121 | break; | |
122 | case 908: | |
123 | v = cregs->u_regs[UREG_I3]; | |
124 | break; | |
125 | case 912: | |
126 | v = cregs->u_regs[UREG_I4]; | |
127 | break; | |
128 | case 916: | |
129 | v = cregs->u_regs[UREG_I5]; | |
130 | break; | |
131 | case 920: | |
132 | v = cregs->u_regs[UREG_I6]; | |
133 | break; | |
134 | case 924: | |
135 | if(tsk->thread.flags & MAGIC_CONSTANT) | |
136 | v = cregs->u_regs[UREG_G1]; | |
137 | else | |
138 | v = 0; | |
139 | break; | |
140 | case 940: | |
141 | v = cregs->u_regs[UREG_I0]; | |
142 | break; | |
143 | case 944: | |
144 | v = cregs->u_regs[UREG_I1]; | |
145 | break; | |
146 | ||
147 | case 948: | |
148 | /* Isn't binary compatibility _fun_??? */ | |
149 | if(cregs->psr & PSR_C) | |
150 | v = cregs->u_regs[UREG_I0] << 24; | |
151 | else | |
152 | v = 0; | |
153 | break; | |
154 | ||
155 | /* Rest of them are completely unsupported. */ | |
156 | default: | |
157 | printk("%s [%d]: Wants to read user offset %ld\n", | |
158 | current->comm, current->pid, offset); | |
159 | pt_error_return(regs, EIO); | |
160 | return; | |
161 | } | |
162 | if (current->personality == PER_SUNOS) | |
163 | pt_succ_return (regs, v); | |
164 | else | |
165 | pt_succ_return_linux (regs, v, addr); | |
166 | return; | |
167 | } | |
168 | ||
169 | static inline void write_sunos_user(struct pt_regs *regs, unsigned long offset, | |
170 | struct task_struct *tsk) | |
171 | { | |
172 | struct pt_regs *cregs = tsk->thread.kregs; | |
173 | struct thread_info *t = tsk->thread_info; | |
174 | unsigned long value = regs->u_regs[UREG_I3]; | |
175 | ||
176 | if(offset >= 1024) | |
177 | offset -= 1024; /* whee... */ | |
178 | if(offset & ((sizeof(unsigned long) - 1))) | |
179 | goto failure; | |
180 | if(offset >= 16 && offset < 784) { | |
181 | offset -= 16; offset >>= 2; | |
182 | *(((unsigned long *)(&t->reg_window[0]))+offset) = value; | |
183 | goto success; | |
184 | } | |
185 | if(offset >= 784 && offset < 832) { | |
186 | offset -= 784; offset >>= 2; | |
187 | *(((unsigned long *)(&t->rwbuf_stkptrs[0]))+offset) = value; | |
188 | goto success; | |
189 | } | |
190 | switch(offset) { | |
191 | case 896: | |
192 | cregs->u_regs[UREG_I0] = value; | |
193 | break; | |
194 | case 900: | |
195 | cregs->u_regs[UREG_I1] = value; | |
196 | break; | |
197 | case 904: | |
198 | cregs->u_regs[UREG_I2] = value; | |
199 | break; | |
200 | case 908: | |
201 | cregs->u_regs[UREG_I3] = value; | |
202 | break; | |
203 | case 912: | |
204 | cregs->u_regs[UREG_I4] = value; | |
205 | break; | |
206 | case 916: | |
207 | cregs->u_regs[UREG_I5] = value; | |
208 | break; | |
209 | case 920: | |
210 | cregs->u_regs[UREG_I6] = value; | |
211 | break; | |
212 | case 924: | |
213 | cregs->u_regs[UREG_I7] = value; | |
214 | break; | |
215 | case 940: | |
216 | cregs->u_regs[UREG_I0] = value; | |
217 | break; | |
218 | case 944: | |
219 | cregs->u_regs[UREG_I1] = value; | |
220 | break; | |
221 | ||
222 | /* Rest of them are completely unsupported or "no-touch". */ | |
223 | default: | |
224 | printk("%s [%d]: Wants to write user offset %ld\n", | |
225 | current->comm, current->pid, offset); | |
226 | goto failure; | |
227 | } | |
228 | success: | |
229 | pt_succ_return(regs, 0); | |
230 | return; | |
231 | failure: | |
232 | pt_error_return(regs, EIO); | |
233 | return; | |
234 | } | |
235 | ||
236 | /* #define ALLOW_INIT_TRACING */ | |
237 | /* #define DEBUG_PTRACE */ | |
238 | ||
239 | #ifdef DEBUG_PTRACE | |
240 | char *pt_rq [] = { | |
241 | /* 0 */ "TRACEME", "PEEKTEXT", "PEEKDATA", "PEEKUSR", | |
242 | /* 4 */ "POKETEXT", "POKEDATA", "POKEUSR", "CONT", | |
243 | /* 8 */ "KILL", "SINGLESTEP", "SUNATTACH", "SUNDETACH", | |
244 | /* 12 */ "GETREGS", "SETREGS", "GETFPREGS", "SETFPREGS", | |
245 | /* 16 */ "READDATA", "WRITEDATA", "READTEXT", "WRITETEXT", | |
246 | /* 20 */ "GETFPAREGS", "SETFPAREGS", "unknown", "unknown", | |
247 | /* 24 */ "SYSCALL", "" | |
248 | }; | |
249 | #endif | |
250 | ||
251 | /* | |
252 | * Called by kernel/ptrace.c when detaching.. | |
253 | * | |
254 | * Make sure single step bits etc are not set. | |
255 | */ | |
256 | void ptrace_disable(struct task_struct *child) | |
257 | { | |
258 | /* nothing to do */ | |
259 | } | |
260 | ||
261 | asmlinkage void do_ptrace(struct pt_regs *regs) | |
262 | { | |
263 | unsigned long request = regs->u_regs[UREG_I0]; | |
264 | unsigned long pid = regs->u_regs[UREG_I1]; | |
265 | unsigned long addr = regs->u_regs[UREG_I2]; | |
266 | unsigned long data = regs->u_regs[UREG_I3]; | |
267 | unsigned long addr2 = regs->u_regs[UREG_I4]; | |
268 | struct task_struct *child; | |
269 | int ret; | |
270 | ||
271 | lock_kernel(); | |
272 | #ifdef DEBUG_PTRACE | |
273 | { | |
274 | char *s; | |
275 | ||
276 | if ((request >= 0) && (request <= 24)) | |
277 | s = pt_rq [request]; | |
278 | else | |
279 | s = "unknown"; | |
280 | ||
281 | if (request == PTRACE_POKEDATA && data == 0x91d02001){ | |
282 | printk ("do_ptrace: breakpoint pid=%d, addr=%08lx addr2=%08lx\n", | |
283 | pid, addr, addr2); | |
284 | } else | |
285 | printk("do_ptrace: rq=%s(%d) pid=%d addr=%08lx data=%08lx addr2=%08lx\n", | |
286 | s, (int) request, (int) pid, addr, data, addr2); | |
287 | } | |
288 | #endif | |
1da177e4 | 289 | |
6b9c7ed8 CH |
290 | if (request == PTRACE_TRACEME) { |
291 | ret = ptrace_traceme(); | |
1da177e4 LT |
292 | pt_succ_return(regs, 0); |
293 | goto out; | |
294 | } | |
1da177e4 | 295 | |
6b9c7ed8 CH |
296 | child = ptrace_get_task_struct(pid); |
297 | if (IS_ERR(child)) { | |
298 | ret = PTR_ERR(child); | |
299 | pt_error_return(regs, -ret); | |
1da177e4 LT |
300 | goto out; |
301 | } | |
302 | ||
303 | if ((current->personality == PER_SUNOS && request == PTRACE_SUNATTACH) | |
304 | || (current->personality != PER_SUNOS && request == PTRACE_ATTACH)) { | |
305 | if (ptrace_attach(child)) { | |
306 | pt_error_return(regs, EPERM); | |
307 | goto out_tsk; | |
308 | } | |
309 | pt_succ_return(regs, 0); | |
310 | goto out_tsk; | |
311 | } | |
312 | ||
313 | ret = ptrace_check_attach(child, request == PTRACE_KILL); | |
314 | if (ret < 0) { | |
315 | pt_error_return(regs, -ret); | |
316 | goto out_tsk; | |
317 | } | |
318 | ||
319 | switch(request) { | |
320 | case PTRACE_PEEKTEXT: /* read word at location addr. */ | |
321 | case PTRACE_PEEKDATA: { | |
322 | unsigned long tmp; | |
323 | ||
324 | if (access_process_vm(child, addr, | |
325 | &tmp, sizeof(tmp), 0) == sizeof(tmp)) | |
326 | pt_os_succ_return(regs, tmp, (long __user *)data); | |
327 | else | |
328 | pt_error_return(regs, EIO); | |
329 | goto out_tsk; | |
330 | } | |
331 | ||
332 | case PTRACE_PEEKUSR: | |
333 | read_sunos_user(regs, addr, child, (long __user *) data); | |
334 | goto out_tsk; | |
335 | ||
336 | case PTRACE_POKEUSR: | |
337 | write_sunos_user(regs, addr, child); | |
338 | goto out_tsk; | |
339 | ||
340 | case PTRACE_POKETEXT: /* write the word at location addr. */ | |
341 | case PTRACE_POKEDATA: { | |
342 | if (access_process_vm(child, addr, | |
343 | &data, sizeof(data), 1) == sizeof(data)) | |
344 | pt_succ_return(regs, 0); | |
345 | else | |
346 | pt_error_return(regs, EIO); | |
347 | goto out_tsk; | |
348 | } | |
349 | ||
350 | case PTRACE_GETREGS: { | |
351 | struct pt_regs __user *pregs = (struct pt_regs __user *) addr; | |
352 | struct pt_regs *cregs = child->thread.kregs; | |
353 | int rval; | |
354 | ||
355 | if (!access_ok(VERIFY_WRITE, pregs, sizeof(struct pt_regs))) { | |
356 | rval = -EFAULT; | |
357 | pt_error_return(regs, -rval); | |
358 | goto out_tsk; | |
359 | } | |
360 | __put_user(cregs->psr, (&pregs->psr)); | |
361 | __put_user(cregs->pc, (&pregs->pc)); | |
362 | __put_user(cregs->npc, (&pregs->npc)); | |
363 | __put_user(cregs->y, (&pregs->y)); | |
364 | for(rval = 1; rval < 16; rval++) | |
365 | __put_user(cregs->u_regs[rval], (&pregs->u_regs[rval - 1])); | |
366 | pt_succ_return(regs, 0); | |
367 | #ifdef DEBUG_PTRACE | |
368 | printk ("PC=%x nPC=%x o7=%x\n", cregs->pc, cregs->npc, cregs->u_regs [15]); | |
369 | #endif | |
370 | goto out_tsk; | |
371 | } | |
372 | ||
373 | case PTRACE_SETREGS: { | |
374 | struct pt_regs __user *pregs = (struct pt_regs __user *) addr; | |
375 | struct pt_regs *cregs = child->thread.kregs; | |
376 | unsigned long psr, pc, npc, y; | |
377 | int i; | |
378 | ||
379 | /* Must be careful, tracing process can only set certain | |
380 | * bits in the psr. | |
381 | */ | |
382 | if (!access_ok(VERIFY_READ, pregs, sizeof(struct pt_regs))) { | |
383 | pt_error_return(regs, EFAULT); | |
384 | goto out_tsk; | |
385 | } | |
386 | __get_user(psr, (&pregs->psr)); | |
387 | __get_user(pc, (&pregs->pc)); | |
388 | __get_user(npc, (&pregs->npc)); | |
389 | __get_user(y, (&pregs->y)); | |
390 | psr &= PSR_ICC; | |
391 | cregs->psr &= ~PSR_ICC; | |
392 | cregs->psr |= psr; | |
393 | if (!((pc | npc) & 3)) { | |
394 | cregs->pc = pc; | |
395 | cregs->npc =npc; | |
396 | } | |
397 | cregs->y = y; | |
398 | for(i = 1; i < 16; i++) | |
399 | __get_user(cregs->u_regs[i], (&pregs->u_regs[i-1])); | |
400 | pt_succ_return(regs, 0); | |
401 | goto out_tsk; | |
402 | } | |
403 | ||
404 | case PTRACE_GETFPREGS: { | |
405 | struct fps { | |
406 | unsigned long regs[32]; | |
407 | unsigned long fsr; | |
408 | unsigned long flags; | |
409 | unsigned long extra; | |
410 | unsigned long fpqd; | |
411 | struct fq { | |
412 | unsigned long *insnaddr; | |
413 | unsigned long insn; | |
414 | } fpq[16]; | |
415 | }; | |
416 | struct fps __user *fps = (struct fps __user *) addr; | |
417 | int i; | |
418 | ||
419 | if (!access_ok(VERIFY_WRITE, fps, sizeof(struct fps))) { | |
420 | i = -EFAULT; | |
421 | pt_error_return(regs, -i); | |
422 | goto out_tsk; | |
423 | } | |
424 | for(i = 0; i < 32; i++) | |
425 | __put_user(child->thread.float_regs[i], (&fps->regs[i])); | |
426 | __put_user(child->thread.fsr, (&fps->fsr)); | |
427 | __put_user(child->thread.fpqdepth, (&fps->fpqd)); | |
428 | __put_user(0, (&fps->flags)); | |
429 | __put_user(0, (&fps->extra)); | |
430 | for(i = 0; i < 16; i++) { | |
431 | __put_user(child->thread.fpqueue[i].insn_addr, | |
432 | (&fps->fpq[i].insnaddr)); | |
433 | __put_user(child->thread.fpqueue[i].insn, (&fps->fpq[i].insn)); | |
434 | } | |
435 | pt_succ_return(regs, 0); | |
436 | goto out_tsk; | |
437 | } | |
438 | ||
439 | case PTRACE_SETFPREGS: { | |
440 | struct fps { | |
441 | unsigned long regs[32]; | |
442 | unsigned long fsr; | |
443 | unsigned long flags; | |
444 | unsigned long extra; | |
445 | unsigned long fpqd; | |
446 | struct fq { | |
447 | unsigned long *insnaddr; | |
448 | unsigned long insn; | |
449 | } fpq[16]; | |
450 | }; | |
451 | struct fps __user *fps = (struct fps __user *) addr; | |
452 | int i; | |
453 | ||
454 | if (!access_ok(VERIFY_READ, fps, sizeof(struct fps))) { | |
455 | i = -EFAULT; | |
456 | pt_error_return(regs, -i); | |
457 | goto out_tsk; | |
458 | } | |
459 | copy_from_user(&child->thread.float_regs[0], &fps->regs[0], (32 * sizeof(unsigned long))); | |
460 | __get_user(child->thread.fsr, (&fps->fsr)); | |
461 | __get_user(child->thread.fpqdepth, (&fps->fpqd)); | |
462 | for(i = 0; i < 16; i++) { | |
463 | __get_user(child->thread.fpqueue[i].insn_addr, | |
464 | (&fps->fpq[i].insnaddr)); | |
465 | __get_user(child->thread.fpqueue[i].insn, (&fps->fpq[i].insn)); | |
466 | } | |
467 | pt_succ_return(regs, 0); | |
468 | goto out_tsk; | |
469 | } | |
470 | ||
471 | case PTRACE_READTEXT: | |
472 | case PTRACE_READDATA: { | |
473 | int res = ptrace_readdata(child, addr, | |
474 | (void __user *) addr2, data); | |
475 | ||
476 | if (res == data) { | |
477 | pt_succ_return(regs, 0); | |
478 | goto out_tsk; | |
479 | } | |
480 | /* Partial read is an IO failure */ | |
481 | if (res >= 0) | |
482 | res = -EIO; | |
483 | pt_error_return(regs, -res); | |
484 | goto out_tsk; | |
485 | } | |
486 | ||
487 | case PTRACE_WRITETEXT: | |
488 | case PTRACE_WRITEDATA: { | |
489 | int res = ptrace_writedata(child, (void __user *) addr2, | |
490 | addr, data); | |
491 | ||
492 | if (res == data) { | |
493 | pt_succ_return(regs, 0); | |
494 | goto out_tsk; | |
495 | } | |
496 | /* Partial write is an IO failure */ | |
497 | if (res >= 0) | |
498 | res = -EIO; | |
499 | pt_error_return(regs, -res); | |
500 | goto out_tsk; | |
501 | } | |
502 | ||
503 | case PTRACE_SYSCALL: /* continue and stop at (return from) syscall */ | |
504 | addr = 1; | |
505 | ||
506 | case PTRACE_CONT: { /* restart after signal. */ | |
7ed20e1a | 507 | if (!valid_signal(data)) { |
1da177e4 LT |
508 | pt_error_return(regs, EIO); |
509 | goto out_tsk; | |
510 | } | |
1da177e4 LT |
511 | |
512 | if (request == PTRACE_SYSCALL) | |
513 | set_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | |
514 | else | |
515 | clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | |
516 | ||
517 | child->exit_code = data; | |
518 | #ifdef DEBUG_PTRACE | |
519 | printk("CONT: %s [%d]: set exit_code = %x %lx %lx\n", | |
520 | child->comm, child->pid, child->exit_code, | |
521 | child->thread.kregs->pc, | |
522 | child->thread.kregs->npc); | |
523 | #endif | |
524 | wake_up_process(child); | |
525 | pt_succ_return(regs, 0); | |
526 | goto out_tsk; | |
527 | } | |
528 | ||
529 | /* | |
530 | * make the child exit. Best I can do is send it a sigkill. | |
531 | * perhaps it should be put in the status that it wants to | |
532 | * exit. | |
533 | */ | |
534 | case PTRACE_KILL: { | |
535 | if (child->exit_state == EXIT_ZOMBIE) { /* already dead */ | |
536 | pt_succ_return(regs, 0); | |
537 | goto out_tsk; | |
538 | } | |
539 | wake_up_process(child); | |
540 | child->exit_code = SIGKILL; | |
541 | pt_succ_return(regs, 0); | |
542 | goto out_tsk; | |
543 | } | |
544 | ||
545 | case PTRACE_SUNDETACH: { /* detach a process that was attached. */ | |
546 | int err = ptrace_detach(child, data); | |
547 | if (err) { | |
548 | pt_error_return(regs, EIO); | |
549 | goto out_tsk; | |
550 | } | |
551 | pt_succ_return(regs, 0); | |
552 | goto out_tsk; | |
553 | } | |
554 | ||
555 | /* PTRACE_DUMPCORE unsupported... */ | |
556 | ||
557 | default: { | |
558 | int err = ptrace_request(child, request, addr, data); | |
559 | if (err) | |
560 | pt_error_return(regs, -err); | |
561 | else | |
562 | pt_succ_return(regs, 0); | |
563 | goto out_tsk; | |
564 | } | |
565 | } | |
566 | out_tsk: | |
567 | if (child) | |
568 | put_task_struct(child); | |
569 | out: | |
570 | unlock_kernel(); | |
571 | } | |
572 | ||
573 | asmlinkage void syscall_trace(void) | |
574 | { | |
575 | #ifdef DEBUG_PTRACE | |
576 | printk("%s [%d]: syscall_trace\n", current->comm, current->pid); | |
577 | #endif | |
578 | if (!test_thread_flag(TIF_SYSCALL_TRACE)) | |
579 | return; | |
580 | if (!(current->ptrace & PT_PTRACED)) | |
581 | return; | |
582 | current->thread.flags ^= MAGIC_CONSTANT; | |
583 | ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD) | |
584 | ? 0x80 : 0)); | |
585 | /* | |
586 | * this isn't the same as continuing with a signal, but it will do | |
587 | * for normal use. strace only continues with a signal if the | |
588 | * stopping signal is not SIGTRAP. -brl | |
589 | */ | |
590 | #ifdef DEBUG_PTRACE | |
591 | printk("%s [%d]: syscall_trace exit= %x\n", current->comm, | |
592 | current->pid, current->exit_code); | |
593 | #endif | |
594 | if (current->exit_code) { | |
595 | send_sig (current->exit_code, current, 1); | |
596 | current->exit_code = 0; | |
597 | } | |
598 | } |