Parent Objects

Support for Parent Objects was added in Solaris 11 Update 1. The following material is adapted from the PSARC arc case, and the Solaris Linker and Libraries Manual.

A "plugin" is a shared object, usually loaded via dlopen(), that is used by a program in order to allow the end user to add functionality to the program. Examples of plugins include those used by web browsers (flash, acrobat, etc), as well as mdb and elfedit modules. The object that loads the plugin at runtime is called the "parent object". Unlike most object dependencies, the parent is not identified by name, but by its status as the object doing the load.

Historically, building a good plugin is has been more complicated than it should be:

  • A parent and its plugin usually share a 2-way dependency: The plugin provides one or more routines for the parent to call, and the parent supplies support routines for use by the plugin for things like memory allocation and error reporting.

  • It is a best practice to build all objects, including plugins, with the -z defs option, in order to ensure that the object specifies all of its dependencies, and is self contained. However:

    • The parent is usually an executable, which cannot be linked to via the usual library mechanisms provided by the link editor.

    • Even if the parent is a shared object, which could be a normal library dependency to the plugin, it may be desirable to build plugins that can be used by more than one parent, in which case embedding a dependency NEEDED entry for one of the parents is undesirable.
The usual way to build a high quality plugin with -z defs uses a special mapfile provided by the parent. This mapfile defines the parent routines, specifying the PARENT attribute (see example below). This works, but is inconvenient, and error prone. The symbol table in the parent already describes what it makes available to plugins — ideally the plugin would obtain that information directly rather than from a separate mapfile.

The new -z parent option to ld allows a plugin to link to the parent and access the parent symbol table. This differs from a typical dependency:

  • No NEEDED record is created.

  • The relationship is recorded as a logical connection to the parent, rather than as an explicit object name
However, it operates in the same manner as any other dependency in terms of making symbols available to the plugin.

When the -z parent option is used, the link-editor records the basename of the parent object in the dynamic section, using the new tag DT_SUNW_PARENT. This is an informational tag, which is not used by the runtime linker to locate the parent, but which is available for diagnostic purposes.

The ld(1) manpage documentation for the -z parent option is:

-z parent=object
Specifies a "parent object", which can be an executable or shared object, against which to link the output object. This option is typically used when creating "plugin" shared objects intended to be loaded by an executable at runtime via the dlopen() function. The symbol table from the parent object is used to satisfy references from the plugin object. The use of the -z parent option makes symbols from the object calling dlopen() available to the plugin.

Example

For this example, we use a main program, and a plugin. The parent provides a function named parent_callback() for the plugin to call. The plugin provides a function named plugin_func() to the parent:
% cat main.c
#include <stdio.h>
#include <dlfcn.h>
#include <link.h>

void
parent_callback(void)
{
        printf("plugin_func() has called parent_callback()\n");
}

int
main(int argc, char **argv)
{
        typedef void plugin_func_t(void);

        void            *hdl;
        plugin_func_t   *plugin_func;

        if (argc != 2) {
                fprintf(stderr, "usage: main plugin\n");
                return (1);
        }

        if ((hdl = dlopen(argv[1], RTLD_LAZY)) == NULL) {
                fprintf(stderr, "unable to load plugin: %s\n", dlerror());
                return (1);
        }

        plugin_func = (plugin_func_t *) dlsym(hdl, "plugin_func");
        if (plugin_func == NULL) {
                fprintf(stderr, "unable to find plugin_func: %s\n",
	             dlerror());
                return (1);
        }

        (*plugin_func)();

        return (0);
}

% cat plugin.c
#include <stdio.h>
extern  void    parent_callback(void);

void
plugin_func(void)
{
        printf("parent has called plugin_func() from plugin.so\n");
        parent_callback();
}
Building this in the traditional manner, without -zdefs:
% cc -o main main.c
% cc -G -o plugin.so plugin.c
% ./main ./plugin.so
parent has called plugin_func() from plugin.so
plugin_func() has called parent_callback()
As noted above, when building any shared object, the -z defs option is recommended, in order to ensure that the object is self contained and specifies all of its dependencies. However, the use of -z defs prevents the plugin object from linking due to the unsatisfied symbol from the parent object:
% cc -zdefs -G -o plugin.so plugin.c
Undefined                       first referenced
 symbol                             in file
parent_callback                     plugin.o
ld: fatal: symbol referencing errors. No output written to plugin.so
A mapfile can be used to specify to ld that the parent_callback symbol is supplied by the parent object.
% cat plugin.mapfile
$mapfile_version 2

SYMBOL_SCOPE {
    global:
        parent_callback         { FLAGS = PARENT };
};
% cc -zdefs -Mplugin.mapfile -G -o plugin.so plugin.c
However, the -z parent option to ld is the most direct solution to this problem, allowing the plugin to actually link against the parent object, and obtain the available symbols from it. An added benefit of using -z parent instead of a mapfile, is that the name of the parent object is recorded in the dynamic section of the plugin, and can be displayed by the file utility:
    % cc -zdefs -zparent=main -G -o plugin.so plugin.c
    % elfdump -d plugin.so | grep PARENT
           [0]  SUNW_PARENT       0xcc                main
    % file plugin.so
    plugin.so: ELF 32-bit LSB dynamic lib 80386 Version 1,
        parent main, dynamically linked, not stripped
    % ./main ./plugin.so
    parent has called plugin_func() from plugin.so
    plugin_func() has called parent_callback()

We can also observe this in elfedit plugins on Solaris systems running Solaris 11 Update 1 or newer:

    % file /usr/lib/elfedit/dyn.so 
    /usr/lib/elfedit/dyn.so: ELF 32-bit LSB dynamic lib 80386 Version 1,
        parent elfedit, dynamically linked, not stripped,
        no debugging information available

Related Other Work

The GNU ld has an option named --just-symbols that can be used in a similar manner:
--just-symbols=filename
Read symbol names and their addresses from filename, but do not relocate it or include it in the output. This allows your output file to refer symbolically to absolute locations of memory defined in other programs. You may use this option more than once.
-z parent is a higher level operation aimed specifically at simplifying the construction of high quality plugins. Although it employs the same operation, it differs from --just symbols in 2 significant ways:
  1. There can only be one parent.

  2. The parent is recorded in the created object, and can be displayed by 'file', or other similar tools.
Comments:

Post a Comment:
Comments are closed for this entry.
About

I work in the core Solaris OS group on the Solaris linkers. These blogs discuss various aspects of linking and the ELF object file format.

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today
Feeds