Go to main content

Oracle® Solaris 11.4 Linkers and Libraries Guide

Exit Print View

Updated: March 2019
 
 

Shared Objects as Filters

Shared objects can be defined to act as filters. This technique involves associating the interfaces that the filter provides with an alternative shared object. At runtime, the alternative shared object supplies one or more of the interfaces provided by the filter. This alternative shared object 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. At link-edit time, 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.

Individual interfaces that are defined within a shared object can be defined as filters by using the mapfile FILTER or AUXILIARY attributes to the SYMBOL_SCOPE and SYMBOL_VERSION directives. Alternatively, a shared object can define all of the interfaces the shared object offers as filters by using the link-editor mapfile FILTER directive or the –F or –f command line options. These techniques are typically used individually, but can also be combined within the same shared object.

Three 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 mapfile FILTER directive, or FILTER per-symbol attribute, or by using the link-editor's –F option. The mapfile keyword or command line option, is qualified with the name of one or more filtees that must supply the symbol definition at runtime.

A filtee that cannot be processed at runtime is skipped. A standard filter symbol that cannot be located within the filtee, also causes the filtee to be skipped. In both of these cases, the symbol definition provided by the filter is not used to satisfy this symbol lookup.

Weak filtering

Weak filtering is a variation of standard filtering. At runtime, weak filters are processed the same as standard filters. Weak filters differ from standard filters in how the link-editor processes weak filter dependencies when unused dependency processing is enabled with the –z discard-unused=dependencies option.

Typically, the link-editor resolves external symbols from libraries to the first library on the command line that offers a symbol definition. In the case of weak filters, such symbols are ignored if the filtee providing the same symbol is also present on the command line. Resolving such symbols directly to the filtee can allow unused dependency processing to eliminate the filter as a dependency, resulting in a simpler and more efficient object.

Interfaces are defined to act as weak filters by using the link-editor's mapfile FILTER directive, or FILTER per-symbol attribute. This mapfile keyword is qualified with the name of one or more filtees that must supply the symbol definition at runtime.

Auxiliary filtering

This filtering provides a similar mechanism to standard filtering, except the filter provides a fallback 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 mapfile FILTER directive, or with the FILTER or AUXILIARY per-symbol attribute, or by using the link-editor's –f option. This mapfile keyword or option, is qualified with the name of one or more filtees that can supply the symbol definition at runtime.

A filtee that cannot be processed at runtime is skipped. An auxiliary filter symbol that cannot be located within the filtee, also causes the filtee to be skipped. In both of these cases, the symbol definition provided by the filter is used to satisfy this symbol lookup.

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, suppling 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

Standard filters can be defined at the object level, or for individual symbols. To declare all of the interfaces offered by a shared object to be filters, use the link-editor's mapfile FILTER directive, or –F command line option. To declare individual interfaces of a shared object to be filters, use a link-editor mapfile and the FILTER symbol attribute.

In the following example, the shared object filter.so.1 is defined to be a filter. filter.so.1 offers the symbols foo and bar, and 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
#include <stdio.h>

char *bar = NULL;

char *foo()
{
        return (NULL);
}
$ 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

A mapfile can be used instead of the –F command line option.

$ cat mapfile
$mapfile_version 2
FILTER {
        FILTEE = filtee.so.1;
        TYPE = STANDARD;
};
$ cc -o filter.so.1 -G -K pic -h filter.so.1 -M mapfile -R. filter.c
$ elfdump -d filter.so.1 | egrep "SONAME|FILTER"
     [2]  SONAME           0xee     filter.so.1
     [3]  FILTER           0xfb     filtee.so.1

The link-editor can reference the standard filter filter.so.1 as a dependency when creating a dynamic object. The link-editor 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();

void 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 the following example, the shared object filter.so.2 defines one of its interfaces, foo, to be a filter on the filtee filtee.so.1.


Note -  As no source code is supplied for foo(), the mapfile FUNCTION symbol attribute is used to ensure a symbol table entry for foo is created.
$ cat filter.c
char *bar = "defined in filter";
$ cat mapfile
$mapfile_version 2
SYMBOL_SCOPE {
        global:
                foo     { TYPE=FUNCTION; FILTER=filtee.so.1 };
};
$ cc -o filter.so.2 -G -K pic -h filter.so.2 -M mapfile -R. filter.c
$ elfdump -d filter.so.2 | egrep "SONAME|FILTER"
     [2]  SONAME           0xd8     filter.so.2
     [3]  SUNW_FILTER      0xfb     filtee.so.1
$ elfdump -y filter.so.2 | egrep "foo|bar"
     [1]  [ FILTER ]    [3] filtee.so.1      foo
    [10]  [ DEPEND ]        <self>           bar

At runtime, any reference to the symbol foo of the filter, results in the additional loading of the filtee filtee.so.1. The runtime linker uses the filtee to resolve only the symbol foo defined by filter.so.2. Reference to the symbol bar always uses the symbol from filter.so.2, as no filtee processing is defined for this symbol.

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

$ cc -o prog main.c -R. filter.so.2
$ 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. The filtee 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 convenient mechanism for defining a subset interface of an existing shared object. Standard filters provide for the creation of 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 Oracle Solaris OS.

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

