text: /*	$NetBSD$	*/
  
  /*-
   * Copyright (c) 2019 The NetBSD Foundation, Inc.
   * All rights reserved.
   *
   * This code is derived from software contributed to The NetBSD Foundation
   * by Kamil Rytarowski.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in the
   *    documentation and/or other materials provided with the distribution.
   *
   * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
   * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
   * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
   * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
   * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   * POSSIBILITY OF SUCH DAMAGE.
   */
  
  
  #include <sys/syscall.h>
  
  #include <assert.h>
  #include <elf.h>
  #include <stdbool.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <threads.h>
  #include <unistd.h>
  
  #include <util.h>
  
  #include "children.h"
  #include "misc.h"
  #include "syscalls.h"
  #include "trace.h"
  #include "xutils.h"
  
  #include "truss.h"
  
  #include "events.h"
  
  static void setup_ops(void);
  
  static void report(pid_t pid, lwpid_t lwp, char *format, ...);
  static void resolve_child_name(pid_t, char *, size_t);
  static const char *sig2string(int);
  static const char *sicode2string(int, int);
  static void read_argv(pid_t);
  static void read_env(pid_t);
  static void read_elf_auxv(pid_t);
  static char *copyinstr(pid_t, void *);
  static void detach_child(pid_t);
  static void signal_handler(int);
  static void siginfo_child(pid_t);
  static void siginfo_handler(int);
  
  static int mode;
  static FILE *output;				/* -o */
  
  #define SPRINTF(a,...)							\
  	do {								\
  		if (n < sizeof(buf))					\
  			n += xsnprintf(buf + n, sizeof(buf) - n,	\
  				(a), ## __VA_ARGS__);			\
  	} while (0)
  
  #define PRINT(fmt, ...)							\
  	do {								\
  		fprintf(output, fmt, ## __VA_ARGS__);			\
  	} while (0)
  
  
  /* Global variables */
  static struct timespec start_ts;
  static struct timespec previous_ts;
  
  static mtx_t mtx;		/* Protects pid_tree */
  
  /* Process related variables */
  struct pid_context {
  	char name[NAME_MAX];
  };
  
  static void *pid_tree;
  static thread_local struct pid_context *pid_ctx;
  
  
  void
  events_main(FILE *out, int m, size_t s)
  {
  	assert(out);
  	assert(!(m & MODE_COUNT));
  
  	output = out;
  	mode = m;
  	set_string_max_size(s);
  
  	setup_ops();
  
  	xmtx_init(&mtx, mtx_plain);
  	pid_tree = children_tree_init();
  
  	signal(SIGINT, signal_handler);
  	signal(SIGTERM, signal_handler);
  	signal(SIGHUP, signal_handler);
  
  	signal(SIGINFO, siginfo_handler);
  
  	xtimespec_get(&start_ts, TIME_UTC);
  
  	previous_ts = start_ts;
  }
  
  static void
  events_end(void)
  {
  }
  
  static void
  events_startup(pid_t pid)
  {
  	struct timespec ts;
  	ptrace_event_t pe;
  
  	pid_ctx = emalloc(sizeof(*pid_ctx));
  
  	resolve_child_name(pid, pid_ctx->name, sizeof(pid_ctx->name));
  
  	mtx_lock(&mtx);
  	children_tree_insert(pid_tree, pid, pid_ctx);
  	mtx_unlock(&mtx);
  
  	if (mode & MODE_INHERIT) {
  		pe.pe_set_event |= PTRACE_FORK;
  		pe.pe_set_event |= PTRACE_VFORK;
  		pe.pe_set_event |= PTRACE_VFORK_DONE;
  		pe.pe_set_event |= PTRACE_POSIX_SPAWN;
  	}
  
  	pe.pe_set_event |= PTRACE_LWP_CREATE;
  	pe.pe_set_event |= PTRACE_LWP_EXIT;
  
  	xptrace(PT_SET_EVENT_MASK, pid, &pe, sizeof(pe));
  }
  
  static void
  events_unstop(pid_t pid)
  {
  	ptrace_siginfo_t psi;
  	int signo;
  
  	xptrace(PT_GET_SIGINFO, pid, &psi, sizeof(psi));
  	signo = psi.psi_siginfo.si_signo;
  
  	if (signo == SIGTRAP) {
  		switch (psi.psi_siginfo.si_code) {
  		case TRAP_SCE:
  			/* FALLTHROUGH */
  		case TRAP_SCX:
  			/* FALLTHROUGH */
  		case TRAP_EXEC:
  			/* FALLTHROUGH */
  		case TRAP_LWP:
  			/* FALLTHROUGH */
  		case TRAP_CHLD:
  			/* FALLTHROUGH */
  		case TRAP_BRKPT:
  			/* FALLTHROUGH */
  		case TRAP_DBREG:
  			/* FALLTHROUGH */
  		case TRAP_TRACE:
  			signo = 0;
  			break;
  		default:
  			break;
  		}
  	}
  
  	xptrace(PT_SYSCALL, pid, (void *)1, signo);
  }
  
  static void
  events_continued(pid_t pid)
  {
  
  	if (!(mode & MODE_SIGNALS))
  		return;
  
  	report(pid, 0, "process continued");
  
  	xtimespec_get(&previous_ts, TIME_UTC);
  }
  
  static void
  events_signaled(pid_t pid, int sig, int core)
  {
  	char buf[512];
  	int n;
  	const char *s;
  
  	n = 0;
  
  	s = sig2string(sig);
  
  	SPRINTF("process killed, signal = ");
  
  	if (s) {
  		SPRINTF("%s", s);
  	} else {
  		SPRINTF("%d", sig);
  	}
  
  	if (core) {
  		SPRINTF(" (core dumped)");
  	}
  
  	report(pid, 0, "%s", buf);
  
  	xtimespec_get(&previous_ts, TIME_UTC);
  }
  
  static void
  events_exited(pid_t pid, int status)
  {
  
  	report(pid, 0, "process exit, rval = %d", status);
  
  	xtimespec_get(&previous_ts, TIME_UTC);
  }
  
  static void
  events_cleanup(pid_t pid)
  {
  
  	mtx_lock(&mtx);
  	children_tree_remove(pid_tree, pid);
  	mtx_unlock(&mtx);
  
  	free(pid_ctx);
  }
  
  static void
  events_debugregister(pid_t pid, lwpid_t lid)
  {
  }
  
  static void
  events_singlestep(pid_t pid, lwpid_t lid)
  {
  }
  
  static void
  events_breakpoint(pid_t pid, lwpid_t lid)
  {
  }
  
  static void
  events_syscallentry(pid_t pid, lwpid_t lid, siginfo_t *si)
  {
  
  	assert(si);
  	assert(si->si_sysnum < SYS_NSYSENT);
  }
  
  static void
  events_syscallexit(pid_t pid, lwpid_t lid, siginfo_t *si)
  {
  	char buf[1024];
  	char s[512];
  	int n;
  
  	assert(si);
  	assert(si->si_sysnum < SYS_NSYSENT);
  
  	n = 0;
  
  	SPRINTF("%-50s", decode_args(pid, si, s, __arraycount(s)));
  
  	SPRINTF("= %s", decode_retval(si, s, __arraycount(s)));
  
  	report(pid, lid, "%s", buf);
  
  	xtimespec_get(&previous_ts, TIME_UTC);
  }
  
  static void
  events_exec(pid_t pid, lwpid_t lid)
  {
  
  	report(pid, lid, "process exec'd");
  
  	if (mode & MODE_EXEC_ARGS)
  		read_argv(pid);
  	if (mode & MODE_EXEC_ENV)
  		read_env(pid);
  	if (mode & MODE_EXEC_AUXV)
  		read_elf_auxv(pid);
  
  	xtimespec_get(&previous_ts, TIME_UTC);
  }
  
  static void
  events_forked(pid_t pid, lwpid_t lid, pid_t child)
  {
  	int status;
  
  	assert(mode & MODE_INHERIT);
  
  	xwaitpid(child, &status, 0);
  
  	if (!WIFSTOPPED(status)) {
  		warnx("waitpid(%d) returned non-stopped child", child);
  		return;
  	}
  
  	if (WSTOPSIG(status) != SIGTRAP) {
  		warnx("waitpid(%d) returned unexpected signal %s", child,
  		    signalname(WSTOPSIG(status)));
  		return;
  	}
  
  	launch_worker(child);
  
  	xtimespec_get(&previous_ts, TIME_UTC);
  }
  
  static void
  events_vforked(pid_t pid, lwpid_t lid, pid_t child)
  {
  	int status;
  
  	assert(mode & MODE_INHERIT);
  
  	xwaitpid(child, &status, 0);
  
  	if (!WIFSTOPPED(status)) {
  		warnx("waitpid(%d) returned non-stopped child", child);
  		return;
  	}
  
  	if (WSTOPSIG(status) != SIGTRAP) {
  		warnx("waitpid(%d) returned unexpected signal %s", child,
  		    signalname(WSTOPSIG(status)));
  		return;
  	}
  
  	launch_worker(child);
  
  	xtimespec_get(&previous_ts, TIME_UTC);
  }
  
  static void
  events_vforkdone(pid_t pid, lwpid_t lid, pid_t child)
  {
  
  	assert(mode & MODE_INHERIT);
  
  	xtimespec_get(&previous_ts, TIME_UTC);
  }
  
  static void
  events_lwpcreated(pid_t pid, lwpid_t lid, lwpid_t lwp)
  {
  
  	report(pid, lid, "<new thread %d>", lwp);
  
  	xtimespec_get(&previous_ts, TIME_UTC);
  }
  
  static void
  events_lwpexited(pid_t pid, lwpid_t lid, lwpid_t lwp)
  {
  
  	report(pid, lid, "<thread %d exited>", lwp);
  
  	xtimespec_get(&previous_ts, TIME_UTC);
  }
  
  static void
  events_spawned(pid_t pid, lwpid_t lid, pid_t child)
  {
  	int status;
  
  	assert(mode & MODE_INHERIT);
  
  	xwaitpid(child, &status, 0);
  
  	if (!WIFSTOPPED(status)) {
  		warnx("waitpid(%d) returned non-stopped child", child);
  		return;
  	}
  
  	if (WSTOPSIG(status) != SIGTRAP) {
  		warnx("waitpid(%d) returned unexpected signal %s", child,
  		    signalname(WSTOPSIG(status)));
  		return;
  	}
  
  	launch_worker(child);
  
  	xtimespec_get(&previous_ts, TIME_UTC);
  }
  
  static void
  events_crashed(pid_t pid, lwpid_t lid, siginfo_t *si)
  {
  	char buf[512];
  	int n;
  	const char *s;
  
  	n = 0;
  
  	s = sig2string(si->si_signo);
  
  	SPRINTF("SIGNAL %d (%s)", si->si_signo, s ? s : "?");
  
  	s = sicode2string(si->si_signo, si->si_code);
  
  	SPRINTF(" code=");
  
  	if (s) {
  		SPRINTF("%s", s);
  	} else {
  		SPRINTF("%d", si->si_code);
  	}
  
  	SPRINTF(" si_addr=%p si_trap=%d\n", si->si_addr, si->si_trap);
  
  	report(pid, lid, "%s", buf);
  
  	xtimespec_get(&previous_ts, TIME_UTC);
  }
  
  static void
  events_stopped(pid_t pid, lwpid_t lid, siginfo_t *si)
  {
  	char buf[512];
  	const char *s;
  	int n;
  
  	if (!(mode & MODE_SIGNALS))
  		return;
  
  	n = 0;
  
  	s = sig2string(si->si_signo);
  
  	SPRINTF("SIGNAL %d (%s)", si->si_signo, s ? s : "?");
  
  	s = sicode2string(si->si_signo, si->si_code);
  
  	SPRINTF(" code=");
  
  	if (s) {
  		SPRINTF("%s", s);
  	} else {
  		SPRINTF("%d", si->si_code);
  	}
  
  	switch (si->si_code) {
  	case SI_USER:
  	case SI_LWP:
  		SPRINTF(" pid=%d uid=%d", si->si_pid, si->si_uid);
  		break;
  	case SI_QUEUE:
  		SPRINTF(" pid=%d uid=%d", si->si_pid, si->si_uid);
  		/* FALLTHROUGH */
  	case SI_TIMER:
  		SPRINTF(" value=%p", si->si_value.sival_ptr);
  		break;
  	case SI_ASYNCIO:
  		SPRINTF(" si_fd=%d si_band=%#lx", si->si_fd, si->si_band);
  		break;
  	case SI_MESGQ:
  	case SI_NOINFO:
  		/* No extra information */
  		break;
  	}
  
  	report(pid, lid, "%s", buf);
  
  	xtimespec_get(&previous_ts, TIME_UTC);
  }
  
  static void
  setup_ops(void)
  {
  
  	ops.end = events_end;
  	ops.startup = events_startup;
  	ops.unstop = events_unstop;
  	ops.continued = events_continued;
  	ops.signaled = events_signaled;
  	ops.exited = events_exited;
  	ops.cleanup = events_cleanup;
  	ops.debugregister = events_debugregister;
  	ops.singlestep = events_singlestep;
  	ops.breakpoint = events_breakpoint;
  	ops.syscallentry = events_syscallentry;
  	ops.syscallexit = events_syscallexit;
  	ops.exec = events_exec;
  	ops.forked = events_forked;
  	ops.vforked = events_vforked;
  	ops.vforkdone = events_vforkdone;
  	ops.lwpcreated = events_lwpcreated;
  	ops.lwpexited = events_lwpexited;
  	ops.spawned = events_spawned;
  	ops.crashed = events_crashed;
  	ops.stopped = events_stopped;
  }
  
  void
  report(pid_t pid, lwpid_t lwp, char *format, ...)
  {
  	char buf[1024];
  	struct timespec diff_ts;
  	struct timespec now_ts;
  	va_list ap;
  	int n;
  
  	n = 0;
  
  	xtimespec_get(&now_ts, TIME_UTC);
  
  	if (mode & MODE_INHERIT) {
  		SPRINTF("%5d %s ", pid, pid_ctx->name);
  	}
  
  	if (mode & MODE_LWPID) {
  		SPRINTF("%6d ", lwp);
  	}
  
  	if (mode & MODE_ABSOLUTE_TIMESTAMP) {
  		timespecsub(&now_ts, &start_ts, &diff_ts);
  		SPRINTF("%jd.%09ld ", diff_ts.tv_sec, diff_ts.tv_nsec);
  	}
  
  	if (mode & MODE_RELATIVE_TIMESTAMP) {
  		timespecsub(&now_ts, &previous_ts, &diff_ts);
  		SPRINTF("%jd.%09ld ", diff_ts.tv_sec, diff_ts.tv_nsec);
  	}
  
  	va_start(ap, format);
  	n += vsnprintf(buf + n, sizeof(buf) - n, format, ap);
  	va_end(ap);
  
  	PRINT("%s\n", buf);
  
  	previous_ts = now_ts;
  }
  
  static void
  resolve_child_name(pid_t pid, char *child_name, size_t maxlen)
  {
  	char buf[PATH_MAX];
  	size_t buflen;
  	int mib[4];
  
  	mib[0] = CTL_KERN;
  	mib[1] = KERN_PROC_ARGS;
  	mib[2] = pid;
  	mib[3] = KERN_PROC_PATHNAME;
  
  	buflen = sizeof(buf);
  
  	/*
  	 * There must be used an intermediate buffer with sufficient length as
  	 * otherwise the sysctl(3) call will reject the operation.
  	 */
  	xsysctl(mib, __arraycount(mib), buf, &buflen, NULL, 0);
  
  	estrlcpy(child_name, basename(buf), maxlen);
  }
  
  static const char *
  sig2string(int num)
  {
  
  	if (num < 0 || num > MAXSIGNALS)
  		return NULL;
  	else
  		return signals[num].name;
  }
  
  static const char *
  sicode2string(int sig, int code)
  {
  
  #define case_SICODE(a) case a: return #a;
  
  	switch (code) {
  	case_SICODE(SI_USER)
  	case_SICODE(SI_QUEUE)
  	case_SICODE(SI_TIMER)
  	case_SICODE(SI_ASYNCIO)
  	case_SICODE(SI_MESGQ)
  	case_SICODE(SI_LWP)
  	case_SICODE(SI_NOINFO)
  	}
  
  	switch (sig) {
  	case SIGILL:
  		switch (code) {
  		case_SICODE(ILL_ILLOPC)
  		case_SICODE(ILL_ILLOPN)
  		case_SICODE(ILL_ILLADR)
  		case_SICODE(ILL_ILLTRP)
  		case_SICODE(ILL_PRVOPC)
  		case_SICODE(ILL_PRVREG)
  		case_SICODE(ILL_COPROC)
  		case_SICODE(ILL_BADSTK)
  		}
  		break;
  	case SIGFPE:
  		switch (code) {
  		case_SICODE(FPE_INTDIV)
  		case_SICODE(FPE_INTOVF)
  		case_SICODE(FPE_FLTDIV)
  		case_SICODE(FPE_FLTOVF)
  		case_SICODE(FPE_FLTUND)
  		case_SICODE(FPE_FLTRES)
  		case_SICODE(FPE_FLTINV)
  		}
  		break;
  	case SIGSEGV:
  		switch (code) {
  		case_SICODE(SEGV_MAPERR)
  		case_SICODE(SEGV_ACCERR)
  		}
  		break;
  	case SIGBUS:
  		switch (code) {
  		case_SICODE(BUS_ADRALN)
  		case_SICODE(BUS_ADRERR)
  		case_SICODE(BUS_OBJERR)
  		}
  		break;
  	case SIGCHLD:
  		switch (code) {
  		case_SICODE(CLD_EXITED)
  		case_SICODE(CLD_KILLED)
  		case_SICODE(CLD_DUMPED)
  		case_SICODE(CLD_TRAPPED)
  		case_SICODE(CLD_STOPPED)
  		case_SICODE(CLD_CONTINUED)
  		}
  		break;
  	case SIGIO:
  		switch (code) {
  		case_SICODE(POLL_IN)
  		case_SICODE(POLL_OUT)
  		case_SICODE(POLL_MSG)
  		case_SICODE(POLL_ERR)
  		case_SICODE(POLL_PRI)
  		case_SICODE(POLL_HUP)
  		}
  		break;
  	}
  
  #undef case_SICODE
  
  	return NULL;
  }
  
  static void
  read_argv(pid_t pid)
  {
  	int i;
  	char *p;
  	int argc;
  	char *argv;
  	size_t len;
  	int mib[4];
  	lwpid_t lid;
  
  	lid = 0;
  
  	mib[0] = CTL_KERN;
  	mib[1] = KERN_PROC_ARGS;
  	mib[2] = pid;
  	mib[3] = KERN_PROC_NARGV;
  
  	len = sizeof(argc);
  
  	xsysctl(mib, __arraycount(mib), &argc, &len, NULL, 0);
  
  	mib[3] = KERN_PROC_ARGV;
  	len = 0;
  
  	xsysctl(mib, __arraycount(mib), NULL, &len, NULL, 0);
  
  	argv = emalloc(len);
  
  	xsysctl(mib, __arraycount(mib), argv, &len, NULL, 0);
  
  	p = argv;
  	for (i = 0; i < argc; i++) {
  		len = strlen(p);
  		report(pid, lid, "ARGV[%d] '%s'", i, p);
  		p += len + 1;
  	}
  
  	free(argv);
  }
  
  static void
  read_env(pid_t pid)
  {
  	int i;
  	char *p;
  	int envc;
  	char *envv;
  	size_t len;
  	int mib[4];
  	lwpid_t lid;
  
  	lid = 0;
  
  	mib[0] = CTL_KERN;
  	mib[1] = KERN_PROC_ARGS;
  	mib[2] = pid;
  	mib[3] = KERN_PROC_NENV;
  
  	len = sizeof(envc);
  
  	xsysctl(mib, __arraycount(mib), &envc, &len, NULL, 0);
  
  	mib[3] = KERN_PROC_ENV;
  	len = 0;
  
  	xsysctl(mib, __arraycount(mib), NULL, &len, NULL, 0);
  
  	envv = emalloc(len);
  
  	xsysctl(mib, __arraycount(mib), envv, &len, NULL, 0);
  
  	p = envv;
  	for (i = 0; i < envc; i++) {
  		len = strlen(p);
  		report(pid, lid, "ENV[%d] '%s'", i, p);;
  		p += len + 1;
  	}
  
  	free(envv);
  }
  
  static void
  read_elf_auxv(pid_t pid)
  {
  	char vector[1024];
  	const AuxInfo *aux;
  	struct ptrace_io_desc pio;
  	lwpid_t lid;
  	char *name;
  	char buf[512];
  	int n;
  	size_t i;
  
  	lid = 0;
  	i = 0;
  
  	pio.piod_op = PIOD_READ_AUXV;
  	pio.piod_offs = 0;
  	pio.piod_addr = vector;
  	pio.piod_len = sizeof(buf);
  
  	xptrace(PT_IO, pid, &pio, 0);
  
  	for (aux = (const AuxInfo *)vector; aux->a_type != AT_NULL; ++aux) {
  		n = 0; /* used by SNPRINTF */
  
  		SPRINTF("AUXV[%zu] ", i++);
  
  		switch (aux->a_type) {
  		case AT_IGNORE:
  			SPRINTF("AT_IGNORE");
  			break;
  		case AT_EXECFD:
  			SPRINTF("AT_EXECFD=%#lx", aux->a_v);
  			break;
  		case AT_PHDR:
  			SPRINTF("AT_PHDR=%#lx", aux->a_v);
  			break;
  		case AT_PHENT:
  			SPRINTF("AT_PHENT=%#lx", aux->a_v);
  			break;
  		case AT_PHNUM:
  			SPRINTF("AT_PHNUM=%#lx", aux->a_v);
  			break;
  		case AT_PAGESZ:
  			SPRINTF("AT_PAGESZ=%#lx", aux->a_v);
  			break;
  		case AT_BASE:
  			SPRINTF("AT_BASE=%#lx", aux->a_v);
  			break;
  		case AT_FLAGS:
  			SPRINTF("AT_FLAGS=%#lx", aux->a_v);
  			break;
  		case AT_ENTRY:
  			SPRINTF("AT_ENTRY=%#lx", aux->a_v);
  			break;
  		case AT_DCACHEBSIZE:
  			SPRINTF("AT_DCACHEBSIZE=%#lx", aux->a_v);
  			break;
  		case AT_ICACHEBSIZE:
  			SPRINTF("AT_ICACHEBSIZE=%#lx", aux->a_v);
  			break;
  		case AT_UCACHEBSIZE:
  			SPRINTF("AT_UCACHEBSIZE=%#lx", aux->a_v);
  			break;
  		case AT_STACKBASE:
  			SPRINTF("AT_STACKBASE=%#lx", aux->a_v);
  			break;
  
  #if 0
  		case AT_MIPS_NOTELF: /* overlap with AT_DCACHEBSIZE? */
  			SPRINTF("AT_DCACHEBSIZE=%#lx", aux->a_v);
  			break;
  #endif
  		case AT_EUID:
  			SPRINTF("AT_EUID=%ld", aux->a_v);
  			break;
  		case AT_RUID:
  			SPRINTF("AT_RUID=%ld", aux->a_v);
  			break;
  		case AT_EGID:
  			SPRINTF("AT_EGID=%ld", aux->a_v);
  			break;
  		case AT_RGID:
  			SPRINTF("AT_RGID=%ld", aux->a_v);
  			break;
  		case AT_SUN_LDELF:
  			SPRINTF("AT_SUN_LDELF=%#lx", aux->a_v);
  			break;
  		case AT_SUN_LDSHDR:
  			SPRINTF("AT_SUN_LDSHDR=%#lx", aux->a_v);
  			break;
  		case AT_SUN_LDNAME:
  			SPRINTF("AT_SUN_LDNAME=%#lx", aux->a_v);
  			break;
  		case AT_SUN_LPGSIZE:
  			SPRINTF("AT_SUN_LPGSIZE=%#lx", aux->a_v);
  			break;
  		case AT_SUN_PLATFORM:
  			SPRINTF("AT_SUN_PLATFORM=%#lx", aux->a_v);
  			break;
  		case AT_SUN_HWCAP:
  			SPRINTF("AT_SUN_HWCAP=%#lx", aux->a_v);
  			break;
  		case AT_SUN_IFLUSH:
  			SPRINTF("AT_SUN_IFLUSH=%#lx", aux->a_v);
  			break;
  		case AT_SUN_CPU:
  			SPRINTF("AT_SUN_CPU=%#lx", aux->a_v);
  			break;
  		case AT_SUN_EMUL_ENTRY:
  			SPRINTF("AT_SUN_EMUL_ENTRY=%#lx", aux->a_v);
  			break;
  		case AT_SUN_EMUL_EXECFD:
  			SPRINTF("AT_SUN_EMUL_EXECFD=%#lx", aux->a_v);
  			break;
  
  		case AT_SUN_EXECNAME:
  			name = copyinstr(pid, (void *)(intptr_t)aux->a_v);
  			SPRINTF("AT_SUN_EXECNAME=");
  			if (name)
  				SPRINTF("'%s'", name);
  			else
  				SPRINTF("%#" PRIx64, aux->a_v);
  			free(name);
  			break;
  		default:
  			SPRINTF("UNKNOWN-TAG-%d=%#lx", aux->a_type, aux->a_v);
  			break;
  		}
  		report(pid, lid, "%s", buf);
  	}
  }
  
  static char *
  copyinstr(pid_t pid, void *offs)
  {
  	struct ptrace_io_desc pio;
  	char *buf = NULL;
  	size_t bufchunks = 1;
  	const size_t bufchunklen = 32;
  	size_t i, n;
  	bool canonical;
  
  	ereallocarr(&buf, bufchunks, bufchunklen);
  
  	pio.piod_op = PIOD_READ_D;
  	pio.piod_offs = offs;
  	pio.piod_addr = buf;
  	pio.piod_len = bufchunklen;
  
  	n = 0;
  
  	canonical = false;
  
  	for (;;) {
  		errno = 0;
  		ptrace(PT_IO, pid, &pio, 0);
  
  		if (pio.piod_len == 0 || errno != 0) {
  			/* EOF */
  			break;
  		}
  
  		for (i = 0; i < MIN(bufchunklen, pio.piod_len); i++) {
  			if (((char *)pio.piod_addr)[i] == '\0') {
  				canonical = true;
  				break;
  			}
  		}
  
  		if (canonical)
  			break;
  
  		pio.piod_offs = (void *) ((intptr_t)pio.piod_offs + pio.piod_len);
  
  		n += pio.piod_len;
  
  		ereallocarr(&buf, ++bufchunks, bufchunklen);
  
  		pio.piod_addr = (void *)&buf[n];
  	}
  
  	if (canonical)
  		return buf;
  
  	/* Free the buffer */
  	ereallocarr(&buf, 0, bufchunklen);
  
  	return NULL;
  }
  
  static void
  detach_child(pid_t pid)
  {
  	int status;
  
  	kill(pid, SIGSTOP);
  
  	xwaitpid(pid, &status, 0);
  
  	xptrace(PT_DETACH, pid, (void *)1, 0);
  }
  
  static void
  signal_handler(int sig)
  {
  
  	children_tree_dump(pid_tree, detach_child);
  
  	exit(0);
  }
  
  static void
  siginfo_child(pid_t pid)
  {
  
  	printf("%s[%d] attached to child=%d\n", getprogname(), getpid(), pid);
  }
  
  static void
  siginfo_handler(int dummy)
  {
  
  	children_tree_dump(pid_tree, siginfo_child);
  }
  