How To Write Shared Libraries: Ulrich Drepper Red Hat, Inc
How To Write Shared Libraries: Ulrich Drepper Red Hat, Inc
Ulrich Drepper
Red Hat, Inc.
[email protected]
Abstract
Today, shared libraries are ubiquitous. Developers use them for multiple reasons and create
them just as they would create application code. This is a problem, though, since on many
platforms some additional techniques must be applied even to generate decent code. Even more
knowledge is needed to generate optimized code. This paper introduces the required rules and
techniques. In addition, it introduces the concept of ABI (Application Binary Interface) stability
and shows how to manage it.
For a long time, programmers collected commonly used The binary format used initially for Linux was an a.out
code in libraries so that code could be reused. This saves variant. When introducing shared libraries certain design
development time and reduces errors since reused code decisions had to be made to work in the limitations of
only has to be debugged once. With systems running a.out. The main accepted limitation was that no reloca-
dozens or hundreds of processes at the same time reuse tions are performed at the time of loading and afterward.
of the code at link-time solves only part of the problem. The shared libraries have to exist in the form they are
Many processes will use the same pieces of code which used at run-time on disk. This imposes a major restric-
they import for libraries. With the memory management tion on the way shared libraries are built and used: every
systems in modern operating systems it is also possible shared library must have a fixed load address; otherwise it
to share the code at run-time. This is done by loading the would not be possible to generate shared libraries which
code into physical memory only once and reusing it in do not have to be relocated.
multiple processes via virtual memory. Libraries of this
kind are called shared libraries. The fixed load addresses had to be assigned and this has
to happen without overlaps and conflicts and with some
The concept is not very new. Operating system designers future safety by allowing growth of the shared library.
implemented extensions to their system using the infras- It is therefore necessary to have a central authority for
tructure they used before. The extension to the OS could the assignment of address ranges which in itself is a ma-
be done transparently for the user. But the parts the user jor problem. But it gets worse: given a Linux system
directly has to deal with created initially problems. of today with many hundred of DSOs (Dynamic Shared
Objects) the address space and the virtual memory avail-
The main aspect is the binary format. This is the for- able to the application gets severely fragmented. This
mat which is used to describe the application code. Long would limit the size of memory blocks which can be dy-
gone are the days that it was sufficient to provide a mem- namically allocated which would create unsurmountable
ory dump. Multi-process systems need to identify differ- problems for some applications. It would even have hap-
ent parts of the file containing the program such as the pened by today that the assignment authority ran out of
text, data, and debug information parts. For this, binary address ranges to assign, at least on 32-bit machines.
formats were introduced early on. Commonly used in the
early Unix-days were formats such as a.out or COFF. We still have not covered all the drawbacks of the a.out
These binary formats were not designed with shared li- shared libraries. Since the applications using shared li-
braries in mind and this clearly shows. braries should not have to be relinked after changing a
shared library it uses, the entry points, i.e., the function
and variable addresses, must not change. This can only
be guaranteed if the entry points are kept separate from
the actual code since otherwise limits on the size of a
Copyright
c 2002, 2003, 2004, 2005 Ulrich Drepper function would be hard-coded. A table of function stubs
All rights reserved. No redistribution allowed. which call the actual implementation was used in solu-
1
tion used on Linux. The static linker got the address of followed to get any benefits, and some more rules have to
each function stub from a special file (with the filename be followed to get optimal results. Explaining these rules
extension .sa). At run-time a file ending in .so.X.Y.Z will be the topic of a large portion of this paper.
was used and it had to correspond to the used .sa file.
This in turn requires that an allocated entry in the stub Not all uses of DSOs are for the purpose of saving re-
table always had to be used for the same function. The sources. DSOs are today also often used as a way to
allocation of the table had to be carefully taken care of. structure programs. Different parts of the program are
Introducing a new interface meant appending to the ta- put into separate DSOs. This can be a very powerful tool,
ble. It was never possible to retire a table entry. To avoid especially in the development phase. Instead of relink-
using an old shared library with a program linked with a ing the entire program it is only necessary to relink the
newer version, some record had to be kept in the applica- DSO(s) which changed. This is often much faster.
tion: the X and Y parts of the name of the .so.X.Y.Z
suffix was recorded and the dynamic linker made sure Some projects decide to keep many separate DSOs even
minimum requirements were met. in the deployment phase even though the DSOs are not
reused in other programs. In many situations it is cer-
The benefits of the scheme are that the resulting program tainly a useful thing to do: DSOs can be updated indi-
runs very fast. Calling a function in such a shared li- vidually, reducing the amount of data which has to be
braries is very efficient even for the first call. It can transported. But the number of DSOs must be kept to a
be implemented with only two absolute jumps: the first reasonable level. Not all programs do this, though, and
from the user code to the stub, and the second from the we will see later on why this can be a problem.
stub to the actual code of the function. This is probably
faster than any other shared library implementation, but Before we can start discussing all this some understand-
its speed comes at too high a price: ing of ELF and its implementation is needed.
code is replaced with a new program. This means the ad- in the program header table and the e phentsize field
dress space content is replaced by the content of the file contains the size of each entry. This last value is useful
containing the program. This does not happen by sim- only as a run-time consistency check for the binary.
ply mapping (using mmap) the content of the file. ELF
files are structured and there are normally at least three The different segments are represented by the program
different kinds of regions in the file: header entries with the PT LOAD value in the p type field.
The p offset and p filesz fields specify where in the
file the segment starts and how long it is. The p vaddr
• Code which is executed; this region is normally not and p memsz fields specifies where the segment is located
writable; in the the process’ virtual address space and how large
the memory region is. The value of the the p vaddr field
• Data which is modified; this region is normally not
itself is not necessarily required to be the final load ad-
executable;
dress. DSOs can be loaded at arbitrary addresses in the
• Data which is not used at run-time; since not needed virtual address space. But the relative position of the seg-
it should not be loaded at startup. ments is important. For pre-linked DSOs the actual value
of the p vaddr field is meaningful: it specifies the ad-
dress for which the DSO was prelinked. But even this
Modern operating systems and processors can protect mem- does not mean the dynamic linker cannot ignore this in-
ory regions to allow and disallow reading, writing, and formation if necessary.
executing separately for each page of memory.1 It is
preferable to mark as many pages as possible not writable The size in the file can be smaller than the address space
since this means that the pages can be shared between it takes up in memory. The first p filesz bytes of the
processes which use the same application or DSO the memory region are initialized from the data of the seg-
page is from. Write protection also helps to detect and ment in the file, the difference is initialized with zero.
prevent unintentional or malignant modifications of data This can be used to handle BSS sections2 , sections for
or even code. uninitialized variables which are according to the C stan-
dard initialized with zero. Handling uninitialized vari-
For the kernel to find the different regions, or segments ables this way has the advantage that the file size can be
in ELF-speak, and their access permissions, the ELF file reduced since no initialization value has to be stored, no
format defines a table which contains just this informa- data has to be copied from dist to memory, and the mem-
tion, among other things. The ELF Program Header ta- ory provided by the OS via the mmap interface is already
ble, as it is called, must be present in every executable initialized with zero.
and DSO. It is represented by the C types Elf32 Phdr
and Elf64 Phdr which are defined as can be seen in fig- The p flags finally tells the kernel what permissions to
ure 1. use for the memory pages. This field is a bitmap with the
bits given in the following table being defined. The flags
To locate the program header data structure another data are directly mapped to the flags mmap understands.
structure is needed, the ELF Header. The ELF header is
the only data structure which has a fixed place in the file,
starting at offset zero. Its C data structure can be seen
in figure 2. The e phoff field specifies where, counting
from the beginning of the file, the program header table
starts. The e phnum field contains the number of entries 2 A BSS section contains only NUL bytes. Therefore they do not
1A memory page is the smallest entity the memory subsystem of have to be represented in the file on the storage medium. The loader
the OS operates on. The size of a page can vary between different just has to know the size so that it can allocate memory large enough
architectures and even within systems using the same architecture. and fill it with NUL
p flags Value mmap flag Description application is complete. For this a structured way exists.
PF X 1 PROT EXEC Execute Permission The kernel puts an array of tag-value pairs on the stack
PF W 2 PROT WRITE Write Permission of the new process. This auxiliary vector contains be-
PF R 4 PROT READ Read Permission side the two aforementioned values several more values
which allow the dynamic linker to avoid several system
calls. The elf.h header file defines a number of con-
stants with a AT prefix. These are the tags for the entries
After mapping all the PT LOAD segments using the ap- in the auxiliary vector.
propriate permissions and the specified address, or after
freely allocating an address for dynamic objects which After setting up the auxiliary vector the kernel is finally
have no fixed load address, the next phase can start. The ready to transfer control to the dynamic linker in user
virtual address space of the dynamically linked executable mode. The entry point is defined in e entry field of the
itself is set up. But the binary is not complete. The kernel ELF header of the dynamic linker.
has to get the dynamic linker to do the rest and for this
the dynamic linker has to be loaded in the same way as
the executable itself (i.e., look for the loadable segments 1.5 Startup in the Dynamic Linker
in the program header). The difference is that the dy-
namic linker itself must be complete and should be freely
relocatable. The second phase of the program startup happens in the
dynamic linker. Its tasks include:
Which binary implements the dynamic linker is not hard-
coded in the kernel. Instead the program header of the
application contains an entry with the tag PT INTERP.
• Determine and load dependencies;
The p offset field of this entry contains the offset of
a NUL-terminated string which specifies the file name of
this file. The only requirement on the named file is that • Relocate the application and all dependencies;
its load address does not conflict with the load address of
any possible executable it might be used with. In gen-
• Initialize the application and dependencies in the
eral this means that the dynamic linker has no fixed load
correct order.
address and can be loaded anywhere; this is just what dy-
namic binaries allow.
Once the dynamic linker has also been mapped into the In the following we will discuss in more detail only the
memory of the stillborn process we can start the dynamic relocation handling. For the other two points the way
linker. Note it is not the entry point of the application to for better performance is clear: have fewer dependencies.
which control is transfered to. Only the dynamic linker is Each participating object is initialized exactly once but
ready to run. Instead of calling the dynamic linker right some topological sorting has to happen. The identify and
away, one more step is performed. The dynamic linker load process also scales with the number dependencies;
somehow has to be told where the application can be in most (all?) implementations this is not only a linear
found and where control has to be transferred to once the growth.
Histogram for bucket list length in section [ 2] ’.hash’ (total of 191 buckets):
Addr: 0x00000114 Offset: 0x000114 Link to section: [ 3] ’.dynsym’
Length Number % of total Coverage
0 103 53.9%
1 71 37.2% 67.0%
2 16 8.4% 97.2%
3 1 0.5% 100.0%
Average number of tests: successful lookup: 1.179245
unsuccessful lookup: 0.554974
1. Determine the hash value for the symbol name. Note that there is no problem if the scope contains more
than one definition of the same symbol. The symbol
2. In the first/next object in the lookup scope: lookup algorithm simply picks up the first definition it
finds. This has some perhaps surprising consequences.
2.a Determine the hash bucket for the symbol us-
Assume DSO ‘A’ defines and references an interface and
ing the hash value and the hash table size in
DSO ‘B’ defines the same interface. If now ‘B’ precedes
the object.
‘A’ in the scope, the reference in ‘A’ will be satisfied
2.b Get the name offset of the symbol and using by the definition in ‘B’. It is said that the definition in
it as the NUL-terminated name. ‘B’ interposes the definition in ‘A’. This concept is very
2.c Compare the symbol name with the reloca- powerful since it allows more specialized implementation
tion name. of an interface to be used without replacing the general
code. One example for this mechanism is the use of the
2.d If the names match, compare the version names LD PRELOAD functionality of the dynamic linker where
as well. This only has to happen if both, the additional DSOs which were not present at link-time are
reference and the definition, are versioned. It introduced in run-time. But interposition can also lead
requires a string comparison, too. If the ver- to severe problems in ill-designed code. More in this in
sion name matches or no such comparison section 1.5.3.
is performed, we found the definition we are
looking for. Looking at the algorithm it can be seen that the perfor-
2.e If the definition does not match, retry with the mance of each lookup depends, among other factors, on
next element in the chain for the hash bucket. the length of the hash chains and the number of objects
in the lookup scope. These are the two loops described
2.f If the chain does not contain any further ele-
above. The lengths of the hash chains depend on the
ment there is no definition in the current ob-
number of symbols and the choice of the hash table size.
ject and we proceed with the next object in
Since the hash function used in the initial step of the algo-
the lookup scope.
rithm must never change these are the only two remaining
3. If there is no further object in the lookup scope the variables. Many linkers do not put special emphasis on
lookup failed. selecting an appropriate table size. The GNU linker tries
to optimize the hash table size for minimal lengths of the
The increased relative table size means we have signifi- The length of the strings in both mangling schemes is
cantly shorter hash chains. This is especially true for the worrisome since each string has to be compared com-
average chain length for an unsuccessful lookup. The av- pletely when the symbol itself is searched for. The names
erage for the small table is only 28% of that of the large in the example are not extra ordinarily long either. Look-
table. ing through the standard C++ library one can find many
names longer than 120 characters and even this is not the
What these numbers should show is the effect of reduc- longest. Other system libraries feature names longer than
ing the number of symbols in the dynamic symbol ta- 200 characters and complicated, “well designed” C++
ble. With significantly fewer symbols the linker has a projects with many namespaces, templates, and nested
much better chance to counter the effects of the subopti- classes can feature names with more than 1,000 charac-
mal hashing function. ters. One plus point for design, but minus 100 points for
performance.
Another factor in the cost of the lookup algorithm is con- 4 Some people suggested “Why not search from the back?”. Think
nected with the strings themselves. Simple string com- about it, these are C strings, not PASCAL strings. We do not know the
parison is used on the symbol names which are stored length and therefore would have to read every single character of the
in a string table associated with the symbol table data string to determine the length. The result would be worse.
_ZN14some_namespace22some_longer_class_nameC1Ei
_ZN14some_namespace22some_longer_class_name19the_getter_functionEv
ada__calendar__delays___elabb
ada__calendar__delays__timed_delay_nt
ada__calendar__delays__to_duration
With the knowledge of the hashing function and the de- bols’, ‘length of the symbol strings’, ‘number and length
tails of the string lookup let us look at a real-world exam- of common prefixes’,‘number of DSOs’, and ‘hash table
ple: OpenOffice.org. The package contains 144 separate size optimization’ can reduce the costs dramatically. In
DSOs. During startup about 20,000 relocations are per- general the percentage spent on relocations of the time
formed. The number of string comparisons needed dur- the dynamic linker uses during startup is around 50-70%
ing the symbol resolution can be used as a fair value for if the binary is already in the file system cache, and about
the startup overhead. We compute an approximation of 20-30% if the file has to be loaded from disk.5 It is there-
this value now. fore worth spending time on these issues and in the re-
mainder of the text we will introduce methods to do just
The average chain length for unsuccessful lookup in all that. So far to remember: pass -O1 to the linker to gener-
DSOs of the OpenOffice.org 1.0 release on IA-32 is 1.1931. ate the final product.
This means for each symbol lookup the dynamic linker
has to perform on average 72 × 1.1931 = 85.9032 string 1.5.3 Lookup Scope
comparisons. For 20,000 symbols the total is 1,718,064
string comparisons. The average length of an exported
symbol defined in the DSOs of OpenOffice.org is 54.13. The lookup scope has so far been described as an ordered
Even if we are assuming that only 20% of the string is list of most loaded object. While this is correct it has also
searched before finding a mismatch (which is an opti- been intentionally vague. It is now time to explain the
mistic guess since every symbol name is compared com- lookup scope in more detail.
pletely at least once to match itself) this would mean a to-
tal of more then 18.5 million characters have to be loaded The lookup scope consists in fact of up to three parts.
from memory and compared. No wonder that the startup The main part is the global lookup scope. It initially
is so slow, especially since we ignored other costs. consists of the executable itself and all its dependencies.
The dependencies are added in breadth-first order. That
To compute number of lookups the dynamic linker per- means first the dependencies of the executable are added
forms one can use the help of the dynamic linker. If the in the order of their DT NEEDED entries in the executable’s
environment variable LD DEBUG is set to symbols one dynamic section. Then the dependencies of the first de-
only has to count the number of lines which start with pendency are added in the same fashion. DSOs already
symbol=. It is best to redirect the dynamic linker’s out- loaded are skipped; they do not appear more than once
put into a file with LD DEBUG OUTPUT. The number of on the list. The process continues recursively and it will
string comparisons can then be estimate by multiplying stop at some point since there are only a limited number
the count with the average hash chain length. Since the of DSOs available. The exact number of DSOs loaded
collected output contains the name of the file which is this way can vary widely. Some executables depend on
looked at it would even be possible to get more accurate only two DSOs, others on 200.
results by multiplying with the exact hash chain length
for the object. If an executable has the DF SYMBOLIC flag set (see sec-
tion 2.2.7) the object with the reference is added in front
Changing any of the factors ‘number of exported sym- 5 These numbers assume pre-linking is not used.
A more complicated modification of the lookup scope If now libtwo.so is loaded, the additional local scope
happens when DSOs are loaded dynamic using dlopen. could be like this:
If a DSO is dynamically loaded it brings in its own set
of dependencies which might have to be searched. These
objects, starting with the one which was requested in the libdynamic.so → libtwo.so → libc.so
dlopen call, are appended to the lookup scope if the
object with the reference is among those objects which This local scope is searched after the global scope, pos-
have been loaded by dlopen. That means, those objects sibly with the exception of libdynamic.so which is
are not added to the global lookup scope and they are searched first for lookups in this very same DSO if the
not searched for normal lookups. This third part of the DF DYNAMIC flag is used. But what happens if the sym-
lookup scope, we will call it local lookup scope, is there- bol duplicate is required in libdynamic.so? After
fore dependent on the object which has the reference. all we said so far the result is always: the definition in
libone.so is found since libtwo.so is only in the lo-
The behavior of dlopen can be changed, though. If the cal scope which is searched after the global scope. If the
function gets passed the RTLD GLOBAL flag, the loaded two definitions are incompatible the program is in trou-
object and all the dependencies are added to the global ble.
scope. This is usually a very bad idea. The dynami-
cally added objects can be removed and when this hap- This can be changed with a recent enough GNU C library
pens the lookups of all other objects is influenced. The by ORing RTLD DEEPBIND to the flag word passed as the
entire global lookup scope is searched before the dynam- second parameter to dlopen. If this happens, the dy-
ically loaded object and its dependencies so that defini- namic linker will search the local scope before the global
tions would be found first in the global lookup scope ob- scope for all objects which have been loaded by the call
ject before definitions in the local lookup scope. If the to dlopen. For our example this means the search or-
dynamic linker does the lookup as part of a relocation der changes for all lookups in the newly loaded DSOs
this additional dependency is usually taken care of auto- libdynamic.so and libtwo.so, but not for libc.so
matically, but this cannot be arranged if the user looks up since this DSO has already been loaded. For the two af-
symbols in the lookup scope with dlsym. fected DSOs a reference to duplicate will now find the
definition in libtwo.so. In all other DSOs the definition
And usually there is no reason to use RTLD GLOBAL. For in libone.so would be found.
reasons explained later it is always highly advised to cre-
ate dependencies with all the DSOs necessary to resolve While this might sound like a good solution for handling
all references. RTLD GLOBAL is often used to provide im- compatibility problems this feature should only be used
plementations which are not available at link time of a if it cannot be avoided. There are several reasonse for
DSO. Since this should be avoided the need for this flag this:
should be minimal. Even if the programmer has to jump
through some hoops to work around the issues which are
solved by RTLD GLOBAL it is worth it. The pain of debug- • The change in the scope affects all symbols and all
ging and working around problems introduced by adding the DSOs which are loaded. Some symbols might
objects to the global lookup scope is much bigger. have to be interposed by definitions in the global
scope which now will not happen.
The dynamic linker in the GNU C library knows since
• Already loaded DSOs are not affected which could
September 2004 one more extension. This extension helps
cause unconsistent results depending on whether
to deal with situations where multiple definitions of sym-
the DSO is already loaded (it might be dynamically
bols with the same name are not compatible and there-
loaded, so there is even a race condition).
fore cannot be interposed and expected to work. This is
usally a sign of design failures on the side of the peo- • LD PRELOAD is ineffective for lookups in the dy-
ple who wrote the DSOs with the conflicting definitions namically loaded objects since the preloaded ob-
and also failure on the side of the application writer who jects are part of the global scope, having been added
depends on these incompatible DSOs. We assume here right after the executable. Therefore they are looked
that an application app is linked with a DSO libone.so at only after the local scope.
which defines a symbol duplicate and that it dynami-
cally loads a DSO libdynamic.so which depends on • Applications might expect that local definitions are
another DSO libtwo.so which also defines a symbol always preferred over other definitions. This (and
duplicate. When the application starts it might have a the previous point) is already partly already a prob-
lem with the use of DF SYMBOLIC but since this
the same semantics often means higher efficiency Number of Symbols The number of exported and unde-
and performance. Smaller ELF binaries need less fined symbols determines the size of the dynamic
memory at run-time. symbol table, the hash table, and the average hash
In general the programmer will always generate the table chain length. The normal symbol table is not
best code possible and we do not cover this further. used at run-time and it is therefore not necessary
But it must be known that every DSO includes a to strip a binary of it. It has no impact on perfor-
certain overhead in data and code. Therefore fewer mance.
DSOs means smaller text. Additionally, fewer exported symbols means fewer
Number of Objects The fact that a smaller number of chances for conflicts when using pre-linking (not
objects containing the same functionality is bene- covered further).
ficial has been mentioned in several places:
Length of Symbol Strings Long symbol lengths cause
• Fewer objects are loaded at run-time. This often unnecessary costs. A successful lookup of a
directly translates to fewer system call. In the symbol must match the whole string and compar-
GNU dynamic linker implementation loading ing dozens or hundreds of characters takes time.
a DSO requires 8 system calls, all of them can Unsuccessful lookups suffer if common prefixes
be potentially quite expensive. are long as in the new C++ mangling scheme. In
• Related, the application and the dependencies any case do long symbol names cause large string
with additional dependencies must record the tables which must be present at run-time and thereby
names of the dependencies. This is not a ter- is adding costs in load time and in use of address
ribly high cost but certainly can sum up if space which is an issue for 32-bit machines.
there are many dozens of dependencies.
• The lookup scope grows. This is one of the Number of Relocations Processing relocations constitute
dominating factors in cost equation for the re- the majority of work during start and therefore any
locations. reduction is directly noticeable.
• More objects means more symbol tables which
Kind of Relocations The kind of relocations which are
in turn normally means more duplication. Un-
needed is important, too, since processing a rela-
defined references are not collapsed into one
tive relocation is much less expensive than a nor-
and handling of multiple definitions have to
mal relocation. Also, relocations against text seg-
be sorted out by the dynamic linker.
ments must be avoided.
Moreover, symbols are often exported from a
DSO to be used in another one. This would Placement of Code and Data All executable code should
not have to happen if the DSOs would be merged. be placed in read-only memory and the compiler
• The sorting of initializers/finalizers is more normally makes sure this is done correctly. When
complicated. creating data objects it is mostly up to the user
• In general does the dynamic linker have some to make sure it is placed in the correct segment.
overhead for each loaded DSO per process. Ideally data is also read-only but this works only
Every time a new DSO is requested the list of for constants. The second best choice is a zero-
already loaded DSOs must be searched which initialized variable which does not have to be ini-
can be quite time consuming since DSOs can tialized from file content. The rest has to go into
have many aliases. the data segment.
Since relocations play such a vital part of the startup per- Some people try to argue that the use of -fpic/-fPIC
formance some information on the number of relocations on some architectures has too many disadvantages. This
is printed. In the example a total of 133 relocations are is mainly brought forward in argumentations about IA-
performed, from the dynamic linker, the C library, and the 32. Here the use of %ebx as the PIC register deprives
executable itself. Of these 5 relocations could be served the compiler of one of the precious registers it could use
from the relocation cache. This is an optimization imple- for optimization. But this is really not that much of a
mented in the dynamic linker to handle the case of mul- problem. First, not having %ebx available was never a
tiple relocations against the same symbol more efficient. big penalty. Second, in modern compilers (e.g., gcc after
After the program itself terminated the same information release 3.1) the handling of the PIC register is much more
is printed again. The total number of relocations here is flexible. It is not always necessary to use %ebx which
higher since the execution of the application code caused can help eliminating unnecessary copy operations. And
a number, 55 to be exact, of run-time relocations to be third, by providing the compiler with more information as
performed. explained later in this section a lot of the overhead in PIC
can be removed. This all combined will lead to overhead
The number of relocations which are processed is stable which is in most situations not noticeable.
In both cases %l7 is loaded with the address of the GOT Uninitialized If the programmer uses the compiler com-
first. Then the GOT is accessed to get the address of mand line option -fno-common the generated code
global. While in the -fpic case one instruction is suf- will contain uninitialized variables instead of com-
ficient, three instructions are needed in the -fPIC case. mon variables if a variable definition has no ini-
The -fpic option tells the compiler that the size of the tializer. Alternatively, individual variables can be
GOT does not exceed an architecture-specific value (8kB marked like this:
in case of SPARC). If only that many GOT entries can
int foo attribute ((nocommon));
be present the offset from the base of the GOT can be
encoded in the instruction itself, i.e., in the ld instruc- The result at run-time is the same as for common
tion of the first code sequence above. If -fPIC is used variable, no value is stored in the file. But the rep-
no such limit exists and so the compiler has to be pes- resentation in the object file is different and it al-
simistic and generate code which can deal with offsets of lows the linker to find multiple definitions and flag
any size. The difference in the number of instructions in them as errors. Another difference is that it is pos-
this example correctly suggests that the -fpic should be sible to define aliases, i.e., alternative names, for
There is one thing the programmer is responsible for. As When creating a DSO from a collection of object files the
an example look at the following code: dynamic symbol table will by default contain all the sym-
bols which are globally visible in the object files. In most
cases this set is far too large. Only the symbols which are
bool is_empty = true; actually part of the ABI should be exported. Failing to
char s[10]; restrict the set of exported symbols are numerous draw-
backs:
const char *get_s (void) {
return is_empty ? NULL : s;
} • Users of the DSO could use interfaces which they
are not supposed to. This is problematic in revi-
sions of the DSO which are meant to be binary
compatible. The correct assumption of the DSO
The function get s uses the boolean variable is empty
developer is that interfaces, which are not part of
to decide what to do. If the variable has its initial value
the ABI, can be changed arbitrarily. But there are
the variable s is not used. The initialization value of
always users who claim to know better or do not
is empty is stored in the file since the initialize is non-
care about rules.
zero. But the semantics of is empty is chosen arbitrar-
ily. There is no requirement for that. The code could • According to the ELF lookup rules all symbols in
instead be rewritten as: the dynamic symbol table can be interposed (un-
less the visibility of the symbol is restricted). I.e.,
bool not_empty = false;
definitions from other objects can be used. This
char s[10]; means that local references cannot be bound at link
time. If it is known or intended that the local defi-
const char *get_s (void) { nition should always be used the symbol in the ref-
return not_empty ? s : NULL; erence must not be exported or the visibility must
} be restricted.
In the discussions of the various methods we will use one int index (int scale) {
example: return next () << scale;
}
int last;
int next (void) { Compiled in the same way as before we see that all the re-
return ++last; locations introduced by our example code vanished. I.e.,
}
we are left with six relocations and three PLT entries. The
int index (int scale) {
code to access last now looks like this:
return next () << scale;
}
movl last@GOTOFF(%ebx), %eax
incl %eax
movl %eax, last@GOTOFF(%ebx)
Compiled on a IA-32 Linux machine a DSO with only
this code (plus startup code etc) contains seven reloca-
tions, two of which are relative, and four PLT entries (use
the relinfo script). We will see how we can improve on The code improved by avoiding the step which loads the
this. Four of the normal and both relative relocations as address of the variable from the GOT. Instead, both mem-
well as three PLT entries are introduced by the additional ory accesses directly address the variable in memory. At
code used by the linker to create the DSO. The actual ex- link-time the variable location has a fixed offset from the
ample code creates only one normal relocation for last PIC register, indicated symbolically by last@GOTOFF.
and one PLT entry for next. To increment and read the By adding the value to the PIC register value we get the
variable last in next the compiler generates code like address of last. Since the value is known at link-time
this construct does not need a relocation at run-time.
movl last@GOT(%ebx), %edx The situation is similar for the call to next. The IA-32 ar-
movl (%edx), %eax
chitecture, like many others, know a PC-relative address-
incl %eax
movl %eax, (%edx)
ing mode for jumps and calls. Therefore the compiler can
generate a simple jump instruction
call next@PLT
and the assembler generates a PC-relative call. The dif-
ference between the address of the instruction following
These code fragments were explained in section 1.5.4. the call and the address of next is constant at link-time
and therefore also does not need any relocation. Another
advantage is that, in the case of IA-32, the PIC register
2.2.1 Use static
does not have to be set up before the jump. If the com-
piler wouldn’t know the jump target is in the same DSO
The easiest way to not export a variable or function is to the PIC register would have to be set up. Other architec-
the define it file file-local scope. In C and C++ this is tures have similar requirements.
int last
The next best thing to using static is to explicitly de- __attribute__ ((visibility ("hidden")));
fine the visibility of objects in the DSO. The generic ELF
ABI defines visibility of symbols. The specification de- int
fines four classes of which here only two are of interest. __attribute__ ((visibility ("hidden")))
STV DEFAULT denotes the normal visibility. The symbol next (void) {
is exported and can be interposed. The other interesting return ++last;
class is denoted by STV HIDDEN. Symbols marked like }
this are not exported from the DSO and therefore can-
not be used from other objects. There are a number of int index (int scale) {
return next () << scale;
different methods to define visibility.
}
Starting with version 4.0, gcc knows about a the com-
mand line option -fvisibility. It takes a parameter
and the valid forms are these: This defines the variable last and the function next
as hidden. All the object files which make up the DSO
which contains this definition can use these symbols. I.e.,
-fvisibility=default
while static restricts the visibility of a symbol to the
-fvisibility=hidden
-fvisibility=internal file it is defined in, the hidden attribute limits the visibil-
-fvisibility=protected ity to the DSO the definition ends up in. In the example
above the definitions are marked. This does not cause any
harm but it is in any case necessary to mark the declara-
tion. In fact it is more important that the declarations are
Only the first two should ever be used. The default is marked appropriately since it is mainly the code gener-
unsurprisingly default since this is the behavior of the ated for in a reference that is influenced by the attribute.
compiler before the introduction of this option. When
-fvisibility=hidden is specified gcc changes the de- Instead of adding an visibility attribute to each declara-
fault visibility of all defined symbols which have no ex- tion or definition, it is possible to change the default tem-
plicit assignment of visibility: all symbols are defined porarily for all definitions and declarations the compiler
with STV HIDDEN unless specified otherwise. This op- sees at this time. This is mainly useful in header files
tion has to be used with caution since unless the DSO since it reduces changes to a minimum but can also be
is prepared by having all APIs marked as having default useful for definitions. This compiler feature was also in-
visibility, the generated DSO will not have a single ex- troduced in gcc 4.0 and is implemented using a pragma:8
ported symbol. This is usually not what is wanted.
#pragma GCC visibility push(hidden)
In general it is the preference of the author which decides int last;
whether -fvisibility=hidden should be used. If it
is not used, symbols which are not to be exported need int
to be marked in one way or another. The next section
7 Accidentally exporting symbol can mean that programs can use
will go into details. In case the option is used all ex-
and get dependent on them. Then it is hard to remove the symbol again
ported functions need to be declared as having visibility for binary compatibility reasons.
default which usually means the header files are sig- 8 Note: ISO C99 introduced Pragma which allows using pragmas
In case the -fvisibility=hidden command line op- There are some exceptions to these rules. It is possible
tion is used, individual symbols can be marked as ex- to create ELF binaries with non-standard lookup scopes.
portable by using the same syntax as presented in this The simplest example is the use of DF SYMBOLIC (or of
section, except with default in place of hidden. In DT SYMBOLIC in old-style ELF binaries, see page 24).
fact, the names of all four visibilities are allowed in the In these cases the programmer decided to create a non-
attribute or pragma. standard binary and therefore accepts the fact that the
rules of the ISO C standard do not apply.
Beside telling the backend of the compiler to emit code to
flag the symbol as hidden, changing the visibility has an-
2.2.4 Define Visibility for C++ Classes
other purpose: it allows the compiler to assume the defi-
nition is local. This means the addressing of variables and
function can happen as if the definitions would be locally For C++ code we can use the attributes as well but they
defined in the file as static. Therefore the same code have to be used very carefully. Normal function or vari-
sequences we have seen in the previous section can be able definitions can be handled as in C. The extra name
generated. Using the hidden visibility attribute is there- mangling performed has no influence on the visibility.
fore almost completely equivalent to using static; the The story is different when it comes to classes. The sym-
only difference is that the compiler cannot automatically bols and code created for class definitions are member
inline the function since it need not see the definition. functions and static data or function members. These
variables and functions can easily be declared as hidden
We can now refine the rule for using static: merge but one has to be careful. First an example of the syntax.
source files and mark as many functions static as far as
one feels comfortable. In any case merge the files which
contain functions which potentially can be inlined. In all class foo {
static int u __attribute__
other cases mark functions (the declarations) which are
((visibility ("hidden")));
not to be exported from the DSO as hidden.
int a;
public:
Note that the linker will not add hidden symbols to the foo (int b = 1);
dynamic symbol table. I.e., even though the symbol ta- void offset (int n);
bles of the object files contain hidden symbols they will int val () const __attribute__
disappear automatically. By maximizing the number of ((visibility ("hidden")));
hidden declarations we therefore reduce the size of the };
symbol table to the minimum.
int foo::u __attribute__
The generic ELF ABI defines another visibility mode: ((visibility ("hidden")));
foo::foo (int b) : a (b) { }
protected. In this scheme references to symbols defined
void foo::offset (int n) { u = n; }
in the same object are always satisfied locally. But the
int
symbols are still available outside the DSO. This sounds __attribute__ ((visibility ("hidden")))
like an ideal mechanism to optimize DSO by avoiding the foo::val () const { return a + u; }
use of exported symbols (see section 2.2.7) but it isn’t.
Processing references to protected symbols is even more
expensive than normal lookup. The problem is a require-
ment in the ISO C standard. The standard requires that In this example code the static data member u and the
function pointers, pointing to the same function, can be member function val are defined as hidden. The sym-
Because these problems are so hard to debug it is essen- For templatized classes the problems of making sure that
tial to get the compiler involved in making sure the user if necessary only one definition is used is even harder to
follows the necessary rules. The C++ type system is rich fix due to the various approaches to instantiation.
enough to help if the implementor puts some additional
effort in it. The key is to mimic the actual symbol access One sort of function which can safely be kept local and
as closely as possible with the class definition. For this not exported are inline function, either defined in the class
reason the class definitions of the example above should definition or separately. Each compilation unit must have
actually look like this: its own set of all the used inline functions. And all the
functions from all the DSOs and the executable better be
the same and are therefore interchangeable. It is possible
class foo { to mark all inline functions explicitly as hidden but this is
static int u __attribute__ a lot of work. Since version 4.0 gcc knows about the op-
((visibility ("hidden"))); tion -fvisibility-inlines-hidden which does just
int a; what is wanted. If this option is used a referenced in-
public: line function is assumed to be hidden and an out-of-line
All member functions and static data members of foo 2.2.5 Use Export Maps
are automatically defined as hidden. This extends even to
implicitly generated functions and operators if necessary. If for one reason or another none of the previous two so-
lutions are applicable the next best possibility is to in-
The second possibility is to use yet another extension in struct the linker to do something. Only the GNU and
gcc 4.0. It is possible to mark a function as hidden when Solaris linker are known to support this, at least with the
it is defined. The syntax is this: syntax presented here. Using export maps is not only
useful for the purpose discussed here. When discussing
class __attribute ((visibility ("hidden")))
maintenance of APIs and ABIs in chapter 3 the same kind
foo { of input file is used. This does not mean the previous two
... methods should not be preferred. Instead, export (and
}; symbol) maps can and should always be used in addition
to the other methods described.
The fourth method to restrict symbol export is the least Since a symbol cannot be exported and not-exported at
desirable of them. It is the one used by the GNU Libtool the same time the basic approach is to use two names for
program when the -export-symbols option is used. the same variable or function. The two names then can
This option is used to pass to Libtool the name of a file be treated differently. There are multiple possibilities to
which contains the names of all the symbols which should create two names, varying in efficiency and effort.
be exported, one per line. The Libtool command line
might look like this: At this point it is necessary to add a warning. By per-
forming this optimization the semantics of the program
changes since the optimization interferes with the sym-
$ libtool --mode=link gcc -o libfoo.la \ bol lookup rules. It is now possible to use more than one
foo.lo -export-symbols=foo.sym symbol with a given name in the program. Code out-
side the DSO might find a definition of a symbol some-
where else while the code in the DSO always uses the
local definition. This might lead to funny results. Of-
The file foo.sym would contain the list of exported sym- ten it is acceptable since multiple definitions are not al-
bols. foo.lo is the special kind of object files Libtool lowed. A related issue is that one rule of ISO C can be
generates. For more information on this and other strange violated by this. ISO C says that functions are identified
details from the command line consult the Libtool man- by their names (identifiers) and that comparing the func-
ual. tion pointers one can test for equality. The ELF imple-
mentation works hard to make sure this rule is normally
Interesting for us here is the code the linker produces us- obeyed. When forcing the use of local symbols code in-
ing this method. For the GNU linker Libtool converts the side and outside the DSO might find different definitions
-export-symbols option into the completely useless for a given name and therefore the pointers do not match.
-retain-symbols-file option. This option instructs It is important to always consider these side effects before
the linker to prune the normal symbol tables, not the dy- performing the optimization.
namic symbol table. The normal symbol table will con-
tain only the symbols named in the export list file plus the
special STT SECTION symbols which might be needed in Wrapper Functions Only applicable to functions, us-
relocations. All local symbols are gone. The problem is ing wrappers (i.e. alternative entry points) is the most
that the dynamic symbol table is not touched at all and portable but also most costly way to solve the problem.
this is the table which is actually used at runtime. If in our example code we would want to export index
as well as next we could use code like this:
The effect of the using libtool this way is that pro-
grams reading the normal symbol table (for instance nm)
do not find any symbols but those listed in the export static int last;
list. And that is it. There are no runtime effects. Neither
have any symbols been made unavailable for the dynamic static int next_int (void) {
Inlining makes sense even if the inlined function are much tation in the GNU libc.
Since we do not have direct access to the PIC register at But even this is often not possible. The virtual functions
compile-time and cannot express the computations of the can be called not only through the virtual function table
offsets we have to find another base address. In the code but also directly if the compiler can determine the exact
above it is simply one of the target addresses, a0. The type of an C++ object. Therefore virtual function in most
array offsets is in this case really constant and placed cases have to be exported from the DSO. For instance,
in read-only memory since all the offsets are known once the virtual function in the following example is called di-
the compiler finished generating code for the function. rectly.
We now have relative addresses, no relocations are nec-
essary. The type used for offsets might have to be ad-
justed. If the differences are too large (only really pos- struct foo {
sible for 64-bit architectures, and then only for tremen- virtual int virfunc () const;
dously large functions) the type might have to be changed };
to ssize t or something equivalent. In the other direc- foo var;
tion, if it is known that the offsets would fit in a variable int bar () { return var.virfunc (); }
of type short or signed char, these types might be
used to save some memory.
On most platforms the code generated for DSOs differs The x86-64 architecture provides a PC-relative data ad-
from code generated for applications. The code in DSOs dressing mode which is extremely helpful in situations
needs to be relocatable while application code can usu- like this.
ally assume a fixed load address. This inevitably means
that the code in DSOs is slightly slower and probably Another possible optimization is to require the caller to
larger than application code. Sometimes this additional load the PIC register. On IA-64 the gp register is used
overhead can be measured. Small, often called functions for this purpose. Each function pointer consist of a pair
fall into this category. This section shows some problem function address and gp value. The gp value has to be
cases of code in DSOs and ways to avoid them. loaded before making the call. The result is that for our
running example the generated code might look like this:
In the preceding text we have seen that for IA-32 a func-
tion accessing a global variable has to load determine the getfoo:
address of the GOT to use the @GOTOFF operation. As- addl r14=@gprel(foo),gp;;
suming this C code ld4 r8=[r14]
br.ret.sptk.many b0
If this code gets translated as is, both functions will load An example with security relevance could be a call to
the GOT address to access the global variables. This can setuid to drop a process’ privileges which is redirected
be avoided by putting all variables in a struct and passing to perhaps getpid. The attacker could therefore keep
the address of the struct to the functions which can use it. the raised priviledges and later cause greater harm.
For instance, the above code could be rewritten as:
This kind of attack would not be possible if the data GOT
and PLT could not be modified by the user program. For
static struct globals { some platforms, like IA-32, the PLT is already read-only.
int foo; But the GOT must be modifiable at runtime. The dy-
int bar; namic linker is an ordinary part of the program and it
} globals; is therefore not possible to require the GOT in a mem-
static int intfoo (struct globals *g) ory region which is writable by the dynamic linker but
{ return g->foo; }
not the rest of the application. Another possibility would
int getfoo (void)
{ return intfoo(&globals); }
be to have the dynamic linker change the access permis-
int getboth (void) sions for the memory pages containing the GOT and PLT
{ return globals.bar+intfoo(&globals); } whenever it has to change a value. The required calls to
mprotect are prohibitively expensive, ruling out this so-
lution for any system which aims to be performing well.
The code generated for this example does not compute At this point we should remember how the dynamic linker
the GOT address twice for each call to getboth. The works and how the GOT is used. Each GOT entry be-
function intfoo uses the provided pointer and does not longs to a certain symbol and depending on how the sym-
need the GOT address. To preserve the semantics of the bol is used, the dynamic linker will perform the relo-
first code this additional function had to be introduced; cation at startup time or on demand when the symbol
it is now merely a wrapper around intfoo. If it is pos- is used. Of interest here are the relocations of the first
sible to write the sources for a DSO to have all global group. We know exactly when all non-lazy relocation
variables in a structure and pass the additional parameter are performed. So we could change the access permis-
to all internal functions, then the benefit on IA-32 can be sion of the part of the GOT which is modified at startup
big. time to forbid write access after the relocations are done.
Creating objects this way is enabled by the -z relro
But it must be kept in mind that the code generated for the linker option. The linker is instructed to move the sec-
changed example is worse than what would be created for tions, which are only modified by relocations, onto sep-
the original on most other architectures. As can be seen, arate memory page and emit a new program header en-
in the x86-64 case the extra parameter to intfoo would try PT GNU RELRO to point the dynamic linker to these
VERS_1.0 { The problem is that unless the DSO containing the defi-
global: index; nitions is used at link time, the linker cannot add a ver-
local: *; sion name to the undefined reference. Following the rules
};
for symbol versioning [4] this means the earliest version
available at runtime is used which usually is not the in-
VERS_2.0 {
global: index; tended version. Going back to the example in section 3.7,
} VERS_1.0; assume the program using the DSO would be compiled
expecting the new interface index2 . Linking happens
without the DSO, which contains the definition. The ref-
erence will be for index and not index@@VERS 2.0. At
We have two definitions of index and therefore the name runtime the dynamic linker will find an unversioned ref-
must be mentioned by the appropriate sections for the two erence and versioned definitions. It will then select the
versions. oldest definition which happens to be index1 . The re-
sult can be catastrophic.
It might also be worthwhile pointing out once again that
the call to index1 in index2 does not use the PLT It is therefore highly recommended to never depend on
and is instead a direct, usually PC-relative, jump. undefined symbols. The linker can help to ensure this if
-Wl,-z,defs is added to compiler command line. If it
With a simple function definition, like the one in this ex- is really necessary to use undefined symbols the newly
But all this means that the selection of the directories is If either GLOBAL RPATH or LOCAL RPATH is empty the
under the control of the administrator. The program’s au- dynamic linker will be forced to look in the CWD. When
thor can influence these setting only indirectly by doc- constructing strings one must therefore always be careful
umenting the needs. But there is also way for the pro- about empty strings.
grammer to decide the path directly. This is sometimes
important. The system’s setting might not be usable by The second issue run paths have is that either that paths
all programs at the same time. like /usr/lib/someapp are not “relocatable”. I.e., the
package cannot be installed in another place by the user
For each object, DSO as well as executable, the author without playing tricks like creating symbolic links from
can define a “run path”. The dynamic linker will use the /usr/lib/someapp to the real directory. The use of rel-
value of the path string when searching for dependencies ative paths is possible, but highly discouraged. It might
of the object the run path is defined in. Run paths comes be OK in application which always control their CWD,
is two variants, of which one is deprecated. The runpaths but in DSOs which are used in more than one application
are accessible through entries in the dynamic section as using relative paths means calling for trouble since the
field with the tags DT RPATH and DT RUNPATH. The dif- application can change the CWD.
ference between the two value is when during the search
for dependencies they are used. The DT RPATH value is A solution out of the dilemma is an extension syntax for
used first, before any other path, specifically before the all search paths (run paths, but also LD LIBRARY PATH).
path defined in the LD LIBRARY PATH environment vari- If one uses the string $ORIGIN this will represent the ab-
able. This is problematic since it does not allow the user solute path of the directory the file containing this run
to overwrite the value. Therefore DT RPATH is depre- path is in. One common case for using this “dynamic
cated. The introduction of the new variant, DT RUNPATH, string token” (DST) is a program (usually installed in a
corrects this oversight by requiring the value is used after bin/ directory) which comes with one or more DSOs it
the path in LD LIBRARY PATH. needs, which are installed in the corresponding lib/ di-
rectory. For instance the paths could be /bin and /lib
If both a DT RPATH and a DT RUNPATH entry are avail- or /usr/bin and /usr/lib. In such a case the run
able, the former is ignored. To add a string to the run path of the application could contain $ORIGIN/../lib
path one must use the -rpath or -R for the linker. I.e., which will expand in the examples case just mentioned
on the gcc command line one must use something like to /bin/../lib and /usr/bin/../lib respectively.
12 The dynamic linker is of course free to avoid the triple search since
gcc -Wl,-rpath,/some/dir:/dir2 file.o after the first one it knows the result.
$ORIGIN is not the only DST available. The GNU libc This process should not be executed mindlessly, though.
dynamic currently recognizes two more. The first is $LIB Not having the DSO on the direct dependency list any-
which is useful on platforms which can run in 32- and 64- more means the symbol lookup path is altered. If the
bit mode (maybe more in future). On such platforms the DSO in question contains a definition of a symbol which
replacement value in 32-bit binaries is lib and for 64- also appears in a second DSO and now that second DSO
bit binaries it is lib64. This is what the system ABIs is earlier in the lookup path, the program’s semantics
specify as the directories for executable code. If the plat- might be altered. This is a rare occurrence, though.
form does not know more than one mode the replacement
value is lib. This DST makes it therefore easy to write
Makefiles creating the binaries since no knowledge about
the differentiation is needed.
$ ldd -u -r \
/usr/lib/libgtk-x11-2.0.so.0.600.0
Unused direct dependencies:
/usr/lib/libpangox-1.0.so.0
/lib/libdl.so.2
The following script computes the number of normal and relative relocations as well as the number of PLT entries
present in a binary. If an appropriate readelf implementation is used it can also be used to look at all files in an
archive. If prelink [7] is available and used, the script also tries to provide information about how often the DSO is
used. This gives the user some idea how much “damage” an ill-written DSO causes.
#! /usr/bin/perl
eval "exec /usr/bin/perl -S $0 $*"
if 0;
# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005 Red Hat, Inc.
# Written by Ulrich Drepper <[email protected]>, 2000.
printf("%s: %d relocations, %d relative (%d%%), %d PLT entries, %d for local syms (%d%%)",
$ARGV[$cnt], $relent == 0 ? 0 : $relsz / $relent, $relcount,
$relent == 0 ? 0 : ($relcount * 100) / ($relsz / $relent),
$relent == 0 ? 0 : $pltrelsz / $relent,
$relent == 0 ? 0 : $pltrelsz / $relent - $extplt,
$relent == 0 ? 0 : ((($pltrelsz / $relent - $extplt) * 100)
/ ($pltrelsz / $relent)));
if ($users >= 0) {
printf(", %d users", $users);
}
printf("\n");
}
The method to handle arrays of string pointers presented in section 2.4.3 show the principle method to construct data
structures which do not require relocations. But the construction is awkward and error-prone. Duplicating the strings
in multiple places in the sources always has the problem of keeping them in sync.
Bruno Haible suggested something like the following to automatically generate the tables. The programmer only has
to add the strings, appropriately marked, to a data file which is used in the compilation. The framework in the actual
sources looks like this:
#include <stddef.h>
The string data has to be provided in the file stringtab.h. For the example from section 2.4.3 the data would look
like this:
The macro S takes two parameters: the first is the index used to locate the string and the second is the string itself.
The order in which the strings are provided is not important. The value of the first parameter is used to place the offset
in the correct slot of the array. It is worthwhile running these sources through the preprocessor to see the results. This
way of handling string arrays has the clear advantage that strings have to be specified only in one place and that the
order they are specified in is not important. Both these issues can otherwise easily lead to very hard to find bugs.
The array msgidx in this cases uses the type unsigned int which is in most cases 32 bits wide. This is usually far
too much to address all bytes in the string collectionin msgstr. So, if size is an issue the type used could be uint16 t
or even uint8 t.
Note that both arrays are marked with const and therefore are not only stored in the read-only data segment and there-
fore shared between processes, preserving, precious data memory, making the data read-only also prevents possible
security problems resulting from overwriting values which can trick the program into doing something harmful.
read-only memory, . . . . . . . . . . . . . . . . . . . . . . . . . . . 5, 25
relative address, . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
relative relocation, . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5, 28
relinfo, . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
relocatable binary, . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
relocatable object file, . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
relocation, . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
counting, . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
RTLD DEEPBIND, . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
RTLD GLOBAL, . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
run path, . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
scope, lookup, . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
security, . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11, 30
Security Enhanced Linux, . . . . . . . . . . . . . . . . . . . . . . . 31
segment, . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
SELinux, . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
shared library, . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
E Revision History
2004-8-4 Warn about aliases of static objects. Significant change to section 2.2 to introduce new features of gcc 4.0.
Version 1.99.
2004-8-27 Update code in appendices A and B. Version 2.0.
2004-9-23 Document RTLD DEEPBIND and sprof. Version 2.2.