libc.so.1 defines interface filters to the runtime linker. These interfaces provide an abstraction between the symbols referenced in a compilation environment from libc.so.1, and the actual implementation binding produced within the runtime environment to ld.so.1(1).

libnsl.so.1 defines the standard filter gethostname(3C) against libc.so.1. Historically, both libnsl.so.1 and libc.so.1 have provided the same implementation for this symbol. By establishing libnsl.so.1 as a filter, only one implementation of gethostname() need exist. As libnsl.so.1 continues to export gethostname(), the interface of this library continues to remain compatible with previous releases.

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 SYMBOL_SCOPE and SYMBOL_VERSION Directives.

When generating data symbols within a filter, always associate the data with a section. This association can be produced by defining the symbol within a relocatable object file. This association can also be produced by defining the symbol within a mapfile together with a size declaration and no value declaration. See SYMBOL_SCOPE and SYMBOL_VERSION Directives. The resulting data definition ensures that references from a dynamic executable are established correctly.

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 the attributes of the symbols in the filtee. Maintaining attribute consistency ensures that the link-editing process analyzes the filter in a manner that is compatible with the symbol definitions used at runtime. See Symbol Resolution.


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.

Generating Weak Filters

Standard filters provide a simple and effective solution to the problem of maintaining runtime compatibility while allowing the underlying system to evolve. However, filters also impose ongoing overhead. Although such a filter provides no functionality, it must be loaded and processed by the system every time the program runs. It is generally recommended that programs discontinue linking against these filters, and leave them for the benefit of old code that hasn't yet been rebuilt.

Eliminating unnecessary filters when building an object is the best solution when feasible. However, there are cases where this can be difficult to achieve. In particular, many open source packages have complicated configuration systems that are intended to work across a span of operating system versions. On some versions, a library may deliver necessary content, while on newer ones, they are mere filters. Rather then grapple with these complexities, the configuration simply links against everything. On Oracle Solaris, this frequently happens with libraries such as libpthread.

Weak filters, in conjunction with the –z discard-unused=dependencies command line option, offer an automated solution to this problem. Typically, the link-editor resolves external symbols from libraries by taking the symbol from the first library on the command line that provides a symbol definition. In the case of weak filters, such symbols are ignored if the filtee providing the same filter is also present on the command line. Resolving such symbols directly to the filtee allows unused dependency processing to eliminate the filter. This results in a simpler and more efficient object, without requiring large changes to the upstream code base.

The following example uses a filter object named libprint, which provides the standard printf() from libc.

$ cat mapfile-libprint-std
$mapfile_version 2
FILTER {
        FILTEE = "libc.so.1";
        TYPE = STANDARD;
};
SYMBOL_SCOPE {
    global:
        printf { TYPE = FUNCTION };
};
$ ld -o libprint.so.1 -G -h libprint.so.1 -Mmapfile-libprint-std
$ elfdump libprint.so.1 | egrep 'SONAME|FILTER'
      [0]  SONAME          0x1     libprint.so.1
      [1]  FILTER          0x4c    libc.so.1

An application built against libprint, requires that this filter be loaded at runtime even though the filtee libc contains the printf() function used at runtime.

$ cc hello.c -o hello -L. -R. libprint.so.1
$ elfdump -d hello | grep NEEDED
      [0]  NEEDED          0x13b      libprint.so.1
      [1]  NEEDED          0x125      libc.so.1
$ ./hello
hello, world

The libprint filter is rebuilt as a weak filter. The hello world program links to libprint as before, but with unused dependency processing enabled. The resulting program resolves printf() directly from libc. The link-editor is therefore able to discard the unused libprint dependency.

$ cat mapfile-libprint-weak
$mapfile_version 2
FILTER {
        FILTEE = "libc.so.1";
        TYPE = WEAK;
};
SYMBOL_SCOPE {
    global:
        printf { TYPE = FUNCTION };
};
$ ld -o libprint.so.1 -G -h libprint.so.1 -Mmapfile-libprint-weak
$ elfdump libprint.so.1 | egrep 'SONAME|FILTER'
      [0]  SONAME          0x1         libprint.so.1
      [1]  FILTER          0x4c        libc.so.1
     [14]  FLAGS_1         0x20000000  [ WEAKFILTER ]
$ cc hello.c -o hello -L. -R. libprint.so.1 -z discard-unused=dependencies
$ elfdump -d hello | grep NEEDED
      [0]  NEEDED          0x125      libc.so.1
$ ./hello
hello, world

In current versions of Oracle Solaris, libraries such as libpthread are built as weak filters instead of standard filters in order to enable the automatic removal of unnecessary filter dependencies when unused dependency processing is enabled with the –z discard-unused=dependencies option.


Note -  When the –z discard-unused=dependencies option is enabled, and the symbol resolution process determines that a weak filter symbol from a dependency is needed, the filtee for that symbol is added to the end of the link line as an additional dependency. This allows the link-editor to find the symbol from the filtee, and to eliminate the weak filter. Therefore, the runpath for a weak filter must be sufficient to allow the link-editor to find the filtee.

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, supplying the symbol foo.

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

