185 lines
10 KiB
Markdown
185 lines
10 KiB
Markdown
|
# Linkers part 5
|
|||
|
|
|||
|
## Shared Libraries Redux
|
|||
|
|
|||
|
Yesterday I talked about how shared libraries work. I realized that I should
|
|||
|
say something about how linkers implement shared libraries. This discussion
|
|||
|
will again be ELF specific.
|
|||
|
|
|||
|
When the program linker puts position dependent code into a shared library, it
|
|||
|
has to copy more of the relocations from the object file into the shared
|
|||
|
library. They will become dynamic relocations computed by the dynamic linker at
|
|||
|
runtime. Some relocations do not have to be copied; for example, a PC relative
|
|||
|
relocation to a symbol which is local to shared library can be fully resolved
|
|||
|
by the program linker, and does not require a dynamic reloc. However, note that
|
|||
|
a PC relative relocation to a global symbol does require a dynamic relocation;
|
|||
|
otherwise, the main executable would not be able to override the symbol. Some
|
|||
|
relocations have to exist in the shared library, but do not need to be actual
|
|||
|
copies of the relocations in the object file; for example, a relocation which
|
|||
|
computes the absolute address of symbol which is local to the shared library
|
|||
|
can often be replaced with a `RELATIVE` reloc, which simply directs the dynamic
|
|||
|
linker to add the difference between the shared library’s load address and its
|
|||
|
base address. The advantage of using a `RELATIVE` reloc is that the dynamic
|
|||
|
linker can compute it quickly at runtime, because it does not require
|
|||
|
determining the value of a symbol.
|
|||
|
|
|||
|
For position independent code, the program linker has a harder job. The
|
|||
|
compiler and assembler will cooperate to generate special relocs for position
|
|||
|
independent code. Although details differ among processors, there will
|
|||
|
typically be a `PLT` reloc and a `GOT` reloc. These relocs will direct the program
|
|||
|
linker to add an entry to the PLT or the GOT, as well as performing some
|
|||
|
computation. For example, on the i386 a function call in position independent
|
|||
|
code will generate a `R_386_PLT32` reloc. This reloc will refer to a symbol as
|
|||
|
usual. It will direct the program linker to add a PLT entry for that symbol,
|
|||
|
if one does not already exist. The computation of the reloc is then a
|
|||
|
PC-relative reference to the PLT entry. (The `32` in the name of the reloc
|
|||
|
refers to the size of the reference, which is 32 bits). Yesterday I described
|
|||
|
how on the i386 every PLT entry also has a corresponding GOT entry, so the
|
|||
|
`R_386_PLT32` reloc actually directs the program linker to create both a PLT
|
|||
|
entry and a GOT entry.
|
|||
|
|
|||
|
When the program linker creates an entry in the PLT or the GOT, it must also
|
|||
|
generate a dynamic reloc to tell the dynamic linker about the entry. This will
|
|||
|
typically be a `JMP_SLOT` or `GLOB_DAT` relocation.
|
|||
|
|
|||
|
This all means that the program linker must keep track of the PLT entry and the
|
|||
|
GOT entry for each symbol. Initially, of course, there will be no such entries.
|
|||
|
When the linker sees a PLT or GOT reloc, it must check whether the symbol
|
|||
|
referenced by the reloc already has a PLT or GOT entry, and create one if it
|
|||
|
does not. Note that it is possible for a single symbol to have both a PLT entry
|
|||
|
and a GOT entry; this will happen for position independent code which both
|
|||
|
calls a function and also takes its address.
|
|||
|
|
|||
|
The dynamic linker’s job for the PLT and GOT tables is to simply compute the
|
|||
|
`JMP_SLOT` and `GLOB_DAT` relocs at runtime. The main complexity here is the
|
|||
|
lazy evaluation of PLT entries which I described yesterday.
|
|||
|
|
|||
|
The fact that C permits taking the address of a function introduces an
|
|||
|
interesting wrinkle. In C you are permitted to take the address of a function,
|
|||
|
and you are permitted to compare that address to another function address. The
|
|||
|
problem is that if you take the address of a function in a shared library, the
|
|||
|
natural result would be to get the address of the PLT entry. After all, that is
|
|||
|
address to which a call to the function will jump. However, each shared library
|
|||
|
has its own PLT, and thus the address of a particular function would differ in
|
|||
|
each shared library. That means that comparisons of function pointers generated
|
|||
|
in different shared libraries may be different when they should be the same.
|
|||
|
This is not a purely hypothetical problem; when I did a port which got it
|
|||
|
wrong, before I fixed the bug I saw failures in the Tcl shared library when it
|
|||
|
compared function pointers.
|
|||
|
|
|||
|
The fix for this bug on most processors is a special marking for a symbol which
|
|||
|
has a PLT entry but is not defined. Typically the symbol will be marked as
|
|||
|
undefined, but with a non-zero value–the value will be set to the address of
|
|||
|
the PLT entry. When the dynamic linker is searching for the value of a symbol
|
|||
|
to use for a reloc other than a `JMP_SLOT` reloc, if it finds such a specially
|
|||
|
marked symbol, it will use the non-zero value. This will ensure that all
|
|||
|
references to the symbol which are not function calls will use the same value.
|
|||
|
To make this work, the compiler and assembler must make sure that any reference
|
|||
|
to a function which does not involve calling it will not carry a standard PLT
|
|||
|
reloc. This special handling of function addresses needs to be implemented in
|
|||
|
both the program linker and the dynamic linker.
|
|||
|
|
|||
|
## ELF Symbols
|
|||
|
|
|||
|
OK, enough about shared libraries. Let’s go over ELF symbols in more detail.
|
|||
|
I’m not going to lay out the exact data structures–go to the ELF ABI for that.
|
|||
|
I’m going to take about the different fields and what they mean. Many of the
|
|||
|
different types of ELF symbols are also used by other object file formats, but
|
|||
|
I won’t cover that.
|
|||
|
|
|||
|
An entry in an ELF symbol table has eight pieces of information: a name, a
|
|||
|
value, a size, a section, a binding, a type, a visibility, and undefined
|
|||
|
additional information (currently there are six undefined bits, though more may
|
|||
|
be added). An ELF symbol defined in a shared object may also have an associated
|
|||
|
version name.
|
|||
|
|
|||
|
The name is obvious.
|
|||
|
|
|||
|
For an ordinary defined symbol, the section is some section in the file
|
|||
|
(specifically, the symbol table entry holds an index into the section table).
|
|||
|
For an object file the value is relative to the start of the section. For an
|
|||
|
executable the value is an absolute address. For a shared library the value is
|
|||
|
relative to the base address.
|
|||
|
|
|||
|
For an undefined reference symbol, the section index is the special value
|
|||
|
`SHN_UNDEF` which has the value `0`. A section index of `SHN_ABS` (`0xfff1`)
|
|||
|
indicates that the value of the symbol is an absolute value, not relative to
|
|||
|
any section.
|
|||
|
|
|||
|
A section index of `SHN_COMMON` (`0xfff2`) indicates a common symbol. Common
|
|||
|
symbols were invented to handle Fortran common blocks, and they are also often
|
|||
|
used for uninitialized global variables in C. A common symbol has unusual
|
|||
|
semantics. Common symbols have a value of zero, but set the size field to the
|
|||
|
desired size. If one object file has a common symbol and another has a
|
|||
|
definition, the common symbol is treated as an undefined reference. If there is
|
|||
|
no definition for a common symbol, the program linker acts as though it saw a
|
|||
|
definition initialized to zero of the appropriate size. Two object files may
|
|||
|
have common symbols of different sizes, in which case the program linker will
|
|||
|
use the largest size. Implementing common symbol semantics across shared
|
|||
|
libraries is a touchy subject, somewhat helped by the recent introduction of a
|
|||
|
type for common symbols as well as a special section index (see the discussion
|
|||
|
of symbol types below).
|
|||
|
|
|||
|
The size of an ELF symbol, other than a common symbol, is the size of the
|
|||
|
variable or function. This is mainly used for debugging purposes.
|
|||
|
|
|||
|
The binding of an elf symbol is global, local, or weak. A global symbol is
|
|||
|
globally visible. A local symbol is only locally visible (e.g., a static
|
|||
|
function). Weak symbols come in two flavors. A weak undefined reference is like
|
|||
|
an ordinary undefined reference, except that it is not an error if a relocation
|
|||
|
refers to a weak undefined reference symbol which has no defining symbol.
|
|||
|
Instead, the relocation is computed as though the symbol had the value zero.
|
|||
|
|
|||
|
A weak defined symbol is permitted to be linked with a non-weak defined symbol
|
|||
|
of the same name without causing a multiple definition error. Historically
|
|||
|
there are two ways for the program linker to handle a weak defined symbol. On
|
|||
|
SVR4 if the program linker sees a weak defined symbol followed by a non-weak
|
|||
|
defined symbol with the same name, it will issue a multiple definition error.
|
|||
|
However, a non-weak defined symbol followed by a weak defined symbol will not
|
|||
|
cause an error. On Solaris, a weak defined symbol followed by a non-weak
|
|||
|
defined symbol is handled by causing all references to attach to the non-weak
|
|||
|
defined symbol, with no error. This difference in behaviour is due to an
|
|||
|
ambiguity in the ELF ABI which was read differently by different people. The
|
|||
|
GNU linker follows the Solaris behaviour.
|
|||
|
|
|||
|
The type of an ELF symbol is one of the following:
|
|||
|
|
|||
|
* `STT_NOTYPE`: no particular type.
|
|||
|
* `STT_OBJECT`: a data object, such as a variable.
|
|||
|
* `STT_FUNC`: a function
|
|||
|
* `STT_SECTION`: a local symbol associated with a section. This type of symbol
|
|||
|
is used to reduce the number of local symbols required, by changing all
|
|||
|
relocations against local symbols in a specific section to use the
|
|||
|
STT_SECTION symbol instead.
|
|||
|
* `STT_FILE`: a special symbol whose name is the name of the source file which
|
|||
|
produced the object file.
|
|||
|
* `STT_COMMON`: a common symbol. This is the same as setting the section index
|
|||
|
to `SHN_COMMON`, except in a shared object. The program linker will normally
|
|||
|
have allocated space for the common symbol in the shared object, so it will
|
|||
|
have a real section index. The `STT_COMMON` type tells the dynamic linker
|
|||
|
that although the symbol has a regular definition, it is a common symbol.
|
|||
|
* `STT_TLS`: a symbol in the Thread Local Storage area. I will describe this in
|
|||
|
more detail some other day.
|
|||
|
|
|||
|
ELF symbol visibility was invented to provide more control over which symbols
|
|||
|
were accessible outside a shared library. The basic idea is that a symbol may
|
|||
|
be global within a shared library, but local outside the shared library.
|
|||
|
|
|||
|
* `STV_DEFAULT`: the usual visibility rules apply: global symbols are visible
|
|||
|
everywhere.
|
|||
|
* `STV_INTERNAL`: the symbol is not accessible outside the current executable
|
|||
|
or shared library.
|
|||
|
* `STV_HIDDEN`: the symbol is not visible outside the current executable or
|
|||
|
shared library, but it may be accessed indirectly, probably because some code
|
|||
|
took its address.
|
|||
|
* `STV_PROTECTED`: the symbol is visible outside the current executable or
|
|||
|
shared object, but it may not be overridden. That is, if a protected symbol
|
|||
|
in a shared library is referenced by other code in the shared library, that
|
|||
|
other code will always reference the symbol in the shared library, even if
|
|||
|
the executable defines a symbol with the same name.
|
|||
|
|
|||
|
I’ll described symbol versions later.
|
|||
|
|
|||
|
More tomorrow.
|
|||
|
|