/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (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
*/
/*
*/
/*
* DTrace D Language Compiler
*
* The code in this source file implements the main engine for the D language
* compiler. The driver routine for the compiler is dt_compile(), below. The
* compiler operates on either stdio FILEs or in-memory strings as its input
* and can produce either dtrace_prog_t structures from a D program or a single
* dtrace_difo_t structure from a D expression. Multiple entry points are
* The compiler itself is implemented across the following source files:
*
* dt_lex.l - lex scanner
* dt_grammar.y - yacc grammar
* dt_parser.c - parse tree creation and semantic checking
* dt_decl.c - declaration stack processing
* dt_xlator.c - D translator lookup and creation
* dt_ident.c - identifier and symbol table routines
* dt_pragma.c - #pragma processing and D pragmas
* dt_printf.c - D printf() and printa() argument checking and processing
* dt_cc.c - compiler driver and dtrace_prog_t construction
* dt_cg.c - DIF code generator
* dt_as.c - DIF assembler
* dt_dof.c - dtrace_prog_t -> DOF conversion
*
* Several other source files provide collections of utility routines used by
* these major files. The compiler itself is implemented in multiple passes:
*
* (1) The input program is scanned and parsed by dt_lex.l and dt_grammar.y
* and parse tree nodes are constructed using the routines in dt_parser.c.
* This node construction pass is described further in dt_parser.c.
*
* (2) The parse tree is "cooked" by assigning each clause a context (see the
* routine dt_setcontext(), below) based on its probe description and then
* recursively descending the tree performing semantic checking. The cook
* routines are also implemented in dt_parser.c and described there.
*
* (3) For actions that are DIF expression statements, the DIF code generator
* and assembler are invoked to create a finished DIFO for the statement.
*
* (4) The dtrace_prog_t data structures for the program clauses and actions
* are built, containing pointers to any DIFOs created in step (3).
*
* (5) The caller invokes a routine in dt_dof.c to convert the finished program
* into DOF format for use in anonymous tracing or enabling in the kernel.
*
* In the implementation, steps 2-4 are intertwined in that they are performed
* in order for each clause as part of a loop that executes over the clauses.
*
* The D compiler currently implements nearly no optimization. The compiler
* implements integer constant folding as part of pass (1), and a set of very
* simple peephole optimizations as part of pass (3). As with any C compiler,
* a large number of optimizations are possible on both the intermediate data
* structures and the generated DIF code. These possibilities should be
* investigated in the context of whether they will have any substantive effect
* on the overall DTrace probe effect before they are undertaken.
*/
#include <assert.h>
#include <strings.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <ucontext.h>
#include <limits.h>
#include <ctype.h>
#include <dirent.h>
#include <dt_module.h>
#include <dt_program.h>
#include <dt_provider.h>
#include <dt_printf.h>
#include <dt_pid.h>
#include <dt_grammar.h>
#include <dt_ident.h>
#include <dt_string.h>
#include <dt_impl.h>
DIF_TYPE_CTF, CTF_K_INTEGER, 0, 0, 0
};
};
/*ARGSUSED*/
static int
{
return (0);
}
/*ARGSUSED*/
static int
{
return (0);
}
static dtrace_stmtdesc_t *
{
return (sdp);
}
static dtrace_actdesc_t *
{
return (new);
}
/*
* Utility function to determine if a given action description is destructive.
* The dtdo_destructive bit is set for us by the DIF assembler (see dt_as.c).
*/
static int
{
}
static void
{
int commit = 0;
int speculate = 0;
int datarec = 0;
/*
* Make sure that the new statement jibes with the rest of the ECB.
*/
if (commit) {
"not follow commit( )\n");
}
if (datarec) {
"not follow data-recording action(s)\n");
}
continue;
"may not follow commit( )\n");
}
commit = 1;
continue;
}
if (speculate) {
"not follow speculate( )\n");
}
if (commit) {
"not follow commit( )\n");
}
if (datarec) {
"not follow data-recording action(s)\n");
}
speculate = 1;
continue;
}
if (speculate) {
"may not follow speculate( )\n");
}
datarec = 1;
continue;
}
if (speculate) {
if (dt_action_destructive(ap)) {
"may not follow speculate( )\n");
}
"follow speculate( )\n");
}
}
/*
* Exclude all non data-recording actions.
*/
if (dt_action_destructive(ap) ||
continue;
continue;
if (commit) {
"may not follow commit( )\n");
}
if (!speculate)
datarec = 1;
}
}
/*
* For the first element of an aggregation tuple or for printa(), we create a
* simple DIF program that simply returns the immediate value that is the ID
* of the aggregation itself. This could be optimized in the future by
* creating a new in-kernel dtad_kind that just returns an integer.
*/
static void
{
}
}
static void
{
char n[DT_TYPE_NAMELEN];
int argc = 0;
argc++; /* count up arguments for error messages below */
if (argc != 1) {
"%s( ) prototype mismatch: %d args passed, 1 expected\n",
}
"%s( ) argument #1 is incompatible with prototype:\n"
"\tprototype: aggregation\n\t argument: %s\n",
dt_node_type_name(anp, n, sizeof (n)));
}
}
}
static void
{
char n[DT_TYPE_NAMELEN];
int argc = 0;
argc++; /* count up arguments for error messages below */
"%s( ) prototype mismatch: %d args passed, %d expected\n",
}
"%s( ) argument #1 is incompatible with prototype:\n"
"\tprototype: aggregation\n\t argument: %s\n",
dt_node_type_name(anp, n, sizeof (n)));
}
"%s( ) argument #2 must be of scalar type\n",
}
}
if (denormal) {
return;
}
}
static void
{
char n[DT_TYPE_NAMELEN];
int argc = 0;
argc++; /* count up arguments for error messages below */
"%s( ) prototype mismatch: %d args passed, %s expected\n",
}
"%s( ) argument #1 is incompatible with prototype:\n"
"\tprototype: aggregation\n\t argument: %s\n",
dt_node_type_name(anp, n, sizeof (n)));
}
if (argc == 2) {
if (!dt_node_is_scalar(trunc)) {
"%s( ) argument #2 must be of scalar type\n",
}
}
}
if (argc == 1) {
} else {
}
}
static void
{
const char *format;
char n[DT_TYPE_NAMELEN];
argc++; /* count up arguments for error messages below */
case DT_NODE_STRING:
argr = 2;
break;
case DT_NODE_AGG:
argr = 1;
break;
default:
argr = 1;
}
"%s( ) prototype mismatch: %d args passed, %d expected\n",
}
"%s( ) argument #%d is incompatible with "
"prototype:\n\tprototype: aggregation\n"
dt_node_type_name(anp, n, sizeof (n)));
}
}
/*
* If we have multiple aggregations, we must be sure that
* their key signatures match.
*/
} else {
}
sdp->dtsd_fmtdata =
}
argr++;
}
}
static void
{
"%s( ) argument #1 is incompatible with prototype:\n"
"\tprototype: string constant\n\t argument: %s\n",
}
/*
* If this is an freopen(), we use an empty string to denote that
* stdout should be restored. For other printf()-like actions, an
* empty format string is illegal: an empty format string would
* result in malformed DOF, and the compiler thus flags an empty
* format string as a compile-time error. To avoid propagating the
* freopen() special case throughout the system, we simply transpose
* an empty string into a sentinel string (DT_FREOPEN_RESTORE) that
* denotes that stdout should be restored.
*/
if (kind == DTRACEACT_FREOPEN) {
/*
* Our sentinel is always an invalid argument to
* freopen(), but if it's been manually specified, we
* must fail now instead of when the freopen() is
* actually evaluated.
*/
"%s( ) argument #1 cannot be \"%s\"\n",
}
if (str[0] == '\0')
}
}
return;
}
}
}
static void
{
"trace( ) may not be applied to a void expression\n");
}
"trace( ) may not be applied to a dynamic expression\n");
}
}
static void
{
char n[DT_TYPE_NAMELEN];
"tracemem( ) argument #1 is incompatible with "
"prototype:\n\tprototype: pointer or integer\n"
"\t argument: %s\n",
dt_node_type_name(addr, n, sizeof (n)));
}
if (dt_node_is_posconst(size) == 0) {
"be a non-zero positive integral constant expression\n");
}
}
static void
{
} else {
}
"mismatch: too many arguments\n");
}
if (dt_node_is_posconst(arg0) == 0) {
"non-zero positive integral constant expression\n");
}
}
}
static void
{
}
static void
{
} else {
}
if (!dt_node_is_posconst(arg0)) {
"must be a non-zero positive integer constant\n");
}
}
"must be a positive integer constant\n");
}
"mismatch: too many arguments\n");
}
}
}
static void
{
}
static void
{
/*
* The prototype guarantees that we are called with either one or
* two arguments, and that any arguments that are present are strings.
*/
} else {
}
}
/*ARGSUSED*/
static void
{
kind == DTRACEACT_UADDR);
}
static void
{
}
/*ARGSUSED*/
static void
{
/*
* Library actions need a DIFO that serves as an argument. As
* ftruncate() doesn't take an argument, we generate the constant 0
* in a DIFO; this constant will be ignored when the ftruncate() is
* processed.
*/
}
/*ARGSUSED*/
static void
{
}
/*ARGSUSED*/
static void
{
}
/*ARGSUSED*/
static void
{
}
static void
{
}
static void
{
}
static void
{
}
static void
{
}
static void
{
}
static void
{
}
static void
{
case DT_ACT_BREAKPOINT:
break;
case DT_ACT_CHILL:
break;
case DT_ACT_CLEAR:
break;
case DT_ACT_COMMIT:
break;
case DT_ACT_DENORMALIZE:
break;
case DT_ACT_DISCARD:
break;
case DT_ACT_EXIT:
break;
case DT_ACT_FREOPEN:
break;
case DT_ACT_FTRUNCATE:
break;
case DT_ACT_MOD:
break;
case DT_ACT_NORMALIZE:
break;
case DT_ACT_PANIC:
break;
case DT_ACT_PRINTA:
break;
case DT_ACT_PRINTF:
break;
case DT_ACT_RAISE:
break;
case DT_ACT_SETOPT:
break;
case DT_ACT_SPECULATE:
break;
case DT_ACT_STACK:
break;
case DT_ACT_STOP:
break;
case DT_ACT_SYM:
break;
case DT_ACT_SYSTEM:
break;
case DT_ACT_TRACE:
break;
case DT_ACT_TRACEMEM:
break;
case DT_ACT_TRUNC:
break;
case DT_ACT_UADDR:
break;
case DT_ACT_UMOD:
break;
case DT_ACT_USYM:
break;
case DT_ACT_USTACK:
case DT_ACT_JSTACK:
break;
default:
}
}
static void
{
}
static void
{
/*
* If the aggregation has no aggregating function applied to it, then
* this statement has no effect. Flag this as a programming error.
*/
}
}
/*
* The ID of the aggregation itself is implicitly recorded as the first
* member of each aggregation tuple so we can distinguish them later.
*/
n++;
continue;
}
continue;
}
case DT_ACT_UADDR:
continue;
case DT_ACT_USYM:
continue;
case DT_ACT_UMOD:
continue;
case DT_ACT_SYM:
continue;
case DT_ACT_MOD:
continue;
default:
break;
}
}
}
/*
* For linear quantization, we have between two and four
* arguments in addition to the expression:
*
* arg1 => Base value
* arg2 => Limit value
* arg3 => Quantization level step size (defaults to 1)
* arg4 => Quantization increment value (defaults to 1)
*/
"argument #1 must be an integer constant\n");
}
"argument #1 must be a 32-bit quantity\n");
}
"argument #2 must be an integer constant\n");
}
"argument #2 must be a 32-bit quantity\n");
}
"lquantize( ) base (argument #1) must be less "
"than limit (argument #2)\n");
}
if (!dt_node_is_posconst(arg3)) {
"argument #3 must be a non-zero positive "
"integer constant\n");
}
"argument #3 must be a 16-bit quantity\n");
}
}
if (nlevels == 0) {
"lquantize( ) step (argument #3) too large: must "
"have at least one quantization level\n");
}
if (nlevels > UINT16_MAX) {
"(argument #3) too small: number of quantization "
"levels must be a 16-bit quantity\n");
}
((baseval << DTRACE_LQUANTIZE_BASESHIFT) &
if (isp->dis_auxinfo == 0) {
/*
* This is the first time we've seen an lquantize()
* for this aggregation; we'll store our argument
* as the auxiliary signature information.
*/
/*
* If we have seen this lquantize() before and the
* argument doesn't match the original argument, pick
* the original argument apart to concisely report the
* mismatch.
*/
"base (argument #1) doesn't match previous "
"declaration: expected %d, found %d\n",
}
"limit (argument #2) doesn't match previous"
" declaration: expected %d, found %d\n",
}
"step (argument #3) doesn't match previous "
"declaration: expected %d, found %d\n",
}
/*
* We shouldn't be able to get here -- one of the
* parameters must be mismatched if the arguments
* didn't match.
*/
assert(0);
}
argmax = 5;
}
argmax = 2;
}
if (!dt_node_is_scalar(incr)) {
"(argument #%d) must be of scalar type\n",
}
argc++;
"mismatch: %d args passed, at most %d expected",
}
n++;
}
ap->dtad_ntuple = n;
}
}
static void
{
}
}
case DT_NODE_DEXPR:
else
break;
case DT_NODE_DFUNC:
break;
case DT_NODE_AGG:
break;
default:
}
}
}
static void
{
}
static void
{
}
}
void
{
int err;
/*
* Both kernel and pid based providers are allowed to have names
* ending with what could be interpreted as a number. We assume it's
* a pid and that we may need to dynamically create probes for
* that process if:
*
* (1) The provider doesn't exist, or,
* (2) The provider exists and has DTRACE_PRIV_PROC privilege.
*
* On an error, dt_pid_create_probes() will set the error message
* and tag -- we just have to longjmp() out of here.
*/
}
/*
* Call dt_probe_info() to get the probe arguments and attributes. If
* a representative probe is found, set 'pap' to the probe provider's
* attributes. Otherwise set 'pap' to default Unstable attributes.
*/
pap = &_dtrace_prvdesc;
} else {
err = 0;
}
}
dt_dprintf("set context to %s:%s:%s:%s [%u] prp=%p attr=%s argc=%d\n",
/*
* Reset the stability attributes of D global variables that vary
* based on the attributes of the provider and context itself.
*/
}
/*
* Reset context-dependent variables and state at the end of cooking a D probe
* definition clause. This ensures that external declarations between clauses
* do not reference any stale context-dependent data from the previous clause.
*/
void
{
static const char *const cvars[] = {
};
int i;
}
}
static int
{
return (0);
}
/*
* When dtrace_setopt() is called for "version", it calls dt_reduce() to remove
* any identifiers or translators that have been previously defined as bound to
* a version greater than the specified version. Therefore, in our current
* version implementation, establishing a binding is a one-way transformation.
* In addition, no versioning is currently provided for types as our .d library
* files do not define any types and we reserve prefixes DTRACE_ and dtrace_
* for our exclusive use. If required, type versioning will require more work.
*/
int
{
char s[DT_VERSION_STRMAX];
return (0); /* no reduction necessary */
dt_dprintf("reducing api version to %s\n",
dt_version_num2str(v, s, sizeof (s)));
}
return (0);
}
/*
* Fork and exec the cpp(1) preprocessor to run over the specified input file,
* here to simplify the code by leveraging file descriptor inheritance.
*/
static FILE *
{
int c;
goto err;
}
/*
* If the input is a seekable file, see if it is an interpreter file.
* If we see #!, seek past the first line because cpp will choke on it.
* We start cpp just prior to the \n at the end of this line so that
* it still sees the newline, ensuring that #line values are correct.
*/
break;
}
if (c == '\n')
off--; /* start cpp just prior to \n */
}
}
switch (dtp->dt_stdcmode) {
case DT_STDC_XA:
case DT_STDC_XT:
break;
case DT_STDC_XC:
break;
}
/*
* libdtrace must be able to be embedded in other programs that may
* include application-specific signal handlers. Therefore, if we
* need to fork to run cpp(1), we must avoid generating a SIGCHLD
* that could confuse the containing application. To do this,
* we block SIGCHLD and reset its disposition to SIG_DFL.
* We restore our signal state once we are done.
*/
(void) sigemptyset(&mask);
goto err;
}
if (pid == 0) {
}
do {
(int)pid);
if (estat != 0) {
switch (estat) {
case 126:
break;
case 127:
break;
default:
}
goto err;
}
return (ofp);
err:
return (NULL);
}
static void
{
}
int
{
const char *end;
return (-1);
return (-1);
}
}
return (0);
}
{
return (dldn);
}
return (NULL);
}
/*
* Go through all the library files, and, if any library dependencies exist for
* that file, add it to that node's list of dependents. The result of this
* will be a graph which can then be topologically sorted to produce a
* compilation order.
*/
static int
{
"Invalid library dependency in %s: %s\n",
}
library)) != 0) {
return (-1); /* preserve dt_errno */
}
}
}
return (0);
}
static int
{
dpld->dtld_library);
if (dlda->dtld_start == 0 &&
return (-1);
}
return (-1);
}
return (0);
}
static int
{
int count = 0;
return (-1); /* preserve dt_errno */
/*
* Perform a topological sort of the graph that hangs off
* dtp->dt_lib_dep. The result of this process will be a
* dependency ordered list located at dtp->dt_lib_dep_sorted.
*/
if (dld->dtld_start == 0 &&
return (-1); /* preserve dt_errno */;
}
/*
* Check the graph for cycles. If an ancestor's finishing time is
* less than any of its dependent's finishing times then a back edge
* exists in the graph and this is a cycle.
*/
dpld->dtld_library);
"Cyclic dependency detected: %s => %s\n",
}
}
}
return (0);
}
static void
{
}
}
}
}
}
/*
* Open all of the .d library files found in the specified directory and
* compile each one in topological order to cache its inlines and translators,
* etc. We silently ignore any missing directories and other files found
* therein. We only fail (and thereby fail dt_load_libs()) if we fail to
* compile a library and the error is something other than #pragma D depends_on.
* Dependency errors are silently ignored to permit a library directory to
* contain libraries which may not be accessible depending on our privileges.
*/
static int
{
const char *p;
void *rv;
return (0);
}
/* First, parse each file for library dependencies. */
continue; /* skip any filename not ending in .d */
dt_dprintf("skipping library %s: %s\n",
continue;
}
goto err;
goto err;
dt_dprintf("error parsing library %s: %s\n",
}
/*
* Finish building the graph containing the library dependencies
* and perform a topological sort to generate an ordered list
* for compilation.
*/
goto err;
dt_dprintf("skipping library %s: %s\n",
continue;
}
goto err;
dt_dprintf("skipping library %s: %s\n",
} else {
}
}
return (0);
err:
return (-1); /* preserve dt_errno */
}
/*
* Load the contents of any appropriate DTrace .d library files. These files
* contain inlines and translators that will be cached by the compiler. We
* defer this activity until the first compile to permit libdtrace clients to
* add their own library directories and so that we can properly report errors.
*/
static int
{
return (0); /* libraries already processed */
return (-1); /* errno is set for us */
}
}
return (0);
}
static void *
{
void *rv;
int err;
return (NULL);
}
return (NULL); /* errno is set for us */
return (NULL); /* errno is set for us */
pcb.pcb_string = s;
pcb.pcb_strptr = s;
if (context != DT_CTX_DPROG)
else if (cflags & DTRACE_C_CTL)
else
goto out;
/*
* Invoke the parser to evaluate the D source code. If any errors
* occur during parsing, an error function will be called and we
* will longjmp back to pcb_jmpbuf to abort. If parsing succeeds,
* we optionally display the parse tree if debugging is enabled.
*/
if (cflags & DTRACE_C_CTL)
goto out;
}
/*
* If we have successfully created a parse tree for a D program, loop
* over the clauses and actions and instantiate the corresponding
* libdtrace program. If we are parsing a D expression, then we
* simply run the code generator and assembler on the resulting tree.
*/
switch (context) {
case DT_CTX_DPROG:
case DT_NODE_CLAUSE:
break;
case DT_NODE_XLATOR:
break;
case DT_NODE_PROVIDER:
break;
}
}
yypcb->pcb_asxreflen = 0;
break;
case DT_CTX_DEXPR:
break;
case DT_CTX_DTYPE:
if (err != 0)
break;
}
out:
}
{
}
{
}
int
{
}
int
{
}