Author: Mugabi Siro

Category: ELF Support

Summary:

This entry describes user space ELF object file types on a GNU/Linux system. It is written from the programmer's perspective. The C language is mainly assumed. References to ELF structures and constants are according to elf(5) which adheres to definitions in <elf.h>. Development platform used was Ubuntu 12.04 AMD64.

Tags: gnu/linux toolchain gnu linux s/w development elf support

Introduction

An object file is a structured grouping of blocks of bytes according to a certain format. ELF object files are structured according ot the ELF object file format. An ELF file contains header structures that describe the organization of the object file and various sections that hold different kinds of information including:

  • The translated object code or data
  • Symbol and relocation information for the linking phases
  • Debugging information for use with a debugger such as gdb(1)

As far as program linking is concerned, ELF object file types fall under three main categories: relocatable object files, executable object files, and shared object files. There also exists a type of ELF object files known as core dump files which get generated by the operating system upon certain conditions of abnormal process termination. These disk files are an image of (portions of) the process' memory at the time of its termination. Core dump files are typically used with a debugger (e.g. gdb(1)) for inspection of the process' state at the time of its termination. Core dump files will not be discussed here. See core(5).

Now, an application can consist of several programs: an executable and zero or more shared object files. Building the application involves a chain of events by a set of distinct development tools: The compiler proper (e.g. cc1) and assembler, as(1), first translate source code (ASCII text files) into relocatable object files. The next phase is usually the linking stage with the link editor (A.K.A. static linker), ld(1)1. The link editor then generates an executable or a shared object file from the translated relocatable object files. Typically, other system files such as compiler support for language run-time (e.g. C Run-time), the C library and other libraries are involved in the generation of the link editor's output files2. ld(1) appropriately concatenates the various blocks of the object files together, decides on runtime locations for the concatenated blocks, and modifies various locations within the code and data blocks. ld(1) has a minimal understanding of the target architecture; it is mainly concerned with program addressing and instruction formats. The compiler and assembler do most of the work.

An object file may be relinkable, i.e. used as input by the link editor for a subsequent link. Candidates are the relocatable object files and shared object files. An object file may be loadable, i.e. capable of being loaded into memory to be run as the main program (executable object files) or to be used along with the main program (shared objects files).

Relinkable object files contain extensive symbol and relocation information required by the linkers. Statically linked executable files generally require no symbol information. For example strip(1) discards all symbols in the symbol table, .symtab. In the case of dynamically linked executables, strip(1) will skip symbols in the dynamic linking symbol table, .dynsym, to allow symbol lookup and binding by the program interpreter (A.K.A dynamic linker) at execution time.

Relocatable object files

These are the assembler generated object files that haven't yet undergone any processing by ld(1). Relocatable object files are "raw" bacause they contain unrelocated addresses of code and data, and hence cannot be loaded into memory to be executed as the main program, or for dynamic linking into the virtual address space of a main program instance.

Use the -c switch to instruct gcc(1) to stop processing right before the linking phase. Include the -v switch to view the options passed to cc1. By default, relocatable object files have the .o extension.

There also exist a type of archive files which are comprised of a collection of relocatables. These type of archive files typically find application as static libraries.

Executable object files

These are loadable object files that hold machine code suitable for execution. The loadable segments in the executable object file contribute to the memory layout of the process. By default, the gcc(1) compiler driver generates a dynamically linked executable. These executable object files remain incomplete (unresolved external dependencies) and require the help of the program interpreter to undergo further dynamic linking at application load time and run-time. Alternatively, an executable can be statically linked. A statically linked executable is a fully linked executable that, once loaded into memory by the kernel, can run without any further linking. Generating a statically linked executable requires explicit inclusion of the -static switch in gcc(1)'s command line.

Use the gcc -v switch to view the serialized process in which cc1, as, and collect23 get invoked. To view ld(1)'s command line, specify gcc -Wl,-v.

Shared object Files

