Linker and Libraries Guide

Shared Objects as Filters

Shared objects can be defined to act as filters for the interfaces they provide. This technique involves associating filter interfaces with an alternative shared object. The alternative shared object supplies symbol definitions at runtime, and is referred to as a filtee. A filtee is built in the same manner as any shared object is built.

Filtering provides a mechanism of abstracting the compilation environment from the runtime environment. During a link-edit, a symbol reference that binds to a filter interface, is resolved to the filters symbol definition. At runtime, a symbol reference that binds to a filter interface, can be redirected to an alternative shared object. A shared object can define all the interfaces it offers as filters.

Two forms of filtering exist.

Standard filtering

This filtering requires only a symbol table entry for the interface being filtered. At runtime, the implementation of a filter symbol definition must be provided from a filtee.

Interfaces are defined to act as standard filters by using the link-editor's -F flag. This flag is qualified with the name of one or more filtees that must supply the symbol definition at runtime.

If a filtee cannot be processed at runtime, or a filter symbol cannot be located within the filtee, the filtee is skipped for this symbol lookup.

Auxiliary filtering

This filtering provides a similar mechanism to standard filtering, except the filter provides a fall back implementation corresponding to the auxiliary filter interfaces. At runtime, the implementation of the symbol definition can be provided from a filtee.

Interfaces are defined to act as auxiliary filters by using the link-editor's -f flag. This flag is qualified with the name of one or more filtees that may supply the symbol definition at runtime.

If a filtee cannot be processed at runtime, or an auxiliary filter symbol cannot be located within the filtee, the implementation of the symbol within the filter is used.

Generating Standard Filters

To generate a standard filter, you first define a filtee on which the filtering is applied. The following example builds a filtee filtee.so.1, that supplies the symbols foo and bar.


$ cat filtee.c
char * bar = "defined in filtee";

char * foo()
{
        return("defined in filtee");
}
$ cc -o filtee.so.1 -G -K pic filtee.c

To declare all the interfaces offered by a shared object to be filters, the shared object is defined a filter by using the link-editor's -F flag.

In the following example, the shared object filter.so.1, is defined to be a filter. This shared object offers the symbols foo and bar, and indicates it is a filter on the filtee filtee.so.1. In this example, the environment variable LD_OPTIONS is used to circumvent the compiler driver from interpreting the -F option.


$ cat filter.c
char * bar = 0;

char * foo()
{
	return (0);
}
$ LD_OPTIONS='-F filtee.so.1' \
cc -o filter.so.1 -G -K pic -h filter.so.1 -R. filter.c
$ elfdump -d filter.so.1 | egrep "SONAME|FILTER"
    [2]  SONAME           0xee     filter.so.1
    [3]  FILTER           0xfb     filtee.so.1

If the link-editor references the standard filter filtee.so.1, to create a dynamic executable or shared object, it uses information from the symbol table of the filter to satisfy any symbol resolution. However, at runtime, any reference to the symbols of the filter result in the additional loading of the filtee filtee.so.1. The runtime linker uses the filtee to resolve any symbols defined by filter.so.1. If the filtee is not found, or a filter symbol is not found in the filtee, the filter is skipped for this symbol lookup.

For example, the following dynamic executable prog, references the symbols foo and bar, which are resolved during link-edit from the filter filter.so.1. The execution of prog results in foo and bar being obtained from the filtee filtee.so.1, not from the filter filter.so.1.


$ cat main.c
extern char * bar, * foo();

main()
{
        (void) printf("foo is %s: bar is %s\n", foo(), bar);
}
$ cc -o prog main.c -R. filter.so.1
$ prog
foo is defined in filtee: bar is defined in filtee

In these examples, the filtee filtee.so.1 is uniquely associated to the filter, and is not available to satisfy symbol lookup from any other objects that might be loaded as a consequence of executing prog.

Standard filters provide a mechanism for defining a subset interface of an existing shared object, or an interface group spanning a number of existing shared objects. Standard filters also provide a means of redirecting an interface to its implementation. Several standard filters are used in the Solaris operating environment.

The /usr/lib/libsys.so.1 filter provides a subset of the standard C library /usr/lib/libc.so.1. This subset represents the ABI-conforming functions and data items that reside in the C library that must be imported by a conforming application.

The /usr/lib/libxnet.so.1 filter uses multiple filtees. This library provides socket and XTI interfaces from /usr/lib/libsocket.so.1, /usr/lib/libnsl.so.1, and /usr/lib/libc.so.1.

