Skip to contentSkip to conversation

ptrace on stopped processes (2.4)

PATCH In review vic 5 participants 13 messages Dec 21, 2001 – Mar 19, 2002 ptrace support

lore
Share & tools
lore

vic

author
#0 lore raw

PATCH 1/1 ptrace on stopped processes (2.4)

This patch fixes a couple problems with ptrace's interaction with stopped processes on Linux 2.4.

The most significant bug is that gdb cannot attach to a stopped process. Specifically, the wait that follows the PTRACE_ATTACH will block indefinitely.

Another bug is that it is not possible to use PTRACE_DETACH to leave a process stopped, because ptrace ignores SIGSTOPs sent by the tracing process.

This patch is against 2.4.16 on x86. I have tested gdb and strace. After this patch is reviewed, I would be happy to submit an analogous patch for the other platforms, although I cannot test it.

Vic Zandy

arch/i386/kernel/signal.c +3 -3
@@ -620,9 +620,9 @@
 				continue;
 			current->exit_code = 0;
 
-			/* The debugger continued.  Ignore SIGSTOP.  */
-			if (signr == SIGSTOP)
-				continue;
+			/* The debugger continued. */
+			if (signr == SIGSTOP && current->ptrace & PT_PTRACED)
+				continue; /* ignore SIGSTOP */
 
 			/* Update the siginfo structure.  Is this good?  */
 			if (signr != info.si_signo) {
kernel/ptrace.c +4 -2
@@ -89,8 +89,10 @@
 		SET_LINKS(task);
 	}
 	write_unlock_irq(&tasklist_lock);
-
-	send_sig(SIGSTOP, task, 1);
+	if (task->state != TASK_STOPPED)
+		send_sig(SIGSTOP, task, 1);
+	else
+		task->exit_code = SIGSTOP;
 	return 0;
 
 bad:
#1 lore raw
Quoted 1 line · [email protected] said:

This patch is against 2.4.16 on x86. I have tested gdb and strace.

Also make sure UML still runs.

Jeff

#2 lore raw
In reply to vic
Quoted 5 lines · vic
kernel/ptrace.c +0 -0
@@ -89,8 +89,10 @@
 		SET_LINKS(task);
 	}

kernel/ptrace.c line 98

Quoted 10 lines
kernel/ptrace.c +4 -2
 	write_unlock_irq(&tasklist_lock);
-
-	send_sig(SIGSTOP, task, 1);
+	if (task->state != TASK_STOPPED)
+		send_sig(SIGSTOP, task, 1);
+	else
+		task->exit_code = SIGSTOP;
 	return 0;
 
 bad:

It seems that trace is started in the place different from usual. Then, I think PTRACE_KILL doesn't work.

If it need, I think it should wake up a task.

Signature · 1 line
OGAWA Hirofumi <[email protected]>
#3 lore raw
In reply to vic
Quoted 3 lines · vic

The most significant bug is that gdb cannot attach to a stopped process. Specifically, the wait that follows the PTRACE_ATTACH will block indefinitely.

Are you sure this is a bug in ptrace? Maybe gdb and other such programs could notice that a wait is inappropriate in this case and not do it.

(It looks to me like the wait actually does complete when the process is continued.)

Also, is this something that used to work? Or would this be a change in the semantics of ptrace?

Quoted 3 lines

Another bug is that it is not possible to use PTRACE_DETACH to leave a process stopped, because ptrace ignores SIGSTOPs sent by the tracing process.

Unless I'm missing something (frequently the case), there are two cases here:
(1) the tracer wants to leave the tracee stopped, and (2) the tracer wants the process to continue running in as natural a way as possible, meaning without sending it a SIGCONT (which can cause the SIGCONT signal handler to execute). As things currently stand, we have behavior (2), and (1) is not possible. With your change, we'd have behavior (1), and (2) would not be possible.

To me, (2) seems very important, so I wouldn't want to give it up. Is there some way we can get both?

Quoted 1 line

This patch is against 2.4.16 on x86. I have tested gdb and strace.

Although SUBTERFUGUE isn't widely used, I'd be curious to know how it reacts to this patch. (If you're running Debian, there's a package.)

Mike

author
#4 lore raw
In reply to vic

This is to respond to feedback for the ptrace patch I sent toward the end of december. The original message is below.

From: Alan Cox <[email protected]>

Quoted 2 lines

Review

Quoted 1 line
(quoted diff) +1 -0
+			if (signr == SIGSTOP && current->ptrace & PT_PTRACED)

This does not I suspect do what you think - surely you want brackets ?