These are relinkable and loadable object files. Like executable object files, they also feature loadable segments. Typically, they undergo special processing during both the translation and static linking phases as compared to the generation of ordinary executables. At execution time, their loadable segments undergo relocation into the virtual address space of a process. Shared objects are mostly applied as shared libraries (.so extension). Check out the Shared Libraries Tutorial entry for an introduction to building and using shared libraries on a GNU/Linux system. Another form of shared objects are special position independent executables which, like shared libraries, are relocated by the dynamic linker to an arbitrary virtual address that the OS chooses for them. However, like ordinary dynamically linked executables, they remain executable and symbols defined by the module cannot be interposed by shared libraries. These details are discussed in more depth in the next section.

Shared objects hold code and data suitable for dynamic linking only; for example, shared libraries (conventionally) cannot be used in the generation of statically linked executables. Linking with shared objects occurs in two different contexts:

  • Static linking phase with ld(1)

    ld(1) processes a shared object with other relinkable object files to produce a dynamically linked executable or another shared object.

  • Dynamic linking phase with the program interpreter

    • At application load time

      The program interpreter combines the shared object with a dynamically linked executable when creating an application image in memory.

    • During application run-time

      An executing instance of a program, with the help of the program interpreter, may load and execute functions defined in a shared object via the dlopen(3) interface.

Relocation Information

As far as symbols in an object file are concerned, relocation is the process of connecting symbolic references with symbolic definitions. Relinkable object files and, generally, dynamically linked executables must include this internal relocation information which enables modification of the object file's sections during the different linking contexts: Relocatable object files contain relocation info for the link editor, while dynamically linked executables and shared object files contain ld(1) generated relocation info for the program interpreter. Relocation information is stored in the relocation tables of the object file. During the linking phases, the result of the relocation is stored somewhere in the respective object module (instance) with the reference.

For illustration, consider the following program:

$ cat libfoot.c 
#include <stdio.h>

extern int bar;
extern int bfunc(int);
void lfunc(void)
{
    bar = 1;
    printf("libfoot: %d\n", bfunc(bar));
}

NOTE: The build platform used here was Ubuntu 12.04 AMD64: gcc(1)'s -m switches for x86-64 userspace support three code models via -mcmodel=. The default, -mcmodel=small, is used here. Basically, the program and its symbols must be linked in the lower 2GB of the address space. For more details, see gcc(1), and other (online) documentation4.

$ gcc -Wall -O2 -fpic -c libfoot.c 
$ gcc -shared -Wl,-soname=libfoot.so libfoot.o -o libfoot.so

Relocation Tables

Relocation info is contained in the relNAME or relaNAME relocation tables. By convention, NAME is supplied by the section name to which the relocations apply. For example, rel.text applies to text, rel.data applies to data, etc.

Relocation tables are arrays of Elf32_Rel(a) or Elf64_Rel(a) structs (see elf(5)). Members of these structs are:

  • r_offset Gives the location at which to apply the relocation. For a relocatable object file, the value is the byte offset from the beginning of the section to the storage unit affected by the relocation. For a dynamically linked executable or shared object, the value is the virtual address of the storage unit affected by the relocation.

  • r_info Gives both the symbol table index with respect to which the relocation must be made and the type of relocation to apply. Relocation types are processor specific defined in <elf.h>.

  • The Rela types include the r_addend member which specifies a constant addend used to compute the value to be stored into the relocatable field.

Consider the following disassembly of a libfoot.o instance:

$ objdump -D libfoot.o
[...]
0000000000000000 <lfunc>:
     0: 48 83 ec 08             sub    $0x8,%rsp
     4: 48 8b 05 00 00 00 00    mov    0x0(%rip),%rax        # b <lfunc+0xb>
     b: bf 01 00 00 00          mov    $0x1,%edi
    10: c7 00 01 00 00 00       movl   $0x1,(%rax)
    16: e8 00 00 00 00          callq  1b <lfunc+0x1b>
    1b: 48 8d 35 00 00 00 00    lea    0x0(%rip),%rsi        # 22 <lfunc+0x22>
    22: 89 c2                   mov    %eax,%edx
    24: bf 01 00 00 00          mov    $0x1,%edi
    29: 31 c0                   xor    %eax,%eax
    2b: 48 83 c4 08             add    $0x8,%rsp
    2f: e9 00 00 00 00          jmpq   34 <lfunc+0x34>

