LKML Archive on lore.kernel.org
help / color / mirror / Atom feed
* inconsistent behavior with ptrace(TRACEME) and fork/exec
@ 2008-10-27  8:55 Mike Frysinger
  0 siblings, 0 replies; 5+ messages in thread
From: Mike Frysinger @ 2008-10-27  8:55 UTC (permalink / raw)
  To: linux-kernel


[-- Attachment #1.1: Type: text/plain, Size: 1533 bytes --]

i'm hoping my understanding of ptrace is correct and that the attached test 
case (reduced from LTP) isnt just completely broken ... my understanding is 
that if a parent forks and the child does a ptrace(TRACEME) right before 
doing an exec(), the kernel should always halt it and wait indefinitely for 
the parent to start ptracing it.

unfortunately, this behavior seems to be unreliable.  most of the time it 
works, but sometimes the kernel does not halt the child and it gladly does 
the exec() that it set out to do.  i dont believe this to be a race in the 
user space parent component as forcing it to delay via judicious usage of 
sleep() shows the same behavior.

if  all goes well, we should only ever see "SUCCESS!" from the test case:
$ gcc -Wall ptrace-vfork-traceme.c -o ptrace-vfork-traceme
$ while ./ptrace-vfork-traceme ; do :; done
SUCCESS! :D
SUCCESS! :D
SUCCESS! :D
SUCCESS! :D
SUCCESS! :D
SUCCESS! :D
SUCCESS! :D
SUCCESS! :D
SUCCESS! :D
failure, child exited with 17: Child exited
wait() = 12275
status = 1407
        WIFEXITED = 0
        WEXITSTATUS = 5
        WIFSIGNALED = 0
        WTERMSIG = 127 (Unknown signal 127)

i'm testing 2.6.26/2.6.27 atm.  both x86_64 and ppc64 seem to behave the same.  
while gcc-4.3.2 / glibc-2.8 is in use in both places, i dont think that 
matters.

also, while the attached test uses vfork(), same behavior can be observed with 
fork().  vfork() is used because i like my test cases to work on both MMU and 
no-MMU systems.
-mike

[-- Attachment #1.2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 835 bytes --]

[-- Attachment #2: ptrace-vfork-traceme.c --]
[-- Type: text/x-csrc, Size: 1568 bytes --]

#define _GNU_SOURCE
#include <errno.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/wait.h>

#define fail(msg, args...) \
do { \
	fprintf(stderr, "FAIL:%i: " msg "\n", __LINE__, ## args); \
	exit(1); \
} while (0)

static void child_exit(int sig)
{
	int status;
	printf("failure, child exited with %i: %s\n", sig, strsignal(sig));
	printf("wait() = %i\n", wait(&status));
	printf("status = %i\n", status);
	printf("\tWIFEXITED = %i\n", WIFEXITED(status));
	printf("\tWEXITSTATUS = %i\n", WEXITSTATUS(status));
	printf("\tWIFSIGNALED = %i\n", WIFSIGNALED(status));
	printf("\tWTERMSIG = %i (%s)\n", WTERMSIG(status), strsignal(WTERMSIG(status)));
	exit(1);
}

int main(int argc, char *argv[])
{
	long pret;
	pid_t pid;

	/* child process ... shouldnt be executed, but just in case ... */
	if (argc > 1 && !strcmp(argv[1], "child"))
		fail("kernel should have halted me...");

	pid = vfork();
	if (pid == -1)
		fail("vfork() didnt work: %m");
	else if (!pid) {
		/* do the child stuff here */
		errno = 0;
		pret = ptrace(PTRACE_TRACEME, 0, NULL, NULL);
		if (pret && errno)
			fail("ptrace(PTRACE_TRACEME) = %li: %m", pret);

		int eret = execlp(argv[0], argv[0], "child", NULL);
		fail("execlp() = %i", eret);
	}

	/* do the parent stuff here */
	signal(SIGCHLD, child_exit);

	errno = 0;
	pret = ptrace(PTRACE_PEEKUSER, pid, NULL, NULL);
	if (pret && errno)
		fail("ptrace(PTRACE_PEEKUSER, %i) = %li: %m", pid, pret);

	puts("SUCCESS! :D");

	return 0;
}

^ permalink raw reply	[flat|nested] 5+ messages in thread
* Re: inconsistent behavior with ptrace(TRACEME) and fork/exec
@ 2008-10-27 14:56 Jan Kratochvil
  2008-10-27 17:38 ` Mike Frysinger
  0 siblings, 1 reply; 5+ messages in thread
From: Jan Kratochvil @ 2008-10-27 14:56 UTC (permalink / raw)
  To: Mike Frysinger; +Cc: linux-kernel

[-- Attachment #1: Type: text/plain, Size: 728 bytes --]

On Wed, 19 Jul 2006 21:18:29 +0200, Mike Frysinger wrote:
> my understanding is that if a parent forks and the child does
> a ptrace(TRACEME) right before doing an exec(), the kernel should always
> halt it and wait indefinitely for the parent to start ptracing it.

Yes, just the parent must process the event (signal).  In your testcase the
parent finished before the signal could be delivered.  If the tracer exits the
tracee's tracing is finished and it continues freely.


> unfortunately, this behavior seems to be unreliable.

Fixed the races in your code and I do not see there any problem, do you?
The ptrace problems/testsuite is being maintained at:
  http://sourceware.org/systemtap/wiki/utrace/tests


Regards,
Jan

[-- Attachment #2: ptrace-vfork-traceme.c --]
[-- Type: text/plain, Size: 2201 bytes --]

#define _GNU_SOURCE
#include <errno.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/wait.h>

#define fail(msg, args...) \
do { \
	fprintf(stderr, "FAIL:%i: " msg "\n", __LINE__, ## args); \
	exit(1); \
} while (0)

static void child_exit(int sig)
{
	int status;
#if 0 /* Correct behavior.  */
	printf("failure, child exited with %i: %s\n", sig, strsignal(sig));
#endif
	printf("wait() = %i\n", wait(&status));
	printf("status = 0x%x\n", status);
	printf("\tWIFEXITED = %i\n", WIFEXITED(status));
	printf("\tWEXITSTATUS = %i\n", WEXITSTATUS(status));
	printf("\tWIFSIGNALED = %i\n", WIFSIGNALED(status));
	printf("\tWTERMSIG = %i (%s)\n", WTERMSIG(status), strsignal(WTERMSIG(status)));
	/* WIFSTOPPED happens.  */
	printf("\tWIFSTOPPED = %i\n", WIFSTOPPED(status));
	/* SIGTRAP happens.  */
	printf("\tWSTOPSIG = %i (%s)\n", WSTOPSIG(status), strsignal(WSTOPSIG(status)));
#if 0 /* We can continue.  Just calling printf() from a signal handler is not
	 correct.  */
	exit(1);
#endif
}

int main(int argc, char *argv[])
{
	long pret;
	pid_t pid;

	/* child process ... shouldnt be executed, but just in case ... */
	if (argc > 1 && !strcmp(argv[1], "child"))
#if 0 /* Parent did not kill us, after its child_exit() messages we should get
         here.  */
		fail("kernel should have halted me...");
#else
		{ puts ("child exiting"); exit (0); }
#endif

	/* vfork() child must not call ptrace().  */
	pid = fork();
	if (pid == -1)
		fail("vfork() didnt work: %m");
	else if (!pid) {
		/* do the child stuff here */
		errno = 0;
		pret = ptrace(PTRACE_TRACEME, 0, NULL, NULL);
		if (pret && errno)
			fail("ptrace(PTRACE_TRACEME) = %li: %m", pret);

		int eret = execlp(argv[0], argv[0], "child", NULL);
		fail("execlp() = %i", eret);
	}

	/* do the parent stuff here */
	signal(SIGCHLD, child_exit);

	/* We cannot PTRACE_PEEKUSER here as the child still may not have
	   called PTRACE_TRACEME.  */
	pause ();

	errno = 0;
	pret = ptrace(PTRACE_PEEKUSER, pid, NULL, NULL);
	if (pret && errno)
		fail("ptrace(PTRACE_PEEKUSER, %i) = %li: %m", pid, pret);

	puts("SUCCESS! :D");

	return 0;
}

[-- Attachment #3: ptrace-vfork-traceme.c.patch --]
[-- Type: text/plain, Size: 1858 bytes --]

--- ptrace-vfork-traceme.c-orig	2008-10-27 15:41:51.000000000 +0100
+++ ptrace-vfork-traceme.c	2008-10-27 15:47:05.000000000 +0100
@@ -18,14 +18,23 @@ do { \
 static void child_exit(int sig)
 {
 	int status;
+#if 0 /* Correct behavior.  */
 	printf("failure, child exited with %i: %s\n", sig, strsignal(sig));
+#endif
 	printf("wait() = %i\n", wait(&status));
-	printf("status = %i\n", status);
+	printf("status = 0x%x\n", status);
 	printf("\tWIFEXITED = %i\n", WIFEXITED(status));
 	printf("\tWEXITSTATUS = %i\n", WEXITSTATUS(status));
 	printf("\tWIFSIGNALED = %i\n", WIFSIGNALED(status));
 	printf("\tWTERMSIG = %i (%s)\n", WTERMSIG(status), strsignal(WTERMSIG(status)));
+	/* WIFSTOPPED happens.  */
+	printf("\tWIFSTOPPED = %i\n", WIFSTOPPED(status));
+	/* SIGTRAP happens.  */
+	printf("\tWSTOPSIG = %i (%s)\n", WSTOPSIG(status), strsignal(WSTOPSIG(status)));
+#if 0 /* We can continue.  Just calling printf() from a signal handler is not
+	 correct.  */
 	exit(1);
+#endif
 }
 
 int main(int argc, char *argv[])
@@ -35,9 +44,15 @@ int main(int argc, char *argv[])
 
 	/* child process ... shouldnt be executed, but just in case ... */
 	if (argc > 1 && !strcmp(argv[1], "child"))
+#if 0 /* Parent did not kill us, after its child_exit() messages we should get
+         here.  */
 		fail("kernel should have halted me...");
+#else
+		{ puts ("child exiting"); exit (0); }
+#endif
 
-	pid = vfork();
+	/* vfork() child must not call ptrace().  */
+	pid = fork();
 	if (pid == -1)
 		fail("vfork() didnt work: %m");
 	else if (!pid) {
@@ -54,6 +69,10 @@ int main(int argc, char *argv[])
 	/* do the parent stuff here */
 	signal(SIGCHLD, child_exit);
 
+	/* We cannot PTRACE_PEEKUSER here as the child still may not have
+	   called PTRACE_TRACEME.  */
+	pause ();
+
 	errno = 0;
 	pret = ptrace(PTRACE_PEEKUSER, pid, NULL, NULL);
 	if (pret && errno)

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2008-10-29 10:27 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2008-10-27  8:55 inconsistent behavior with ptrace(TRACEME) and fork/exec Mike Frysinger
2008-10-27 14:56 Jan Kratochvil
2008-10-27 17:38 ` Mike Frysinger
2008-10-28 14:58   ` Jan Kratochvil
2008-10-29 10:26     ` Mike Frysinger

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).