14.1 Inserting Static Probe Points

You can embed static probes within the source code for which you want to capture the current state of a module and its data.

The following example pseudo character device driver consists of three source files:

revdev.h

Is the header file for the module.

rev_mod.c

Defines the module's properties and its init and exit routines.

rev_dev.c

Defines the driver's open, read, release, unlocked_ioctl, and write routines. The static probes are inserted in the read, unlocked_ioctl, and write routines, although probes could also be inserted in the other routines, if required.

revdev.h Example

The module header file revdev.h must be prepared, as indicated in bold font in the following example, by adding lines to include linux/sdt.h and to define probe macros.

#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/types.h>
#include <linux/sdt.h>

#define DEVICE "revdev"

#define REVDEV_IOCTL_ENTRY_PROBE(name, file, cmd, arg) \
         DTRACE_PROBE3(ioctl__##name, struct file *, file, \
                       unsigned int, cmd, unsigned long, arg)
#define REVDEV_IOCTL_RETURN_PROBE(name, str) \
        DTRACE_PROBE1(ioctl__##name, struct char *, str)
#define REVDEV_READ_ENTRY_PROBE(name,fp,buf) \
        DTRACE_PROBE2(read__##name, file *, fp, char *, buf)
#define REVDEV_READ_RETURN_PROBE(name,buf,n) \
        DTRACE_PROBE2(read__##name, char *, buf, size_t, n)
#define REVDEV_WRITE_ENTRY_PROBE(name,fp,buf,n) \
        DTRACE_PROBE3(write__##name, file *, fp, char *, buf, size_t, n)
#define REVDEV_WRITE_RETURN_PROBE(name,buf,n) \
        DTRACE_PROBE2(write__##name, char *, buf, size_t, n)

The DTRACE_PROBE macros that are defined in /lib/modules/`uname -r`/build/include/linux/sdt.h support from zero to eight arguments.

You can define your own macros for the inserted probes, as shown in the preceding example. Unlike user-space static probes, you cannot use the dtrace -h command to create a header file that includes suitable probe definitions. You do not need to create a provider definition file for the probes.

The probes are named according to the first argument of the DTRACE_PROBE macro. The suffix N in the macro name DTRACE_PROBEN refers the number of arguments that are passed to the probe. The first argument to the probe macro is the probe name. As described in Section 11.5.1.1, “Declaring Probes”, two consecutive underscores are converted to a single dash. The remaining macro arguments are pairs of arguments that define the DTrace argn variables that are assigned when the probe fires. Each pair of arguments defines the variable type and a variable name, for example:

#define REVDEV_WRITE_ENTRY_PROBE(name, fp, buf, n) \
        DTRACE_PROBE3(write__##name, file *, fp, char *, buf, size_t, n)

The values of fp, buf, and n are made available by the arg0, arg1, and arg2 variables in DTrace when the probe fires.

The provider, module, and function elements of the complete probe are named for sdt, the driver module name (without the .ko), and the driver routine.

The probes inherit the stability attributes of the sdt provider.

rev_mod.c Example

No changes are made in the following example, which does not insert any probes in the module's init and exit routines. Note that there is no restriction on inserting probes in these routines.

#include "revdev.h"

MODULE_AUTHOR("DTrace Example");
MODULE_DESCRIPTION("Using DTrace SDT probes with a device driver");
MODULE_VERSION("v1.0");
MODULE_LICENSE("GPL");

extern const struct file_operations revdev_fops;

static struct miscdevice revdev = {
    .minor = 0,
    .name = DEVICE,
    .fops = &revdev_fops,
};

DEFINE_MUTEX(revdev_mutex);

static int revdev_entry(void){ /* Register device */
    int retval;
    retval = misc_register(&revdev);
    if (retval < 0) {
        printk(KERN_ERR "revdev: Could not register device");
        return retval;
    }
    mutex_init(&revdev_mutex);
    return 0;
}

static void revdev_exit(void){
    misc_deregister(&revdev);
}

/* Define module init and exit calls */
module_init(revdev_entry);
module_exit(revdev_exit);

rev_dev.c Example

No existing lines of code are modified in this example. Only line insertions are required for the entry and return probes in each of the read, unlocked_ioctl, and write routines.

The changes in this example appear in bold font.

#include "revdev.h"

static struct device_buffer {
    char data[80];
} devbuf;

static char *oddeven[] = { "Even", "Odd" };

extern struct mutex revdev_mutex;

static long revdev_ioctl(struct file *file, unsigned int cmd,
                         unsigned long arg) {
    char *cp;
    REVDEV_IOCTL_ENTRY_PROBE(entry, file, cmd, arg);
    cp = oddeven[arg%2];
    REVDEV_IOCTL_RETURN_PROBE(return, cp);
    return -EAGAIN;
}

static int revdev_open(struct inode *inode, struct file *fp){
    if (!mutex_trylock(&revdev_mutex)){
        printk(KERN_INFO "revdev: Device already in use");
        return -EBUSY;
    }
    return 0;
}

static void revstr(char *s) { /* After Kernighan and Ritchie */
    int i, j, t;
    for (i = 0, j = strlen(s)-1; i < j; i++, j--)
        t = s[i], s[i] = s[j], s[j] = t;
}

static ssize_t revdev_read(struct file *fp, char* buf, size_t n, loff_t *o){
    int retval;
    REVDEV_READ_ENTRY_PROBE(entry, fp, devbuf.data);
    revstr(devbuf.data);
    n = strlen(devbuf.data);
    retval = copy_to_user(buf, devbuf.data, n);
    REVDEV_READ_RETURN_PROBE(return, buf, n);
    if (retval != 0) return -EINVAL;
    return 0;
}

static ssize_t revdev_write(struct file *fp, const char* buf, size_t n, loff_t *o){
    int retval;
    REVDEV_WRITE_ENTRY_PROBE(entry, fp, buf, n);
    retval = copy_from_user(devbuf.data, buf, n);
    devbuf.data[n-retval] = '\0';
    REVDEV_WRITE_RETURN_PROBE(return, devbuf.data, n);
    if (retval != 0) return -EINVAL;
    return 0;
}

static int revdev_close(struct inode *inode, struct file *fp){
    mutex_unlock(&revdev_mutex);
    return 0;
}

const struct file_operations revdev_fops = {
    .owner = THIS_MODULE,
    .read = revdev_read,
    .write = revdev_write,
    .unlocked_ioctl = revdev_ioctl,
    .open = revdev_open,
    .release = revdev_close,
};