ELF is the object and executable format used by Linux and many Unix-like systems. Its ELF header locates the program-header table, used by the loader, and the section-header table, used mainly by linkers and other tooling.
Sections #
Sections organize object-file content such as code, data, symbols, relocations, and string tables. They are not the same as loadable segments: a linker groups sections into segments according to the runtime memory layout.
| Section | Purpose | Typical Type |
|---|---|---|
.text | Executable machine code | SHT_PROGBITS |
.rodata | Read-only data | SHT_PROGBITS |
.data | Initialized writable data | SHT_PROGBITS |
.bss | Zero-initialized writable data | SHT_NOBITS |
.symtab / .dynsym | Static or dynamic symbols | SHT_SYMTAB / SHT_DYNSYM |
.rela.* / .rel.* | Relocation entries | SHT_RELA / SHT_REL |
.shstrtab | Section-name strings | SHT_STRTAB |
Migrated Reference Material #
The Executable and linkable Format was developed in the late 1980’s by folks at the Unix System Laboratories, a short-lived company owned by AT&T whose primary errand is to develop and maintain the Unix System V. ELF-formatted binaries first appeared in the System V Release 4 in 1989, accompanied by a System V ABI document the as the specification.
All 4 types of ELF files are referred to as object files by the specification.
This series of articles will align with the specification and avoid calling .o file as object files.
Standardization #
The Executable and linkable Format was developed in the late 1980’s by folks at the Unix System Laboratories, a short-lived company owned by AT&T whose primary errand is to develop and maintain the Unix System V. ELF-formatted binaries first appeared in the System V Release 4 in 1989, accompanied by a System V ABI document the as the specification.
Linux used a.out format in its early days since 1991 and switched to ELF c. 1995.
first appeared standardized by the Tool Interface Standards (TIS) Committee. TIS was an organization consists of representatives from Absoft, Autodesk, Borland International Corporation, IBM Corporation, Intel Corporation.
After the committee dismissed, the maintainance of
ELF is designed with extension mechanism in mind. Specifically, TIS wanted to allow third-party ABI developer … . As a result, many fields only contains 1 legal value (macro) as per the spec, but may has new value of ad-hoc semantics up to some third-party.
For portability consideration, ELF uses no bit fields (sub-byte data unit).
The Executable and Linkable File (ELF) format is widely used by various *NIX systems, including Linux, Solaris and *BSD, as the format of object files, shared objects (equivalence of Windows’ DLL) and executables. Modern Linux kernel itself is also packaged as an ELF executable before compressed as vmlinuz.
This article gives an overview of the ELF format.
Overview #
There are 4 types of ELF files (defined by e_type enum below), all sharing the same ELF layout in the following diagram,
with the exception that ELF Relocatables (ET_REL, i.e. .o files) don’t have the program header table:
The ELF specification:
- mandates that the ELF header must appear at the beginning of the file.
- allows arbitrary ordering and file offset of the PHT, SHT, and sections, although all known assemblers & linkers put the PHT (if present) immediately after the ELF header, and SHT before the end of the file.
Data Types #
The ELF specification defines 2 variants of ELF files, ELF32 and ELF64, for delivering programs targeting 32-bit word-length CPU (e.g. protected mode x86/x86-64, ARM, etc) and 64-bit CPU (e.g. 64-bit mode x86-64, ARM64, etc). ELF32 and ELF64 share most structure definitions and differ only by the size of pointers and file offset being 32 or 64 bits.
For most Linux distros, ELF-related structures are defined in the /usr/include/elf.h (from glibc).
Regardless of ELF32 or 64:
ELF*_Halfare 16-bit unsigned integersELF*_Word/ ELF*_Swordare 32-bit unsigned / signed integersELF*_Xword/ ELF*_Sxwordare 64-bit unsigned / signed integers
/* Type for a 16-bit quantity. */
typedef uint16_t Elf32_Half;
typedef uint16_t Elf64_Half;
/* Types for signed and unsigned 32-bit quantities. */
typedef uint32_t Elf32_Word;
typedef int32_t Elf32_Sword;
typedef uint32_t Elf64_Word;
typedef int32_t Elf64_Sword;
/* Types for signed and unsigned 64-bit quantities. */
typedef uint64_t Elf32_Xword;
typedef int64_t Elf32_Sxword;
typedef uint64_t Elf64_Xword;
typedef int64_t Elf64_Sxword;
/* Type of addresses. */
typedef uint32_t Elf32_Addr;
typedef uint64_t Elf64_Addr;
/* Type of file offsets. */
typedef uint32_t Elf32_Off;
typedef uint64_t Elf64_Off;
/* Type for section indices, which are 16-bit quantities. */
typedef uint16_t Elf32_Section;
typedef uint16_t Elf64_Section;
/* Type for version symbol information. */
typedef Elf32_Half Elf32_Versym;
typedef Elf64_Half Elf64_Versym;ELF (File) Header #
The ELF header contains the offset of the section header table (SHT) and program header table (PHT) (if present) in the file, which in turn contains program headers and section headers that locate each section and segment.
The ELF header is defined as:
#define EI_NIDENT (16)
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf64_Half e_type; /* Object file type */
Elf64_Half e_machine; /* Architecture */
Elf64_Word e_version; /* Object file version */
Elf64_Addr e_entry; /* Entry point virtual address */
Elf64_Off e_phoff; /* Program header table file offset */
Elf64_Off e_shoff; /* Section header table file offset */
Elf64_Word e_flags; /* Processor-specific flags */
Elf64_Half e_ehsize; /* ELF header size in bytes */
Elf64_Half e_phentsize; /* Program header table entry size */
Elf64_Half e_phnum; /* Program header table entry count */
Elf64_Half e_shentsize; /* Section header table entry size */
Elf64_Half e_shnum; /* Section header table entry count */
Elf64_Half e_shstrndx; /* Section header string table index */
} Elf64_Ehdr;The comment should have explained most fields. Section header table (SHT) and Program header table (PHT) will be discussed in detail after the ELF header is explained.
e_ident[]: The ELF Identification
#
The first 16 bytes of the ELF header (i.e. also the first 16 bytes of an ELF file) are critical metadata about this ELF file.
e_type - identifies one of the 4 ELF types
#
e_type identifies this ELF file being one of the 4 aforementioned types: relocatable (.o), executable, shared object, or kernel dump.
/* Legal values for e_type (object file type). */
#define ET_NONE 0 /* unknown file type */
#define ET_REL 1 /* Relocatable file */
#define ET_EXEC 2 /* Executable file */
#define ET_DYN 3 /* Shared object file */
#define ET_CORE 4 /* Core file */
#define ET_NUM 5 /* Number of defined types */
#define ET_LOOS 0xfe00 /* OS-specific range start */
#define ET_HIOS 0xfeff /* OS-specific range end */
#define ET_LOPROC 0xff00 /* Processor-specific range start */
#define ET_HIPROC 0xffff /* Processor-specific range end */- Interpretation of value in
[ET_LOOS, ET_HIOS]are subjected to differentEI_OSABI - Interpretation of value in
[ET_LOPROC, ET_HIPROC]are subjected to differente_machine
Search for ET_* in elf.h for valid values of OS-specific / processor-specific e_type.
e_machine - identifies the ISA of instructions
#
e_machine identifies the target CPU ISA (instruction set architecture) of instructions held in code sections e.g. .text, .init / .fini etc.
Some examples are:
/* Legal values for e_machine (architecture). */
#define EM_NONE 0 /* No machine */
#define EM_SPARC 2 /* SUN SPARC */
#define EM_386 3 /* Intel 80386 */
#define EM_ARM 40 /* ARM */
#define EM_X86_64 62 /* AMD x86-64 architecture */
#define EM_BPF 247 /* Linux BPF -- in-kernel virtual machine */
#define EM_CSKY 252 /* C-SKY */
#define EM_NUM 253e_version
#
Must be the same as e_ident[EI_VERSION], must be EV_CURRENT=1.
e_entry
#
- For executables (i.e.
e_type = ET_EXEC),e_entryholds the memory address of the first instruction where the process starts executing. OS kernel should set thePCregister toe_entryafter loading is finished. Depending on the CPU model on which / modes in which this executable is designed to run, this may or may not be a virtual address (think about supporting x86 real mode). - Otherwise, this field must hold 0.
e_flags - Processor-specific flags
#
e_flags documents processor-specific flags associated with the file, whose interpretation is subject to e_machine.
Search EF_* in elf.h for valid values.
ELF for x86 and x86_64 doesn’t use this field, and usually holds 0. e.g. some valid values for ARM CPUs are:
#define EF_ARM_RELEXEC 0x01
#define EF_ARM_HASENTRY 0x02
#define EF_ARM_INTERWORK 0x04
#define EF_ARM_APCS_26 0x08
#define EF_ARM_APCS_FLOAT 0x10
#define EF_ARM_PIC 0x20e_ehsize - the size of this ELF header
#
e_ehsize holds the size of its containing Elf32_Ehdr or Elf64_Ehdr in bytes, i.e. being either 52 or 64 bytes.
Section and Segment View #
There are 2 views into an ELF file:
- the section view is for the static linker (
ld), and divides an ELF file into sections. A static linker doesn’t care about PHT or segments. - the segment view is for the dynamic linker (
/lib/ld.so), and divides and ELF file into segments. A dynamic linker doesn’t care about SHT or sections. That being said, a segment is always an aggregate of a few adjacent sections, such that sections with similar properties are always loaded as a whole chunk at runtime.
SHT and sections, and PHT and segments are explained the next chapters.
The Section View #
SHT and sections define the section view of an ELF file. A static linker doesn’t care about PHT or segments, which is discussed later.
This section explains how SHT works, and gives an introduction to some special sections.
Section Header Table (SHT) #
The SHT is a list of struct Elf64_Shdr each known as a section header.
A section header provides metadata about a section.
The location and size of the SHT within an ELF file can be determined by inspecting the ELF header:
- the offset of the SHT into an ELF file is given in
e_shoff - the size of each section header (in bytes) is given in
e_shentsize - The number of section headers in the SHT is given in
e_shnum
The first section header in SHT must be full zero. (with one exception for AMD64 when the number of program headers >= 0xffff (an outrageous number). Search for PN_XNUM here for details)
The structure of each section header is defined as:
typedef struct
{
Elf64_Word sh_name; /* Section name (string tbl index into .shstrtab) */
Elf64_Word sh_type; /* Section type */
Elf64_Xword sh_flags; /* Section flags */
Elf64_Addr sh_addr; /* Section virtual addr at execution */
// ELF compiled for AMD64 should not use this field.
Elf64_Off sh_offset; /* Section file offset */
Elf64_Xword sh_size; /* Section size in bytes */
Elf64_Word sh_link; /* Link to another section */
Elf64_Word sh_info; /* Additional section information */
Elf64_Xword sh_addralign; /* Section alignment */
Elf64_Xword sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;Most fields should be axiomatic, others are explained below.
Info.
There is not a clear boundary between the semantics of
sh_typeandsh_flags, and not all mix-and-match of the values of these fields makes sense.Roughly
sh_typeidentify what the content of the section is, andsh_flagtellsThere are 2 reasons for this.
- The specification dictates a few special sections, identified by their name, with fixed
sh_typeandsh_flags. These special sections satisfy almost all needs for modern use of ELF. There is very few reason for compilers and linkers to use a . We will be talking about these special sections later.- Even if you writes assembly and use custom section names, the assembler known whether its a code or data section Usage of these special sections are very pervasive, to the extend that a compiler- Mainstream assemblers and linkers always create and the same collection of sections with stereotyped patterns of types and flags. can only have fixed
sh_typeThe design ofsh_typeandsh_flagshas become rather meaningless because these special sections must have Such patterns have become a , to the extent that not all mix-and-match ofsh_typeandsh_typeare meaningful, or somesh_typeandsh_flagsare only permitted for a section that has a specific special name. For example, ELF specification leaves it unspecified what if anSHT_SYMTABsection is not named.symtab.
sh_type, sh_link, and sh_info
#
sh_typeindicates the type of content held in this section, and determines the interpretation ofsh_linkandsh_info.sh_link, if used, is always an index of another section into the SHT.sh_info, if used, holds an arbitrary 32-bit value, but is usually an index into a string table.
An entry must be of this type iff. it is the first entry in the SHT.
Program data.
The section holds information defined by the program written by a programmer, as opposed to compiler-generated sections for ELF metadata.
Symbol table.
This section holds a section table used by the static linker.
only 1 section of such type is allowed in any ELF file, and its name must be .symtab.
See also SHT_DYNSYM.
One greater than the symbol table index of the last local symbol (binding STB_LOCAL).
.shstrtab
.rela.text
Don’t get confused with GNU_HASH, see below.
Dynamic linking information.
This section will stay in an dedicated DYNAMIC segment.
Note.
This section allows a compiler toolchain vendor to save arbitrary information for later use. It content must conform to the structure described in SysVABI note_section, instead of being arbitrary bytes.
.note.gnu.property
Identical to SHT_PROGBITS (i.e. program data), except that this section doesn’t occupy space in the ELF file.
.bss
same as SHT_RELA.
same as SHT_RELA.
Same as SHT_SYMTAB.
Same as SHT_SYMTAB.
The one and only .dynsym
This type of sections may only appear in relocatable, and defines a section group.
See this section for how section group are handled by the linker.
Relative relocation entries without explicit addends or info.
RELR sections are introuduced recently c. 2017.
.relr.dyn
The table above list standard, platform-independent values of sh_type, which are numerically smaller; while
values in the range
0x60000000 ~ 0x6fffffffare OS-specificA production example are the
GNU_HASHsection type found in many ELF files:c#define SHT_GNU_HASH 0x6ffffff6 /* GNU-style hash table. */values in the range
0x70000000 ~ 0x7fffffffare processor-specific.values in the range
0x80000000 ~ 0x8fffffffare reserved for applications and won’t be used by the specification.
sh_flags - section attributes
#
sh_flags is a collection of bit flags OR-ed together, each known as an attribute of this section.
Its type is Word (i.e. 32 bits) for ELF32, but Xword (i.e. 64 bits) for ELF64.
The difference in bit length is not a problem, since currently only less than a dozen of flags are currently defined.
Lower bits have standard defined semantics, while the most significant 12 bits are OS-specific and processor-specific.
SHF_WRITE (1 << 0)
is writable.
Memory occupied by this section at runtime may be written to by the program, thus the linker should be move this section to a writable segment.
.data
.rodata, .text
SHF_ALLOC (1 << 1)
occupies memory during execution, as opposed to temporary link-time symbol table or string table that will never loaded into runtime memory.
.rela.*, .shstrtab, etc.
SHF_EXECINSTR (1 << 2)
contains executable instructions.
SHF_MERGE (1 << 4)
holds content that may be merged to eliminate duplication.
- if
SHF_STRINGSflag is unset, this section must holds a list of elements of eachsh_entsizebytes in size. - if
SHF_STRINGSflag is also set, this section must holds a list of elements where each element is a null-terminated string.
Regardlessly, a linker when linking multiple relocatables, should compare the elements of this section, with that of all sections in all relocatables with the same section name, the same sh_type and sh_flag. Identical elements should be merged and only one should make it into the final linking result.
SHF_STRINGS (1 << 5)
contains null-terminated strings. - according to the specification.
That being said, in production this flag is only used together with SHF_MERGE;
specifically, a normal .strtab or .shstrtab will not use this flag.
SHF_INFO_LINK (1 << 6)
SHF_LINK_ORDER (1 << 7)
SHF_OS_NONCONFORMING (1 << 8)
SHF_GROUP (1 << 9)
SHF_TLS (1 << 10)
SHF_COMPRESSED (1 << 11)
Special Sections #
This section is not supposed to be stripped in any type of ELF file (relocatables, executables, shared objects).
Removing this section is not possible without hacking - objcopy and strip won’t do.
A corrupted .shstrtab in a .o file won’t prevent it from being linked into an executable or SO, but
some sections / segments may be incorrectly merged,
since Section names of a relocatable is important to the linker
Section names are significant in that special section, identified by their name,
e.g. .text, are significant when the linker is merging .text.
These 2 sections
- always appears in an
.ofile (relocatables), and - will be
strip-ed by default, regardless of the ELF file type. - are usually adjacent in an ELF file.
.interp
in-place string bytes (not a string table index) of the dynamic interperter path:
Hex dump of section '.interp':
0x00000318 2f6c6962 36342f6c 642d6c69 6e75782d /lib64/ld-linux-
0x00000328 7838362d 36342e73 6f2e3200 x86-64.so.2.GNU system has some interesting values defined as being OS-specific:
#define SHT_LOOS 0x60000000 /* Start OS-specific. */
#define SHT_GNU_ATTRIBUTES 0x6ffffff5 /* Object attributes. */
#define SHT_GNU_HASH 0x6ffffff6 /* GNU-style hash table. */
#define SHT_GNU_LIBLIST 0x6ffffff7 /* Prelink library list */
#define SHT_CHECKSUM 0x6ffffff8 /* Checksum for DSO content. */
#define SHT_LOSUNW 0x6ffffffa /* Sun-specific low bound. */
#define SHT_SUNW_move 0x6ffffffa
#define SHT_SUNW_COMDAT 0x6ffffffb
#define SHT_SUNW_syminfo 0x6ffffffc
#define SHT_GNU_verdef 0x6ffffffd /* Version definition section. */
#define SHT_GNU_verneed 0x6ffffffe /* Version needs section. */
#define SHT_GNU_versym 0x6fffffff /* Version symbol table. */
#define SHT_HISUNW 0x6fffffff /* Sun-specific high bound. */
#define SHT_HIOS 0x6fffffff /* End OS-specific type */
#define SHT_LOPROC 0x70000000 /* Start of processor-specific */
#define SHT_HIPROC 0x7fffffff /* End of processor-specific */
#define SHT_LOUSER 0x80000000 /* Start of application-specific */
#define SHT_HIUSER 0x8fffffff /* End of application-specific */Program Header Table (PHT) & Segments #
Symbols #
Symbol Tables #
SHT_SYMTAB records static-link symbols, whereas SHT_DYNSYM records symbols needed by the dynamic linker. Each symbol stores a name offset, binding, type, section index, value, and size.
Symbols #
ELF has 2 symbol tables. The primary reason for needing 2 is because the dynamic one must be readable by the ld.so thus must go into a LOAD segment, and the static one does not.
ELF spec allows all 4 types to have 1 or both. But usually:
- a reloctable (
.o) contains only.symtab - an executable contains none
- an shared object
.socontains only.dynsym
.Dynamic #
If an ELF file participates in dynamic linking, its PHT will have an item of type PT_DYNAMIC which locates a segment containing only 1 section,
the .dynamic section.
The kernel and dynamic linker will not inspect the SHT (nor the ELF header other than the entry point and PHT location, you can break the ELF file header in any other way and still make an executable work)and is not aware of any information about sections at run time.
It obtain all its required information from .dynamic.
The .dynamic section (and its dedicated segment) is meant to be the dynamic-linker-oriented index page of this ELF file.
Consider this section the index page for dynamic linker.
This is the first place ld.so should go first for any information.
This section contains very much information, as this is the only approach to provide information to the dynamic linker.
everything necessary for the dynamic linker to work, including
Among others, this section gives to the ld.so:
- where is
.strtab, for library names and SONAME indexes - what other shared objects are required by this file
- where is
.dynsymand (offset and size of.dynstr) , for symbols import / export .rela.dynand.relr.dyn, relocation table, offset, total size, and entry size.- misc
- where to find
.init/.fini/.init_arrray/fini_arraysection - the SONAME of this ELF file
.dynamic section always resides in that dedicated segment, and contains a list of the following structures:
/* Dynamic section entry. */
typedef struct
{
Elf32_Sword d_tag; /* Dynamic entry type */
union
{
Elf32_Word d_val; /* Integer value */
Elf32_Addr d_ptr; /* Address value */
} d_un;
} Elf32_Dyn;
typedef struct
{
Elf64_Sxword d_tag; /* Dynamic entry type */
union
{
Elf64_Xword d_val; /* Integer value */
Elf64_Addr d_ptr; /* Address value */
} d_un;
} Elf64_Dyn;The interpretation of d_un depends on the value of d_tag.
The list is marked end by a d_tag = DT_NULL (0) entry.
On some Linux distro the famous /usr/lib/libc.so is a plain text file:
/* GNU ld script
Use the shared library, but some functions are only in
the static library, so try that secondarily. */
OUTPUT_FORMAT(elf64-x86-64)
GROUP ( /usr/lib/libc.so.6 /usr/lib/libc_nonshared.a AS_NEEDED ( /usr/lib/ld-linux-x86-64.so.2 ) )The kernel upon receiving a
execvesyscalls first loads the ELF header into the memory to inspect thee_entryentrance point and location and size of the PHT.PHT is loaded into the memory (hopefully already done when loading the ELF header, since both structures reside at the beginning of an ELF file) to locate the
PH_DYNAMICsegment.The
Elf32_Dyn / Elf64_Dynarray within that segment is inspected for the following information- offset and size of
.strtab, for string table of library names and SONAME; - (
DT_NEEDED) other shared objects this file depends on; - offset of the dynamic symbol table (in section view’s
.dynsym), and offset and size of its companion string table (in section view’s.dynstr), for symbols to import / export. - (in section view’s
.rela.dynand.relr.dyn), relocation table, offset, total size, and entry size - other …documented behavior
- offset and size of
ld.sopreloads requested shared objects, specified asLD_PRELOADenv var, if presented- the
--preloadCLI option when invoking the dynamic linker directly. /etc/ld.so.preload, if exist
names of shared objects listed as
DT_NEEDEDare handled in the following way:- if the name starts with a slash (
/), it’s treated as an absolute path; - otherwise, if the name contains a slash (
/), it’s treated as a path relative to the current working directory; - otherwise (no slash), the shared object is searched in the following order, stop at the first one found:
- if
DT_RPATHis present ANDDT_RUNPATHis absent, the directory specified byDT_RPATH. The use ofDT_RPATHfield is deprecated. - if not in “secure-execution” mode, directoies specified by env var
LD_LIBRARY_PATH. - if
DT_RUNPATHis present, the directories specified by it. /etc/ld.so.cache, which maps unqualified soname to absolute FS path for all installed so libs./libor/lib64/usr/libor/usr/lib64
- if
If the above procedure failed to locate an shared object, execution is abort.
- if the name starts with a slash (
…
RPATH Respect #
Imagine an executable foo that has an 0x01 NEEDED dependency on a libfoo.so.8 which resolves to /opt/lib/libfoo.so.8.
This shared object is custom build which has a RPATH embeded
This article explains how the STB_WEAK value of ST_BIND how such a symbol works.
For Static Linked Objects #
During the static linking stage:
- multiple global (a.k.a strong) symbols of the same name during the causes a
multiple definitionerror. - a unique global symbol and one or more weak symbol(s) of the same name, the definition of the global symbol is used
- mroe weak symbol(s) of the same name and no global symbol, the first weak symbol definition appear in the ordered input to the linker win.
- one weak symbol, no global symbol, the weak is used.
For Dynamic Linked Objects #
During the static linking between main.o and shared objects
- multiple strong symbol doesn’t trigger an error. The first
.sothat provides a the symbol definition is linked against (i.e. write intolddrequired SO)
global foo:weak;exporting a weak symbol with global <label>:weak
Comments
Read or comment on GitHub (sign-in required to comment)