class Elf::File

Attributes

abi[R]
abi_version[R]
data_encoding[R]
ehsize[R]
elf_class[R]
entry_address[R]
flags[R]
machine[R]
phentsize[R]
phnum[R]
phoff[R]
shentsize[R]
shnum[R]
shoff[R]

raw data access

shstrndx[R]
string_table[R]
type[R]
version[R]

Public Class Methods

new(path) click to toggle source
Calls superclass method
# File lib/elf/file.rb, line 151
def initialize(path)
  _checkvalidpath(path)

  super(path, "rb")

  begin
    begin
      raise NotAnELF unless readexactly(4) == MagicString
    rescue EOFError
      raise NotAnELF
    end

    begin
      @elf_class = Class[read_u8]
    rescue Value::OutOfBound => e
      raise InvalidElfClass.new(e.val)
    end

    begin
      @data_encoding = DataEncoding[read_u8]
    rescue Value::OutOfBound => e
      raise InvalidDataEncoding.new(e.val)
    end

    @version = read_u8
    raise UnsupportedElfVersion.new(@version) if @version > 1

    begin
      @abi = OsAbi[read_u8]
    rescue Value::OutOfBound => e
      raise InvalidOsAbi.new(e.val)
    end
    @abi_version = read_u8

    seek(16, IO::SEEK_SET)
    set_endian(DataEncoding::BytestreamMapping[@data_encoding])

    begin
      @type = Type[read_half]
    rescue Value::OutOfBound => e
      raise InvalidElfType.new(e.val)
    end
    
    begin
      @machine = Machine[read_half]
    rescue Value::OutOfBound => e
      raise InvalidMachine.new(e.val)
    end

    @version = read_word
    @entry_address = read_addr
    @phoff = read_off
    @shoff = read_off
    @flags = read_word
    @ehsize = read_half
    @phentsize = read_half
    @phnum = read_half
    @shentsize = read_half
    @shnum = read_half
    @shstrndx = read_half

    elf32 = elf_class == Class::Elf32
    @sections = {}

    @sections_data = []
    seek(@shoff)
    for i in 1..@shnum
      sectdata = {}
      sectdata[:idx]       = i-1
      sectdata[:name_idx]  = read_word
      sectdata[:type_id]   = read_word
      sectdata[:flags_val] = elf32 ? read_word : read_xword
      sectdata[:addr]      = read_addr
      sectdata[:offset]    = read_off
      sectdata[:size]      = elf32 ? read_word : read_xword
      sectdata[:link]      = read_word
      sectdata[:info]      = read_word
      sectdata[:addralign] = elf32 ? read_word : read_xword
      sectdata[:entsize]   = elf32 ? read_word : read_xword

      @sections_data << sectdata
    end

    # When the section header string table index is set to zero,
    # there is not going to be a string table in the file, this
    # happens usually when the file is a static ELF file built
    # directly with an assembler, or when it was passed through
    # the elfkickers' sstrip utility.
    #
    # To handle this specific case, set the @string_table attribute
    # to false, that is distinct from nil, and raise
    # MissingStringTable on request. If the string table is not yet
    # loaded raise instead StringTableNotLoaded.
    if @shstrndx == 0 or not self[@shstrndx].is_a? StringTable
      @string_table = false
    else
      @string_table = self[@shstrndx]

      @sections_names = {}
      @sections_data.each do |sectdata|
        @sections_names[@string_table[sectdata[:name_idx]]] = sectdata[:idx]
      end
    end
  rescue ::Exception => e
    close
    raise e
  end
end

Public Instance Methods

[](sect_idx_or_name) click to toggle source
# File lib/elf/file.rb, line 298
def [](sect_idx_or_name)
  if sect_idx_or_name.is_a? String and not @string_table.is_a? Elf::Section
    raise MissingStringTable.new(sect_idx_or_name) if @string_table == false
    raise StringTableNotLoaded.new(sect_idx_or_name) if @string_table.nil?
  end

  load_section(sect_idx_or_name) unless
    @sections.has_key? sect_idx_or_name

  return @sections[sect_idx_or_name]
end
address_print_size() click to toggle source

Returns the hex address size for the file.

Since each ELF file uses either 32- or 64-bit addresses, it is important to know how many characters a file’s address would require when printed as an hexadecimal string.

# File lib/elf/file.rb, line 346
def address_print_size
  (@elf_class == Elf::Class::Elf32 ? 8 : 16)
end
arm_be8?() click to toggle source
# File lib/elf/file.rb, line 400
def arm_be8?
  return nil if machine != Elf::Machine::ARM

  return (@flags & ARM::EFlags_BE8) == ARM::EFlags_BE8
end
arm_eabi_version() click to toggle source
# File lib/elf/file.rb, line 394
def arm_eabi_version
  return nil if machine != Elf::Machine::ARM

  return (@flags & ARM::EFlags_EABI_Mask) >> 24