Auxiliary filtering can be defined at the object level, or for individual symbols. To declare all of the interfaces offered by a shared object to be auxiliary filters, use the link-editor mapfile FILTER directive, or –f command line option. To declare individual interfaces of a shared object to be auxiliary filters, use a link-editor mapfile FILTER directive or –f command line option. To declare individual interfaces of a shared object to be auxiliary filters, use a link-editor mapfile and the AUXILIARY or FILTER symbol attribute.

In the following example, the shared object filter.so.1 is defined to be an auxiliary filter. filter.so.1 offers the symbols foo and bar, and 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 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|AUXILIARY"
     [2]  SONAME           0xee     filter.so.1
     [3]  AUXILIARY        0xfb     filtee.so.1

A mapfile can be used instead of the –f command line option.

$ cat mapfile
$mapfile_version 2
FILTER {
        FILTEE = filtee.so.1;
        TYPE = AUXILIARY;
};
$ cc -o filter.so.1 -G -K pic -h filter.so.1 -M mapfile -R. filter.c
$ elfdump -d filter.so.1 | egrep "SONAME|AUXILIARY"
     [2]  SONAME           0xee     filter.so.1
     [3]  AUXILIARY        0xfb     filtee.so.1

The link-editor can reference the auxiliary filter filter.so.1 as a dependency when creating a dynamic object. The link-editor 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();

void 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 the following example, the shared object filter.so.2 defines the interface foo, to be an auxiliary filter on the filtee filtee.so.1.

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

char *foo()
{
        return ("defined in filter");
}
$ cat mapfile
$mapfile_version 2
SYMBOL_SCOPE {
        global:
                foo     { AUXILIARY=filtee.so.1 };
};
$ cc -o filter.so.2 -G -K pic -h filter.so.2 -M mapfile -R. filter.c
$ elfdump -d filter.so.2 | egrep "SONAME|AUXILIARY"
     [2]  SONAME           0xd8     filter.so.2
     [3]  SUNW_AUXILIARY   0xfb     filtee.so.1
$ elfdump -y filter.so.2 | egrep "foo|bar"
     [1]  [ AUXILIARY ]    [3] filtee.so.1      foo
    [10]  [ DEPEND]            <self>           bar

At runtime, any reference to the symbol foo of the filter, results in a search for the filtee filtee.so.1. If the filtee is found, the filtee is loaded. The filtee is then used to resolve the symbol foo defined by filter.so.2. If the filtee is not found, symbol foo defined by filter.so.2 is used. Reference to the symbol bar always uses the symbol from filter.so.2, as no filtee processing is defined for this symbol.

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

$ cc -o prog main.c -R. filter.so.2
$ prog
foo is defined in filtee: bar is defined in filter

If the filtee filtee.so.1 does not exist, the execution of prog results in foo and bar being obtained from the filter filter.so.2.

$ prog
foo is defined in filter: bar is defined in filter

In these examples, the filtee filtee.so.1 is uniquely associated to the filter. The filtee 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 Oracle Solaris OS to provide optimized functionality within hardware capability, and platform specific shared objects. See Capability 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.

Filtering Combinations

Individual interfaces that define standard filters, together with individual interfaces that define auxiliary filters, can be defined within the same shared object. This combination of filter definitions is achieved by using the mapfile keywords FILTER and AUXILIARY to assign the required filtees.

A shared object that defines all of its interfaces to be filters by using the –F, or –f option, or the mapfile FILTER directive, is either a standard weak, or auxiliary filter.

A shared object can define individual interfaces to act as filters, together with defining all the interfaces of the object to act as a filters. In this case, the individual filtering defined for an interface is processed first. When a filtee for an individual interface filter can not be established, the filtee defined for all he interfaces of the filter provides a fallback if appropriate.

For example, consider the filter filter.so.1. This filter defines that all interfaces act as auxiliary filters against the filtee filtee.so.1 using the link-editor's –f option. filter.so.1 also defines the individual interface foo to be a standard filter against the filtee foo.so.1 using the mapfile FILTER symbol attribute. filter.so.1 also defines the individual interface bar to be an auxiliary filter against the filtee bar.so.1 using the mapfile AUXILIARY symbol attribute.

An external reference to foo results in processing the filtee foo.so.1. If foo can not be found from foo.so.1, then no further processing of the filter is carried out. In this case, no fallback processing is performed because foo is defined to be a standard filter.

An external reference to bar results in processing the filtee bar.so.1. If bar can not be found from bar.so.1, then processing falls back to the filtee filtee.so.1. In this case, fallback processing is performed because bar is defined to be an auxiliary filter. If bar can not be found from filtee.so.1, then the definition of bar within the filter filter.so.1 is finally used to resolve the external reference.

Filtee Processing

The runtime linker processing of a filter defers loading a filtee until a filter symbol is referenced. This implementation is analogous to the filter performing a dlopen(3C), using mode RTLD_LOCAL, on each of its filtees as the filtee is 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 all filtees within a process, can be triggered by setting the LD_LOADFLTR environment variable to any value.