dtrace.c revision 0b38a8bdfd75ac6144f9d462bb38d0c1b3f0ca50
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <dtrace.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include <limits.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <alloca.h>
#include <libgen.h>
#include <libproc.h>
typedef struct dtrace_cmd {
char *dc_arg; /* argument from main argv */
const char *dc_name; /* name for error messages */
const char *dc_desc; /* desc for error messages */
} dtrace_cmd_t;
#define DMODE_VERS 0 /* display version information and exit (-V) */
#define E_SUCCESS 0
#define E_ERROR 1
#define E_USAGE 2
static const char DTRACE_OPTSTR[] =
"3:6:aAb:c:CD:ef:FGHi:I:lL:m:n:o:p:P:qs:SU:vVwx:X:Z";
static char **g_argv;
static int g_argc;
static char **g_objv;
static int g_objc;
static dtrace_cmd_t *g_cmdv;
static int g_cmdc;
static struct ps_prochandle **g_psv;
static int g_psc;
static int g_pslive;
static char *g_pname;
static int g_quiet;
static int g_flowindent;
static int g_intr;
static int g_impatient;
static int g_newline;
static int g_total;
static int g_cflags;
static int g_oflags;
static int g_verbose;
static int g_exec = 1;
static int g_mode = DMODE_EXEC;
static int g_grabanon = 0;
static dtrace_hdl_t *g_dtp;
static const char *g_etcbegin = "* vvvv Added by DTrace";
static const char *g_etcend = "* ^^^^ Added by DTrace";
static const char *g_etc[] = {
"*",
"* The following forceload directives were added by dtrace(1M) to allow for",
"* tracing during boot. If these directives are removed, the system will",
"* continue to function, but tracing will not occur during boot as desired.",
"* To remove these directives (and this block comment) automatically, run",
"* \"dtrace -A\" without additional arguments. See the \"Anonymous Tracing\"",
"* chapter of the Solaris Dynamic Tracing Guide for details.",
"*",
NULL };
static int
{
static const char predact[] = "[[ predicate ] action ]";
"[-b bufsz] [-c cmd] [-D name[=def]]\n\t[-I path] [-L path] "
"[-o output] [-p pid] [-s script] [-U name]\n\t"
"[-x opt[=val]] [-X a|c|s|t]\n\n"
"\t[-P provider %s]\n"
"\t[-m [ provider: ] module %s]\n"
"\t[-f [[ provider: ] module: ] func %s]\n"
"\t[-n [[[ provider: ] module: ] func: ] name %s]\n"
"\t[-i probe-id %s] [ args ... ]\n\n", g_pname,
"\t-32 generate 32-bit D programs and ELF files\n"
"\t-64 generate 64-bit D programs and ELF files\n\n"
"\t-a claim anonymous tracing state\n"
"\t-A generate driver.conf(4) directives for anonymous tracing\n"
"\t-b set trace buffer size\n"
"\t-c run specified command and exit upon its completion\n"
"\t-C run cpp(1) preprocessor on script files\n"
"\t-D define symbol when invoking preprocessor\n"
"\t-e exit after compiling request but prior to enabling probes\n"
"\t-f enable or list probes matching the specified function name\n"
"\t-F coalesce trace output by function\n"
"\t-G generate an ELF file containing embedded dtrace program\n"
"\t-H print included files when invoking preprocessor\n"
"\t-i enable or list probes matching the specified probe id\n"
"\t-I add include directory to preprocessor search path\n"
"\t-l list probes matching specified criteria\n"
"\t-L add library directory to library search path\n"
"\t-m enable or list probes matching the specified module name\n"
"\t-n enable or list probes matching the specified probe name\n"
"\t-o set output file\n"
"\t-p grab specified process-ID and cache its symbol tables\n"
"\t-P enable or list probes matching the specified provider name\n"
"\t-q set quiet mode (only output explicitly traced data)\n"
"\t-s enable or list probes according to the specified D script\n"
"\t-S print D compiler intermediate code\n"
"\t-U undefine symbol when invoking preprocessor\n"
"\t-v set verbose mode (report stability attributes, arguments)\n"
"\t-V report DTrace API version\n"
"\t-w permit destructive actions\n"
"\t-x enable or modify compiler and tracing options\n"
"\t-X specify ISO C conformance settings for preprocessor\n"
"\t-Z permit probe descriptions that match zero probes\n");
return (E_USAGE);
}
static void
{
}
/*PRINTFLIKE1*/
static void
{
}
/*PRINTFLIKE1*/
static void
{
}
}
/*PRINTFLIKE1*/
static void
{
}
/*PRINTFLIKE1*/
static void
{
if (g_quiet)
return; /* -q or quiet pragma suppresses notice()s */
}
/*PRINTFLIKE1*/
static void
{
int n;
if (n < 0) {
fatal("failed to write to %s",
}
}
}
static char **
make_argv(char *s)
{
const char *ws = "\f\n\r\t\v ";
int argc = 0;
char *p = s;
return (NULL);
if (argc == 0)
return (argv);
}
static void
{
char *buf;
/*
* This is okay only if the file doesn't exist at all.
*/
return;
}
continue;
/*
* This is only a match if it's in the 0th column.
*/
continue;
if (msg++ == 0) {
error("cleaned up old anonymous "
"enabling in %s\n", fname);
}
/*
* We have a match. First write out our data up until now.
*/
if (i != mark) {
}
/*
* Now scan forward until we scan past a newline.
*/
continue;
/*
* Reset our mark.
*/
break;
i = j;
}
}
}
static void
etcsystem_prune(void)
{
int fd;
goto out;
fatal("embedded nul byte in %s; manual repair of %s "
}
fatal("multiple start sentinels in %s; manual repair of %s "
}
fatal("missing end sentinel in %s; manual repair of %s "
}
fatal("end sentinel preceeds start sentinel in %s; manual "
}
}
}
out:
}
static void
etcsystem_add(void)
{
const char *mods[20];
sizeof (mods) / sizeof (char *) - 1);
fatal("unexpectedly large number of modules!");
}
static void
print_probe_info(const dtrace_probeinfo_t *p)
{
int i;
oprintf("\n\tProbe Description Attributes\n");
oprintf("\t\tIdentifier Names: %s\n",
oprintf("\t\tData Semantics: %s\n",
oprintf("\t\tDependency Class: %s\n",
oprintf("\n\tArgument Attributes\n");
oprintf("\t\tIdentifier Names: %s\n",
oprintf("\t\tData Semantics: %s\n",
oprintf("\t\tDependency Class: %s\n",
oprintf("\n\tArgument Types\n");
for (i = 0; i < p->dtp_argc; i++) {
}
if (p->dtp_argc == 0)
oprintf("\t\tNone\n");
oprintf("\n");
}
/*ARGSUSED*/
static int
{
return (0);
oprintf("\n%s:%s:%s:%s\n",
print_probe_info(&p);
return (0);
}
/*
* Execute the specified program by enabling the corresponding instrumentation.
* If -e has been specified, we get the program info but do not enable it. If
* -v has been specified, we print a stability report for the program.
*/
static void
{
if (!g_exec) {
} else {
notice("%s '%s' matched %u probe%s\n",
}
if (g_verbose) {
oprintf("\nStability attributes for %s %s:\n",
oprintf("\n\tMinimum Probe Description Attributes\n");
oprintf("\t\tIdentifier Names: %s\n",
oprintf("\t\tData Semantics: %s\n",
oprintf("\t\tDependency Class: %s\n",
oprintf("\n\tMinimum Statement Attributes\n");
oprintf("\t\tIdentifier Names: %s\n",
oprintf("\t\tData Semantics: %s\n",
oprintf("\t\tDependency Class: %s\n",
if (!g_exec) {
} else
oprintf("\n");
}
}
/*
* Print out the specified DOF buffer as a set of ASCII bytes appropriate for
* storing in a driver.conf(4) file associated with the dtrace driver.
*/
static void
{
const uchar_t *p, *q;
q = p + dof->dofh_loadsz;
oprintf("dof-data-%d=0x%x", n, *p++);
while (p < q)
oprintf(",0x%x", *p++);
oprintf(";\n");
}
/*
* Link the specified D program in DOF form into an ELF file for use in either
* helpers, userland provider definitions, or both. If -o was specified, that
* path is used as the output file name. If -o wasn't specified and the input
* program is from a script whose name is %.d, use basename(%.o) as the output
* file name. Otherwise we use "d.out" as the default output file name.
*/
static void
{
char *p;
strcmp(p, ".d") == 0) {
p[0] = '\0'; /* strip .d suffix */
} else {
}
}
/*ARGSUSED*/
static int
{
print_probe_info(&p);
return (0);
}
/*ARGSUSED*/
static int
{
return (0);
error("failed to match %s:%s:%s:%s: %s\n",
}
return (0);
}
/*
* List the probes corresponding to the specified program by iterating over
* each statement and then matching probes to the statement probe descriptions.
*/
static void
{
}
static void
{
char *arg0;
}
static void
{
char *p;
*p = '\0'; /* crop name for reporting */
}
/*ARGSUSED*/
static void
{
char name[SIG2STR_MAX];
switch (Pstate(P)) {
case PS_UNDEAD:
/*
* Ideally we would like to always report pr_wstat here, but it
* isn't possible given current /proc semantics. If we grabbed
* the process, Ppsinfo() will either fail or return a zeroed
* psinfo_t depending on how far the parent is in reaping it.
* When /proc provides a stable pr_wstat in the status file,
* this code can be improved by examining this new pr_wstat.
*/
notice("pid %d exited with status %d\n",
} else {
}
g_pslive--;
break;
case PS_LOST:
g_pslive--;
break;
}
}
/*ARGSUSED*/
static int
{
return (DTRACE_HANDLE_OK);
}
/*ARGSUSED*/
static int
{
return (DTRACE_HANDLE_OK);
}
/*ARGSUSED*/
static int
{
/*
* We have processed the final record; output the newline if
* we're not in quiet mode.
*/
if (!g_quiet)
oprintf("\n");
return (DTRACE_CONSUME_NEXT);
}
if (act == DTRACEACT_EXIT) {
return (DTRACE_CONSUME_NEXT);
}
return (DTRACE_CONSUME_THIS);
}
/*ARGSUSED*/
static int
{
static int heading;
if (g_impatient) {
g_newline = 0;
return (DTRACE_CONSUME_ABORT);
}
if (heading == 0) {
if (!g_flowindent) {
if (!g_quiet) {
oprintf("%3s %6s %32s\n",
"CPU", "ID", "FUNCTION:NAME");
}
} else {
}
heading = 1;
}
if (!g_flowindent) {
if (!g_quiet) {
}
} else {
char *name;
} else {
}
}
return (DTRACE_CONSUME_THIS);
}
static void
go(void)
{
int i;
struct {
char *name;
char *optname;
} bufs[] = {
{ "buffer size", "bufsize" },
{ "aggregation size", "aggsize" },
{ "speculation size", "specsize" },
{ "dynamic variable size", "dynvarsize" },
{ NULL }
}, rates[] = {
{ "cleaning rate", "cleanrate" },
{ "status rate", "statusrate" },
{ NULL }
};
}
}
dfatal("could not enable tracing");
continue;
continue;
continue;
continue;
} else {
(long long)nsize);
}
}
char *dir;
continue;
continue;
continue;
continue;
}
error("%s %s to once every %lld seconds\n",
continue;
}
error("%s %s to once every %lld nanoseconds\n",
}
}
/*ARGSUSED*/
static void
{
if (!g_intr)
g_newline = 1;
if (g_intr++)
g_impatient = 1;
}
int
{
int err, i;
char c, *p, **v;
struct ps_prochandle *P;
if (argc == 1)
fatal("failed to allocate memory for arguments");
/*
* Make an initial pass through argv[] processing any arguments that
* affect our behavior mode (g_mode) and flags used for dtrace_open().
* We also accumulate arguments that are not affiliated with getopt
* options into g_argv[], and abort if any invalid options are found.
*/
switch (c) {
case '3':
"%s: illegal option -- 3%s\n",
}
g_oflags &= ~DTRACE_O_LP64;
break;
case '6':
"%s: illegal option -- 6%s\n",
}
g_oflags &= ~DTRACE_O_ILP32;
break;
case 'a':
g_grabanon++; /* also checked in pass 2 below */
break;
case 'A':
g_mode = DMODE_ANON;
g_exec = 0;
mode++;
break;
case 'e':
g_exec = 0;
done = 1;
break;
case 'G':
g_mode = DMODE_LINK;
g_exec = 0;
mode++;
break;
case 'l':
g_mode = DMODE_LIST;
mode++;
break;
case 'V':
g_mode = DMODE_VERS;
mode++;
break;
default:
}
}
}
if (mode > 1) {
"can be specified at a time\n", g_pname);
return (E_USAGE);
}
if (g_mode == DMODE_VERS)
/*
* Open libdtrace. If we are not actually going to be enabling any
* instrumentation attempt to reopen libdtrace using DTRACE_O_NODEV.
*/
continue;
}
fatal("failed to initialize dtrace: %s\n",
}
/*
* If -G is specified, enable -xlink=dynamic and -xunodefs to permit
* references to undefined symbols to remain as unresolved relocations.
* If -A is specified, enable -xlink=primary to permit static linking
* only to kernel symbols that are defined in a primary kernel module.
*/
if (g_mode == DMODE_LINK) {
/*
* We still use g_argv[0], the name of the executable.
*/
g_argc = 1;
} else if (g_mode == DMODE_ANON)
/*
* Now that we have libdtrace open, make a second pass through argv[]
* to perform any dtrace_setopt() calls and change any compiler flags.
* We also accumulate any program specifications into our g_cmdv[] at
* this time; these will compiled as part of the fourth processing pass.
*/
switch (c) {
case 'a':
dfatal("failed to set -a");
break;
case 'b':
if (dtrace_setopt(g_dtp,
"bufsize", optarg) != 0)
break;
case 'C':
g_cflags |= DTRACE_C_CPP;
break;
case 'D':
break;
case 'f':
break;
case 'F':
dfatal("failed to set -F");
break;
case 'H':
dfatal("failed to set -H");
break;
case 'i':
break;
case 'I':
break;
case 'L':
break;
case 'm':
break;
case 'n':
break;
case 'P':
break;
case 'q':
dfatal("failed to set -q");
break;
case 'o':
break;
case 's':
break;
case 'S':
break;
case 'U':
break;
case 'v':
g_verbose++;
break;
case 'w':
dfatal("failed to set -w");
break;
case 'x':
*p++ = '\0';
break;
case 'X':
break;
case 'Z':
break;
default:
}
}
}
/*
* In our third pass we handle any command-line options related to
* grabbing or creating victim processes. The behavior of these calls
* may been affected by any library options set by the second pass.
*/
switch (c) {
case 'c':
fatal("failed to allocate memory");
P = dtrace_proc_create(g_dtp, v[0], v);
if (P == NULL)
free(v);
break;
case 'p':
errno = 0;
if (P == NULL)
break;
}
}
}
/*
* In our fourth pass we finish g_cmdv[] by calling dc_func to convert
* each string or file specification into a compiled program structure.
*/
for (i = 0; i < g_cmdc; i++)
if (g_mode != DMODE_LIST) {
dfatal("failed to establish error handler");
dfatal("failed to establish drop handler");
dfatal("failed to establish proc handler");
}
/*
* Now make a fifth and final pass over the options that have been
* turned into programs and saved in g_cmdv[], performing any mode-
* specific processing. If g_mode is DMODE_EXEC, we will break out
* of the switch() and continue on to the data processing loop. For
* other modes, we will exit dtrace once mode-specific work is done.
*/
switch (g_mode) {
case DMODE_EXEC:
for (i = 0; i < g_cmdc; i++)
if (done && !g_grabanon)
return (g_status);
break;
case DMODE_ANON:
g_ofile = "/kernel/drv/dtrace.conf";
etcsystem_prune(); /* string out any forceload directives */
if (g_cmdc == 0)
return (g_status);
for (i = 0; i < g_cmdc; i++) {
}
/*
* Dump out the DOF corresponding to the error handler and the
* current options as the final DOF property in the .conf file.
*/
/*
* These messages would use notice() rather than error(), but
* we don't want them suppressed when -A is run on a D program
* that itself contains a #pragma D option quiet.
*/
error("run update_drv(1M) or reboot to enable changes\n");
return (g_status);
case DMODE_LINK:
for (i = 0; i < g_cmdc; i++)
for (i = 0; i < g_cmdc; i++)
}
return (g_status);
case DMODE_LIST:
oprintf("%5s %10s %17s %33s %s\n",
"ID", "PROVIDER", "MODULE", "FUNCTION", "NAME");
for (i = 0; i < g_cmdc; i++)
if (g_cmdc == 0)
return (g_status);
}
/*
* If -a and -Z were not specified and no probes have been matched, no
* probe criteria was specified on the command line and we abort.
*/
/*
* Start tracing. Once we dtrace_go(), reload any options that affect
* our globals in case consuming anonymous state has changed them.
*/
go();
if (opt != DTRACEOPT_UNSET)
notice("allowing destructive actions\n");
/*
* Now that tracing is active and we are ready to consume trace data,
* continue any grabbed or created processes, setting them running
* using the /proc control mechanism inside of libdtrace.
*/
for (i = 0; i < g_psc; i++)
do {
if (g_newline) {
/*
* Output a newline just to make the output look
* slightly cleaner. Note that we do this even in
* "quiet" mode...
*/
oprintf("\n");
g_newline = 0;
}
done = 1;
dfatal("couldn't stop tracing");
}
case DTRACE_WORKSTATUS_DONE:
done = 1;
break;
case DTRACE_WORKSTATUS_OKAY:
break;
default:
dfatal("processing aborted");
}
} while (!done);
oprintf("\n");
if (!g_impatient) {
dfatal("failed to print aggregations");
}
return (g_status);
}