Disassembly of section .rodata.str1.1:

0000000000000000 <.LC0>:
     0: 6c                      insb   (%dx),%es:(%rdi)
     1: 69 62 66 6f 6f 74 3a    imul   $0x3a746f6f,0x66(%rdx),%esp
     8: 20                      .byte 0x20
     9: 25                      .byte 0x25
     a: 64 0a 00                or     %fs:(%rax),%al
[...]

where .LC0 is the disassembly of the "libfoot: %d\n" string constant in ASCII. Now, checking against the corresponding as(1) generated relocation info:

$ readelf --relocs libfoot.o

Relocation section '.rela.text' at offset 0x638 contains 4 entries:
    Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000007  000c00000009 R_X86_64_GOTPCREL 0000000000000000 bar - 4
000000000017  000d00000004 R_X86_64_PLT32    0000000000000000 bfunc - 4
00000000001e  000800000002 R_X86_64_PC32     0000000000000000 .LC0 - 4
000000000030  000e00000004 R_X86_64_PLT32    0000000000000000 __printf_chk - 4
[...]

the values in the r_offset field give the displacements into the object file where the link editor will apply the symbol's relocation. A description of the relocation types can be found in /usr/include/elf.h5:

#define R_X86_64_PC32       2   /* PC relative 32 bit signed */
#define R_X86_64_PLT32      4   /* 32 bit PLT address */
#define R_X86_64_GOTPCREL   9   /* 32 bit signed PC relative */

The relocation types instruct the link editor on the type of relocation to apply. In this case:

$ objdump -D libfoot.so
[...]
0000000000000660 <lfunc>:
 660:   48 83 ec 08             sub    $0x8,%rsp
 664:   48 8b 05 5d 09 20 00    mov    0x20095d(%rip),%rax        # 200fc8 <_DYNAMIC+0x1a0>
 66b:   bf 01 00 00 00          mov    $0x1,%edi
 670:   c7 00 01 00 00 00       movl   $0x1,(%rax)
 676:   e8 e5 fe ff ff          callq  560 <bfunc@plt>
 67b:   48 8d 35 64 00 00 00    lea    0x64(%rip),%rsi        # 6e6 <_fini+0xe>
 682:   89 c2                   mov    %eax,%edx
 684:   bf 01 00 00 00          mov    $0x1,%edi
 689:   31 c0                   xor    %eax,%eax
 68b:   48 83 c4 08             add    $0x8,%rsp
 68f:   e9 dc fe ff ff          jmpq   570 <__printf_chk@plt>
[...]
00000000000006e6 <.rodata>:
 6e6:   6c                      insb   (%dx),%es:(%rdi)
 6e7:   69 62 66 6f 6f 74 3a    imul   $0x3a746f6f,0x66(%rdx),%esp
 6ee:   20                      .byte 0x20
 6ef:   25                      .byte 0x25
 6f0:   64 0a 00                or     %fs:(%rax),%al
[...]

where:

  • R_X86_64_GOTPCREL results in the mov 0x20095d(%rip),%rax PC-relative addressing instruction at address 0x664 : The 64-bit %rip Program Counter (PC) register always points to the next instruction and the 0x20095d constant is the displacement between that location and a slot in the Global Offset Table (GOT), for instance 0x200fc8 = 0x20095d + 0x66b. In other words, even though the run-time addresses in this shared object will take different values, this displacement still remains fixed, regardless. This addressing scheme allows for Position Independent Code (PIC). These concepts will be illustrated in the next subsections.

  • R_X86_64_PLT32 causes the link editor to generate 32-bit signed PC-relative addresses into the Procedure Linkage Table (PLT). Notice that callq at address 0x676 is a relative near call and jmpq at address 0x68f is a relative near jump. The immediate operands are signed displacements that get added to %rip. Since these offsets to the PLT slots remain fixed regardless of run-time addresses, these call and branch mechanisms allow for PIC. These concepts will be illustrated in more detail in the next subsections.

  • R_X86_64_PC32 results in a 32-bit signed PC-relative addressing scheme in the lea 0x64(%rip),%rsi instruction. The value 0x64 is the displacement between the .rodata section and this instruction. Again, this offset remains fixed regardless of the run-time addresses, thereby facilitating PIC.

