LQ Newbie
Registered: Apr 2008
Posts: 6
Rep:
|
ptrace ATTACH, then CONT with signal fails on FC6
ptrace behavior apparently changed big-time between FC3=RHEL4=2.6.9 and FC6=2.6.18, such that continuing the debugged process with a signal after attaching no longer works. I can work around it but I want to know if it's a bug or an intentional feature, and if a feature, when was it introduced and why?
The following is my test program. It's long and verbose, I'm sorry, but I wanted to be clear. As comments say, there are 2 ways to build it -- the one we use in our real app is -DCHILD_IS_DEBUGGER.
/*
* ptracebug.c
* Test case showing change in ptrace behavior.
* There are two variations:
* gcc ptracebug.c -o ptrace_from_parent
* gcc -DCHILD_IS_DEBUGGER ptracebug.c -o ptrace_from_child
* Both print TEST PASSED on 2.4.18, 2.6.9, but
* debugged process fails on 2.6.18=FC6.
*/
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <sys/ptrace.h>
#include <linux/ptrace.h>
#include <sys/wait.h>
static char *Msg = "EXITED TOO EARLY\n";
static void AppExit(void)
{
printf("PID=%d: %s\n\n", getpid(), Msg);
}
int main (int argc, char **argv)
{
sigset_t sigset;
sigset_t oldset;
struct sigaction NewAction;
int parentpid = getpid();
int childpid;
fprintf(stderr,
"--- Show that ptrace(CONT, blocked-signal) fails on FC6\n"
" If test dones't print PASSED, it failed.\n");
/* block sigusr1, inherited by forked process */
sigemptyset (&sigset);
sigaddset (&sigset, SIGUSR1);
sigprocmask (SIG_BLOCK, &sigset, &oldset);
atexit(AppExit);
/* fork */
childpid = fork ();
if (childpid < 0)
{
perror("fork failed\n");
return 0;
}
#ifdef CHILD_IS_DEBUGGER
if (childpid == 0)
{ /* child */
int deb = getpid();
int app = parentpid;
int Status;
fprintf(stderr, "AFTER FORK, CHILD = %d\n", getpid());
#else
if (childpid > 0)
{ /* parent */
int deb = getpid();
int app = childpid;
int Status;
fprintf(stderr, "AFTER FORK, PARENT = %d\n", getpid());
#endif
/* wait for app to continue after fork (makes no difference) */
fprintf (stderr, "DEB=%d sleeping for a second\n", deb);
sleep(1);
/* (1) attach to application */
fprintf (stderr,
"DEB=%d: ptrace attach to APP=%d\n",
deb, app);
fflush(stderr);
if (ptrace (PTRACE_ATTACH, app, 0, 0) == -1)
{
fprintf (stderr,
"DEB=%d: PTRACE_ATTACH to %d failed, errno %d\n",
deb, app, errno);
return 0;
}
/* (2) wait for app to stop */
waitpid (app, &Status, 0);
if (WIFEXITED (Status))
{
fprintf (stderr, "*** APP EXITED already?\n");
return 0;
}
/* (3) continue the application with sigusr1.
* Expected (and pre-FC6) behavior is that since SIGUSR1 is blocked,
* app will ignore it, but app's sigwait(SIGUSR1) will return it */
fprintf (stderr,
"DEB=%d: CONT APP=%d with signal %d\n",
deb, app, SIGUSR1);
fflush(stderr);
if (ptrace (PTRACE_CONT, app, 0, SIGUSR1) == -1)
{
fprintf (stderr,
"DEB=%d: FAILED to continue APP=%d, err = %d\n",
deb, app, errno);
return 0;
}
/* normally would wait for SIGTRAP from exec here */
Msg = "DEB OK";
/* success, detach the debugger */
ptrace (PT_DETACH, app, 0, 0, 0);
fprintf (stderr, "DEB=%d looks ok\n", deb);
}
else
#ifdef CHILD_IS_DEBUGGER
{ /* child == debugger */
int deb = getpid();
int app = childpid;
int sig, return_sig;
fprintf(stderr, "AFTER FORK, PARENT = %d\n", getpid());
#else
{ /* child == app */
int deb = parentpid;
int app = getpid();
int sig, return_sig;
fprintf(stderr, "AFTER FORK, CHILD = %d\n", getpid());
#endif
/* (1) wait for SIGUSR1 from debugger to make sure it's ready */
fprintf (stderr,
"APP=%d: wait for SIGUSR1 from DEB=%d\n", app, deb);
fflush(stderr);
return_sig = sigwait (&sigset, &sig);
/* handle old behavior */
if ( sig <= 0 ) sig = return_sig;
/* normally would restore signal mask here before exec */
Msg = "APP OK";
/* normally would exec the real app here (sleep not relevant */
fprintf (stderr, "APP=%d sleeping for a second\n", app);
sleep(1);
fprintf (stderr, "APP=%d looks ok\n", app);
fprintf (stderr, "=== TEST PASSED!\n", app);
fflush(stderr);
}
return 0;
}
TIA for any insights,
...Tom
|