Go to main content

Oracle® Solaris 11.4 DTrace (Dynamic Tracing) Guide

Exit Print View

Updated: September 2020

fbt Provider

This section describes the Function Boundary Tracing (FBT) provider, which provides probes associated with the entry to and return from most functions in the Oracle Solaris kernel. A function is the fundamental unit of program text. In a well-designed system, each function performs a discrete and well-defined operation on a specified object or series of like objects. Therefore, even on the smallest Oracle Solaris systems, fbt provides 20,000 probes.

Similar to other DTrace providers, fbt has no probe effect when it is not explicitly enabled. When enabled, fbt only induces a probe effect in probed functions. While the fbt implementation is highly specific to the instruction set architecture, fbt has been implemented on both SPARC and x86 platforms. For each instruction set, there are a small number of functions that do not call other functions and are highly optimized by the compiler, called leaf functions, that cannot be instrumented by fbt. Probes for these functions are not present in DTrace.

Effective use of fbt probes requires knowledge of the operating system implementation. Therefore, it is recommended that you use fbt only when developing kernel software or when other providers are not sufficient. Other DTrace providers, including syscall, sched, proc, and io, can be used to answer most system analysis questions without requiring operating system implementation knowledge.

fbt Probes

fbt provides a probe at the boundary of most functions in the kernel. The boundary of a function is crossed by entering the function and by returning from the function. fbt thus provides two functions for every function in the kernel: one upon entry to the function, and one upon return from the function. These probes are named entry and return, respectively. The function name, and module name are specified as part of the probe. All fbt probes specify a function name and module name.

fbt Probe Arguments

The following probe arguments are supported for the fbt provider.

fbt entry Probes

The arguments to entry probes are the same as the arguments to the corresponding operating system kernel function. These arguments may be accessed in a typed fashion by using the args[] array. These arguments may be accessed as int64_t's by using the arg0 .. arg n variables.

fbt return Probes

While a given function only has a single point of entry, it may have many different points where it returns to its caller. You are usually interested in either the value that a function returned or the fact that the function returned at all rather than the specific return path taken. fbt therefore collects a function's multiple return sites into a single return probe. If you want to know the exact return path, you can examine the return probe args[0] value, which indicates the offset in bytes of the returning instruction in the function text.

If the function has a return value, the return value is stored in args[1]. If a function does not have a return value, args[1] is not defined.

Note -  Using the args[] array works provided that the probe description matches either a single probe or several probes with the same return type. If the probes have different return types, then an error message similar to the following example is displayed:
dtrace: failed to compile script ./gdb.d: line 19: args[ ] may not be 
referenced because probe description fbt:::return matches an unstable set of probes

For probes with different return types, you might need to use the untyped argn instead.

Using the fbt Provider

You can use fbt to easily explore the kernel's implementation. The following example script records the first ioctl from any xclock process and then follows the subsequent code path through the kernel.

 * To make the output more readable, indent every function entry
 * and unindent every function return.  This is done by setting the
 * "flowindent" option.
#pragma D option flowindent

/execname == "xclock" && guard++ == 0/
        self->traceme = 1;
        printf("fd: %d", arg0);


        self->traceme = 0;

Running this script results in output similar to the following example:

# dtrace -s ./xioctl.d 
dtrace: script './xioctl.d' matched 26254 probes
CPU FUNCTION                                 
  0  => ioctl                                 fd: 3
  0    -> ioctl                               
  0      -> getf                              
  0        -> set_active_fd                   
  0        <- set_active_fd                   
  0      <- getf                              
  0      -> fop_ioctl                         
  0        -> sock_ioctl                      
  0          -> strioctl                      
  0            -> job_control_type            
  0            <- job_control_type            
  0            -> strcopyout                  
  0              -> copyout                   
  0              <- copyout                   
  0            <- strcopyout                  
  0          <- strioctl                      
  0        <- sock_ioctl                      
  0      <- fop_ioctl                         
  0      -> releasef                          
  0        -> clear_active_fd                 
  0        <- clear_active_fd                 
  0        -> cv_broadcast                    
  0        <- cv_broadcast                    
  0      <- releasef                          
  0    <- ioctl                               
  0  <= ioctl

The output shows that an xclock process called ioctl on a file descriptor that appears to be associated with a socket.

You can also use fbt when trying to understand kernel drivers. For example, the ssd driver has many code paths by which EIO may be returned. fbt can be easily used to determine the precise code path that resulted in an error condition, as shown in the following example:

/arg1 == EIO/
        printf("%s+%x returned EIO.", probefunc, arg0);

For more information about any one return of EIO, you might want to speculatively trace all fbt probes, and then commitor discard based on the return value of a specific function. For more information about speculative tracing, see Speculative Tracing in DTrace.