PIC

With Position Independent Code (PIC), all references to locations within the module as well as references to global (internal or external) symbols (variables or functions) are done via PC-relative addressing with known offsets/displacements. The -fPIC/-fpic (shared libraries) and -fPIE/-fpie (position independent executables) compiler switches cause the generation of relocatable object files with PIC code and the appropriate relocation information for the link editor.

PIC shared objects get loaded into arbitrary memory areas of a process' address space. But since references within the module are PC-relative, they continue to work regardless of the run-time address of code and data. However, to account for references to external symbols, indirect references through entries in special tables that contain the relocated addresses of the external symbols are used - of course, internal references to the table entries are also PC-relative. The table entries get updated by the program interpreter at execution time. These special tables are the Global Offset Table (GOT) and Procedure Linkage Table (PLT).

GOT and PLT

The GOT mechanism is based on the fact that since the data segment always follows the fixed size code segment, the displacement/offset between any instruction and the beginning of the data segment is also fixed. By including a table of pointers, i.e. the GOT, at/near the beginning of the data section, and having the program interpreter initialize these pointers with the appropriate run-time addresses, it is then possible for program instructions to perform PC-relative addressing into the GOT for indirect references to externally defined symbols regardless of the run-time addresses. The GOT contains an entry for each externally defined data object that is referenced by the object module.

For relocations of function references, the PLT structure is used. A function call is performed indirectly via a PLT entry. The structure of the GOT and PLT is architecture specific. On most arch's, the PLT is read-only and, therefore, used in combination with the read-write GOT - which gets updated by the program interpreter. Generally, in such cases, each PLT entry requires at least one GOT entry. On other arch's, the PLT is writable and gets updated directly by the program interpreter. Whichever the case, the PLT structure is always used if the function is not guaranteed to be defined in the object file which references it.

The GOT and PLT structures are generated by the link editor according to the relocation info in its input relocatable object files. The link editor also generates a relocation record for each entry in these tables. When generating a shared library, the -shared switch for the link editor is required. Among other things, this option flags ld(1) on the appropriate language run-time support files to use. In the case of PIE generation, the -pie switch is used. This flag causes ld(1) to, say, generate additional relocation info: for example, R_X86_64_RELATIVE (i.e. a relative relocation)6.

At execution time, PIC/PIE modules have to get relocated to their load addresses in arbitrary virtual memory areas of the process' address space. Ordinary executables, on the other hand, have fixed load addresses. Once the application's modules have been loaded (the executable and its shared object dependencies), the remaining part of the relocation process involves updating the contents of the locations pointed to by the r_offset members of each module's relocation tables to point to the appropriate load addresses of their targets. Every external symbol referenced in the module has a corresponding entry in the relocation tables. Nevertheless, it is also possible that a certain location or symbol within the module gets an entry in the relocation tables. Generally, the location pointed to by r_offset resides in a writable section in the data segment of the module - typically in a GOT slot or somewhere in .data. But there exist some obscure scenarios where r_offset points to location in the read-only code segment e.g. the operand of a data transfer or a branching instruction. This is pertinent to object files marked with DF_TEXTREL in the DT_FLAGS entry of the dynamic section (or DT_TEXTREL older ELF binaries).

With PIC/PIE modules and ordinary dynamically linked executables, relocation for references to external symbols always involves the GOT/PLT. The GOT (and PLT for some archs) is updated by the program interpreter and this symbol relocation process involves symbol lookup and binding from a lookup scope. This GOT/PLT based mechanism facilitates symbol interposition7. With PIC shared library modules, internal references to their own global symbols may also go through the GOT and/or PLT to facilitate interposition. In contrast, internal references in a PIE module, to its own global symbols, do not go through the GOT and/or PLT since interposition is not allowed for symbols defined by these shared objects.

The following illustration is specific to x86-64 (which is generally based on the well known IA32 case):

The GOT entry for the externally defined bar object symbol looks like:

Disassembly of section .got:

