Go to main content

Oracle® Solaris 11.4 DTrace (Dynamic Tracing) Guide

Exit Print View

Updated: September 2020
 
 

Translator Declarations

A translator is a collection of D assignment statements provided by the supplier of an interface that can be used to translate an input expression into an object of struct type. To understand the need for and use of translators, consider the ANSI-C standard library routines defined in stdio.h as an example. These routines operate on a data structure named FILE whose implementation artifacts are abstracted away from C programmers. A standard technique for creating a data structure abstraction is to provide only a forward declaration of a data structure in public header files, while keeping the corresponding struct definition in a separate private header file.

If you are writing a C program and wish to know the file descriptor corresponding to a FILE struct, you can use the fileno() function to obtain the descriptor rather than dereferencing a member of the FILE struct directly. The Oracle Solaris header files enforce this rule by defining FILE as an opaque forward declaration tag so it cannot be dereferenced directly by C programs that include stdio.h. Inside the libc.so.1 library, you can suppose that fileno is implemented in C as follows:

int
fileno(FILE *fp)
{
        struct file_impl *ip = (struct file_impl *)fp;

        return (ip->fd);
}

The hypothetical fileno takes a FILE pointer as an argument and casts it to a pointer to a corresponding internal libc structure, struct file_impl, and then returns the value of the fd member of the implementation structure. By abstracting the details of the current libc implementation away from client programs, it is possible to maintain a commitment to strong binary compatibility while continuing to evolve and change the internal implementation details of libc. In this example, the fd member could change size or position within struct file_impl, even in a patch, and existing binaries calling fileno would not be affected by this change because they do not depend on these artifacts.

However, observability software such as DTrace must peer inside the implementation in order to provide useful results, and does not have the luxury of calling arbitrary C functions defined in Oracle Solaris libraries or in the kernel. You could declare a copy of struct file_impl in the D program in order to instrument the routines declared in stdio.h, but then the D program would rely on Private implementation artifacts of the library that might break in a future micro or minor release, or even in a patch. Ideally, you would provide a construct for use in D programs that is bound to the implementation of the library and is updated accordingly, but still provides an additional layer of abstraction associated with greater stability.

A new translator is created by using a declaration of the form:

translator output-type  <input-type> input-identifier  {
        member-name = expression ;
        member-name = expression ;
        ...
};

The output-type names a struct that will be the result type for the translation. The input-type specifies the type of the input expression, and is surrounded in angle brackets < > and followed by an input-identifier that can be used in the translator expressions as an alias for the input expression. The body of the translator is surrounded in braces { } and terminated with a semicolon (;), and consists of a list of member-name and identifiers corresponding translation expressions. Each member declaration must name a unique member of the output-type and must be assigned an expression of a type compatible with the member type, according to the rules for the D assignment (=) operator.

For example, define a struct of stable information about stdio files based on some of the available libc interfaces:

struct file_info {
        int file_fd;   /* file descriptor from fileno(3C) */
        int file_eof;  /* eof flag from feof(3C) */
};

A hypothetical D translator from FILE to file_info can be declared in D as follows:

translator struct file_info < FILE *F > {
        file_fd = ((struct file_impl *)F)->fd;
        file_eof = ((struct file_impl *)F)->eof;
};

In the example translator, the input expression is of the type FILE * and is assigned the input-identifier F. The identifier F can then be used in the translator member expressions as a variable of type FILE * that is only visible within the body of the translator declaration. To determine the value of the output file_fd member, the translator performs a cast and dereference similar to the hypothetical implementation of fileno shown in the preceding example. A similar translation is performed to obtain the value of the EOF indicator.