So /usr/bin/time nor wait4 wasn't going to do it for me...

So I needed to stop just before process exit.

ptrace can do that..
 PTRACE_O_TRACEEXIT (since Linux 2.5.60)
                     Stop the child at exit with SIGTRAP | PTRACE_EVENT_EXIT << 
8.  The child???s exit status can
                     be  retrieved  with  PTRACE_GETEVENTMSG.  This stop will 
be done early during process exit
                     when registers are still available, allowing the tracer to 
see where  the  exit  occurred,
                     whereas  the normal exit notification is done after the 
process is finished exiting.  Even
                     though context is available, the tracer cannot prevent the 
exit  from  happening  at  this
                     point.

But since Laziness is a virtue of a Good programmer... (See "man perl") I said
ptrace PTRACE_O_TRACEEXIT /proc to Google and turned up...

 http://www.technovelty.org/linux/ptrace-exit.html

A short tweak later (adding the -f and -c options (that's the Hubris
virtue)) and I can say....

./stopper -f status -c ruby -e 'a=[];for i in 0..1000000;a<<i.to_s;end'
Name:   ruby
State:  T (tracing stop)
SleepAVG:       53%
Tgid:   27021
Pid:    27021
PPid:   27020
TracerPid:      27020
Uid:    1001    1001    1001    1001
Gid:    65534   65534   65534   65534
FDSize: 32
Groups: 4 20 24 25 29 30 44 46 106 110 112 1008 65534 VmPeak: 49888 kB
VmSize:    49888 kB
VmLck:         0 kB
VmHWM:     47428 kB
VmRSS:     47428 kB
VmData:    47544 kB
VmStk:        84 kB
VmExe:       672 kB
VmLib:      1536 kB
VmPTE:        64 kB
Threads:        1
SigQ:   0/4294967295
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000000000
SigCgt: 0000000000007e45
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
Cpus_allowed:   03
Mems_allowed:   1




John Carter                             Phone : (64)(3) 358 6639
Tait Electronics                        Fax   : (64)(3) 359 4632
PO Box 1645 Christchurch                Email : [EMAIL PROTECTED]
New Zealand

======================================================================
/* very simple ptrace example to stop a process before it exits.  This
 * allows us to inspect /proc/pid/ values, for example.
 * (C) 2007 Ian Wienand <[EMAIL PROTECTED]>
 * Public Domain
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <stdbool.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/wait.h>

#include <sys/ptrace.h>

/* for some reason glibc doesn't yet include the PTRACE_OPTIONS,
 * clagged from kernel */

/* options set using PTRACE_SETOPTIONS */
#define PTRACE_O_TRACESYSGOOD   0x00000001
#define PTRACE_O_TRACEFORK      0x00000002
#define PTRACE_O_TRACEVFORK     0x00000004
#define PTRACE_O_TRACECLONE     0x00000008
#define PTRACE_O_TRACEEXEC      0x00000010
#define PTRACE_O_TRACEVFORKDONE 0x00000020
#define PTRACE_O_TRACEEXIT      0x00000040

#define PTRACE_O_MASK           0x0000007f

/* Wait extended result codes for the above trace options.  */
#define PTRACE_EVENT_FORK       1
#define PTRACE_EVENT_VFORK      2
#define PTRACE_EVENT_CLONE      3
#define PTRACE_EVENT_EXEC       4
#define PTRACE_EVENT_VFORK_DONE 5
#define PTRACE_EVENT_EXIT       6

pid_t pid;

void sigint_handler(int signum __attribute__((unused)))
{
        /* let the child die */
        if (ptrace(PTRACE_CONT, pid, 0, 0)) {
                perror("stopper: ptrace(PTRACE_CONT, ...)");
                exit(1);
        }
        exit(0);
}

/* wait for the pid to exit */
void wait_for_exit( bool carryOn, char * fileName)
{
        int status;
        char command[80];

        while (1) {
                /* wait for signal from child */
                waitpid(pid, &status, 0);

                /* any signal stops the child, so check that
                   the incoming signal is a SIGTRAP and the
                   event_exit flag is set */
                if ( (WSTOPSIG(status) == SIGTRAP) &&
                     (status & (PTRACE_EVENT_EXIT << 8)))
                        break;

                /* if not, pass the original signal onto the child */
                if (ptrace(PTRACE_CONT, pid, 0, WSTOPSIG(status))) {
                        perror("stopper: ptrace(PTRACE_CONT, ...)");
                        exit(1);
                }
        }

        /* at this point, register the clean-up signal handler
         * (previously we just want a normal exit */
        signal(SIGINT, sigint_handler);

        if( strcmp( fileName, "") != 0) { // Have a filename
           if( strcmp( fileName, "list") == 0 || strcmp( fileName, "*") == 0) {
              sprintf( command, "ls -l /proc/%d", pid);
              system( command);
           } else {
              sprintf( command, "cat /proc/%d/%s", pid, fileName);
              system( command);
           }
        }

        if (!carryOn) {
           printf("\n-----------------------------------------------------\n");
           printf("\n\t  STOPPED PID %d\n", pid);
           printf("\t ctrl-c to continue\n");
           printf("\n-----------------------------------------------------\n");
           while (1)
              sleep(1000);
        }
}


