[PATCH 31] ptrace_report_clone: rework the stop/resume logic

Oleg Nesterov oleg at redhat.com
Sun Sep 13 20:11:13 UTC 2009


TODO:

	1. CLEANUP !!!!

	2. CLONE_UNTRACE check is wrong

	3. child->pid is wrong, we must use tracer's namespace


Note:

	Ugly, but we have to add context->o_did_clone_vfork bolean,
	it is true if the last do_fork() had CLONE_VFORK.

	We can't figure out whether we need PTRACE_O_TRACEVFORKDONE
	at ptrace_report_clone() time, the tracer can change options
	after we report PTRACE_EVENT_VFORK.

Passes this test:

	#include <stdio.h>
	#include <unistd.h>
	#include <stdlib.h>
	#include <signal.h>
	#include <errno.h>
	#include <sys/wait.h>
	#include <sys/ptrace.h>
	#include <assert.h>

	#define WEVENT(s) ((s & 0xFF0000) >> 16)

	static unsigned long __step(int line, int pid, int o, int e)
	{
		int stat, cont = PTRACE_CONT;
		unsigned long msg;

		if (o == PTRACE_SYSCALL) {
			cont = o;
		} else if (o) {
			assert(0 == ptrace(PTRACE_SETOPTIONS, pid, 0, o));
		}

		assert(0 == ptrace(cont, pid, 0, 0));
		assert(waitpid(pid, &stat, __WALL) == pid);
		assert(WIFSTOPPED(stat));
		assert(WSTOPSIG(stat) == SIGTRAP);
		assert(WEVENT(stat) == e);
		assert(0 == ptrace(PTRACE_GETEVENTMSG, pid, 0, &msg));

		return msg;
	}

	#define step(pid, o, e) __step(__LINE__, pid, o, e)

	#define T_FORK_A	0x01
	#define T_FORK_Z	0x02
	#define T_SYSCALL	0x04
	#define T_SIGNAL	0x08

	#define T_ALL	(T_FORK_A | T_FORK_Z | T_SYSCALL | T_SIGNAL)

	static void p_todo(int todo)
	{
		printf("todo: ");

		#define P(n)	if (todo & n) printf(#n " ")
		P(T_FORK_A); P(T_FORK_Z); P(T_SYSCALL); P(T_SIGNAL);
		#undef P

		if (todo & ~T_ALL) printf("ERR!!!");
		printf("\n");
	}

	static void tst(int todo)
	{
		int pid, cpid=-1, stat;
		long msg;

		p_todo(todo);

		pid = fork();
		if (!pid) {
			int vp;

			if (!(todo & T_SIGNAL))
				signal(SIGCHLD, SIG_IGN);

			assert(0 == ptrace(PTRACE_TRACEME, 0,0,0));
			kill(getpid(), SIGSTOP);

			vp = vfork();
			exit(0x43);
		}

		assert(wait(&stat) == pid);
		assert(WIFSTOPPED(stat) && WSTOPSIG(stat) == SIGSTOP);

		if (todo & T_SYSCALL) {
			msg = step(pid, PTRACE_SYSCALL, 0);
			assert(msg == 0);
		}

		if (todo & T_FORK_A) {
			cpid = step(pid, PTRACE_O_TRACEVFORK, PTRACE_EVENT_VFORK);
			assert(waitpid(cpid, &stat, __WALL) == cpid);
			assert(WIFSTOPPED(stat));
			assert(WSTOPSIG(stat) == SIGSTOP);
			assert(0 == ptrace(PTRACE_CONT, cpid, 0, 0));
			assert(waitpid(cpid, &stat, __WALL) == cpid);
			assert(WIFEXITED(stat));
		}

		if (todo & T_FORK_Z) {
			msg = step(pid, PTRACE_O_TRACEVFORKDONE, PTRACE_EVENT_VFORK_DONE);
			if (todo & T_FORK_A) assert(msg == cpid);
		}

		if (todo & T_SIGNAL) {
			assert(0 == ptrace(PTRACE_CONT, pid, 0, 0));
			assert(waitpid(pid, &stat, __WALL) == pid);
			if (!(todo & T_FORK_A) && stat == 0x4300)
				goto parent_exited_first;
			assert(WIFSTOPPED(stat));
			assert(WSTOPSIG(stat) == SIGCHLD);
		}

		if (todo & T_SYSCALL) {
			msg = step(pid, PTRACE_SYSCALL, 0);
			if (todo & T_FORK_A) assert(msg == cpid);
		}

		assert(0 == ptrace(PTRACE_CONT, pid, 0, 0));
		assert(waitpid(pid, &stat, __WALL) == pid);
		assert(WIFEXITED(stat));

	parent_exited_first:
		assert(waitpid(-1, &stat, __WALL) == -1);
		assert(errno == ECHILD);
	}

	int main(int argc, char* argv[])
	{
		int todo;

		if (argc > 1) {
			todo = atoi(argv[1]);
			tst(todo);
		} else {
			int todo;
			for (todo = 0; todo <= T_ALL; ++todo)
				tst(todo);
		}

		return 0;
	}

