"""
ELF64 constants and structures for building vmcore files.
"""
import struct
from enum import IntEnum
from typing import NamedTuple
# ELF Magic
ELF_MAGIC = b"\x7fELF"
# ELF Class (32-bit vs 64-bit)
[docs]
class ElfClass(IntEnum):
ELFCLASS32 = 1
ELFCLASS64 = 2
# ELF Data Encoding (Endianness)
[docs]
class ElfData(IntEnum):
ELFDATA2LSB = 1 # Little endian
ELFDATA2MSB = 2 # Big endian
# ELF Version
EV_CURRENT = 1
# ELF OS/ABI
ELFOSABI_NONE = 0
# ELF Type
[docs]
class ElfType(IntEnum):
ET_NONE = 0
ET_REL = 1
ET_EXEC = 2
ET_DYN = 3
ET_CORE = 4 # Core dump - this is what we need
# ELF Machine Types
[docs]
class ElfMachine(IntEnum):
EM_NONE = 0
EM_386 = 3
EM_X86_64 = 62
EM_ARM = 40
EM_AARCH64 = 183
EM_S390 = 22
EM_PPC64 = 21
EM_RISCV = 243
# Program Header Types
[docs]
class PhdrType(IntEnum):
PT_NULL = 0
PT_LOAD = 1
PT_NOTE = 4
# Program Header Flags
[docs]
class PhdrFlags(IntEnum):
PF_X = 0x1 # Execute
PF_W = 0x2 # Write
PF_R = 0x4 # Read
# Note Types
[docs]
class NoteType(IntEnum):
NT_PRSTATUS = 1
NT_PRFPREG = 2
NT_PRPSINFO = 3
# ELF64 Header size: 64 bytes
ELF64_EHDR_SIZE = 64
# ELF64 Program Header size: 56 bytes
ELF64_PHDR_SIZE = 56
[docs]
class ArchInfo(NamedTuple):
"""Architecture-specific information."""
machine: ElfMachine
endianness: ElfData
page_size: int = 4096
# Supported architectures
ARCHITECTURES = {
"x86_64": ArchInfo(ElfMachine.EM_X86_64, ElfData.ELFDATA2LSB),
"aarch64": ArchInfo(ElfMachine.EM_AARCH64, ElfData.ELFDATA2LSB),
"arm64": ArchInfo(ElfMachine.EM_AARCH64, ElfData.ELFDATA2LSB), # alias
"s390x": ArchInfo(ElfMachine.EM_S390, ElfData.ELFDATA2MSB),
"ppc64le": ArchInfo(ElfMachine.EM_PPC64, ElfData.ELFDATA2LSB),
"ppc64": ArchInfo(ElfMachine.EM_PPC64, ElfData.ELFDATA2MSB),
"riscv64": ArchInfo(ElfMachine.EM_RISCV, ElfData.ELFDATA2LSB),
}
[docs]
def pack_elf64_ehdr(
machine: ElfMachine, endianness: ElfData, phdr_count: int, phdr_offset: int
) -> bytes:
"""
Pack an ELF64 header for a core dump.
Args:
machine: Target architecture (e.g., EM_X86_64)
endianness: Little or big endian
phdr_count: Number of program headers
phdr_offset: File offset to program headers
Returns:
64 bytes representing the ELF64 header
"""
# Determine struct format based on endianness
fmt_prefix = "<" if endianness == ElfData.ELFDATA2LSB else ">"
# e_ident (16 bytes)
e_ident = bytearray(16)
e_ident[0:4] = ELF_MAGIC
e_ident[4] = ElfClass.ELFCLASS64
e_ident[5] = endianness
e_ident[6] = EV_CURRENT
e_ident[7] = ELFOSABI_NONE
# Rest is padding (already zeros)
# Pack the rest of the header
# Format: HHIQQQIHHHHHH
# H = uint16, I = uint32, Q = uint64
header_data = struct.pack(
f"{fmt_prefix}HHIQQQIHHHHHH",
ElfType.ET_CORE, # e_type (2 bytes)
machine, # e_machine (2 bytes)
EV_CURRENT, # e_version (4 bytes)
0, # e_entry (8 bytes) - not used for core dumps
phdr_offset, # e_phoff (8 bytes) - program header offset
0, # e_shoff (8 bytes) - no section headers
0, # e_flags (4 bytes)
ELF64_EHDR_SIZE, # e_ehsize (2 bytes)
ELF64_PHDR_SIZE, # e_phentsize (2 bytes)
phdr_count, # e_phnum (2 bytes)
0, # e_shentsize (2 bytes) - no sections
0, # e_shnum (2 bytes) - no sections
0, # e_shstrndx (2 bytes) - no section string table
)
return bytes(e_ident) + header_data
[docs]
def pack_elf64_phdr(
endianness: ElfData,
p_type: PhdrType,
p_flags: int,
p_offset: int,
p_vaddr: int,
p_paddr: int,
p_filesz: int,
p_memsz: int,
p_align: int = 0,
) -> bytes:
"""
Pack an ELF64 program header.
Args:
endianness: Little or big endian
p_type: Segment type (PT_LOAD, PT_NOTE, etc.)
p_flags: Segment flags (read/write/execute)
p_offset: File offset of segment data
p_vaddr: Virtual address
p_paddr: Physical address
p_filesz: Size in file
p_memsz: Size in memory
p_align: Alignment
Returns:
56 bytes representing the program header
"""
fmt_prefix = "<" if endianness == ElfData.ELFDATA2LSB else ">"
# ELF64 Phdr format: IIQQQQQQ
return struct.pack(
f"{fmt_prefix}IIQQQQQQ",
p_type, # p_type (4 bytes)
p_flags, # p_flags (4 bytes)
p_offset, # p_offset (8 bytes)
p_vaddr, # p_vaddr (8 bytes)
p_paddr, # p_paddr (8 bytes)
p_filesz, # p_filesz (8 bytes)
p_memsz, # p_memsz (8 bytes)
p_align, # p_align (8 bytes)
)
[docs]
def pack_elf_note(
endianness: ElfData, name: bytes, note_type: int, desc: bytes
) -> bytes:
"""
Pack an ELF note entry.
Notes have the format:
- namesz (4 bytes): length of name including null terminator
- descsz (4 bytes): length of descriptor
- type (4 bytes): note type
- name (namesz bytes, padded to 4-byte boundary)
- desc (descsz bytes, padded to 4-byte boundary)
Args:
endianness: Little or big endian
name: Note name (without null terminator)
note_type: Note type identifier
desc: Note descriptor data
Returns:
The packed note entry
"""
fmt_prefix = "<" if endianness == ElfData.ELFDATA2LSB else ">"
# Add null terminator to name
name_with_null = name + b"\x00"
namesz = len(name_with_null)
descsz = len(desc)
# Calculate padding to 4-byte alignment
def align4(size: int) -> int:
return (size + 3) & ~3
name_padded = name_with_null.ljust(align4(namesz), b"\x00")
desc_padded = desc.ljust(align4(descsz), b"\x00")
header = struct.pack(f"{fmt_prefix}III", namesz, descsz, note_type)
return header + name_padded + desc_padded