diff --git a/include/linux/ptrace.h b/include/linux/ptrace.h index c6de3a4ea70a5febf8dc802aed6e73e996d64e8d..706b420fb5c9cc9a43707576af4b53524eea30f2 100644 --- a/include/linux/ptrace.h +++ b/include/linux/ptrace.h @@ -26,6 +26,8 @@ /* 0x4200-0x4300 are reserved for architecture-independent additions. */ #define PTRACE_SETOPTIONS 0x4200 #define PTRACE_GETEVENTMSG 0x4201 +#define PTRACE_GETSIGINFO 0x4202 +#define PTRACE_SETSIGINFO 0x4203 /* options set using PTRACE_SETOPTIONS */ #define PTRACE_O_TRACESYSGOOD 0x00000001 @@ -33,12 +35,16 @@ #define PTRACE_O_TRACEVFORK 0x00000004 #define PTRACE_O_TRACECLONE 0x00000008 #define PTRACE_O_TRACEEXEC 0x00000010 +#define PTRACE_O_TRACEVFORKDONE 0x00000020 +#define PTRACE_O_TRACEEXIT 0x00000040 /* Wait extended result codes for the above trace options. */ #define PTRACE_EVENT_FORK 1 #define PTRACE_EVENT_VFORK 2 #define PTRACE_EVENT_CLONE 3 #define PTRACE_EVENT_EXEC 4 +#define PTRACE_EVENT_VFORK_DONE 5 +#define PTRACE_EVENT_EXIT 6 #include <asm/ptrace.h> #include <linux/sched.h> diff --git a/include/linux/sched.h b/include/linux/sched.h index 78970007590fb70270850539aa0aa57acb4a579d..ea5d949f946cc1b5c9bc6fa9fa9b6452f6838e93 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -417,6 +417,7 @@ struct task_struct { struct backing_dev_info *backing_dev_info; unsigned long ptrace_message; + siginfo_t *last_siginfo; /* For ptrace use. */ }; extern void __put_task_struct(struct task_struct *tsk); @@ -457,6 +458,8 @@ do { if (atomic_dec_and_test(&(tsk)->usage)) __put_task_struct(tsk); } while(0) #define PT_TRACE_VFORK 0x00000020 #define PT_TRACE_CLONE 0x00000040 #define PT_TRACE_EXEC 0x00000080 +#define PT_TRACE_VFORK_DONE 0x00000100 +#define PT_TRACE_EXIT 0x00000200 #if CONFIG_SMP extern void set_cpus_allowed(task_t *p, unsigned long new_mask); diff --git a/kernel/exit.c b/kernel/exit.c index febad08ae9ef531706cffe074d010aaa07c785cd..fbc00cfae0304345b5e5ce41a2651baeb9b98587 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -199,6 +199,17 @@ static inline int has_stopped_jobs(int pgrp) for_each_task_pid(pgrp, PIDTYPE_PGID, p, l, pid) { if (p->state != TASK_STOPPED) continue; + + /* If p is stopped by a debugger on a signal that won't + stop it, then don't count p as stopped. This isn't + perfect but it's a good approximation. */ + if (unlikely (p->ptrace) + && p->exit_code != SIGSTOP + && p->exit_code != SIGTSTP + && p->exit_code != SIGTTOU + && p->exit_code != SIGTTIN) + continue; + retval = 1; break; } @@ -594,7 +605,7 @@ static void exit_notify(struct task_struct *tsk) * is about to become orphaned. */ - t = tsk->parent; + t = tsk->real_parent; if ((t->pgrp != tsk->pgrp) && (t->session == tsk->session) && @@ -627,8 +638,16 @@ static void exit_notify(struct task_struct *tsk) tsk->exit_signal = SIGCHLD; - if (tsk->exit_signal != -1) - do_notify_parent(tsk, tsk->exit_signal); + /* If something other than our normal parent is ptracing us, then + * send it a SIGCHLD instead of honoring exit_signal. exit_signal + * only has special meaning to our real parent. + */ + if (tsk->exit_signal != -1) { + if (tsk->parent == tsk->real_parent) + do_notify_parent(tsk, tsk->exit_signal); + else + do_notify_parent(tsk, SIGCHLD); + } tsk->state = TASK_ZOMBIE; /* @@ -661,6 +680,9 @@ NORET_TYPE void do_exit(long code) profile_exit_task(tsk); + if (unlikely(current->ptrace & PT_TRACE_EXIT)) + ptrace_notify((PTRACE_EVENT_EXIT << 8) | SIGTRAP); + acct_process(code); __exit_mm(tsk); diff --git a/kernel/fork.c b/kernel/fork.c index 988a195bcc932a26f9757a9791ab68b438edd6f5..9e12b35e39243b599bfdba76372be6c6b4a84e03 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -1085,9 +1085,11 @@ struct task_struct *do_fork(unsigned long clone_flags, ptrace_notify ((trace << 8) | SIGTRAP); } - if (clone_flags & CLONE_VFORK) + if (clone_flags & CLONE_VFORK) { wait_for_completion(&vfork); - else + if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE)) + ptrace_notify ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP); + } else /* * Let the child process run first, to avoid most of the * COW overhead when the child exec()s afterwards. diff --git a/kernel/ptrace.c b/kernel/ptrace.c index a16dfb90d4121f9133e4334dfcfffa44f3ea100a..14d158864d9ea1ec8993e53fd04366d685faf29d 100644 --- a/kernel/ptrace.c +++ b/kernel/ptrace.c @@ -277,15 +277,43 @@ static int ptrace_setoptions(struct task_struct *child, long data) else child->ptrace &= ~PT_TRACE_EXEC; + if (data & PTRACE_O_TRACEVFORKDONE) + child->ptrace |= PT_TRACE_VFORK_DONE; + else + child->ptrace &= ~PT_TRACE_VFORK_DONE; + + if (data & PTRACE_O_TRACEEXIT) + child->ptrace |= PT_TRACE_EXIT; + else + child->ptrace &= ~PT_TRACE_EXIT; + if ((data & (PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE - | PTRACE_O_TRACEEXEC)) + | PTRACE_O_TRACEEXEC | PTRACE_O_TRACEEXIT + | PTRACE_O_TRACEVFORKDONE)) != data) return -EINVAL; return 0; } +static int ptrace_getsiginfo(struct task_struct *child, long data) +{ + if (child->last_siginfo == NULL) + return -EINVAL; + return copy_siginfo_to_user ((siginfo_t *) data, child->last_siginfo); +} + +static int ptrace_setsiginfo(struct task_struct *child, long data) +{ + if (child->last_siginfo == NULL) + return -EINVAL; + if (copy_from_user (child->last_siginfo, (siginfo_t *) data, + sizeof (siginfo_t)) != 0) + return -EFAULT; + return 0; +} + int ptrace_request(struct task_struct *child, long request, long addr, long data) { @@ -301,6 +329,12 @@ int ptrace_request(struct task_struct *child, long request, case PTRACE_GETEVENTMSG: ret = put_user(child->ptrace_message, (unsigned long *) data); break; + case PTRACE_GETSIGINFO: + ret = ptrace_getsiginfo(child, data); + break; + case PTRACE_SETSIGINFO: + ret = ptrace_setsiginfo(child, data); + break; default: break; } diff --git a/kernel/signal.c b/kernel/signal.c index a095215cffb1041f7a627e7632a2637aa2f23f54..670141c149a7cec491c5c3fbda13d06e8320a7a3 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -1427,17 +1427,23 @@ int get_signal_to_deliver(siginfo_t *info, struct pt_regs *regs) /* Let the debugger run. */ current->exit_code = signr; + current->last_siginfo = info; set_current_state(TASK_STOPPED); notify_parent(current, SIGCHLD); schedule(); + current->last_siginfo = NULL; + /* We're back. Did the debugger cancel the sig? */ signr = current->exit_code; if (signr == 0) continue; current->exit_code = 0; - /* Update the siginfo structure. Is this good? */ + /* Update the siginfo structure if the signal has + changed. If the debugger wanted something + specific in the siginfo structure then it should + have updated *info via PTRACE_SETSIGINFO. */ if (signr != info->si_signo) { info->si_signo = signr; info->si_errno = 0;