The /usr/lib/libdl.so.1 filter defines the user interface to the runtime linker. This interface provides an abstraction between the symbols referenced in a compilation environment from libdl.so.1 and the actual implementation binding produced within the runtime environment from ld.so.1(1).

Because the code in a standard filter is never referenced at runtime, adding content to any functions defined as filters is redundant. Any filter code might require relocation, which would result in an unnecessary overhead when processing the filter at runtime. Functions are best defined as empty routines, or directly from a mapfile. See Defining Additional Symbols.


Note –

The link-editor uses the ELF class of the first relocatable file that is processed to govern the class of object that is created. Use the link-editor's -64 option to create a 64–bit filter solely from a mapfile.


When generating data symbols within a filter, always initialize the data items. The resulting data definition insures that references are established correctly from dynamic executables.

Some of the more complex symbol resolutions carried out by the link-editor require knowledge of a symbol's attributes, including the symbol's size. Therefore, you should generate the symbols in the filter so that their attributes match those of the symbols in the filtee. This ensures that the link-editing process analyzes the filter in a manner compatible with the symbol definitions used at runtime. See Symbol Resolution.

Generating Auxiliary Filters

To generate an auxiliary filter, you first define a filtee on which the filtering is applied. The following example builds a filtee filtee.so.1, that supplies the symbol foo.


$ cat filtee.c
char * foo()
{
        return("defined in filtee");
}
$ cc -o filtee.so.1 -G -K pic filtee.c

To declare all of the interfaces offered by a shared object to be auxiliary filters, the shared object is defined to be an auxiliary filter using the link-editor's -f flag.

In the following example, the shared object filter.so.1 is defined to be an auxiliary filter. This shared object offers the symbols foo and bar, and indicates it is an auxiliary filter on the filtee filtee.so.1. In this example, the environment variable LD_OPTIONS is used to circumvent the compiler driver from interpreting the -f option.


$ cat filter.c
char * bar = "defined in filter";

char * foo()
{
        return ("defined in filter");
}
$ LD_OPTIONS='-f libbar.so.1' \
cc -o filter.so.1 -G -K pic -h filter.so.1 -R. filter.c
$ elfdump -d filter.so.1 | egrep "SONAME|AUXILIARY"
    [2]  SONAME           0xee     filter.so.1
    [3]  AUXILIARY        0xfb     filtee.so.1

If the link-editor references the auxiliary filter filter.so.1, to create a dynamic executable or shared object, it uses information from the symbol table of the filter to satisfy any symbol resolution. However, at runtime, any reference to the symbols of the filter result in a search for the filtee filtee.so.1. If this filtee is found, the runtime linker uses the filtee to resolve any symbols defined by filter.so.1. If the filtee is not found, or a symbol from the filter is not found in the filtee, then the original symbol within the filter is used.

For example, the following dynamic executable prog, references the symbols foo and bar, which are resolved during link-edit from the filter filter.so.1. The execution of prog results in foo being obtained from the filtee filtee.so.1, not from the filter filter.so.1. However, bar is obtained from the filter filter.so.1, as this symbol has no alternative definition in the filtee filtee.so.1.


$ cat main.c
extern char * bar, * foo();

main()
{
        (void) printf("foo is %s: bar is %s\n", foo(), bar);
}
$ cc -o prog main.c -R. filter.so.1
$ prog
foo is defined in filtee: bar is defined in filter

In these examples, the filtee filtee.so.1 is uniquely associated to the filter, and is not available to satisfy symbol lookup from any other objects that might be loaded as a consequence of executing prog.

Auxiliary filters provide a mechanism for defining an alternative interface of an existing shared object. This mechanism is used in the Solaris operating environment to provide optimized functionality within platform specific shared objects. See Instruction Set Specific Shared Objects, and System Specific Shared Objects for examples.


Note –

The environment variable LD_NOAUXFLTR can be set to disable the runtime linkers auxiliary filter processing. Because auxiliary filters are frequently employed to provide platform specific optimizations, this option can be useful in evaluating filtee use and their performance impact.


Filtee Processing

The runtime linker's processing of a filter defers the loading of a filtee until a filter symbol is referenced. This implementation is analogous to the filter performing a dlopen(3DL), using mode RTLD_LOCAL, on each of its filtees as they are required. This implementation accounts for differences in dependency reporting that can be produced by tools such as ldd(1).

The link-editor's -z loadfltr option can be used when creating a filter to cause the immediate processing of its filtees at runtime. In addition, the immediate processing of any filtees within a process can be triggered by setting the LD_LOADFLTR environment variable to any value.