I agree the second term should be wrapped in parens (it is now in the patch below); but isn't that logically equivalent to what I had?

From: Mike Coleman <[email protected]>:

Quoted 2 lines

Also, is this something that used to work? Or would this be a change in the semantics of ptrace?

This is a change of semantics at least going back to 2.2.

Quoted 10 lines
Quoted 3 lines

Another bug is that it is not possible to use PTRACE_DETACH to leave a process stopped, because ptrace ignores SIGSTOPs sent by the tracing process.

Unless I'm missing something (frequently the case), there are two cases here:
(1) the tracer wants to leave the tracee stopped, and (2) the tracer wants the process to continue running in as natural a way as possible, meaning without sending it a SIGCONT (which can cause the SIGCONT signal handler to execute). As things currently stand, we have behavior (2), and (1) is not possible. With your change, we'd have behavior (1), and (2) would not be possible.

I agree that the ability to do (2) should be preserved, but I don't see how this patch breaks it; do you have an example?

From: OGAWA Hirofumi <[email protected]>:

Quoted 14 lines
Quoted 14 lines
kernel/ptrace.c +4 -2
@@ -89,8 +89,10 @@
 		SET_LINKS(task);
 	}
 	write_unlock_irq(&tasklist_lock);
-
-	send_sig(SIGSTOP, task, 1);
+	if (task->state != TASK_STOPPED)
+		send_sig(SIGSTOP, task, 1);
+	else
+		task->exit_code = SIGSTOP;
 	return 0;
 
Quoted 4 lines

kernel/ptrace.c line 98

Quoted 1 line
kernel/ptrace.c +0 -0
 bad:

It seems that trace is started in the place different from usual. Then, I think PTRACE_KILL doesn't work.

I don't agree, it seems to work for me.

I'd still want to check uml and subterfuge, which I'll do after these points are cleared up.

Thanks,
Vic

From: vic <[email protected]>
Subject: [PATCH] ptrace on stopped processes (2.4)
To: [email protected]
Cc: [email protected], [email protected],
[email protected]
Date: Fri, 21 Dec 2001 13:53:32 -0600

This patch fixes a couple problems with ptrace's interaction with stopped processes on Linux 2.4.

The most significant bug is that gdb cannot attach to a stopped process. Specifically, the wait that follows the PTRACE_ATTACH will block indefinitely.

Another bug is that it is not possible to use PTRACE_DETACH to leave a process stopped, because ptrace ignores SIGSTOPs sent by the tracing process.

This patch is against 2.4.16 on x86. I have tested gdb and strace. After this patch is reviewed, I would be happy to submit an analogous patch for the other platforms, although I cannot test it.

Vic Zandy

arch/i386/kernel/signal.c Wed Jan 16 22:19:16 2002 renamed from arch/i386/kernel/signal.c Fri Sep 14 16:15:40 2001 +3 -3
@@ -620,9 +620,9 @@
                                continue;
                        current->exit_code = 0;
 
-                       /* The debugger continued.  Ignore SIGSTOP.  */
-                       if (signr == SIGSTOP)
-                               continue;
+                       /* The debugger continued. */
+                       if (signr == SIGSTOP && (current->ptrace & PT_PTRACED))
+                               continue; /* ignore SIGSTOP */
 
                        /* Update the siginfo structure.  Is this good?  */
                        if (signr != info.si_signo) {
kernel/ptrace.c Fri Dec 21 10:42:44 2001 renamed from kernel/ptrace.c Wed Nov 21 16:43:01 2001 +4 -2
@@ -89,8 +89,10 @@
                SET_LINKS(task);
        }
        write_unlock_irq(&tasklist_lock);
-
-       send_sig(SIGSTOP, task, 1);
+       if (task->state != TASK_STOPPED)
+               send_sig(SIGSTOP, task, 1);
+       else
+               task->exit_code = SIGSTOP;
        return 0;
 
 bad:
#5 lore raw
In reply to vic
Quoted 15 lines · vic

From: OGAWA Hirofumi <[email protected]>:

Quoted 14 lines
Quoted 14 lines
kernel/ptrace.c +4 -2
@@ -89,8 +89,10 @@
 		SET_LINKS(task);
 	}
 	write_unlock_irq(&tasklist_lock);
-
-	send_sig(SIGSTOP, task, 1);
+	if (task->state != TASK_STOPPED)
+		send_sig(SIGSTOP, task, 1);
+	else
+		task->exit_code = SIGSTOP;
 	return 0;
 
Quoted 6 lines
Quoted 4 lines

kernel/ptrace.c line 98

Quoted 1 line
kernel/ptrace.c +0 -0
 bad:

