Go to main content

Oracle® Solaris 11.4 Linkers and Libraries Guide

Exit Print View

Updated: March 2019
 
 

Creating a Family of Symbol Capabilities Functions

Developers often desire to provide multiple instances of functions, each optimized for a particular set of capabilities, within a single object. It is desirable for the selection and use of these instances to be transparent to any consumers. A generic, front-end function can be created to provide an external interface. This generic instance, together with the optimized instances, can be combined into one object. The generic instance might use getisax(2) to determine the systems capabilities and then call the appropriate optimized function instance to handle a task. Although this model works, it suffers from a lack of generality, and incurs a runtime overhead.

Symbol capabilities offer an alternative mechanism to construct such an object. This mechanism is simpler, more efficient, and does not require you to write additional front-end code. Multiple instances of a function can be created and associated with different capabilities. These instances, together with a default instance of the function, can be combined into a single dynamic object. The selection of the most appropriate member from this family of symbols is carried out by the runtime linker using the symbol capabilities information.

In the following example, the x86 objects foobar.mmx.o and foobar.sse.o, contain the same function foo() and bar(), that have been compiled to use the MMX and SSE instructions respectively.

$ elfdump -H foobar.mmx.o

Capabilities Section:  .SUNW_cap

 Symbol Capabilities:
    index  tag               value
      [1]  CA_SUNW_ID        mmx
      [2]  CA_SUNW_HW_1      0x40  [ MMX ]

  Symbols:
     index    value      size  type bind oth ver shndx    name
      [10]        0      0x21  FUNC LOCL  D    0 .text    foo%mmx
      [16]     0x24      0x1e  FUNC LOCL  D    0 .text    bar%mmx

$ elfdump -H foobar.sse.o

Capabilities Section:  .SUNW_cap

 Symbol Capabilities:
    index  tag               value
      [1]  CA_SUNW_ID        sse
      [2]  CA_SUNW_HW_1      0x800  [ SSE ]

  Capabilities symbols:
     index    value      size  type bind oth ver shndx    name
      [16]        0      0x2f  FUNC LOCL  D    0 .text    foo%sse
      [18]     0x48      0x30  FUNC LOCL  D    0 .text    bar%sse

Each of these objects contain a local symbol identifying the capabilities function foo%*() and bar%*(). In addition, each object also defines a global reference to the function foo() and bar(). Any internal references to foo() or bar() are relocated through these global references, as are any external references.

These two objects can now be combined with a default instance of foo() and bar(). These default instances satisfy the global references, and provide an implementation that is compatible with any object capabilities. These default instances are said to lead each capabilities family. If no object capabilities exist, this default instance should also require no capabilities. Effectively, three instances of foo() and bar() exist, the global instance provides the default, and the local instances provide implementations that are used at runtime if the associated capabilities are available.

$ cc -o libfoobar.so.1 -G foobar.o foobar.sse.o foobar.mmx.o
$ elfdump -sN.dynsym libfoobar.so.1 | egrep "foo|bar"
     [2]     0x700    0x21  FUNC LOCL  D    0 .text    foo%mmx
     [4]     0x750    0x2f  FUNC LOCL  D    0 .text    foo%sse
     [8]     0x784    0x1e  FUNC LOCL  D    0 .text    bar%mmx
     [9]     0x7b0    0x30  FUNC LOCL  D    0 .text    bar%sse
    [15]     0x7a0    0x14  FUNC GLOB  D    1 .text    foo
    [17]     0x7c0    0x14  FUNC GLOB  D    1 .text    bar

The capabilities information for a dynamic object displays the capabilities symbols, and reveals the capabilities families that are available.

$ elfdump -H libfoobar.so.1

Capabilities Section:  .SUNW_cap

 Symbol Capabilities:
   index  tag               value
     [1]  CA_SUNW_ID        mmx
     [2]  CA_SUNW_HW_1      0x40  [ MMX ]

  Symbols:
   index    value     size  type bind oth ver shndx    name
     [2]    0x700     0x21  FUNC LOCL  D    0 .text    foo%mmx
     [8]    0x784     0x1e  FUNC LOCL  D    0 .text    bar%mmx

 Symbol Capabilities:
   index  tag               value
     [4]  CA_SUNW_ID        sse
     [5]  CA_SUNW_HW_1      0x800  [ SSE ]

  Symbols:
   index    value     size  type bind oth ver shndx    name
     [4]    0x750     0x2f  FUNC LOCL  D    0 .text    foo%sse
     [9]    0x7b0     0x30  FUNC LOCL  D    0 .text    bar%sse

