Reducing Symbol Scope
Symbol definitions that are defined to have local scope within a
mapfile
can be used to reduce the
symbol's eventual binding. This mechanism removes the symbol's
visibility to future link-edits which use the generated file as part
of their input. In fact, this mechanism can provide for the precise
definition of a file's interface, and so restrict the functionality
made available to others.
For example, say you want to generate a simple shared object from the
files foo.c
and bar.c
. The
file foo.c
contains the global symbol
foo
, which provides the service that you
want to make available to others. The file
bar.c
contains the symbols
bar
and str
, which
provide the underlying implementation of the shared object. A shared
object created with these files, typically results in the creation
of three symbols with global scope.
$ cat foo.c extern const char *bar(); const char *foo() { return (bar()); } $ cat bar.c const char *str = "returned from bar.c"; const char *bar() { return (str); } $ cc -o libfoo.so.1 -G foo.c bar.c $ elfdump -sN.symtab libfoo.so.1 | egrep 'foo$|bar$|str$' [41] 0x560 0x18 FUNC GLOB D 0 .text bar [44] 0x520 0x2c FUNC GLOB D 0 .text foo [45] 0x106b8 0x4 OBJT GLOB D 0 .data str
You can now use the functionality offered by
libfoo.so.1
as part of the link-edit of
another application. References to the symbol foo
are bound to the implementation provided by the shared
object.
Because of their global binding, direct reference to the symbols
bar
and str
is also
possible. This visibility can have dangerous consequences, as you
might later change the implementation that underlies the function
foo
. In so doing, you could
unintentionally cause an existing application that had bound to
bar
or str
to fail or
misbehave.
Another consequence of the global binding of the symbols
bar
and str
is that
these symbols can be interposed upon by symbols of the same name.
The interposition of symbols within shared objects is covered in
section Simple Resolutions. This interposition can be intentional and be
used as a means of circumventing the intended functionality offered
by the shared object. On the other hand, this interposition can be
unintentional, the result of the same common symbol name used for
both the application and the shared object.
When developing the shared object, you can protect against these
scenarios by reducing the scope of the symbols
bar
and str
to a local
binding. In the following example, the symbols
bar
and str
are no
longer available as part of the shared object's interface. Thus,
these symbols cannot be referenced, or interposed upon, by an
external object. You have effectively defined an interface for the
shared object. This interface can be managed while hiding the
details of the underlying implementation.
$ cat mapfile $mapfile_version 2 SYMBOL_SCOPE { local: bar; str; }; $ cc -o libfoo.so.1 -M mapfile -G foo.c bar.c $ elfdump -sN.symtab libfoo.so.1 | egrep 'foo$|bar$|str$' [24] 0x548 0x18 FUNC LOCL H 0 .text bar [25] 0x106a0 0x4 OBJT LOCL H 0 .data str [45] 0x508 0x2c FUNC GLOB D 0 .text foo
This symbol scope reduction has an additional performance advantage.
The symbolic relocations against the symbols bar
and str
that would have been necessary at runtime
are now reduced to relative relocations. See When Relocations are Performed for
details of symbolic relocation overhead.
As the number of symbols that are processed during a link-edit
increases, defining local scope reduction within a
mapfile
becomes harder to maintain. An
alternative and more flexible mechanism enables you to define the
shared object's interface in terms of the global symbols that should
be maintained. Global symbol definitions allow the link-editor to
reduce all other symbols to local binding. This mechanism is
achieved using the special auto-reduction
directive "*". For example, the previous
mapfile
definition can be rewritten to
define foo
as the only global symbol required in
the output file generated.
$ cat mapfile $mapfile_version 2 SYMBOL_VERSION ISV_1.1 { global: foo; local: *; }; $ cc -o libfoo.so.1 -M mapfile -G foo.c bar.c $ elfdump -sN.symtab libfoo.so.1 | egrep 'foo$|bar$|str$' [26] 0x570 0x18 FUNC LOCL H 0 .text bar [27] 0x106d8 0x4 OBJT LOCL H 0 .data str [50] 0x530 0x2c FUNC GLOB D 0 .text foo
This example also defines a version name,
ISV_1.1
, as part of the
mapfile
directive. This version name
establishes an internal version definition that defines the file's
symbolic interface. The creation of a version definition is
recommended. The definition forms the foundation of an internal
versioning mechanism that can be used throughout the evolution of
the file. See Interfaces and Versioning.
Note:
If a version name is not supplied, the output file name is used to label the version definition. The versioning information that is created within the output file can be suppressed using the link-editor's-z noversion
option.
Whenever a version name is specified, all global symbols must be assigned to a version definition. If any global symbols remain unassigned to a version definition, the link-editor generates a fatal error condition.
$ cat mapfile $mapfile_version 2 SYMBOL_VERSION ISV_1.1 { global: foo; }; $ cc -o libfoo.so.1 -M mapfile -G foo.c bar.c Undefined first referenced symbol in file str bar.o (symbol has no version assigned) bar bar.o (symbol has no version assigned) ld: fatal: symbol referencing errors
The -B local
option can be used to
assert the auto-reduction directive
"*" from the command line. The previous example an be
compiled successfully as follows.
$ cc -o libfoo.so.1 -M mapfile -B local -G foo.c bar.c
When generating an executable or shared object, any symbol reduction
results in the recording of version definitions within the output
image. When generating a relocatable object, the version definitions
are created but the symbol reductions are not processed. The result
is that the symbol entries for any symbol reductions still remain
global. For example, using the previous mapfile
with the auto-reduction directive and associated relocatable
objects, an intermediate relocatable object is created with no
symbol reduction.
$ cat mapfile $mapfile_version 2 SYMBOL_VERSION ISV_1.1 { global: foo; local: *; }; $ ld -o libfoo.o -M mapfile -r foo.o bar.o $ elfdump -s libfoo.o | egrep 'foo$|bar$|str$' [29] 0x10 0x2c FUNC GLOB D 2 .text foo [30] 0 0x4 OBJT GLOB H 0 .data str
The version definitions created within this image show that symbol
reductions are required. When the relocatable object is used
eventually to generate a dynamic object, the symbol
reductions occur. In other words, the link-editor reads and
interprets symbol reduction information that is contained in the
relocatable objects in the same manner as versioning data is
processed from a mapfile
.
Thus, the intermediate relocatable object produced in the previous example can now be used to generate a shared object.
$ ld -o libfoo.so.1 -G libfoo.o $ elfdump -sN.symtab libfoo.so.1 | egrep 'foo$|bar$|str$' [24] 0x508 0x18 FUNC LOCL H 0 .text bar [25] 0x10644 0x4 OBJT LOCL H 0 .data str [42] 0x4c8 0x2c FUNC GLOB D 0 .text foo
Symbol reduction at the point at which an executable or
shared object is created is typically the most common requirement.
However, symbol reductions can be forced to occur when creating a
relocatable object by using the link-editor's
-B reduce
option.
$ ld -o libfoo.o -M mapfile -B reduce -r foo.o bar.o $ elfdump -sN.symtab libfoo.o | egrep 'foo$|bar$|str$' [20] 0x50 0x18 FUNC LOCL H 0 .text bar [21] 0 0x4 OBJT LOCL H 0 .data str [30] 0x10 0x2c FUNC GLOB D 2 .text foo