It seems that trace is started in the place different from usual. Then, I think PTRACE_KILL doesn't work.

I don't agree, it seems to work for me.

I tested the following on linux-2.4.16 + your_patch:

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <unistd.h>

int main(int argc, char *argv[])

{
	pid_t pid;
	int ret, status;

	pid = fork();
	if (pid == -1)
		exit(1);
	if (pid == 0) {
		raise(SIGSTOP);
		while (1)
			;
		_exit(1);
	}

	ret = waitpid(pid, &status, WUNTRACED);
	if (ret == -1) {
		perror("waitpid (1)");
		exit(1);
	}

	ret = ptrace(PTRACE_ATTACH, pid, NULL, NULL);
	if (ret == -1) {
		perror("PTRACE_ATTACH");
		exit(1);
	}
	ret = waitpid(pid, &status, 0);
	if (ret == -1) {
		perror("waitpid (2)");
		exit(1);
	}
	ret = ptrace(PTRACE_KILL, pid, NULL, NULL);
	if (ret == -1) {
		perror("PTRACE_KILL");
		exit(1);
	}

	return 0;
}

Test result:

hirofumi@devron (ptrace)[1111]$ ps ax|grep ptrace
688 tty1 S 0:00 grep ptrace
hirofumi@devron (ptrace)[1112]$ ls
ptrace ptrace.c
hirofumi@devron (ptrace)[1113]$ ./ptrace hirofumi@devron (ptrace)[1114]$ ps ax|grep ptrace
691 tty1 R 0:04 ./ptrace
693 tty1 S 0:00 grep ptrace
hirofumi@devron (ptrace)[1115]$

Do I misunderstand something?

Signature · 1 line
OGAWA Hirofumi <[email protected]>
#6 lore raw
In reply to vic
Quoted 5 lines · vic

From: Mike Coleman <[email protected]>:

Quoted 2 lines

Also, is this something that used to work? Or would this be a change in the semantics of ptrace?

This is a change of semantics at least going back to 2.2.

Okay. Is it at least backward compatible? Or are some tools expected to break?

Quoted 9 lines
Quoted 6 lines

Unless I'm missing something (frequently the case), there are two cases here:
(1) the tracer wants to leave the tracee stopped, and (2) the tracer wants the process to continue running in as natural a way as possible, meaning without sending it a SIGCONT (which can cause the SIGCONT signal handler to execute). As things currently stand, we have behavior (2), and (1) is not possible. With your change, we'd have behavior (1), and (2) would not be possible.

I agree that the ability to do (2) should be preserved, but I don't see how this patch breaks it; do you have an example?

No, I was just going by reading the kernel code. Can you describe how each of
(1) and (2) are accomplished by the ptracing program (with your patch)?

Mike

author
#7 lore raw
In reply to vic

Now I see the problem with PTRACE_KILL. Thanks for the example.

I'm looking into it. I need to justify the quoted portion of the patch or find a better way to get its effect.

In the meantime, the problem could be fixed by changing the PTRACE_KILL implementation to call send_sig instead of setting exit_code. How does that strike people?

More generally, could someone explain to me why there are two mechanisms for interprocess signaling within the kernel, exit_code and send_sig?

Thanks,
Vic

Quoted 88 lines
Quoted 15 lines · vic

From: OGAWA Hirofumi <[email protected]>:

Quoted 14 lines
Quoted 14 lines
kernel/ptrace.c +4 -2
@@ -89,8 +89,10 @@
 		SET_LINKS(task);
 	}
 	write_unlock_irq(&tasklist_lock);
-
-	send_sig(SIGSTOP, task, 1);
+	if (task->state != TASK_STOPPED)
+		send_sig(SIGSTOP, task, 1);
+	else
+		task->exit_code = SIGSTOP;
 	return 0;
 
Quoted 6 lines
Quoted 4 lines

kernel/ptrace.c line 98

Quoted 1 line
kernel/ptrace.c +0 -0
 bad:

It seems that trace is started in the place different from usual. Then, I think PTRACE_KILL doesn't work.

I don't agree, it seems to work for me.

I tested the following on linux-2.4.16 + your_patch:

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <unistd.h>

int main(int argc, char *argv[])