0000000000200fc8 <.got>:
                ...

where the .got section in the GOT is associated with symbols that will be relocated at load-time. By default on x86-64, the GOT will also include the .got.plt section which is associated with PLT entries.

The link editor also generates a relocation record for each entry in the GOT. For the bar external symbol, its relocation table entry looks like:

Relocation section '.rela.dyn' at offset 0x478 contains 5 entries:
    Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000200fc8  000200000006 R_X86_64_GLOB_DAT 0000000000000000 bar + 0
[...]

where the .rel.dyn relocation table is associated with entries in .got and any other global data symbol (e.g. __dso_handle in .data) that will require relocation.

At module load time, the program interpreter relocates each entry in the GOT so that it contains an absolute run-time address. The R_X86_64_GLOB_DAT relocation entry instructs the program interpreter to update the corresponding GOT entry with the target symbol's load address. See /usr/include/elf.h for a description of relocation types.

In the case of function calls, e.g. bfunc and printf, the references go through the PLT in .plt and the PLT associated section in the in GOT i.e. .got.plt.

$ objdump -D libfoot.so
[...]
Disassembly of section .plt:

0000000000000550 <bfunc@plt-0x10>:
 550:   ff 35 9a 0a 20 00       pushq  0x200a9a(%rip)        # 200ff0 <_GLOBAL_OFFSET_TABLE_+0x8>
 556:   ff 25 9c 0a 20 00       jmpq   *0x200a9c(%rip)        # 200ff8 <_GLOBAL_OFFSET_TABLE_+0x10>
 55c:   0f 1f 40 00             nopl   0x0(%rax)

0000000000000560 <bfunc@plt>:
 560:   ff 25 9a 0a 20 00       jmpq   *0x200a9a(%rip)        # 201000 <_GLOBAL_OFFSET_TABLE_+0x18>
 566:   68 00 00 00 00          pushq  $0x0
 56b:   e9 e0 ff ff ff          jmpq   550 <_init+0x18>

0000000000000570 <__printf_chk@plt>:
 570:   ff 25 92 0a 20 00       jmpq   *0x200a92(%rip)        # 201008 <_GLOBAL_OFFSET_TABLE_+0x20>
 576:   68 01 00 00 00          pushq  $0x1
 57b:   e9 d0 ff ff ff          jmpq   550 <_init+0x18>
[...]
Disassembly of section .got.plt:

0000000000200fe8 <.got.plt>:
    200fe8:       28 0e                   sub    %cl,(%rsi)
    200fea:       20 00                   and    %al,(%rax)
                ...
    201000:       66 05 00 00             add    $0x0,%ax
    201004:       00 00                   add    %al,(%rax)
    201006:       00 00                   add    %al,(%rax)
    201008:       76 05                   jbe    20100f <_GLOBAL_OFFSET_TABLE_+0x27>
    20100a:       00 00                   add    %al,(%rax)
    20100c:       00 00                   add    %al,(%rax)
    20100e:       00 00                   add    %al,(%rax)
    201010:       86 05 00 00 00 00       xchg   %al,0x0(%rip)        # 201016 <_GLOBAL_OFFSET_TABLE_+0x2e>
                ...
[...]

Take the bfunc case for example. Upon the first time call to bfunc, control transfers to the bfunc@plt PLT slot by way of a PC-relative signed displacement. The first instruction in this PLT entry performs a memory-indirect near jump to the value contained in the GOT slot that is 0x200a9a bytes away, i.e. the value contained in GOT slot corresponding to the static address 0x201000. This value actually just points back to the next instruction in the same PLT entry i.e. pushq 0x0 at static address 0x566. Notice that other PLT entries, save the first (which is special), have the same structure. The value in this pushq instruction is specific to this PLT entry and is the offset of the relocation entry for the function which should be called. The next instruction, at static address 0x56b, jumps to the first and special PLT entry which pushes another value to the stack before jumping to back the GOT, but this time, to the location corresponding to the static address 0x200ff8. This slot is automatically populated by the dynamic linker at load time and is its entry point. The dynamic linker now does its thing: according to the link editor generated relocation information for bfunc,

