fbt Provider
This chapter describes the Function Boundary Tracing (FBT) provider, which provides probes associated with the entry to and return from most functions in the Solaris kernel. The 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 Solaris systems, FBT will provide on the order of 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 (so-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.
Top
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.
Top
Probe arguments
Top
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 .. argn variables.
Top
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 the exact return path is of interest, 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.
Top
Examples
You can use FBT to easily explore the kernel's implementation. The following example script records the first ioctl(2)
from any xclock process and then follows the subsequent code path through the kernel:
Running this script results in output similar to the following example:
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(7D)
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:
For more information on any one return of EIO, one may wish to speculatively trace all fbt probes, and then commit(or discard) based on the return value of a specific function. See Chapter 13, Speculative Tracing for details on speculative tracing.
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:
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(9F)
takes a pointer to a queue(9S)
structure as its first member. The q_qinfo member of the queue structure is a pointer to a qinit(9S)
structure. The qi_minfo member of the qinit structure has a pointer to a module_info(9S)
structure, which contains the module name in its mi_idname member. The following example puts this information together by using the FBT probe in putnext to track putnext(9F)
calls by module name:
Running the above script results in output similar to the following example:
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(9F)
and delay(9F)
.
This example script is particularly interesting to run during boot. Chapter 36, Anonymous Tracing describes the procedure for performing anonymous tracing during system boot. Upon reboot, you might see output similar to the following example:
Top
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 this optimization causes the return probe of the calling function to fire before the entry probe of the called function. This ordering can lead to quite a bit of 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:
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 deoptimized on the fly, and DTrace does not wish to engage in a lie about how code is structured. Therefore, you should 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:
Or in source code similar to the following example:
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:
You can determine whether a call has been tail-call optimized using the following technique:
- 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(1)
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:
While this command is running, run a program that performs a dup(2)
, such as a bash process. The above command should provide output similar to the following example:
Now examine the function with mdb:
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.
Top
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 should see the entry to one function and the return from another function at the same stack depth.
Top
Instruction Set Limitations
Some functions cannot be instrumented by FBT. The exact nature of uninstrumentable functions is specific to the instruction set architecture.
Top
x86 Limitations
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. Actual numbers vary, but typically fewer than five percent of functions cannot be instrumented on the x86 platform.
Top
SPARC Limitations
Leaf routines hand-coded in assembly language on SPARC systems cannot be instrumented by FBT. The majority of the kernel is written in C, and all functions written in C can be instrumented by FBT. Actual numbers vary, but typically fewer cannot be instrumented on the SPARC platform.
Top
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 will refuse 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.
Top
Module Loading
The Solaris kernel can dynamic 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 unenabled 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.
Top
Stability
The FBT provider uses DTrace's stability mechanism to describe its stabilities, as shown in the following table. For more information about the stability mechanism, see Chapter 39, Stability.
| Element |
Name stability |
Data stability |
Dependency class |
| Provider |
Evolving |
Evolving |
ISA |
| Module |
Private |
Private |
Unknown |
| Function |
Private |
Private |
Unknown |
| Name |
Evolving |
Evolving |
ISA |
| Arguments |
Private |
Private |
ISA |
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: they 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.