{
	pid_t pid;
	int ret, status;

	pid = fork();
	if (pid == -1)
		exit(1);
	if (pid == 0) {
		raise(SIGSTOP);
		while (1)
			;
		_exit(1);
	}

	ret = waitpid(pid, &status, WUNTRACED);
	if (ret == -1) {
		perror("waitpid (1)");
		exit(1);
	}

	ret = ptrace(PTRACE_ATTACH, pid, NULL, NULL);
	if (ret == -1) {
		perror("PTRACE_ATTACH");
		exit(1);
	}
	ret = waitpid(pid, &status, 0);
	if (ret == -1) {
		perror("waitpid (2)");
		exit(1);
	}
	ret = ptrace(PTRACE_KILL, pid, NULL, NULL);
	if (ret == -1) {
		perror("PTRACE_KILL");
		exit(1);
	}

	return 0;
}

Test result:

hirofumi@devron (ptrace)[1111]$ ps ax|grep ptrace
688 tty1 S 0:00 grep ptrace
hirofumi@devron (ptrace)[1112]$ ls
ptrace ptrace.c
hirofumi@devron (ptrace)[1113]$ ./ptrace hirofumi@devron (ptrace)[1114]$ ps ax|grep ptrace
691 tty1 R 0:04 ./ptrace
693 tty1 S 0:00 grep ptrace
hirofumi@devron (ptrace)[1115]$

Do I misunderstand something?

Signature · 1 line
OGAWA Hirofumi <[email protected]>
#8 lore raw
In reply to vic
Quoted 8 lines · vic

Now I see the problem with PTRACE_KILL. Thanks for the example.

I'm looking into it. I need to justify the quoted portion of the patch or find a better way to get its effect.

In the meantime, the problem could be fixed by changing the PTRACE_KILL implementation to call send_sig instead of setting exit_code. How does that strike people?

PTRACE_SYSCALL, PTRACE_CONT, and PTRACE_SINGLESTEP can't send a signal by the same reason. Please read the do_signal().

Signature · 1 line
OGAWA Hirofumi <[email protected]>
author
#9 lore raw
In reply to vic
Quoted 2 lines

PTRACE_SYSCALL, PTRACE_CONT, and PTRACE_SINGLESTEP can't send a signal by the same reason. Please read the do_signal().

I've read that function, but I don't see why it would not get along with my suggestion to send SIGKILL rather than set exit_code to implement PTRACE_KILL.

No doubt I can be rather thick; in this case, induction doesn't help me.

#10 lore raw
In reply to vic
Quoted 8 lines · vic
Quoted 2 lines

PTRACE_SYSCALL, PTRACE_CONT, and PTRACE_SINGLESTEP can't send a signal by the same reason. Please read the do_signal().

I've read that function, but I don't see why it would not get along with my suggestion to send SIGKILL rather than set exit_code to implement PTRACE_KILL.

No doubt I can be rather thick; in this case, induction doesn't help me.

kill(pid, SIGKILL) != ptrace(PTRACE_KILL, pid, NULL, NULL).

Whether the same effect as kill() is required for PTRACE_KILL is the problem which is unrelated to this problem. If so, please argue on another thread.

And If PTRACE_SYSCALL, PTRACE_CONT, and PTRACE_SINGLESTEP can send the signal, PTRACE_KILL also work.

BTW, did you read my first email? What do you think of my suggestion?

In an example,

ptrace_attach(),

	if (task->p_pptr != current) {
		REMOVE_LINKS(task);
		task->p_pptr = current;
		SET_LINKS(task);
	}
	write_unlock_irq(&tasklist_lock);

	stopped = (task->state == TASK_STOPPED);
	send_sig(SIGSTOP, task, 1);
	if (stopped)
		wake_up_process(task);

	return 0;

Note, this code isn't investigating at all.

Signature · 1 line
OGAWA Hirofumi <[email protected]>
author
#11 lore raw
In reply to vic

I'm sorry for this long message and my delayed replies to everyone's feedback.

Quoted 2 lines · Mike Coleman

Okay. Is it at least backward compatible? Or are some tools expected to break?

Some tools may break: those that (1) attach to a stopped process or (2) detach and leave the process stopped. However, I expect the maintainers of such tools will appreciate this patch, as it would simplify the way they do these things now.

First, a tool that wants to attach to a stopped process needs some tricks. For example, if you SIGSTOP any process and then try to attach gdb to it, gdb will hang. The problem is that when the process is already stopped, the call to wait after PTRACE_ATTACH can block indefinitely because the process exit_code is left at 0 (so wait blocks) but the process is not awake. Currently tools need to detect this condition and manually wind the process past the pending SIGSTOP. gdb doesn't do that.

Second, our tool Paradyn (www.paradyn.org) sometimes wants to detach and leave a process stopped; I am not aware of any other tool that needs to do this, but it's not a strange request. The natural way you would do this with ptrace (PTRACE_DEATCH with SIGSTOP) does not work. We found that we had to read the kernel to figure out how to do it reliably on Linux (i.e., to understand the race conditions). The algorithm that we eventually settled on is non-intuitive and involves calling sleep to avoid a race we saw no other way around.

