Hi,

I wrote a little test for the ptrace-testsuite that I believe should
PASS. It does PASS on x86_64 (2.6.24.4-64.fc8) and some older x86
(2.6.20-1.2933.fc6) kernels. It doesn't on a recent fedora 8 kernel for
x86 (2.6.24.4-64.fc8).

The test waits for a signal in the child, and then uses ptrace
singlestep to go through the signal handler setup in the child till the
process returns to where it was interrupted. In the failing case at the
sigret syscall the child process runs free instead of single stepping.

Questions:
- If the testcase looks sane, and it is expected that a singlestep never
sets the child process running free, could this test be added to the
ptrace-testsuite?
- If the assumption/test is wrong, then what is the expected behavior?
Should the tracing process watch for special syscalls and manipulate the
sigcontext on the stack to explicitly set the stepping flag? (This seems
to be what gdb does.) Are there other special sigcalls?

Thanks,

Mark

Successful run (2.6.24.4-64.fc8 x86_64):

$ ./tests/step-through-sigret -v
#parent (#63) forking
#child (#71) Setting up handler and alarm
#child (#82) spinning till signal...
#parent (#94) ptrace attach
#parent (#98) wait for attach
#parent (#105) let child run free (till signal)
#parent (#109) waiting for child SIGALRM
#parent (#117) child got signal at 0x400e48
#parent (#120) do first step into signal handler
#parent (#131) stepped into sig_handler (0x400750)
#parent (#137) single step child (0x400750)
#parent (#137) single step child (0x40075a)
#parent (#137) single step child (0x3a16830f30)
#parent (#137) single step child (0x3a16830f37)
#parent (#150) done stepping sig_handler (0x400e48)
#parent (#153) Let child run free (till exit)
#parent (#157) waiting for child exit
#child (#86) done
#parent (#163) done

Failing run (2.6.24.4-64.fc8 x86):

