Next: , Previous: , Up: Top   [Contents][Index]


4 ELF Configurations

4.1 ELF Configuration Parameters

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.

4.2 The ELF Configuration Registry

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:

4.3 Enumeration configuration parameters

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." },
            ...];

4.4 Mask configuration parameters

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." },
            ...];

4.5 Configuration parameters used by this pickle

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.

4.6 Getting printed representations of configuration parameters

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"

4.7 Checking valid configuration parameters

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

4.8 Using configuration parameters in types

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.

4.9 Debugging the registry

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: , Previous: , Up: Top   [Contents][Index]