class PatchELF::Patcher

Class to handle all patching things.

Attributes

elf[R]

@!macro [new] note_apply

@note This setting will be saved after {#save} being invoked.

Public Class Methods

new(filename, on_error: :log, logging: true) click to toggle source

Instantiate a {Patcher} object. @param [String] filename

Filename of input ELF.

@param [Boolean] logging

*deprecated*: use +on_error+ instead

@param [:log, :silent, :exception] on_error

action when the desired segment/tag field isn't present
  :log = logs to stderr
  :exception = raise exception related to the error
  :silent = ignore the errors
# File lib/patchelf/patcher.rb, line 28
def initialize(filename, on_error: :log, logging: true)
  @in_file = filename
  @elf = ELFTools::ELFFile.new(File.open(filename))
  @set = {}
  @rpath_sym = :runpath
  @on_error = !logging ? :exception : on_error

  on_error_syms = %i[exception log silent]
  raise ArgumentError, "on_error must be one of #{on_error_syms}" unless on_error_syms.include?(@on_error)
end

Public Instance Methods

add_needed(need) click to toggle source

Add the needed library. @param [String] need @return [void] @macro note_apply

# File lib/patchelf/patcher.rb, line 81
def add_needed(need)
  @set[:needed] ||= needed_
  @set[:needed] << need
end
interpreter() click to toggle source

@return [String?]

Get interpreter's name.

@example

PatchELF::Patcher.new('/bin/ls').interpreter
#=> "/lib64/ld-linux-x86-64.so.2"
# File lib/patchelf/patcher.rb, line 44
def interpreter
  @set[:interpreter] || interpreter_
end
interpreter=(interp) click to toggle source

Set interpreter's name.

If the input ELF has no existent interpreter, this method will show a warning and has no effect. @param [String] interp @macro note_apply

# File lib/patchelf/patcher.rb, line 54
def interpreter=(interp)
  return if interpreter_.nil? # will also show warning if there's no interp segment.

  @set[:interpreter] = interp
end
needed() click to toggle source

Get needed libraries. @return [Array<String>] @example

patcher = PatchELF::Patcher.new('/bin/ls')
patcher.needed
#=> ["libselinux.so.1", "libc.so.6"]
# File lib/patchelf/patcher.rb, line 66
def needed
  @set[:needed] || needed_
end
needed=(needs) click to toggle source

Set needed libraries. @param [Array<String>] needs @macro note_apply

# File lib/patchelf/patcher.rb, line 73
def needed=(needs)
  @set[:needed] = needs
end
remove_needed(need) click to toggle source

Remove the needed library. @param [String] need @return [void] @macro note_apply

# File lib/patchelf/patcher.rb, line 90
def remove_needed(need)
  @set[:needed] ||= needed_
  @set[:needed].delete(need)
end
replace_needed(src, tar) click to toggle source

Replace needed library src with tar.

@param [String] src

Library to be replaced.

@param [String] tar

Library replace with.

@return [void] @macro note_apply

# File lib/patchelf/patcher.rb, line 103
def replace_needed(src, tar)
  @set[:needed] ||= needed_
  @set[:needed].map! { |v| v == src ? tar : v }
end
rpath() click to toggle source

Get rpath return [String?]

# File lib/patchelf/patcher.rb, line 142
def rpath
  @set[:rpath] || runpath_(:rpath)
end
rpath=(rpath) click to toggle source

Set rpath

Modify / set DT_RPATH of the given ELF. similar to runpath= except DT_RPATH is modifed/created in DYNAMIC segment. @param [String] rpath @macro note_apply

# File lib/patchelf/patcher.rb, line 152
def rpath=(rpath)
  @set[:rpath] = rpath
end
runpath() click to toggle source

Get runpath. @return [String?]

# File lib/patchelf/patcher.rb, line 136
def runpath
  @set[@rpath_sym] || runpath_(@rpath_sym)
end
runpath=(runpath) click to toggle source

Set runpath.

If DT_RUNPATH is not presented in the input ELF, a new DT_RUNPATH attribute will be inserted into the DYNAMIC segment. @param [String] runpath @macro note_apply

# File lib/patchelf/patcher.rb, line 162
def runpath=(runpath)
  @set[@rpath_sym] = runpath
end
save(out_file = nil, patchelf_compatible: false) click to toggle source

Save the patched ELF as out_file. @param [String?] out_file

If +out_file+ is +nil+, the original input file will be modified.

@param [Boolean] patchelf_compatible

When +patchelf_compatible+ is true, tries to produce same ELF as the one produced by NixOS/patchelf.

@return [void]

# File lib/patchelf/patcher.rb, line 179
def save(out_file = nil, patchelf_compatible: false)
  # If nothing is modified, return directly.
  return if out_file.nil? && !dirty?

  out_file ||= @in_file
  saver = if patchelf_compatible
            require 'patchelf/alt_saver'
            PatchELF::AltSaver.new(@in_file, out_file, @set)
          else
            PatchELF::Saver.new(@in_file, out_file, @set)
          end

  saver.save!
end
soname() click to toggle source

Get the soname of a shared library. @return [String?] The name. @example

patcher = PatchELF::Patcher.new('/bin/ls')
patcher.soname
# [WARN] Entry DT_SONAME not found, not a shared library?
#=> nil

@example

PatchELF::Patcher.new('/lib/x86_64-linux-gnu/libc.so.6').soname
#=> "libc.so.6"
# File lib/patchelf/patcher.rb, line 118
def soname
  @set[:soname] || soname_
end
soname=(name) click to toggle source

Set soname.

If the input ELF is not a shared library with a soname, this method will show a warning and has no effect. @param [String] name @macro note_apply

# File lib/patchelf/patcher.rb, line 128
def soname=(name)
  return if soname_.nil?

  @set[:soname] = name
end
use_rpath!() click to toggle source

Set all operations related to DT_RUNPATH to use DT_RPATH. @return [self]

# File lib/patchelf/patcher.rb, line 168
def use_rpath!
  @rpath_sym = :rpath
  self
end

Private Instance Methods

dirty?() click to toggle source

@return [Boolean]

# File lib/patchelf/patcher.rb, line 228
def dirty?
  @set.any?
end
dynamic_or_log() click to toggle source
# File lib/patchelf/patcher.rb, line 242
def dynamic_or_log
  @elf.segment_by_type(:dynamic).tap do |s|
    if s.nil?
      log_or_raise 'DYNAMIC segment not found, might be a statically-linked ELF?', PatchELF::MissingSegmentError
    end
  end
end
interpreter_() click to toggle source
# File lib/patchelf/patcher.rb, line 202
def interpreter_
  segment = @elf.segment_by_type(:interp)
  return log_or_raise 'No interpreter found.', PatchELF::MissingSegmentError if segment.nil?

  segment.interp_name
end
log_or_raise(msg, exception = PatchELF::PatchError) click to toggle source
# File lib/patchelf/patcher.rb, line 196
def log_or_raise(msg, exception = PatchELF::PatchError)
  raise exception, msg if @on_error == :exception

  PatchELF::Logger.warn(msg) if @on_error == :log
end
needed_() click to toggle source

@return [Array<String>]

# File lib/patchelf/patcher.rb, line 210
def needed_
  segment = dynamic_or_log
  return if segment.nil?

  segment.tags_by_type(:needed).map(&:name)
end
runpath_(rpath_sym = :runpath) click to toggle source

@return [String?]

# File lib/patchelf/patcher.rb, line 218
def runpath_(rpath_sym = :runpath)
  tag_name_or_log(rpath_sym, "Entry DT_#{rpath_sym.to_s.upcase} not found.")
end
soname_() click to toggle source

@return [String?]

# File lib/patchelf/patcher.rb, line 223
def soname_
  tag_name_or_log(:soname, 'Entry DT_SONAME not found, not a shared library?')
end
tag_name_or_log(type, log_msg) click to toggle source
# File lib/patchelf/patcher.rb, line 232
def tag_name_or_log(type, log_msg)
  segment = dynamic_or_log
  return if segment.nil?

  tag = segment.tag_by_type(type)
  return log_or_raise log_msg, PatchELF::MissingTagError if tag.nil?

  tag.name
end