---

 kernel/ptrace.c |   50 ++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 48 insertions(+), 2 deletions(-)

--- PU/kernel/ptrace.c~31_TRACEVFORKDONE	2009-09-12 01:04:59.000000000 +0200
+++ PU/kernel/ptrace.c	2009-09-13 21:22:24.000000000 +0200
@@ -32,6 +32,8 @@ struct ptrace_context {
 
 	int stopped_code;
 	resume_stopped_t resume_stopped;
+
+	bool o_did_clone_vfork;	// XXX: move into ->options
 };
 
 static inline struct ptrace_context *
@@ -56,6 +58,8 @@ void __ptrace_link(struct task_struct *c
 static const struct utrace_engine_ops ptrace_utrace_ops; /* forward decl */
 static int ptrace_attach_task(struct task_struct *tracee, int options);
 static void ptrace_abort_attach(struct task_struct *tracee);
+static void ptrace_wake_up(struct utrace_engine *engine,
+		struct task_struct *tracee, enum utrace_resume_action action);
 
 static void ptrace_detach_task(struct task_struct *child, int sig)
 {
@@ -221,6 +225,35 @@ static void ptrace_clone_attach(struct t
 	set_tsk_thread_flag(child, TIF_SIGPENDING);
 }
 
+static void ptrace_resume_vfork_done(struct utrace_engine *engine,
+				struct task_struct *tracee, long data)
+{
+	 ptrace_wake_up(engine, tracee, UTRACE_RESUME);
+}
+
+static bool ck_o_tracevforkdone(struct utrace_engine *engine,
+				struct task_struct *tracee)
+{
+	struct ptrace_context *context = ptrace_context(engine);
+
+	if (!(context->o_did_clone_vfork &&
+	     (context->options & PTRACE_O_TRACEVFORKDONE)))
+		return false;
+
+	context->resume_stopped = ptrace_resume_vfork_done;
+	context->stopped_code = (PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP;
+
+	utrace_control(tracee, engine, UTRACE_REPORT);
+	return true;
+}
+
+static void ptrace_resume_clone(struct utrace_engine *engine,
+				struct task_struct *tracee, long data)
+{
+	ck_o_tracevforkdone(engine, tracee);
+	ptrace_wake_up(engine, tracee, UTRACE_RESUME);
+}
+
 static u32 ptrace_report_clone(enum utrace_resume_action action,
 			       struct utrace_engine *engine,
 			       struct task_struct *parent,
@@ -230,11 +263,16 @@ static u32 ptrace_report_clone(enum utra
 	struct ptrace_context *context = ptrace_context(engine);
 	int event;
 
+	WARN_ON(context->resume_stopped);
+	WARN_ON(context->stopped_code);
+
 	if (clone_flags & CLONE_UNTRACED)
 		return UTRACE_RESUME;
 
 	event = 0;
+	context->o_did_clone_vfork = false;
 	if (clone_flags & CLONE_VFORK) {
+		context->o_did_clone_vfork = true;
 		if (context->options & PTRACE_O_TRACEVFORK)
 			event = PTRACE_EVENT_VFORK;
 	} else if ((clone_flags & CSIGNAL) != SIGCHLD) {
@@ -251,8 +289,16 @@ static u32 ptrace_report_clone(enum utra
 	if (event || (clone_flags & CLONE_PTRACE))
 		ptrace_clone_attach(parent, child, context->options);
 
-	if (event)
-		return utrace_ptrace_event(parent, event, child->pid);
+	// XXX: child->pid is wrong! use tracer's pid_ns
+	if (event) {
+		parent->ptrace_message = child->pid;
+		context->resume_stopped = ptrace_resume_clone;
+		context->stopped_code = (event << 8) | SIGTRAP;
+
+		return UTRACE_STOP;
+	} else if (ck_o_tracevforkdone(engine, parent)) {
+		parent->ptrace_message = child->pid;
+	}
 
 	return UTRACE_RESUME;
 }




More information about the utrace-devel mailing list