Differences Between ELF-32 and ELF-64

Have you ever wondered if ELF is portable between 32-bit and 64-bit targets? Probably not, but this might be a common scenario for you if you work on 32-bit embedded devices but use a 64-bit host. Or maybe you’ve developed tooling for 32-bit MCUs and are transitioning to working on 64-bit targets.

The ELF object file format is one of the most commonly used today. Most build systems provide an output to this format, and ELF is commonly used to output coredumps. The format varies in subtle ways for 32-bit and 64-bit targets though, which can present problems for tools supporting both. This post will highlight the main differences and is intended as a quick reference for these differences.

ELF Background

If you’re running a program on a UNIX-ish system, you’re probably using an ELF file. If you’re building firmware for your MCU, it’s probably an ELF file targeted for your MCU architecture. MacOS and Windows of course have their own slightly different formats, Mach-O and Portable Executable. This format contains information on loading your program into memory or linking with other object files to form an executable object. Static libraries are ELFs. Compiling a source file produces an ELF. Coredumps can be collected as ELFs. You might even be an ELF 😱!

ELF Internals

This post won’t detail the complete specification. For that task, I recommend these:

ELF-64 vs ELF-32

At a high level, the two formats are structurally very similar but will differ in the following ways:

  • Data Representation: data types for addresses, offsets, and sizes will match their native sizes and alignments
  • Padding: Equivalent data types in ELF-64 may be packed differently compared to ELF-32 to minimize padding

Throughout this post I will highlight the differences in the internal structures in the following ways:

  • If a data type differs or is a different native size, the data type will be bold (i.e. Elf64_Addr)
  • If a field is in a different order within a structure, the field name will be bold (i.e. st_size )
  • If both a data type and field order differ, both the data type and field name will be bold

At the end of each structure section, I will also include a few bullet points to summarize if sizes, field order, or both differ.

Data Representation Differences

The main place where the two formats differ is in how they represent data. One difficult part of looking at either standard is that data types are not defined with a naming scheme that makes the size of each type obvious, e.g. uint32_t. The following tables highlight the differences between the two:

ELF-32 Type Size Alignment ELF-64 Type Size Alignment
Elf32_Addr 4 4 Elf64_Addr 8 8
Elf32_Off 4 4 Elf64_Off 8 8
Elf32_Half 2 2 Elf64_Half 2 2
Elf32_Word 4 4 Elf64_Word 4 4
Elf32_Sword 4 4 Elf64_Sword 4 4
unsigned char 1 1 unsigned char 1 1
- - - Elf64_Xword 8 8
- - - Elf64_Sxword 8 8

The differences are that address and offset fields match their native sizes of 32 vs 64 bits and ELF-64 introduces a long type with signed and unsigned variants.

File Header

The table below shows the structure file headers for both types.

Field ELF-32 Type Field ELF-64 Type
e_ident[16] unsigned char e_ident[16] unsigned char
e_type Elf32_Half e_type Elf64_Half
e_machine Elf32_Half e_machine Elf64_Half
e_version Elf32_Word e_version Elf64_Word
e_entry Elf32_Addr e_entry Elf64_Addr
e_phoff Elf32_Off e_phoff Elf64_Off
e_shoff ELf32_Off e_shoff Elf64_Off
e_flags Elf32_Word e_flags Elf64_Word
e_ehsize Elf32_Half e_ehsize Elf64_Half
e_phentsize Elf32_Half e_phentsize Elf64_Half
e_phnum Elf32_Half e_phnum Elf64_Half
e_shentsize Elf32_Half e_shentsize Elf64_Half
e_shnum Elf32_Half e_shnum Elf64_Half
e_shstrndx Elf32_Half e_shstrndx Elf64_Half

The file headers differ in two ways:

  • Header size
  • e_ident byte specification

e_ident Bytes

