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
andexit
routines.-
rev_dev.c
Defines the driver's
open
,read
,release
,unlocked_ioctl
, andwrite
routines. The static probes are inserted in theread
,unlocked_ioctl
, andwrite
routines, although probes could also be inserted in the other routines, if required.
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_PROBE
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
N
arg
variables
that are assigned when the probe fires. Each pair of arguments
defines the variable type and a variable name, for example:
n
#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.
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);
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, };