2019-04-19 15:38:03 +09:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2018-10-18 21:42:43 -04:00
|
|
|
# @see https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header
|
2017-12-01 16:29:50 -08:00
|
|
|
module ELFShim
|
|
|
|
MAGIC_NUMBER_OFFSET = 0
|
2019-04-19 15:38:03 +09:00
|
|
|
MAGIC_NUMBER_ASCII = "\x7fELF"
|
2017-12-01 16:29:50 -08:00
|
|
|
|
|
|
|
OS_ABI_OFFSET = 0x07
|
|
|
|
OS_ABI_SYSTEM_V = 0
|
|
|
|
OS_ABI_LINUX = 3
|
|
|
|
|
|
|
|
TYPE_OFFSET = 0x10
|
|
|
|
TYPE_EXECUTABLE = 2
|
|
|
|
TYPE_SHARED = 3
|
|
|
|
|
|
|
|
ARCHITECTURE_OFFSET = 0x12
|
|
|
|
ARCHITECTURE_I386 = 0x3
|
|
|
|
ARCHITECTURE_POWERPC = 0x14
|
|
|
|
ARCHITECTURE_ARM = 0x28
|
|
|
|
ARCHITECTURE_X86_64 = 0x62
|
|
|
|
ARCHITECTURE_AARCH64 = 0xB7
|
|
|
|
|
|
|
|
def read_uint8(offset)
|
2019-10-13 10:13:42 +01:00
|
|
|
read(1, offset).unpack1("C")
|
2017-12-01 16:29:50 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def read_uint16(offset)
|
2019-10-13 10:13:42 +01:00
|
|
|
read(2, offset).unpack1("v")
|
2017-12-01 16:29:50 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def elf?
|
|
|
|
return @elf if defined? @elf
|
|
|
|
return @elf = false unless read(MAGIC_NUMBER_ASCII.size, MAGIC_NUMBER_OFFSET) == MAGIC_NUMBER_ASCII
|
|
|
|
|
|
|
|
# Check that this ELF file is for Linux or System V.
|
|
|
|
# OS_ABI is often set to 0 (System V), regardless of the target platform.
|
|
|
|
@elf = [OS_ABI_LINUX, OS_ABI_SYSTEM_V].include? read_uint8(OS_ABI_OFFSET)
|
|
|
|
end
|
|
|
|
|
|
|
|
def arch
|
|
|
|
return :dunno unless elf?
|
|
|
|
|
|
|
|
@arch ||= case read_uint16(ARCHITECTURE_OFFSET)
|
|
|
|
when ARCHITECTURE_I386 then :i386
|
|
|
|
when ARCHITECTURE_X86_64 then :x86_64
|
|
|
|
when ARCHITECTURE_POWERPC then :powerpc
|
|
|
|
when ARCHITECTURE_ARM then :arm
|
|
|
|
when ARCHITECTURE_AARCH64 then :arm64
|
|
|
|
else :dunno
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def elf_type
|
|
|
|
return :dunno unless elf?
|
|
|
|
|
|
|
|
@elf_type ||= case read_uint16(TYPE_OFFSET)
|
|
|
|
when TYPE_EXECUTABLE then :executable
|
|
|
|
when TYPE_SHARED then :dylib
|
|
|
|
else :dunno
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def dylib?
|
|
|
|
elf_type == :dylib
|
|
|
|
end
|
|
|
|
|
|
|
|
def binary_executable?
|
|
|
|
elf_type == :executable
|
|
|
|
end
|
|
|
|
|
2019-07-18 15:22:09 +08:00
|
|
|
def with_interpreter?
|
|
|
|
return @with_interpreter if defined? @with_interpreter
|
|
|
|
|
|
|
|
@with_interpreter = if binary_executable?
|
|
|
|
true
|
|
|
|
elsif dylib?
|
2020-06-02 18:36:13 +05:30
|
|
|
if HOMEBREW_PATCHELF_RB
|
|
|
|
begin
|
|
|
|
patchelf_patcher.interpreter.present?
|
|
|
|
rescue PatchELF::PatchError => e
|
2020-06-22 01:22:22 +05:30
|
|
|
opoo e unless e.to_s.start_with? "No interpreter found"
|
2020-06-02 18:36:13 +05:30
|
|
|
false
|
|
|
|
end
|
|
|
|
elsif which "readelf"
|
2019-07-18 15:22:09 +08:00
|
|
|
Utils.popen_read("readelf", "-l", to_path).include?(" INTERP ")
|
|
|
|
elsif which "file"
|
|
|
|
Utils.popen_read("file", "-L", "-b", to_path).include?(" interpreter ")
|
|
|
|
else
|
|
|
|
raise "Please install either readelf (from binutils) or file."
|
|
|
|
end
|
|
|
|
else
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-12-01 16:29:50 -08:00
|
|
|
def dynamic_elf?
|
|
|
|
return @dynamic_elf if defined? @dynamic_elf
|
|
|
|
|
2020-06-02 18:36:13 +05:30
|
|
|
@dynamic_elf = if HOMEBREW_PATCHELF_RB
|
|
|
|
patchelf_patcher.instance_variable_get(:@elf).segment_by_type(:DYNAMIC).present?
|
|
|
|
elsif which "readelf"
|
2017-12-01 16:29:50 -08:00
|
|
|
Utils.popen_read("readelf", "-l", to_path).include?(" DYNAMIC ")
|
|
|
|
elsif which "file"
|
|
|
|
!Utils.popen_read("file", "-L", "-b", to_path)[/dynamic|shared/].nil?
|
|
|
|
else
|
|
|
|
raise "Please install either readelf (from binutils) or file."
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class Metadata
|
|
|
|
attr_reader :path, :dylib_id, :dylibs
|
|
|
|
|
|
|
|
def initialize(path)
|
|
|
|
@path = path
|
|
|
|
@dylibs = []
|
|
|
|
@dylib_id, needed = needed_libraries path
|
|
|
|
return if needed.empty?
|
|
|
|
|
|
|
|
ldd = DevelopmentTools.locate "ldd"
|
|
|
|
ldd_output = Utils.popen_read(ldd, path.expand_path.to_s).split("\n")
|
|
|
|
return unless $CHILD_STATUS.success?
|
|
|
|
|
|
|
|
ldd_paths = ldd_output.map do |line|
|
|
|
|
match = line.match(/\t.+ => (.+) \(.+\)|\t(.+) => not found/)
|
|
|
|
next unless match
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2017-12-01 16:29:50 -08:00
|
|
|
match.captures.compact.first
|
|
|
|
end.compact
|
|
|
|
@dylibs = ldd_paths.select do |ldd_path|
|
|
|
|
next true unless ldd_path.start_with? "/"
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2017-12-01 16:29:50 -08:00
|
|
|
needed.include? File.basename(ldd_path)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def needed_libraries(path)
|
2020-06-02 18:36:13 +05:30
|
|
|
if HOMEBREW_PATCHELF_RB
|
|
|
|
needed_libraries_using_patchelf_rb path
|
|
|
|
elsif DevelopmentTools.locate "readelf"
|
2017-12-01 16:29:50 -08:00
|
|
|
needed_libraries_using_readelf path
|
|
|
|
elsif DevelopmentTools.locate "patchelf"
|
|
|
|
needed_libraries_using_patchelf path
|
|
|
|
else
|
2020-05-15 04:36:15 +05:30
|
|
|
return [nil, []] if path.basename.to_s == "patchelf"
|
|
|
|
|
2017-12-01 16:29:50 -08:00
|
|
|
raise "patchelf must be installed: brew install patchelf"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-06-02 18:36:13 +05:30
|
|
|
def needed_libraries_using_patchelf_rb(path)
|
|
|
|
patcher = path.patchelf_patcher
|
|
|
|
return [nil, []] unless patcher
|
|
|
|
|
|
|
|
soname = begin
|
|
|
|
patcher.soname
|
|
|
|
rescue PatchELF::PatchError => e
|
|
|
|
opoo e unless e.to_s.start_with? "Entry DT_SONAME not found, not a shared library?"
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
needed = begin
|
|
|
|
patcher.needed
|
|
|
|
rescue PatchELF::PatchError => e
|
|
|
|
opoo e
|
|
|
|
[]
|
|
|
|
end
|
|
|
|
[soname, needed]
|
|
|
|
end
|
|
|
|
|
2017-12-01 16:29:50 -08:00
|
|
|
def needed_libraries_using_patchelf(path)
|
2019-01-02 10:25:10 -08:00
|
|
|
return [nil, []] unless path.dynamic_elf?
|
|
|
|
|
2017-12-01 16:29:50 -08:00
|
|
|
patchelf = DevelopmentTools.locate "patchelf"
|
|
|
|
if path.dylib?
|
|
|
|
command = [patchelf, "--print-soname", path.expand_path.to_s]
|
2018-07-16 23:17:16 +02:00
|
|
|
soname = Utils.safe_popen_read(*command).chomp
|
2017-12-01 16:29:50 -08:00
|
|
|
end
|
|
|
|
command = [patchelf, "--print-needed", path.expand_path.to_s]
|
2018-07-16 23:17:16 +02:00
|
|
|
needed = Utils.safe_popen_read(*command).split("\n")
|
2017-12-01 16:29:50 -08:00
|
|
|
[soname, needed]
|
|
|
|
end
|
|
|
|
|
|
|
|
def needed_libraries_using_readelf(path)
|
|
|
|
soname = nil
|
|
|
|
needed = []
|
|
|
|
command = ["readelf", "-d", path.expand_path.to_s]
|
2019-02-08 20:44:50 +01:00
|
|
|
lines = Utils.popen_read(*command, err: :out).split("\n")
|
2017-12-01 16:29:50 -08:00
|
|
|
lines.each do |s|
|
2019-02-08 20:44:50 +01:00
|
|
|
next if s.start_with?("readelf: Warning: possibly corrupt ELF header")
|
|
|
|
|
2017-12-01 16:29:50 -08:00
|
|
|
filename = s[/\[(.*)\]/, 1]
|
|
|
|
next if filename.nil?
|
2018-09-17 02:45:00 +02:00
|
|
|
|
2017-12-01 16:29:50 -08:00
|
|
|
if s.include? "(SONAME)"
|
|
|
|
soname = filename
|
|
|
|
elsif s.include? "(NEEDED)"
|
|
|
|
needed << filename
|
|
|
|
end
|
|
|
|
end
|
|
|
|
[soname, needed]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-06-02 18:36:13 +05:30
|
|
|
def patchelf_patcher
|
|
|
|
return unless HOMEBREW_PATCHELF_RB
|
|
|
|
|
|
|
|
@patchelf_patcher ||= begin
|
|
|
|
Homebrew.install_bundler_gems!
|
|
|
|
require "patchelf"
|
|
|
|
PatchELF::Patcher.new to_s, logging: false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-12-01 16:29:50 -08:00
|
|
|
def metadata
|
|
|
|
@metadata ||= Metadata.new(self)
|
|
|
|
end
|
|
|
|
|
|
|
|
def dylib_id
|
|
|
|
metadata.dylib_id
|
|
|
|
end
|
|
|
|
|
|
|
|
def dynamically_linked_libraries(*)
|
|
|
|
metadata.dylibs
|
|
|
|
end
|
|
|
|
end
|