Class: Rubycc::ObjFile::ELFReader

Inherits:
Object
  • Object
show all
Defined in:
lib/rubycc/objfile/elf_reader.rb

Overview

Reads an ELF64 little-endian x86_64 object for the linker. Two shapes are supported: a relocatable object (ET_REL, a compiler-emitted .o) is fully parsed — sections with their raw bytes, the .symtab, and every .rela.* relocation table; a shared object (ET_DYN, a .so) is read for its dynamic-linking interface — the .dynsym exported/imported symbols and the .dynamic array's DT_SONAME / DT_NEEDED strings.

This is the counterpart of ELFWriter and is designed to round-trip everything that writer emits (see test/test_elf_reader.rb): the parsed Section/Symbol/Relocation value objects carry exactly the fields the writer sets, resolved back from their on-disk encoding (section names via .shstrtab, symbol names via the linked string table, relocation targets via r_info's symbol index and the RELA section's sh_info).

The model is load-then-query: read/read_file parse the whole image up front into arrays and name-indexed lookups so the linker can address any section, symbol, or relocation randomly rather than through a callback stream. Nothing here writes or mutates an object.

For a .so the dynamic tables are located strictly through the section header table (.dynsym / .dynstr / .dynamic by sh_type, sized by sh_size / sh_entsize). The allocated dynamic sections survive strip, so a section-header-less .so is rare enough that the PT_DYNAMIC program header fallback (deriving the symbol count from DT_HASH / DT_GNU_HASH) is deliberately not built here — YAGNI until a real input demands it.

Defined Under Namespace

Classes: DynamicEntry, RawSection, Relocation, RelocationSection, Section, Symbol, VersionDefinition

Constant Summary collapse

ELFMAG =

ELF identification (e_ident) and header field encodings.

"\x7FELF".b
EI_CLASS =
4
EI_DATA =
5
EI_VERSION =
6
ELFCLASS64 =
2
ELFDATA2LSB =
1
EV_CURRENT =
1
EHDR_SIZE =
64
SHDR_SIZE =
64
ET_REL =

e_type values the reader consumes; others are reported numerically and rejected by #read. ET_EXEC is read back like a shared object — through the allocated .dynsym / .dynamic tables located by the section header table — so the executable writer's output can be inspected the same way.

1
ET_EXEC =
2
ET_DYN =
3
EM_X86_64 =

The machines this reader understands. Both are little-endian ELF64, so the whole header/section/symbol layer is shared; only the relocation type names differ (see RELOC_TYPES).

62
EM_AARCH64 =
183
SUPPORTED_MACHINES =
[EM_X86_64, EM_AARCH64].freeze
SHT_NULL =

Section header types.

0
SHT_PROGBITS =
1
SHT_SYMTAB =
2
SHT_STRTAB =
3
SHT_RELA =
4
SHT_DYNAMIC =
6
SHT_NOBITS =
8
SHT_DYNSYM =
11
SHT_GNU_VERDEF =

The version-definition table (.gnu.version_d): the symbol versions this object defines. Its type lives in the gABI's OS-specific range (SHT_LOOS = 0x60000000 upwards), which is why the number looks nothing like the ones above.

0x6FFFFFFD
SHN_UNDEF =

Reserved section indices that a symbol's st_shndx may carry instead of a real section number: an undefined (imported) symbol, an absolute value, and a not-yet-allocated COMMON block.

0
SHN_LORESERVE =
0xFF00
SHN_ABS =
0xFFF1
SHN_COMMON =
0xFFF2
SHN_XINDEX =
0xFFFF
SYM_ENTSIZE =
24
RELA_ENTSIZE =
24
DYN_ENTSIZE =
16
VERDEF_SIZE =