The e_ident field is composed of 16 bytes, which differ very minimally between the two formats. You very likely would not encounter an issue here, but there is a slight difference. ELF-64 specifies byte 7 as EI_OSABI and byte 8 as EI_ABIVERSION with padding starting at byte 9. ELF-32 lacks these components and instead starts padding at byte 7. ELF-32 specifies padding bytes as 0, so we at least have well-defined values for forward compatibility.

Byte Name ELF-32 Index ELF-64 Index
EI_MAG0 0 0
EI_MAG1 1 1
EI_MAG2 2 2
EI_MAG3 3 3
EI_CLASS 4 4
EI_DATA 5 5
EI_VERSION 6 6
EI_OSABI - 7
EI_ABIVERSION - 8
EI_PAD 7 9

The other noticeable difference in practice is byte 4, EI_CLASS. For ELF-32 this byte is 0x1, while for ELF-64 it is 0x2.

Section Header

Field ELF-32 Type Field ELF-64 Type
sh_name Elf32_Word sh_name Elf64_Word
sh_type Elf32_Word sh_type Elf64_Word
sh_flags Elf32_Word sh_flags Elf64_Word
sh_addr Elf32_Addr sh_addr Elf64_Addr
sh_offset Elf32_Off sh_offset Elf64_Off
sh_size Elf32_Word sh_size Elf64_Word
sh_link Elf32_Word sh_link Elf64_Word
sh_info Elf32_Word sh_info Elf64_Word
sh_addralign Elf32_Word sh_addralign Elf64_Word
sh_entsize Elf32_Word sh_entsize Elf64_Word

Section headers differ due to:

  • Header size

Symbol Table Entry

Field ELF-32 Type   Field ELF-64 Type
st_name Elf32_Word st_name Elf64_Word
st_value Elf32_Addr st_info unsigned char
st_size Elf32_Word st_other unsigned char
st_info unsigned char st_shndx Elf64_Half
st_other unsigned char st_value Elf64_Addr
st_shndx Elf32_Half st_size Elf64_Xword

Symbol Table Entries differ by:

  • Field order
  • Entry size

Relocation Entries

Relocation Entry Without Addend

Field ELF-32 Type   Field ELF-64 Type
r_offset Elf32_Addr r_offset Elf64_Addr
r_info Elf32_Word r_info Elf64_Xword

Relocation Entries without addend differ by:

  • Entry size

Relocation Entry With Addend

Field ELF-32 Type   Field ELF-64 Type
r_offset Elf32_Addr r_offset Elf64_Addr
r_info Elf32_Word r_info Elf64_Xword
r_addend ELf32_Sword r_addend Elf64_Sxword

Relocation Entries with Addend differ by:

  • Entry size

Program Headers

Field ELF-32 Type   Field ELF-64 Type
p_type Elf32_Word p_type Elf64_Word
p_offset Elf32_Off p_flags Elf64_Word
p_vaddr Elf32_Addr p_offset Elf64_Off
p_paddr Elf32_Addr p_vaddr Elf64_Addr
p_filesz Elf32_Word p_paddr Elf64_Addr
p_memsz Elf32_Word p_filesz Elf64_Xword
p_flags Elf32_Word p_memsz Elf64_Xword
p_align Elf32_Word p_align Elf64_Xword

Program Headers differ due to the following:

  • Field order
  • Header size

Other Differences

  • Slight differences between the two for “None” values of certain fields. ELF-32 generally more consistent and specifies them while ELF-64 only sometimes does.
  • ELF-64 provides some reserved ranges for OS-specific values. These reserved ranges are not specified for ELF-32.

Conclusion

If you’ve made it this far, first, apologies because that probably means you’ve hit a nasty bug or problem with a tool. Second, thanks for sticking around, and I hope this provides a quick and easy resource to compare ELF-32 vs ELF-64.

Eric Johnson is a Firmware Solutions Engineer at Memfault. Eric previously worked on embedded software teams at Walgreens Health and Athos