Relocation section '.rela.plt' at offset 0x4f0 contains 3 entries:
    Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000201000  000400000007 R_X86_64_JUMP_SLO 0000000000000000 bfunc + 0

it first updates the GOT entry corresponding to current static location 0x201000 with bfunc's absolute run-time address, before jumping to the symbol's definition. This has the effect that all future calls to bfunc@plt will now only perform an indirect jump to the symbol's definition through the updated GOT slot without the intervention of the dynamic linker.

Notice that the relocation processing of function references is non-trivial, i.e. the initial update of .got.plt entries is relatively time costly. So, by default, most archs (including x86-64) defer the processing of relocations for function calls until the point when the call is actually made. All other kinds of relocations are done at module load time before the shared object can be used. For this reason, the .got section resides in the PT_GNU_RELRO segment (part of the data segment) which is marked read-only as soon as the dynamic linker completes performing symbol relocations. Interestingly, the .got.plt section partly resides in PT_GNU_RELRO and partly in the read-write area of the data segment: The first couple or so slots in .got.plt are special and include the entry point to the dynamic linker. These are populated at load-time and reside in PT_GNU_RELRO. The rest of the slots are associated with the "ordinary" entries in the PLT and therefore reside in the read-write part of the data segment so that they can get updated at run-time.

Delaying relocation for function calls is refered to as lazy relocation/binding. This default behaviour can be changed dynamically at module load time via the LD_BIND_NOW environment variable of the ld-linux.so program interpreter. Alternatively, specifying gcc(1)'s -Wl,-z,now option (i.e. ld(1)'s -z now switch) for the static linking phase will mark the output object file with the DF_BIND_NOW flag in the DT_FLAGS entry of the its dynamic section. This will unconditionally enforce symbol relocation for all symbols referenced by the module at load time. Generally, in this case, only one GOT related section, say, .got, will be present (and entirely) in PT_GNU_RELRO.

Dynamic Linking Information

The Dynamic Section

Dynamically linked object files contain a dynamic section, .dynamic. This table holds the relevant dynamic linking information and is an array of Elf32_Dyn or Elf64_Dyn structures (see elf(5)). The elements of an object module's dynamic section are automatically populated by the link editor to contain overall dynamic linking information.

Of particular interest is the d_tag member of an Elf{32,64}_Dyn struct. Its value indicates the type of table entry. The full listing can be seen in elf(5). DT_NEEDED tags are practically used by all dynamically linked objects, save maybe the program interpreter itself. This tag indicates the required shared object dependencies. Other tags common among dynamically linked objects include those that specify the locations and sizes of the various sections/structures involved in dynamic linking process:

  • DT_RELENT or DT_RELAENT Size in bytes of a single entry in the Rel or Rela (with an addend) relocation tables.
  • DT_REL or DT_RELA Address of .rel.dyn or .rela.dyn
  • DT_RELSZ or DT_RELASZ Total size in bytes of .rel.dyn or .rela.dyn
  • DT_JMPREL Address of .rel.plt or .rela.plt
  • DT_PLTRELSZ Total size in bytes of .rel.plt or .rela.plt
  • DT_PLTREL Type of PLT reloc table, i.e. .rel.plt or .rela.plt
  • DT_PLTGOT Address of the PLT associated .got.plt section in the GOT
  • DT_SYMTAB and DT_SYMENT Address of the dynamic linking symbol table, .dynsym, and size of a single entry in the table
  • DT_STRTAB and DT_STRSZ Address of the dynamic string table, .dynstr, and total size in bytes of the table
  • DT_HASH or DT_GNU_HASH Address of the ELF or GNU-style symbol hash table

The presence of a few others depend on the link editor's command line switches. These include:

  • DT_RPATH/DT_RUNPATH String table offset to library search path as specified via gcc -Wl,-rpath= [-Wl,--enable-new-dtags]8.
  • DT_BIND_NOW Instruct the program interpreter to process all relocations at module load time. This tag will be present if the object file was built with gcc -Wl,-z,now (i.e. ld -z now)

In the more rare cases, the object file may include tags such as DT_TEXTREL which means some relocation updates will also apply to the read-only code segment.

