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);
}