Tapsets & translators

We already discussed problem with probe stability. Some issues may be related to changing data structures in kernel, or several variants may exist in kernel, for example for 32- and 64-bit calls. Let's see how access to fields of that structure may be unified.

DTrace has a translators for doing that:

  Script file stat.d

struct stat_info {
    long long st_size;
};

translator struct stat_info < uintptr_t s > {
    st_size = * ((long long*) copyin(s + offsetof(struct stat64_32, st_size),
                                     sizeof (long long)));
};

syscall::fstatat64:entry
{
        self->filename = copyinstr(arg1);
        self->statptr = arg2;
}

syscall::fstatat64:return
{
        printf("STAT %s size: %d\n", self->filename, 
               xlate < struct stat_info* > (self->statptr)->st_size);
}

In this example translator describes rules of converting source structure stat64_32 to a structure with known format defined in DTrace stat_info. After that, xlate operator is called which receives pointer to stat64_32 structure to a stat_info. Note that our translator also responsible for copying data from userspace to kernel. Built-in DTrace translators are located in /usr/lib/dtrace.

SystemTap doesn't have translators, but you can create prologue or epilogue alias which performs necessary conversions before (or after, respectively) probe is called. These aliases are grouped into script libraries called tapsets and put into /usr/share/systemtap/tapset directory. Many probes that we will use in following modules are implemented in such tapsets.

Linux has several variants for stat structure in stat() system call, some of them deprecated, some are intended to support 64-bit sizes for 32-bit callers. By using following tapset we will remove such differences and make them universally available through filename and size variables:

  Script file lstat.stp

probe lstat = kernel.function("sys_lstat64").return ? ,
              kernel.function("sys32_lstat64").return ? {
    filename = user_string($filename);
    size = user_uint64(& @cast($statbuf, "struct stat64")->st_size);
}

probe lstat = kernel.function("sys_newlstat").return ? {
    filename = user_string($filename);
%( arch == "x86_64"
%?  size = user_uint64(& @cast($statbuf, "struct stat")->st_size);
%:  size = user_uint32(& @cast($statbuf, "struct stat")->st_size);
%)
}

This example is unrealistic: it is easier to attach to vfs_lstat function which has universal representation of stat structure and doesn't involve copying from userspace. Summarizing the syntax of creating aliases:

probe alias-name {=|+=} probe-name-1 [?] [,probe-name-2 [?] ...] probe-body

Here = is used for creating prologue aliases and += is for epilogue aliases. Question mark ? suffix is optional and used if some functions are not present in kernel –- it allows to choose probe from multiple possibilities.

Warning

Note that this tapset only checks for 64-bit Intel architecture. You will need additional checks for PowerPC, AArch64 and S/390 architectures.

After we created this tapset, it can be used very easy:

  Script file lstat.stp

probe lstat {
    printf("%s %d\n", filename, size);
}

Also, sometimes we have to define constants in dynamic tracing scripts that match corresponding kernel or application constants. You can use enumerations for that in DTrace, or define a constant variable with inline keyword:

inline int TS_RUN = 2;

You may use initializer for global variable to do that in SystemTap:

global TASK_RUNNING = 0;

If you have enabled preprocessor with -C option, you may use #define to create macro as well.

References