$ tests/step-through-sigret -v
#parent (#63) forking
#child (#71) Setting up handler and alarm
#child (#82) spinning till signal...
#parent (#94) ptrace attach
#parent (#98) wait for attach
#parent (#105) let child run free (till signal)
#parent (#109) waiting for child SIGALRM
#parent (#117) child got signal at 0x8048dbe
#parent (#120) do first step into signal handler
#parent (#131) stepped into sig_handler (0x8048590)
#parent (#137) single step child (0x8048590)
#parent (#137) single step child (0x8048591)
#parent (#137) single step child (0x8048593)
#parent (#137) single step child (0x8048594)
#parent (#137) single step child (0x804859e)
#parent (#137) single step child (0x110420)
#parent (#137) single step child (0x110421)
#parent (#137) single step child (0x110426)
#child (#86) done
step-through-sigret: step-through-sigret.c:143: main: Assertion
`((((__extension__ ({ union { __typeof(status) __in; int __i; } __u;
__u.__in = (status); __u.__i; }))) & 0xff) == 0x7f)' failed.
Aborted
// Tests whether we can ptrace single step through a child signal
// handler and return where the signal interrupted the child
// afterwards. In particular tests whether we can single step over the
// sigret syscall at the end of the handler. On some kernels
// (e.g. 2.6.24.4-64.fc8 x86) this cleared the stepping flag and let
// the child run free.
//
// Author: Mark Wielaard <[EMAIL PROTECTED]>

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/user.h>
#include <sys/wait.h>

#ifdef __i386__
#define PC eip
#endif
#ifdef __x86_64__
#define PC rip
#endif

#ifndef PC // unsupported architecture
int main (int argc, char **argv) { return 77; }
#else

// The process to trace.
static pid_t pid = -1;

// Whether to be verbose (just give any argument to the program).
static int verbose;

#define VERBOSE(output...) do { if (verbose) { \
  printf ("#%s (#%d) ", (pid != 0 ? "parent" : "child"), __LINE__); \
  printf (output); \
} } while (0)

// Signal ran and process done?
static volatile int done = 0;

// When SIGALRM comes in set done flag. Nothing interesting,
// we are just interested in stepping through it, including the sigret.
static void sig_handler(int sig) { done = 1; }

// Returns the current pc value of the child pid (must be stopped).
static long int
get_pc(int pid)
{
  struct user_regs_struct regs;
  long r = ptrace (PTRACE_GETREGS, pid, NULL, &regs);
  assert (r != -1);
  return regs.PC;
}

int
main (int argc, char **argv)
{
  verbose = (argc > 1);

  VERBOSE ("forking\n");
  pid = fork ();
  assert (pid != -1);
  switch (pid)
    {
    case 0:
      {
        // Child, sets up a sigalrm handler, sets the alarm and spins.
	VERBOSE ("Setting up handler and alarm\n");
	struct sigaction sa;
	sa.sa_handler = sig_handler;
	int r = sigemptyset(&sa.sa_mask);
	assert (r == 0);
	sa.sa_flags = 0;
	r = sigaction(SIGALRM, &sa, NULL);
	assert (r == 0);
	r = alarm(1);
	assert (r == 0);

	VERBOSE ("spinning till signal...\n");
	while (! done)
          /* do something */ ;

	VERBOSE ("done\n");
	exit (0);
      }

    default:
      {
	// Parent, attched to child, waits for signal, steps through.

	VERBOSE ("ptrace attach\n");
	long r = ptrace (PTRACE_ATTACH, pid, NULL, NULL);
        assert (r == 0);

	VERBOSE ("wait for attach\n");
	int status;
        pid_t waitpid = wait (&status);
	assert (waitpid == pid);
	assert (WIFSTOPPED (status));
	assert (WSTOPSIG (status) == SIGSTOP);

	VERBOSE ("let child run free (till signal)\n");
        r = ptrace (PTRACE_CONT, pid, 0, 0);
	assert (r == 0);

	VERBOSE ("waiting for child SIGALRM\n");
	waitpid = wait (&status);
	assert (waitpid == pid);
	assert (WIFSTOPPED (status));
	assert (WSTOPSIG (status) == SIGALRM);

	// Record the current pc to see when we have returned from signal
	long int ret_addr = get_pc(pid);
	VERBOSE ("child got signal at 0x%lx\n", ret_addr);

	// Step into child signal handler.
	VERBOSE ("do first step into signal handler\n");
        r = ptrace (PTRACE_SINGLESTEP, pid, 0, SIGALRM);
	assert (r == 0);

        waitpid = wait (&status);
	assert (waitpid == pid);
	assert (WIFSTOPPED (status));
	assert (WSTOPSIG (status) == SIGTRAP);

	// Check current pc to see that we are in the signal handler.
	long int pc = get_pc(pid);
	VERBOSE ("stepped into sig_handler (0x%lx)\n", pc);
	assert (pc == (long int) sig_handler);

	// Now single step child to return from sig_handler
        do
          {
	    VERBOSE ("single step child (0x%lx)\n", pc);
            long r = ptrace (PTRACE_SINGLESTEP, pid, 0, 0);
	    assert (r == 0);

	    waitpid = wait (&status);
	    assert (waitpid == pid);
	    assert (WIFSTOPPED (status));
	    assert (WSTOPSIG (status) == SIGTRAP);

	    pc = get_pc(pid);
	  }
        while (pc != ret_addr);

	VERBOSE ("done stepping sig_handler (0x%lx)\n", pc);
	assert (pc == ret_addr);

	VERBOSE ("Let child run free (till exit)\n");
        r = ptrace (PTRACE_CONT, pid, 0, 0);
	assert (r == 0);

        VERBOSE ("waiting for child exit\n");
        waitpid = wait (&status);
	assert (waitpid == pid);
	assert (WIFEXITED (status));
	assert (WEXITSTATUS (status) == 0);

	VERBOSE ("done\n");
        exit (0);
      }
    }
}
#endif // supported architecture

Reply via email to