It's been a while since the last set of ptrace patches; here's what I've
queued up. They'll follow in individual messages, or they're available by
BitKeeper (details below).
These are all small changes; there may be some more interesting ones in a
couple of weeks, after GDB support for NPTL is more mature. Overview:
- Two new traceable events, end-of-vfork and process exit.
- Ptrace calls to get and set the siginfo struct associated with a signal.
- Two separate signal handling fixes for the fork-tracing and CLONE_PTRACE
support.
- A bugfix to prevent zombie processes when debugging LinuxThreads.
Linus, please do a
bk pull http://ptrace.bkbits.net/for-linus-2.5
This will update the following files:
include/linux/ptrace.h | 6 ++++++
include/linux/sched.h | 3 +++
kernel/exit.c | 30 ++++++++++++++++++++++++++----
kernel/fork.c | 8 +++++---
kernel/ptrace.c | 36 +++++++++++++++++++++++++++++++++++-
kernel/signal.c | 8 +++++++-
6 files changed, 82 insertions(+), 9 deletions(-)
through these ChangeSets:
<[email protected]> (03/02/06 1.961)
Signal handling bugs for thread exit + ptrace
<[email protected]> (03/02/06 1.960)
Add PTRACE_O_TRACEVFORKDONE and PTRACE_O_TRACEEXIT facilities.
<[email protected]> (03/02/04 1.959)
Use force_sig_specific to send SIGSTOP to newly-created CLONE_PTRACE processes.
<[email protected]> (03/01/18 1.958)
Add PTRACE_GETSIGINFO and PTRACE_SETSIGINFO
These new ptrace commands allow a debugger to control signals more precisely;
for instance, store a signal and deliver it later, as if it had come from the
original outside process or in response to the same faulting memory access.
<[email protected]> (03/01/18 1.957)
Tweak has_stopped_jobs for use with debugging
--
Daniel Jacobowitz
MontaVista Software Debian GNU/Linux Developer
I posted this patch a couple of weeks ago, here it is again. The basic
problem it's solving is this: suppose you have GDB debugging an application,
using the new PTRACE_EVENT_FORK. The application forks. There can be a
point where GDB resumes the parent, which gets scheduled, and exits, before
GDB gets a chance to resume the child. has_stopped_jobs causes the kernel
to decide the still-stopped-in-GDB child (which will be an orphaned pgrp) is
going to be lost, so it sends a SIGHUP and SIGCONT to it, confusing GDB
terribly. Normally those signals wouldn't be sent.
The longer explanation:
POSIX has this terribly useful thing to say:
# If the exit of the process causes a process group to become orphaned, and if
# any member of the newly-orphaned process group is stopped, then a SIGHUP
# signal followed by a SIGCONT signal shall be sent to each process in the
# newly-orphaned process group.
The Rationale is at least a little chattier. See
http://www.opengroup.org/onlinepubs/007904975/functions/exit.html#tag_03_131
if you want to read it.
Basically, this is so that a stopped process group won't unintentionally
stay stopped when its shell no longer has a connection to it. For whatever
that's worth.
I think this patch is well within the spirit of that definition. If a
process is stopped, but there is a debugger attached to it and the stopping
signal is not one that would normally stop the process, then don't count it
as a stopped job. Without this, when you continue past a fork() call and
the parent quickly exits, the child will get an unaccountable SIGHUP.
It's not perfect, of course - the application might be SIG_IGN'ing SIGTSTP,
but stopped in the debugger for it anyway. It's not worth being that
complicated here, though.
# --------------------------------------------
# 03/01/18 [email protected] 1.957
# Tweak has_stopped_jobs for use with debugging
# --------------------------------------------
diff -Nru a/kernel/exit.c b/kernel/exit.c
--- a/kernel/exit.c Thu Feb 6 16:57:39 2003
+++ b/kernel/exit.c Thu Feb 6 16:57:39 2003
@@ -203,6 +203,17 @@
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;
}
--
Daniel Jacobowitz
MontaVista Software Debian GNU/Linux Developer
Right now, CLONE_PTRACE uses send_sig(SIGSTOP, p, 1). If you use
CLONE_THREAD | CLONE_PTRACE, though, this SIGSTOP gets broadcast to the
entire thread group. That's not what was intended; we only want the one
new thread to stop. Fixed like so.
# --------------------------------------------
# 03/02/04 [email protected] 1.959
# Use force_sig_specific to send SIGSTOP to newly-created CLONE_PTRACE processes.
# --------------------------------------------
diff -Nru a/kernel/fork.c b/kernel/fork.c
--- a/kernel/fork.c Thu Feb 6 16:57:32 2003
+++ b/kernel/fork.c Thu Feb 6 16:57:32 2003
@@ -1036,7 +1036,7 @@
}
if (p->ptrace & PT_PTRACED)
- send_sig(SIGSTOP, p, 1);
+ force_sig_specific(SIGSTOP, p);
wake_up_forked_process(p); /* do this last */
++total_forks;
--
Daniel Jacobowitz
MontaVista Software Debian GNU/Linux Developer
This is a pretty old patch; the first version of it that I saw came from
Mark Kettenis about two and a half years ago, and Alan said he'd look at it
for the next 2.2 kernel at the time. It got lost somewhere along the line.
The problem is that we check current->parent->self_exec_domain in
exit_notify, where we should be checking current->real_parent instead.
Then, when that's fixed, we hardcode a SIGCHLD in sys_wait4 where we should
be using p->exit_signal.
Without both fixes, when debugging a LinuxThreads application, the manager
thread receives SIGCHLDs instead of SIGRTMIN+1's every time a thread exits;
and it never reaps them, so they stick around as zombies until the whole
process exits and init gets a chance to reap them.
# The following is the BitKeeper ChangeSet Log
# --------------------------------------------
# 03/02/06 [email protected] 1.961
# Signal handling bugs for thread exit + ptrace
# --------------------------------------------
diff -Nru a/kernel/exit.c b/kernel/exit.c
--- a/kernel/exit.c Thu Feb 6 16:57:26 2003
+++ b/kernel/exit.c Thu Feb 6 16:57:26 2003
@@ -586,7 +586,7 @@
* is about to become orphaned.
*/
- t = current->parent;
+ t = current->real_parent;
if ((t->pgrp != current->pgrp) &&
(t->session == current->session) &&
@@ -619,8 +619,16 @@
current->exit_signal = SIGCHLD;
- if (current->exit_signal != -1)
- do_notify_parent(current, current->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 (current->exit_signal != -1) {
+ if (current->parent == current->real_parent)
+ do_notify_parent(current, current->exit_signal);
+ else
+ do_notify_parent(current, SIGCHLD);
+ }
current->state = TASK_ZOMBIE;
/*
@@ -877,7 +885,7 @@
if (p->real_parent != p->parent) {
write_lock_irq(&tasklist_lock);
__ptrace_unlink(p);
- do_notify_parent(p, SIGCHLD);
+ do_notify_parent(p, p->exit_signal);
p->state = TASK_ZOMBIE;
write_unlock_irq(&tasklist_lock);
} else
--
Daniel Jacobowitz
MontaVista Software Debian GNU/Linux Developer
This patch adds PTRACE_GETSIGINFO and PTRACE_SETSIGINFO, as suggested by
someone on linux-kernel (discussion between Andi Kleen and Roland McGrath, I
think). You can use these to query the information about a signal or forge
it, which is useful if (for instance) a debugger needs to queue a signal
for later delivery.
This implements the feature for all architectures which use the generic
get_signal_to_deliver. Doing it for the rest is straightforward.
# --------------------------------------------
# 03/01/18 [email protected] 1.958
# Add PTRACE_GETSIGINFO and PTRACE_SETSIGINFO
#
# These new ptrace commands allow a debugger to control signals more precisely;
# for instance, store a signal and deliver it later, as if it had come from the
# original outside process or in response to the same faulting memory access.
# --------------------------------------------
diff -Nru a/include/linux/ptrace.h b/include/linux/ptrace.h
--- a/include/linux/ptrace.h Thu Feb 6 16:57:36 2003
+++ b/include/linux/ptrace.h Thu Feb 6 16:57:36 2003
@@ -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
diff -Nru a/include/linux/sched.h b/include/linux/sched.h
--- a/include/linux/sched.h Thu Feb 6 16:57:36 2003
+++ b/include/linux/sched.h Thu Feb 6 16:57:36 2003
@@ -400,6 +400,7 @@
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);
diff -Nru a/kernel/ptrace.c b/kernel/ptrace.c
--- a/kernel/ptrace.c Thu Feb 6 16:57:36 2003
+++ b/kernel/ptrace.c Thu Feb 6 16:57:36 2003
@@ -286,6 +286,23 @@
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)
{
@@ -300,6 +317,12 @@
break;
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 -Nru a/kernel/signal.c b/kernel/signal.c
--- a/kernel/signal.c Thu Feb 6 16:57:36 2003
+++ b/kernel/signal.c Thu Feb 6 16:57:36 2003
@@ -1244,10 +1244,13 @@
if ((current->ptrace & PT_PTRACED) && signr != SIGKILL) {
/* 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)
@@ -1258,7 +1261,10 @@
if (signr == SIGSTOP)
continue;
- /* 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;
--
Daniel Jacobowitz
MontaVista Software Debian GNU/Linux Developer
This patch adds two new event hooks to ptrace:
PTRACE_EVENT_EXIT, which triggers in do_exit(). This is useful to quickly
find out where a program is making an exit syscall from, etc. - it
triggers before the mm is released, so we can still get backtraces et
cetera.
PTRACE_EVENT_VFORK_DONE triggers in do_fork() after the vfork completion,
i.e. when the child is done with the mm. This is a safe way to figure out
when we can re-insert breakpoints in the parent without the child hitting
them. It's the only safe way, in fact.
# --------------------------------------------
# 03/02/06 [email protected] 1.960
# Add PTRACE_O_TRACEVFORKDONE and PTRACE_O_TRACEEXIT facilities.
# --------------------------------------------
diff -Nru a/include/linux/ptrace.h b/include/linux/ptrace.h
--- a/include/linux/ptrace.h Thu Feb 6 16:57:29 2003
+++ b/include/linux/ptrace.h Thu Feb 6 16:57:29 2003
@@ -35,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 -Nru a/include/linux/sched.h b/include/linux/sched.h
--- a/include/linux/sched.h Thu Feb 6 16:57:29 2003
+++ b/include/linux/sched.h Thu Feb 6 16:57:29 2003
@@ -441,6 +441,8 @@
#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 -Nru a/kernel/exit.c b/kernel/exit.c
--- a/kernel/exit.c Thu Feb 6 16:57:29 2003
+++ b/kernel/exit.c Thu Feb 6 16:57:29 2003
@@ -653,6 +653,9 @@
profile_exit_task(tsk);
+ if (unlikely(current->ptrace & PT_TRACE_EXIT))
+ ptrace_notify((PTRACE_EVENT_EXIT << 8) | SIGTRAP);
+
fake_volatile:
acct_process(code);
__exit_mm(tsk);
diff -Nru a/kernel/fork.c b/kernel/fork.c
--- a/kernel/fork.c Thu Feb 6 16:57:29 2003
+++ b/kernel/fork.c Thu Feb 6 16:57:29 2003
@@ -1046,9 +1046,11 @@
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 -Nru a/kernel/ptrace.c b/kernel/ptrace.c
--- a/kernel/ptrace.c Thu Feb 6 16:57:29 2003
+++ b/kernel/ptrace.c Thu Feb 6 16:57:29 2003
@@ -277,9 +277,20 @@
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;
--
Daniel Jacobowitz
MontaVista Software Debian GNU/Linux Developer