Capabilities Chain Section:  .SUNW_capchain

 Capabilities family: foo
  chainndx  symndx      name
         1  [15]        foo
         2  [2]         foo%mmx
         3  [4]         foo%sse

 Capabilities family: bar
  chainndx  symndx      name
         5  [17]        bar
         6  [8]         bar%mmx
         7  [9]         bar%sse

At runtime, all references to foo() and bar() are initially bound to the global symbols. However, the runtime linker recognizes that these functions are the lead instance of a capabilities family. The runtime linker inspects each family member to determine if a better capability function is available. There is a one time cost to this operation, which occurs on the first call to the function. Subsequent calls to foo() and bar() are bound directly to the function instance selected by the first call. This function selection can be observed by using the runtime linkers debugging capabilities.

In the following example, the underlying system does not provide MMX or SSE support. The lead instance of foo() requires no special capabilities support, and thus satisfies any relocation reference.

$ LD_DEBUG=symbols  main
....
debug: symbol=foo;  lookup in file=./libfoo.so.1  [ ELF ]
debug:   symbol=foo[15]:  capability family default
debug:   symbol=foo%mmx[2]:  capability specific (CA_SUNW_HW_1):  [ 0x40  [ MMX ] ]
debug:   symbol=foo%mmx[2]:  capability rejected
debug:   symbol=foo%sse[4]:  capability specific (CA_SUNW_HW_1):  [ 0x800  [ SSE ] ]
debug:   symbol=foo%sse[4]:  capability rejected
debug:   symbol=foo[15]:  used

In the following example, MMX is available, but SSE is not. The MMX capable instance of foo() satisfies any relocation reference.

$ LD_DEBUG=symbols  main
....
debug: symbol=foo;  lookup in file=./libfoo.so.1  [ ELF ]
debug:   symbol=foo[15]:  capability family default
debug:   symbol=foo%mmx[2]:  capability specific (CA_SUNW_HW_1):  [ 0x40  [ MMX ] ]
debug:   symbol=foo%mmx[2]:  capability candidate
debug:   symbol=foo%sse[4]:  capability specific (CA_SUNW_HW_1):  [ 0x800  [ SSE ] ]
debug:   symbol=foo%sse[4]:  capability rejected
debug:   symbol=foo[2]:  used

A family of capabilities function instances must be accessed from a procedure linkage table entry. See Procedure Linkage Table (Processor-Specific). This procedure linkage reference requires the runtime linker to resolve the function. During this process, the runtime linker can process the associated symbol capabilities information, and select the best function from the available family of function instances.

When symbol capabilities are not used, there are cases where the link-editor can resolve references to code without the need of a procedure linkage table entry. For example, within an executable, a reference to a function that exists within the executable can be bound internally at link-edit time. Hidden and protected functions within shared objects can also be bound internally at link-edit time. In these cases, there is normally no need for the runtime linker to be involved in resolving a reference to these functions.

However, when symbol capabilities are used, the function must be resolved from a procedure linkage table entry. This entry is necessary in order for the runtime linker to be involved in selecting the appropriate function, while maintaining a read-only text segment. This mechanism results in an indirection through a procedure linkage table entry for all calls to a capability function. This indirection might not be necessary if symbol capabilities are not used. Therefore, there is a small trade off between the cost of calling the capability function, and any performance improvement gained from using the capability function over its default counterpart.


Note -  Although a capability function must be accessed through a procedure linkage table entry, the function can still be defined as hidden or protected. The runtime linker honors these visibility states and restricts any binding to these functions. This behavior results in the same bindings as are produced when symbol capabilities are not associated with the function. A hidden function can not be bound to from an external object. A reference to a protected function from within an object will only be bound to within the same object.