Alternatively, you can use fbt to understand the functions called within a specified module. The following example lists all of the functions called in UFS:

# dtrace -n fbt:ufs::entry'{@a[probefunc] = count()}'
dtrace: description 'fbt:ufs::entry' matched 353 probes
  ufs_ioctl                                                         1
  ufs_statvfs                                                       1
  ufs_readlink                                                      1
  ufs_trans_touch                                                   1
  wrip                                                              1
  ufs_dirlook                                                       1
  bmap_write                                                        1
  ufs_fsync                                                         1
  ufs_iget                                                          1
  ufs_trans_push_inode                                              1
  ufs_putpages                                                      1
  ufs_putpage                                                       1
  ufs_syncip                                                        1
  ufs_write                                                         1
  ufs_trans_write_resv                                              1
  ufs_log_amt                                                       1
  ufs_getpage_miss                                                  1
  ufs_trans_syncip                                                  1
  getinoquota                                                       1
  ufs_inode_cache_constructor                                       1
  ufs_alloc_inode                                                   1
  ufs_iget_alloced                                                  1
  ufs_iget_internal                                                 2
  ufs_reset_vnode                                                   2
  ufs_notclean                                                      2
  ufs_iupdat                                                        2
  blkatoff                                                          3
  ufs_close                                                         5
  ufs_open                                                          5
  ufs_access                                                        6
  ufs_map                                                           8
  ufs_seek                                                         11
  ufs_addmap                                                       15
  rdip                                                             15
  ufs_read                                                         15
  ufs_rwunlock                                                     16
  ufs_rwlock                                                       16
  ufs_delmap                                                       18
  ufs_getattr                                                      19
  ufs_getpage_ra                                                   24
  bmap_read                                                        25
  findextent                                                       25
  ufs_lockfs_begin                                                 27
  ufs_lookup                                                       46
  ufs_iaccess                                                      51
  ufs_imark                                                        92
  ufs_lockfs_begin_getpage                                        102
  bmap_has_holes                                                  102
  ufs_getpage                                                     102
  ufs_itimes_nolock                                               107
  ufs_lockfs_end                                                  125
  dirmangled                                                      498
  dirbadname                                                      498

If you know the purpose or arguments of a kernel function, you can use fbt to understand how or why the function is being called. For example, putnext takes a pointer to a queue structure as its first member. The q_qinfo member of the queue structure is a pointer to a qinit structure. The qi_minfo member of the qinit structure has a pointer to a module_info structure, which contains the module name in its mi_idname member. For more information, see putnext(9F), queue(9S), qinit(9S), and module_info(9S) man pages.

The following example puts this information together by using the fbt probe in putnext to track putnext calls by module name:

        @calls[stringof(args[0]->q_qinfo->qi_minfo->mi_idname)] = count();

Running the preceding script results in output similar to the following example:

# dtrace -s ./putnext.d


  pfmod                        1
  timod                        2
  vpnmod                       2
  pts                          40
  conskbd                      42
  kb8042                       42
  tl                           58
  arp                          108
  tcp                          126
  ptm                          249
  ip                           313
  ptem                         340
  vuid2ps2                     361
  ttcompat                     412
  ldterm                       413
  udp                          569
  strwhead                     624
  mouse8042                    726

You can also use fbt to determine the time spent in a particular function. The following example shows how to determine the callers of the DDI delaying routines drv_usecwait and delay. For more information, see drv_usecwait(9F) and delay(9F) man pages.

        self->in = timestamp

        @snoozers[stack()] = quantize(timestamp - self->in);
        self->in = 0;

This example script is particularly interesting to run during boot. For information about procedures performing anonymous tracing during system boot, see Anonymous Tracing in DTrace. Upon reboot, you might see output similar to the following example:

# dtrace -ae


           value  ------------- Distribution ------------- count    
            2048 |                                         0        
            4096 |@@@@@@@@@@@@@@@@@@@@@                    4105     
            8192 |@@@@                                     783      
           16384 |@@@@@@@@@@@@@@                           2793     
           32768 |                                         16       
           65536 |                                         0


           value  ------------- Distribution ------------- count    
          262144 |                                         0        
          524288 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      221      
         1048576 |@@@@                                     29       
         2097152 |                                         0


           value  ------------- Distribution ------------- count    
        33554432 |                                         0        
        67108864 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 3        
       134217728 |                                         0

fbt and Tail-Call Optimization

When one function ends by calling another function, the compiler can engage in tail-call optimization, in which the function being called reuses the caller's stack frame. This procedure is most commonly used in the SPARC architecture, where the compiler reuses the caller's register window in the function being called in order to minimize register window pressure.

