Using Stub Objects to Hide Obsolete Interfaces
Libraries evolve, and sometimes the original functionality proves to
be undesirable. It is common for new abilities to be added, and for
older ones to be considered obsolete. When backward compatibility is
a concern, it is necessary to maintain such older functionality in
the library for the benefit of existing objects. However, you may
wish to prevent new use of these features. Stub objects can be used
to enforce this policy. The mapfile
STUB_ELIMINATE
flag can be used to mark functions
or data from an object that should be eliminated from the stub
object, while remaining in the real object. This prevents new code,
which links to the stub object, from using these obsolete items, and
encourages code to be rewritten to use the preferred interfaces.
Since the real objects still contain these items, existing objects
are able to use them.
The libidx5
example from the previous section
illustrates this. That library demonstrates how to export global
data from an object. However, exported global data introduces
complexity to dynamic linking, and is best avoided. It is usually a
better design to provide a function to access such data, such as the
idx5_func
()function provided by
libidx5
. Continuing that example,
STUB_ELIMINATE
can be used to make the
global data unavailable to new code that links to the stub, while
providing those old interfaces in the real object for the benefit of
existing programs.
The mapfile
is rewritten to apply
STUB_ELIMINATE
to the two global data
symbols. A benefit of applying STUB_ELIMINATE
to
global data is that it is no longer necessary to provide an
ASSERT
directive to provide the data
size. In this example, the ASSERT
is commented
out. A real mapfile
might omit it
entirely.
$ cat better_mapfile
$mapfile_version 2
STUB_OBJECT;
SYMBOL_SCOPE {
_idx5 {
FLAGS=STUB_ELIMINATE;
#ASSERT { TYPE=data; SIZE=4[5] };
};
idx5 {
FLAGS=STUB_ELIMINATE;
#ASSERT { BINDING=weak; ALIAS=_idx5 };
};
idx5_func;
local:
*;
};
A new version of the test program only uses the functional interface.
$ cat better_main.c
#include <stdio.h>
extern int idx5_func(int);
int
main(int argc, char **argv)
{
int i;
for (i = 0; i < 5; i++)
(void) printf("[%d] %d\n", i, idx5_func(i));
return (0);
}
The old test program is saved, the stub object is rebuilt using the
new mapfile
, and the test program is rebuilt,
linking against the new stub object that employs
STUB_ELIMINATE
:
$ cp a.out original_a.out $ cc -Kpic -G -M better_mapfile -h libidx5.so.1 idx5.c -o stublib/libidx5.so.1 -z stub $ cc better_main.c -o better_a.out -L stublib -R '$ORIGIN/lib' -lidx5 -lc $ ./better_a.out [0] 0 [1] 1 [2] 2 [3] 3 [4] 4
The original test program can no longer be built, because the stub library lacks the necessary global data symbols. However, the preexisting binary that used them continues to function because the real library still provides the global data symbols.
$ cc main.c -L stublib -R '$ORIGIN/lib' -lidx5 -lc Undefined first referenced symbol in file idx5 main.o _idx5 main.o ld: fatal: symbol referencing errors $ ./original_a.out [0] 0 0 0 [1] 1 1 1 [2] 2 2 2 [3] 3 3 3 [4] 4 4 4