end
each_section() { |sections[sectdata| ... } click to toggle source
# File lib/elf/file.rb, line 310
def each_section
  @sections_data.each do |sectdata|
    load_section(sectdata[:idx])
    yield @sections[sectdata[:idx]]
  end
end
find_section_by_addr(addr) click to toggle source
# File lib/elf/file.rb, line 317
def find_section_by_addr(addr)
  @sections_data.each do |sectdata|
    next unless sectdata[:addr] == addr
    load_section(sectdata[:idx])
    return @sections[sectdata[:idx]]
  end
end
has_section?(sect_idx_or_name) click to toggle source
# File lib/elf/file.rb, line 325
def has_section?(sect_idx_or_name)

  if sect_idx_or_name.is_a? String and not @string_table.is_a? Elf::Section
    raise MissingStringTable.new(sect_idx_or_name) if @string_table == false
    raise StringTableNotLoaded.new(sect_idx_or_name) if @string_table.nil?
  end

  if sect_idx_or_name.is_a? Integer
    return @sections_data[sect_idx_or_name] != nil
  elsif sect_idx_or_name.is_a? String
    return @sections_names.has_key?(sect_idx_or_name)
  else
    raise TypeError.new("wrong argument type #{sect_idx_or_name.class} (expected String or Integer)")
  end
end
is_compatible(other) click to toggle source

Checks whether two ELF files are compatible one with the other for linking

This function has to check whether two ELF files can be linked together (either at build time or at load time), and thus checks for class, encoding, versioning, ABI and machine type.

Note that it explicitly does not check for ELF file type since you can link different type of files together, like an Executable with a Dynamic library.

# File lib/elf/file.rb, line 372
def is_compatible(other)
  raise TypeError.new("wrong argument type #{other.class} (expected Elf::File)") unless
    other.is_a? Elf::File

  compatible_abi = (@abi.linux_compatible? && other.abi.linux_compatible?) \
    || ([@abi, @abi_version] == [other.abi, other.abi_version])

  @elf_class == other.elf_class and
    @data_encoding == other.data_encoding and
    @version == other.version and
    @machine == other.machine and
    compatible_abi
end
load_section(sect_idx_or_name) click to toggle source
# File lib/elf/file.rb, line 266
def load_section(sect_idx_or_name)
  if sect_idx_or_name.is_a? Integer
    raise MissingSection.new(sect_idx_or_name) unless
      @sections_data[sect_idx_or_name]

    @sections[sect_idx_or_name] = Section.read(self, sect_idx_or_name, @sections_data[sect_idx_or_name])
  else
    raise MissingSection.new(sect_idx_or_name) unless
      @sections_names[sect_idx_or_name]
    
    load_section @sections_names[sect_idx_or_name]

    @sections[sect_idx_or_name] = @sections[@sections_names[sect_idx_or_name]]
  end
end
read_addr() click to toggle source
# File lib/elf/file.rb, line 94
def read_addr
  case @elf_class
  when Class::Elf32 then read_u32
  when Class::Elf64 then read_u64
  end
end
read_off() click to toggle source
# File lib/elf/file.rb, line 101
def read_off
  case @elf_class
  when Class::Elf32 then read_u32
  when Class::Elf64 then read_u64
  end
end
sections() click to toggle source
# File lib/elf/file.rb, line 294
def sections
  return @sections_data.size
end
summary() click to toggle source
# File lib/elf/file.rb, line 350
def summary
  $stdout.puts "ELF file #{path}"
  $stdout.puts "ELF class: #{@elf_class} #{@data_encoding} ver. #{@version}"
  $stdout.puts "ELF ABI: #{@abi} ver. #{@abi_version}"
  $stdout.puts "ELF type: #{@type} machine: #{@machine}"
  $stdout.puts "Sections:"
  @sections.values.uniq.each do |sh|
    sh.summary
  end

  return nil
end

Private Instance Methods

_checkvalidpath(path) click to toggle source
# File lib/elf/file.rb, line 119
def _checkvalidpath(path)
  # We're going to check the path we're given for a few reasons,
  # the first of which is that we do not want to open a pipe or
  # something like that. If we were just to leave it to File.open,
  # we would end up stuck waiting for data on a named pipe, for
  # instance.
  #
  # We cannot just use File.file? either because it'll be ignoring
  # ENOENT by default (which would be bad for us).
  #
  # If we were just to use File.ftype we would have to handle
  # manually the links... since Pathname will properly report
  # ENOENT for broken links, we're going to keep it this way.
  path = Pathname.new(path) unless path.is_a? Pathname

  case path.ftype
  when "directory" then raise Errno::EISDIR
  when "file" then # do nothing
  when "link"
    # we use path.realpath here; the reason is that if
    # we're to use readlink we're going to have a lot of
    # trouble to find the correct path. We cannot just
    # always use realpath as that will run too many stat
    # calls and have a huge hit on performance.
    _checkvalidpath(path.realpath)
  else
    raise Errno::EINVAL
  end
end