An Elf64_Verdef record and the Elf64_Verdaux records behind it. Neither is an array element: each carries its own byte distance to the next one (vd_next / vda_next), so these sizes bound a single record rather than stride a table (see #parse_version_definitions).

20
VERDAUX_SIZE =
8
VER_DEF_CURRENT =

The only vd_version (VER_DEF_CURRENT) this layout is defined for; a record announcing another revision is not the structure decoded below.

1
VER_FLG_BASE =

vd_flags bits: the definition that names the object itself rather than a version its symbols can bind to, and one whose version holds no symbols.

0x1
VER_FLG_WEAK =
0x2
SYM_BINDINGS =

Symbol binding (st_info >> 4) and type (st_info & 0xF), mapped to symbols for readable queries; an unrecognized value passes through as its integer.

{ 0 => :local, 1 => :global, 2 => :weak }.freeze
SYM_TYPES =
{
  0 => :notype, 1 => :object, 2 => :func, 3 => :section, 4 => :file,
  6 => :tls, 10 => :ifunc
}.freeze
SYM_VISIBILITIES =

Symbol visibility (st_other & 0x3).

{ 0 => :default, 1 => :internal, 2 => :hidden, 3 => :protected }.freeze
RELOC_TYPES =

Relocation types (r_info & 0xFFFFFFFF), keyed by e_machine because the numbering is per-architecture. The ones the toolchain emits are named; any other type is preserved numerically (its name is nil) rather than rejected, so an unfamiliar object is still fully readable. The x86_64 GOTPCREL family addresses a symbol's Global Offset Table slot: 9 is the plain PC-relative GOT reference, and 41/42 its "relaxable" forms the psABI defines for a mov that a linker may rewrite in place to a lea (42 is the REX-prefixed form a 64-bit mov rax, sym@GOTPCREL(%rip) uses).

On aarch64 the picture is different because that machine forms an address in two instructions: CALL26 is the 26-bit branch immediate of a bl/b to a named symbol, ABS64 an absolute 64-bit pointer slot, and the remaining four come in pairs. ADR_PREL_PG_HI21 carries the 21-bit page distance of an adrp and ADD_ABS_LO12_NC the 12-bit within-page offset of the add behind it; ADR_GOT_PAGE and LD64_GOT_LO12_NC are the same split applied to a symbol's Global Offset Table slot, the second patching the scaled immediate of the ldr that reads it.

{
  EM_X86_64 => {
    1 => :R_X86_64_64,
    2 => :R_X86_64_PC32,
    4 => :R_X86_64_PLT32,
    9 => :R_X86_64_GOTPCREL,
    10 => :R_X86_64_32,
    11 => :R_X86_64_32S,
    41 => :R_X86_64_GOTPCRELX,
    42 => :R_X86_64_REX_GOTPCRELX
  }.freeze,
  EM_AARCH64 => {
    257 => :R_AARCH64_ABS64,
    275 => :R_AARCH64_ADR_PREL_PG_HI21,
    277 => :R_AARCH64_ADD_ABS_LO12_NC,
    283 => :R_AARCH64_CALL26,
    311 => :R_AARCH64_ADR_GOT_PAGE,
    312 => :R_AARCH64_LD64_GOT_LO12_NC
  }.freeze
}.freeze
DT_NULL =

Dynamic array tags (.dynamic) this reader interprets: the terminator, the DT_NEEDED shared-library dependencies, and the DT_SONAME of the object itself. Every tag's raw value is still exposed; only these drive behavior.

0
DT_NEEDED =
1
DT_SONAME =
14

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(bytes) ⇒ ELFReader

Returns a new instance of ELFReader.



235
236
237
238
239
240
241
242
243
244
245
# File 'lib/rubycc/objfile/elf_reader.rb', line 235

def initialize(bytes)
  @data = bytes.b
  @sections = []
  @symbols = []
  @relocation_sections = []
  @dynamic_symbols = []
  @dynamic_entries = []
  @needed = []
  @soname = nil
  @version_definitions = []
end

Instance Attribute Details

#dynamic_entriesObject (readonly)

Returns the value of attribute dynamic_entries.



231
232
233
# File 'lib/rubycc/objfile/elf_reader.rb', line 231

def dynamic_entries
  @dynamic_entries
end

#dynamic_symbolsObject (readonly)

Returns the value of attribute dynamic_symbols.



231
232
233
# File 'lib/rubycc/objfile/elf_reader.rb', line 231

def dynamic_symbols
  @dynamic_symbols
end

#entryObject (readonly)

Returns the value of attribute entry.



231
232
233
# File 'lib/rubycc/objfile/elf_reader.rb', line 231

def entry
  @entry
end

#machineObject (readonly)

Returns the value of attribute machine.



231
232
233
# File 'lib/rubycc/objfile/elf_reader.rb', line 231

def machine
  @machine
end

#neededObject (readonly)

Returns the value of attribute needed.



231
232
233
# File 'lib/rubycc/objfile/elf_reader.rb', line 231

def needed
  @needed
end

#relocation_sectionsObject (readonly)

Returns the value of attribute relocation_sections.



231
232
233
# File 'lib/rubycc/objfile/elf_reader.rb', line 231

def relocation_sections
  @relocation_sections
end

#sectionsObject (readonly)

Returns the value of attribute sections.



231
232
233
# File 'lib/rubycc/objfile/elf_reader.rb', line 231

def sections
  @sections
end

#sonameObject (readonly)

Returns the value of attribute soname.



231
232
233
# File 'lib/rubycc/objfile/elf_reader.rb', line 231

def soname
  @soname
end

#symbolsObject (readonly)

Returns the value of attribute symbols.



231
232
233
# File 'lib/rubycc/objfile/elf_reader.rb', line 231

def symbols
  @symbols
end

#typeObject (readonly)

Returns the value of attribute type.



231
232
233
# File 'lib/rubycc/objfile/elf_reader.rb', line 231

def type
  @type
end

#version_definitionsObject (readonly)

Returns the value of attribute version_definitions.



231
232
233
# File 'lib/rubycc/objfile/elf_reader.rb', line 231

def version_definitions
  @version_definitions
end

Class Method Details

.read(bytes) ⇒ Object

Parses an in-memory ELF image (an ASCII-8BIT String) and returns a ready-to-query ELFReader.



221
222
223
# File 'lib/rubycc/objfile/elf_reader.rb', line 221

def read(bytes)
  new(bytes).tap(&:parse!)
end

.read_file(path) ⇒ Object

Reads a file from disk and parses it. Convenience over read(File.binread).



226
227
228
# File 'lib/rubycc/objfile/elf_reader.rb', line 226

def read_file(path)
  read(File.binread(path))
end

Instance Method Details

#dynamic_symbol(name) ⇒ Object

The first dynamic symbol with the given name in .dynsym, or nil — the export query for a shared object ("is printf here and defined?").



276
277
278
# File 'lib/rubycc/objfile/elf_reader.rb', line 276

def dynamic_symbol(name)
  @dynamic_symbols.find { |sym| sym.name == name }
end

#executable?Boolean

Returns:

  • (Boolean)


259
# File 'lib/rubycc/objfile/elf_reader.rb', line 259

def executable? = @type == ET_EXEC

#parse!Object



247
248
249
250
251
252
253
254
255
256
# File 'lib/rubycc/objfile/elf_reader.rb', line 247

def parse!
  parse_header
  parse_sections
  @symbols = symbol_table_by_index[symtab_section&.index] || []
  parse_relocations
  parse_dynamic_symbols
  parse_dynamic
  parse_version_definitions
  self
end

#relocatable?Boolean

Returns:

  • (Boolean)


258
# File 'lib/rubycc/objfile/elf_reader.rb', line 258

def relocatable? = @type == ET_REL

#relocations_for(target_name) ⇒ Object

The relocations that patch the named section (e.g. "relocations_for('.text')" yields the .rela.text entries), or an empty array.



282
283
284
285
# File 'lib/rubycc/objfile/elf_reader.rb', line 282

def relocations_for(target_name)
  rs = @relocation_sections.find { |r| r.target&.name == target_name }
  rs ? rs.relocations : []
end

#section(name) ⇒ Object

The first section with the given name, or nil. Section names are not unique in ELF, but the ones this reader is asked about (.text, .symtab, .rela.text, .dynsym, ...) are, so first-match is the useful lookup.



265
266
267
# File 'lib/rubycc/objfile/elf_reader.rb', line 265

def section(name)
  @sections.find { |sec| sec.name == name }
end

#shared_object?Boolean

Returns:

  • (Boolean)


260
# File 'lib/rubycc/objfile/elf_reader.rb', line 260

def shared_object? = @type == ET_DYN

#symbol(name) ⇒ Object

The first symbol with the given name in .symtab, or nil.



270
271
272
# File 'lib/rubycc/objfile/elf_reader.rb', line 270

def symbol(name)
  @symbols.find { |sym| sym.name == name }
end