Again, tools that work around either of these problem, like ours, will be affected by this patch: they will need to retire their kludges and instead do what they probably wanted to do in the first place.

Offline I corresponded with OGAWA Hirofumi and he gave me an approach to the PTRACE_KILL issue that I have been slow to understand. Now when attaching, I always send the SIGSTOP, but wake the process if it was already stopped. I leave the exit_code the way it was.

Mike Coleman is concerned about SIGCONT and whether this patch prevents processes from being continued naturally, without handling a SIGCONT. Nothing has changed in this aspect. If the tracing process wants to continue a process, it just either does PTRACE_CONT or PTRACE_DETACH as usual.

As promised, I will check UML and Subterfuge; I haven't had the time. If anyone can alert me to potentially problematic operations in these programs, that would very helpful.

The updated patch is below.

Thanks,
Vic

home/vic/src/linux-2.4.16.1/kernel/ptrace.c renamed from home/vic/src/linux-2.4.16/kernel/ptrace.c +8 -0
@@ -54,6 +54,8 @@
 
 int ptrace_attach(struct task_struct *task)
 {
+	int stopped;
+
 	task_lock(task);
 	if (task->pid <= 1)
 		goto bad;
@@ -90,7 +92,13 @@
 	}
 	write_unlock_irq(&tasklist_lock);
 
+	stopped = (task->state == TASK_STOPPED);
 	send_sig(SIGSTOP, task, 1);
+	/* If it was stopped when we got here,
+	   clear the pending SIGSTOP. */
+	if (stopped)
+		wake_up_process(task);
+
 	return 0;
 
 bad:
home/vic/src/linux-2.4.16.1/arch/i386/kernel/signal.c renamed from home/vic/src/linux-2.4.16/arch/i386/kernel/signal.c +3 -3
@@ -620,9 +620,9 @@
 				continue;
 			current->exit_code = 0;
 
-			/* The debugger continued.  Ignore SIGSTOP.  */
-			if (signr == SIGSTOP)
-				continue;
+			/* The debugger continued. */
+			if (signr == SIGSTOP && (current->ptrace & PT_PTRACED))
+				continue; /* ignore SIGSTOP */
 
 			/* Update the siginfo structure.  Is this good?  */
 			if (signr != info.si_signo) {
author
#12 lore raw
In reply to vic

This is a repost of the ptrace patch to 2.4 kernels we've discussed in recent months.

Since the last post, I have updated it to linux 2.4.18 (no changes) and tested it with subterfuge and uml.

Subterfuge seems to be unaffected.

UML needs minor modifications; I've discussed them with Jeff Dike and (I believe) he is happy.

I believe I have addressed everyone's concerns.

The patch fixes these two bugs:

1. gdb and other tools cannot attach to a stopped process. The wait that follows the PTRACE_ATTACH will block indefinitely.

2. It is not possible to use PTRACE_DETACH to leave a process stopped, because ptrace ignores SIGSTOPs sent by the tracing process.

Vic

home/vic/p/linux-2.4.18/kernel/ptrace.c renamed from home/vic/p/linux-2.4.18.orig/kernel/ptrace.c +7 -0
@@ -54,6 +54,7 @@
 
 int ptrace_attach(struct task_struct *task)
 {
+	int stopped;
 	task_lock(task);
 	if (task->pid <= 1)
 		goto bad;
@@ -90,7 +91,13 @@
 	}
 	write_unlock_irq(&tasklist_lock);
 
+	stopped = (task->state == TASK_STOPPED);
 	send_sig(SIGSTOP, task, 1);
+	/* If it was stopped when we got here,
+	   clear the pending SIGSTOP. */
+	if (stopped)
+		wake_up_process(task);
+
 	return 0;
 
 bad:
home/vic/p/linux-2.4.18/arch/i386/kernel/signal.c renamed from home/vic/p/linux-2.4.18.orig/arch/i386/kernel/signal.c +3 -3
@@ -620,9 +620,9 @@
 				continue;
 			current->exit_code = 0;
 
-			/* The debugger continued.  Ignore SIGSTOP.  */
-			if (signr == SIGSTOP)
-				continue;
+			/* The debugger continued. */
+			if (signr == SIGSTOP && current->ptrace & PT_PTRACED)
+				continue; /* ignore SIGSTOP */
 
 			/* Update the siginfo structure.  Is this good?  */
 			if (signr != info.si_signo) {