Next: ELF Basic Types, Previous: Pickles Overview, Up: Top [Contents][Index]
The ELF object format specification, unlike most (all?) its predecessors, was designed with the goal of being extremely flexible and extensible, in order to cover the needs of any conceivable hardware architecture and operating system.
In order to achieve this goal, many of the entities that appear in ELF files, such as sections, symbols, or segments, are pretty generic and configurable. For example, consider the following (simplified) definition of an ELF64 relocation:
type Elf64_RelInfo = struct { uint<32> r_sym; uint<32> r_type; };
Where r_type
contains a code identifying the type of the
relocation. The ELF specification itself doesn’t say what values may
go in r_type
; it is the different supplements for particular
architectures (or machines in ELF parlance) that list the
relocation types used in their machines.
Relocation types, understood as the set of valid codes to be set in a
r_type
field, is just one example of what in poke-elf we call
an ELF configuration parameter.
There are many other configuration parameters: section flags, section types, symbol types, and a large etc. They are often dependent of particular machines and OSes, and there can be many of them: there are often literally hundreds of different relocation types defined by some particular architecture.
As the different ELF pickles get loaded, they populate a registry of
configuration parameters. This registry is a value of the struct type
Elf_Config
that is defined in elf-config.pk, and is
stored in the global variable elf_config
.
There are two kinds of configuration parameters: enumerations and masks. The registry contains several collections of them:
Enumeration configuration parameters, or enums for short, are
sets of numbers or codes. Each entry in an enum represents an
alternative value for some parameter. New enum entries are
constructed using the Elf_Config_UInt
struct type:
type Elf_Config_UInt = struct { uint<32> value; string name; string doc; };
Where name
is a short and descriptive name for the parameter
value and doc
is an English statement describing the meaning of
this particular value.
For example, this is how the definition of a X86_64 relocation type looks like:
Elf_Config_UInt { value = ELF_R_X86_64_PC32, name = "pc32", doc = "PC relative 32 bit signed." }
Adding new enum configuration parameters to the registry is done by
using the add_enum
method of Elf_Config
:
method add_enum = (int<32> machine = -1, string class = "", Elf_Config_UInt[] entries = Elf_Config_UInt[]()) void:
Where machine
is either -1 or an ELF machine code (likely one
of the ELF_EM_*
values defined in elf-common.pk). If
the former, the new parameter is added to the set of common enums.
Otherwise it is added to the set of enums defined for the specified
machine type. Finally, entries
is an array of the different
values this parameter may adopt.
The class
argument is a string that gives a name to the new
configuration parameter. These have names like
reloc-types
or file-classes
. Our ELF pickles use a
definite set of names, documented below, but nothing prevents you to
use your own.
This is how we would add a couple of common relocation types to the register (note the actual ELF specification has none of these, they are all machine-specific):
elf_config.add_enum :class "reloc-types" :entries [Elf_Config_UInt { value = 0, name = "null reloc" }, Elf_Config_UInt { value = 1, name = "PC-relative 16-bit displacement." }];
And this is how we would add relocation types for the X86_64 architecture:
elf_config.add_enum :class "relocation_types" :entries [Elf_Config_UInt { value = ELF_R_X86_64_PC32, name = "pc32", doc = "PC relative 32 bit signed." }, Elf_Config_UInt { value = ELF_R_X86_64_GOT32, name = "got32", doc = "32 bit GOT entry." }, ...];
Mask configuration parameters are sets of bit-masks. Each entry is an
unsigned number determining some valid configuration of bits for the
value of some parameter. New mask entries are constructed using the
Elf_Config_Mask
type:
type Elf_Config_Mask = struct { uint<64> value; string name; string doc; };
Where name
is a short an descriptive name summarizing the
quality of the bit of bits set in value
, and doc
is an
English statement describing the meaning of these particular bits.
For example, this is how the definition of an ARM section flag looks like:
Elf_Config_Mask { value = ELF_SHF_ARM_PURECODE, name = "purecode", doc = "Section contains only code and no data." }
Adding new mask configuration parameters to the registry is done by
using the add_mask
method of Elf_Config
:
method add_mask = (int<32> machine = -1, string class = "", Elf_Config_Mask[] entries = Elf_Config_Mask[]()) void:
Where machine
is either -1 or an ELF machine code. If the
former, the new mask is added to the set of common masks. Otherwise
it is added to the set of masks defined for the specified machine
type. Finally, entries
is an array of the different sub-masks
this parameter may adopt.
As with enums, the class
argument is a string that gives a name
to the new configuration parameter. Masks have names like
"section-flags"
or "segment-flags"
.
This is how we would register a couple of common section flags:
elf_config.add_mask :class "section-flags" :entries [Elf_Config_Mask { value = ELF_SHF_WRITE, name = "write" }, Elf_Config_Mask { value = ELF_SHF_ALLOC, name = "alloc" }];
And this is how we would register section flags for the ARM architecture:
elf_config.add_mask :machine ELF_EM_ARM :class "section-flags" :entries [Elf_Config_Mask { value = ELF_SHF_ARM_ENTRYSECT, name = "entrysect", doc = "Section contains an entry point." }, Elf_Config_Mask { value = ELF_SHF_ARM_PURECODE, name = "purecode", doc = "Section contains only code and no data." }, ...];
As we have mentioned, it is possible to register new configuration parameters in the registry, with arbitrary names. This is certainly useful to the happy poker that is working on some weird ELF extension, or simply playing around.
However, the set of elf-*pk pickles are designed to work with a closed set of configuration parameters. Having extra parameters in the registry is perfectly ok, but if you mess with the parameters below, you are gonna have to face the consequences :)
Note however that adding support for a new machine type or a new operating system shouldn’t require extending the set of configuration parameters: just to add new values to them.
The enum configuration parameters used by this pickle are:
elf-machines
Valid values in e_machine
fields.
file-osabis
Valid values in ei_osabi
fields.
file-encodings
Valid values in ei_data fields.
file-classes
Valid values in ei_class fields.
file-types
Valid values in e_type fields.
section-types
Valid values in sh_type fields.
section-indices
Indices in the file section header table with special meanings.
section-other
Valid values in sh_other fields.
segment-types
Valid values in p_type fields.
reloc-types
Valid values in r_type fields.
dynamic-tag-types
Valid values in d_tag fields.
symbol-types
Valid values in st_type fields.
symbol-bindings
Valid values in st_bind fields.
symbol-visibilities
Valid values in st_visibility fields.
note-tags
Valid tags for notes stored in notes sections.
gnu-properties
Valid values for pr_type
fields in GNU properties.
The mask configuration parameters used by this pickle are:
file-flags
Valid bits in e_flags fields.
section-flags
Valid bits in sh_flags fields.
segment-flags
Valid bits in p_flags fields.
The architecture-specific enum configuration parameters used by this pickle are:
mips-abis
Valid values in the ELF_EF_MIPS_ABI
bits of e_flags
in
MIPS machines.
mips-machines
Valid values in the ELF_EF_MIPS_MACH
bits of e_flags
in
MIPS machines.
mips-architectures
Valid values in the ELF_EF_MIPS_ARCH
bits of eflags
in
MIPS machines.
The architecture-specific mask configuration parameters used by this pickle are:
mips-l-flags
Valid bits in l_flags
fields.
The format_enum
and format_mask
methods of
Elf_Config
return the user-friendly printed representation of
the given alternative value or bitmap. They have the following
prototypes:
method format_enum = (string class, uint<16> machine, uint<32> value) string: method format_mask = (string class, uint<16> machine, uint<64> value) string:
The printed representation of an enum is simply the name
that
was provided when registering it. For example:
(poke) elf_config.format_enum ("reloc-types", ELF_EM_X86_64, 2) "pc32"
The printed representation of a mask is a sequence of the names given
to the different bitmaps at registration time, separated by comma
(,
) characters. For example:
(poke) elf_config.format_mask ("section-flags", ELF_EM_X86_64, 0xf00) "os-nonconforming,group,tls,compressed"
The check_enum
and check_mask
methods of
Elf_Config
check whether the given values are valid for some
particular configuration parameter. They have the following
prototypes:
method check_enum = (string class, uint<16> machine, uint<32> value) int<32>: method check_mask = (string class, uint<16> machine, uint<64> value) int<32>:
Where machine
specifies the machine type and class
the
name of the configuration parameter. They determe wether value
is a valid class
.
For example, this is how we would check whether 57 identifies a valid relocation type in RISCV:
(poke) elf_config.check_enum ("reloc-types", ELF_EM_RISCV, 57) 0
Turns out it doesn’t! :D
The formatting and checking methods described above are mainly used in the ELF pickles in order to implement pretty-printers and data integrity constraints in the several ELF structures holding such values.
For example:
type Elf64_Shdr = struct { Elf_Word sh_type : elf_config.check_enum ("section-types", elf_mach, sh_type); Elf64_Xword sh_flags : elf_config.check_mask ("section-flags", elf_mach, sh_flags); [...] method _print_sh_type = void: { printf "#<%s>", elf_config.format_enum ("section-types", elf_mach, sh_type); } method _print_sh_flags = void: { printf "#<%s>", elf_config.format_mask ("section-flags", elf_mach, sh_flags); } };
However, they are also very useful to the user while poking at existing data (“if these bytes were to be interpreted as ELF section flags in some given arch, which ones they would be?”), composing new data and also when generating reports and statistics.
If you want to get a trace of the configuration parameters as they are
being added to the registry, simply set the elf_config_debug
variable to a non zero value and reload the ELF pickles:
(poke) elf_config_debug = 1 (poke) load elf
Next: ELF Basic Types, Previous: Pickles Overview, Up: Top [Contents][Index]