The presence of tail-call optimization causes the return probe of the calling function to fire before the entry probe of the called function. This ordering can lead to confusion. For example, if you wanted to record all functions called from a particular function and any functions that this function calls, you might use the following script:

        self->traceme = 1;

        printf("called %s", probefunc);

        self->traceme = 0;

However, if foo ends in an optimized tail-call, the tail-called function, and therefore any functions that it calls, will not be captured. The kernel cannot be dynamically de-optimized, and DTrace does not engage in a lie about how code is structured. Therefore, you must be aware of when tail-call optimization might be used.

Tail-call optimization is likely to be used in source code similar to the following example:

 return (bar());

Tail-call optimization can also be used in a source code similar to the following example:

        (void) bar();

Conversely, function source code that ends like the following example cannot have its call to bar optimized, because the call to bar is not a tail-call:

        return (rval);

    You can determine whether a call has been tail-call optimized using the following techniques:

  • While running DTrace, trace arg0 of the return probe in question. arg0 contains the offset of the returning instruction in the function.

  • After DTrace has stopped, use mdb to look at the function. If the traced offset contains a call to another function instead of an instruction to return from the function, the call has been tail-call optimized.

Due to the instruction set architecture, tail-call optimization is far more common on SPARC systems than on x86 systems. The following example uses mdb to discover tail-call optimization in the kernel's dup function:

# dtrace -q -n fbt::dup:return'{printf("%s+0x%x", probefunc, arg0);}'

While this command is running, run a program that performs a dup(2), such as a bash process. The preceding command should provide output similar to the following example:


Now examine the function with mdb:

# echo "dup::dis" | mdb -k
dup:                            sra       %o0, 0, %o0
dup+4:                          mov       %o7, %g1
dup+8:                          clr       %o2
dup+0xc:                        clr       %o1
dup+0x10:                       call      -0x1278       <fcntl>
dup+0x14:                       mov       %g1, %o7

The output shows that dup+0x10 is a call to the fcntl() function and not a ret instruction. Therefore, the call to fcntl is an example of tail-call optimization.

fbt and Assembly Functions

You might observe functions that seem to enter but never return or vice versa. Such rare functions are generally hand-coded assembly routines that branch to the middle of other hand-coded assembly functions. These functions should not impede analysis: the branched-to function must still return to the caller of the branched-from function. That is, if you enable all FBT probes, you can see the entry to one function and the return from another function at the same stack depth.

fbt and Instruction Set Limitations

Some functions cannot be instrumented by fbt. The exact nature of those functions is specific to the instruction set architecture.

x86 Limitations With fbt

Functions that do not create a stack frame on x86 systems cannot be instrumented by fbt. Because the register set for x86 is extraordinarily small, most functions must put data on the stack and therefore create a stack frame. However, some x86 functions do not create a stack frame and therefore cannot be instrumented.

SPARC Limitations With fbt

FBT cannot instrument leaf routines hand-coded in assembly language on SPARC systems. The majority of the kernel is written in C, and FBT can instrument all functions written in C.

fbt and Breakpoint Interaction

fbt works by dynamically modifying kernel text. Because kernel breakpoints also work by modifying kernel text, if a kernel breakpoint is placed at an entry or return site before loading DTrace, fbt refuses to provide a probe for the function, even if the kernel breakpoint is subsequently removed. If the kernel breakpoint is placed after loading DTrace, both the kernel breakpoint and the DTrace probe will correspond to the same point in text. In this situation, the breakpoint will trigger first, and then the probe will fire when the debugger resumes the kernel. It is recommended that kernel breakpoints not be used concurrently with DTrace. If breakpoints are required, use the DTrace breakpoint action instead.

fbt and Module Loading

The Oracle Solaris kernel can dynamically load and unload kernel modules. When fbt is loaded and a module is dynamically loaded, fbt automatically provides new probes associated with the new module. If a loaded module has no enabled fbt probes, the module may be unloaded; the corresponding probes will be destroyed as the module is unloaded. If a loaded module has enabled fbt probes, the module is considered busy, and cannot be unloaded.

FBT Stability Mechanism

The FBT provider uses stability mechanism of DTrace to describe its stabilities, as shown in the following table. For more information about the stability mechanism, see DTrace Stability Mechanisms.

Table 24  Stability Mechanism for the fbt Provider
Name Stability
Data Stability
Dependency Class

As FBT exposes the kernel implementation, nothing about it is Stable and the Module and Function name and data stability are explicitly Private. The data stability for Provider and Name are Evolving, but all other data stabilities are Private, which are artifacts of the current implementation. The dependency class for FBT is ISA. While FBT is available on all current instruction set architectures, there is no guarantee that FBT will be available on arbitrary future instruction set architectures.