Class: ELFTools::Structs::ELFStruct

Inherits:
BinData::Record
  • Object
show all
Defined in:
lib/elftools/structs.rb

Overview

The base structure to define common methods.

Constant Summary collapse

CHOICE_SIZE_T =

DRY. Many fields have different type in different arch.

proc do |t = 'uint'|
  { selection: :elf_class, choices: { 32 => :"#{t}32", 64 => :"#{t}64" }, copy_on_change: true }
end
UNPACK_TEMPLATES =

How an integer of each width is packed, for String#unpack, whether it records a sign or not.

{
  little: {
    false => { 1 => 'C', 2 => 'v', 4 => 'V', 8 => 'Q<' },
    true => { 1 => 'c', 2 => 's<', 4 => 'l<', 8 => 'q<' }
  },
  big: {
    false => { 1 => 'C', 2 => 'n', 4 => 'N', 8 => 'Q>' },
    true => { 1 => 'c', 2 => 's>', 4 => 'l>', 8 => 'q>' }
  }
}.freeze
SET_BITS =

Bytes of set bits to read a prototype from, longer than any structure here, so that reading one never runs short of them.

("\xff" * 256).b.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#elf_classInteger

Returns 32 or 64.

Returns:

  • (Integer)

    32 or 64.



37
38
39
# File 'lib/elftools/structs.rb', line 37

def elf_class
  @elf_class
end

#offsetInteger

Returns The file offset of this header.

Returns:

  • (Integer)

    The file offset of this header.



38
39
40
# File 'lib/elftools/structs.rb', line 38

def offset
  @offset
end

Class Method Details

.new(*args) ⇒ Object

Hooks the constructor.

BinData::Record doesn't allow us to override #initialize, so we hack new here.

Keyword arguments have to be taken as a trailing Hash instead of **kwargs: bindata defines new on each record class taking *args only, then re-dispatches it to the endian-specific subclass this method actually runs on, which collapses the caller's keywords into a positional Hash on the way. See override_new_in_class in https://github.com/dmendel/bindata/blob/master/lib/bindata/dsl.rb, which is the same in 2.5.1, 3.0.0, and master.



118
119
120
121
122
# File 'lib/elftools/structs.rb', line 118

def new(*args)
  kwargs = args.last.is_a?(Hash) ? args.last : {}
  offset = kwargs.delete(:offset)
  super.tap { |obj| obj.offset = offset }
end

.num_bytes(elf_class:, endian:) ⇒ Integer

How many bytes a structure of this kind takes.

Examples:

ELF64_sym.num_bytes(elf_class: 64, endian: :little)
#=> 24

Parameters:

  • elf_class (Integer)

    32 or 64, which decides how wide the fields recording an address are.

  • endian (:little, :big)

    The endianness the file records it in.

Returns:

  • (Integer)

    The number.



163
164
165
166
167
168
# File 'lib/elftools/structs.rb', line 163

def num_bytes(elf_class:, endian:)
  @num_bytes ||= {}
  # Nested rather than keyed by the pair, which would make an array of
  # it for every structure read.
  (@num_bytes[elf_class] ||= {})[endian] ||= prototype(elf_class, endian).num_bytes
end

.pack(val, bytes) ⇒ String

Deprecated.

Nothing here packs a patch by hand anymore, see #patches. This is kept for anyone who called it and goes in the next major.

Packs an integer to string.

Parameters:

  • val (Integer)
  • bytes (Integer)

Returns:

  • (String)

Raises:

  • (ArgumentError)


178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/elftools/structs.rb', line 178

def pack(val, bytes)
  raise ArgumentError, "Not supported assign type #{val.class}" unless val.is_a?(Integer)

  number = val & ((1 << (8 * bytes)) - 1)
  out = []
  bytes.times do
    out << (number & 0xff)
    number >>= 8
  end
  out = out.pack('C*')
  self_endian == :little ? out : out.reverse
end

.self_endian:little, :big

Gets the endianness of current class.

A class is of one endianness for as long as it exists, and asking bindata what it is named costs more than remembering the answer.

Returns:

  • (:little, :big)

    The endianness.



129
130
131
# File 'lib/elftools/structs.rb', line 129

def self_endian
  @self_endian ||= bindata_name[-2..] == 'be' ? :big : :little
end

.unpack_fields(bytes, elf_class:, endian:) ⇒ Hash{Symbol => Integer}

What the fields of a structure record, read straight from its bytes.

Reading a table of structures costs a structure for every entry of it otherwise, which is most of what reading the table costs. Nothing is remembered of the bytes, so a caller that means to assign to a field wants a structure instead.

Examples:

ELF64_sym.unpack_fields(bytes, elf_class: 64, endian: :little)
#=> { st_name: 1, st_info: 18, st_other: 0, st_shndx: 15, st_value: 4198864, st_size: 101 }

Parameters:

  • bytes (String)

    The bytes a structure is recorded in.

  • elf_class (Integer)

    32 or 64, which decides how wide the fields recording an address are.

  • endian (:little, :big)

    The endianness the file records it in.

Returns:

  • (Hash{Symbol => Integer})

    Each field, and what it records.

Raises:



147
148
149
150
151
152
153
154
# File 'lib/elftools/structs.rb', line 147

def unpack_fields(bytes, elf_class:, endian:)
  values = bytes.unpack(unpack_template(elf_class, endian))
  fields = {}
  # Paired by hand rather than zipped, which would make an array for
  # every field of every structure read.
  field_names(elf_class, endian).each_with_index { |name, i| fields[name] = values[i] }
  fields
end

Instance Method Details

#patchesHash{Integer => String}

Which bytes of this structure have been changed since it was read.

Every field answers alike, however deeply it is nested, because what is compared is the bytes the structure occupies rather than the assignments that were made to it. A field assigned the value it already held leaves nothing behind.

Examples:

header.e_ident.ei_abiversion = 41
header.patches
#=> { 8 => "\x29" }

Returns:

  • (Hash{Integer => String})

    Where each run of changed bytes starts, as an offset into the structure, and the bytes it is to be replaced with.



64
65
66
67
68
# File 'lib/elftools/structs.rb', line 64

def patches
  return {} if @source.nil?

  changed_runs(@source, to_binary_s)
end

#read(io) ⇒ ELFTools::Structs::ELFStruct

Reads the structure, remembering the bytes it was read from.

They are taken back off the stream. A stream that cannot be seeked is serialized instead.

Parameters:

  • io (#pos=, #read)

    The streaming object.

Returns:



46
47
48
49
# File 'lib/elftools/structs.rb', line 46

def read(io)
  start = io.pos if io.respond_to?(:pos)
  super.tap { @source = start.nil? ? to_binary_s : bytes_read(io, start) }
end