To view the entries in the dynamic section, the readelf [-d|--dynamic] command may be used.

Dynamic Symbol and String Tables

The dynamic linking symbol table, .dynsym, is the set of symbols which are visible from dynamic objects at run-time. In other words, this section is involved in the symbol lookup process during dynamic linking. External symbols that are referenced in the object module are included in this table. Shared libraries automatically add the symbols that they define and export in this section. For executables, using the gcc(1)'s -rdynamic (ld(1)'s -E/--export-dynamic) switch is will cause the link editor to add all internally defined global symbols to .dynsym. Otherwise, the link editor will only add the internally defined global symbols that are referenced by some shared object on its command line. Also see --dynamic-list in ld(1). Unlike .symtab, the .dynsym table is not deleted by strip(1). This table can be viewed via readelf --dyn-syms.

The dynamic string table, dynstr, holds symbol names in .dynsym as well as information such as the sonames of needed shared libraries. This table can be viewed via readelf -[p|R] '.dynstr'

The Symbol Hash Table

The symbol hash table is used during the symbol lookup process and, therefore, an object module participating in dynamic linking must have one. An object file may have only one symbol hash table.

Currently, there exist two symbol hash table implementations on a GNU/Linux system: the older ELF hash table handling and the newer GNU-style hash table handling. The latter introduces a number of optimizations (better CPU cache utilization, a bloom filter, less string comparisons, etc) over the former. The .hash section label becomes .gnu.hash with the GNU version, and the DT_HASH tag of the dynamic section becomes DT_GNU_HASH. If the toolchain supports the newer GNU version, then both the link editor and program interpreter will use it. The hash table implementations are self-contained in an object module and, therefore, individual object modules of an application can independently use one form or the other without conflict.

Resources and Further Reading

  • The readelf(5), elf(5), gcc(1) and ld(1) man pages.

  • ELF: Executable and Linkable Format, Potable Formats Specification, Version 1.1, Tool Interface Standards (TIS). (Available online)

  • How To Write Shared Libraries, Ulrich Drepper, (Available online - Please read it.)

  • ELF: From The Programmer's Perspective, Hongjui Lu. (Available online)

  • The ELF Object File Format: Introduction, Eric Youngdale (Available online)

  • Using LD, The GNU linker (Available online)

  • AMD64 Architecture Programmer's Manual, Vol1: Application Programming, Rev. 3.14, September 2007 (Available online)

  • System V Application Binary Interface: AMD64 Architecture Processor Suppliment (Available online).

  • libelf by Example, Joseph Koshy (Available online)

  • Books

    • Linkers and Loaders, John Levine

    • Computer Systems: A Programmer's Perspective, Randal E. Bryant, David R. O'Hallaron, 2011, Prentice Hall. Written with the student in mind, this is not (strictly) a book on GNU/Linux development but it includes a few chapters that methodically introduce the concepts behind program linking and virtual memory on a GNU/Linux system.

Footnotes

1. An intermediate archive file generation step, which doesn't involve ld(1) but rather the GNU archiver, ar(1), is sometimes performed. [go back]

2. See An Introduction to the GNU/Linux Toolchain. [go back]

3. collect2 is a GCC linker utility that eventually invokes the real static linker ld(1). The former is part of GCC while the latter belongs to the Binutils package. Consult this link for a more details. [go back]

4. For example, check out Section "Architectural Constraints" in the System V Application Binary Interface: AMD64 Architecture Processor Suppliment (available online). [go back]

5. Also check out Section "Conventions" in the System V Application Binary Interface: AMD64 Architecture Processor Suppliment (available online). [go back]

6. The r_offset of a R_X86_64_RELATIVE relocation record may point to an entry in .got which will hold the relocated address of some internally defined function. At execution time, the dynamic linker updates such .got entries with the sum of the load address of the PIE module plus r_addend, which holds the offset of the function definition in the file. The values in these .got entries can then be passed to external modules that need to perform callbacks into the PIE module. [go back]

7. See Interposing Library Functions in Building and Using Shared Libraries Tutorial. [go back]

8. See Using -rpath, An Example [go back]