char *usage = "Usage:
  stopper [-f file] [-c] [ [-p pid] | command ]

-f file\t\tCopy 'file' to stdout
\t\t\tIf file is list, ls -l /proc/pid

-c Just dump file and carry on.

-p pid  Attach to process pid

command Exec this command.


Will stop the process just before exit to allow you to peek into /proc/pid

";

int main(int argc, char *argv[])
{
        extern char *optarg;
        extern int optind, opterr, optopt;
        char c;
        bool attaching = false;
        char fileName[80]="";
        bool carryOn = false;

        while ((c = getopt(argc, argv,
                           "+f:cp:")) != -1) {
                switch (c) {

                case 'p':
                        pid = atoi(optarg);
                        attaching = true;
                        break;
                case 'f':
                        strcpy( fileName, optarg);
                break;
                case 'c' :
                carryOn = true;
                break;
                default:
                        printf("%s", usage);
                        exit(1);
                }
        }

        if (optind == argc && !attaching) {
                printf("%s\n", usage);
                exit(1);
        }

        if (!attaching) {
                /* find the file to exec, borrowed from strace */
                struct stat statbuf;
                char *filename;
                char pathname[MAXPATHLEN];

                filename = argv[optind];
                if (strchr(filename, '/')) {
                        if (strlen(filename) > sizeof(pathname) - 1) {
                                errno = ENAMETOOLONG;
                                perror("stopper: exec failed");
                                exit(1);
                        }
                        strcpy(pathname, filename);
                }
                else {
                        char *path;
                        int m, len;
                        unsigned int n;

                        for (path = getenv("PATH"); path && *path; path += m) {
                                if (strchr(path, ':')) {
                                        n = strchr(path, ':') - path;
                                        m = n + 1;
                                }
                                else
                                        m = n = strlen(path);
                                if (n == 0) {
                                        getcwd(pathname, MAXPATHLEN);
                                        len = strlen(pathname);
                                }
                                else if (n > sizeof pathname - 1)
                                        continue;
                                else {
                                        strncpy(pathname, path, n);
                                        len = n;
                                }
                                if (len && pathname[len - 1] != '/')
                                        pathname[len++] = '/';
                                strcpy(pathname + len, filename);
                                if (stat(pathname, &statbuf) == 0 &&
                                    /* Accept only regular files
                                       with some execute bits set.
                                       XXX not perfect, might still fail */
                                    S_ISREG(statbuf.st_mode) &&
                                    (statbuf.st_mode & 0111))
                                        break;
                        }
                }

                if (stat(pathname, &statbuf) < 0) {
                        fprintf(stderr, "%s: %s: command not found\n",
                                argv[0], filename);
                        exit(1);
                }

                switch (pid = fork()) {
                case -1:
                        perror("stopper: fork");
                        exit(1);
                        break;
                case 0:
                        if (ptrace(PTRACE_TRACEME, 0, (char *)1, 0) < 0) {
                                perror("stopper: ptrace(PTRACE_TRACEME, ...)");
                                return -1;
                        }
                        execv(pathname, &argv[optind]);
                        perror("stopper: execv");
                        _exit(1);

                default:
                        /* wait for first signal, which is from child when it 
execs */
                        wait(NULL);

                        /* set option to tell us when the signal exits */
                        if (ptrace(PTRACE_SETOPTIONS, pid, 0, 
PTRACE_O_TRACEEXIT)) {
                                perror("stopper: ptrace(PTRACE_SETOPTIONS, 
...)");
                                return -1;
                        }

                        /* allow child to continue */
                        if (ptrace(PTRACE_CONT, pid, 0, (void*)0)) {
                                perror("stopper: ptrace(PTRACE_CONT, ...)");
                                return -1;
                        }

                        wait_for_exit( carryOn, fileName);
                }

        } else {
                /* we are attaching to a running PID */

                if (ptrace(PTRACE_ATTACH, pid, 0, 0)) {
                        perror("stopper: can not attach to process");
                        return -1;
                }

                /* set option to tell us when the signal exits */
                if (ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACEEXIT)) {
                        perror("stopper: ptrace(PTRACE_SETOPTIONS, ...)");
                        return -1;
                }

                wait_for_exit(carryOn, fileName);
        }

        /* shouldn't get here */
        exit(1);
}





Reply via email to