Parent Objects
- by Ali Bahrami
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:
There can only be one parent.
The parent is recorded in the created object, and can be
displayed by 'file', or other similar tools.