From e5847b652f93744d41f45b5636e67bb2748d67c7 Mon Sep 17 00:00:00 2001 From: Shaun Jackman Date: Fri, 1 Dec 2017 16:29:50 -0800 Subject: [PATCH 1/2] extend/pathname: Add os/linux/elf.rb --- .../extend/os/linux/extend/pathname.rb | 20 +-- .../Homebrew/extend/os/mac/extend/pathname.rb | 2 +- Library/Homebrew/os/linux/elf.rb | 160 ++++++++++++++++++ 3 files changed, 164 insertions(+), 18 deletions(-) create mode 100644 Library/Homebrew/os/linux/elf.rb diff --git a/Library/Homebrew/extend/os/linux/extend/pathname.rb b/Library/Homebrew/extend/os/linux/extend/pathname.rb index eb6ea409bcc80..604351da708f0 100644 --- a/Library/Homebrew/extend/os/linux/extend/pathname.rb +++ b/Library/Homebrew/extend/os/linux/extend/pathname.rb @@ -1,19 +1,5 @@ -class Pathname - # @private - def elf? - # See: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header - read(4) == "\x7fELF" - end +require "os/linux/elf" - # @private - def dynamic_elf? - if which "readelf" - popen_read("readelf", "-l", to_path).include?(" DYNAMIC ") - elsif which "file" - !popen_read("file", "-L", "-b", to_path)[/dynamic|shared/].nil? - else - raise StandardError, "Neither `readelf` nor `file` is available "\ - "to determine whether '#{self}' is dynamically or statically linked." - end - end +class Pathname + prepend ELFShim end diff --git a/Library/Homebrew/extend/os/mac/extend/pathname.rb b/Library/Homebrew/extend/os/mac/extend/pathname.rb index 5fd59e1e799a8..4ced5a09442d6 100644 --- a/Library/Homebrew/extend/os/mac/extend/pathname.rb +++ b/Library/Homebrew/extend/os/mac/extend/pathname.rb @@ -1,5 +1,5 @@ require "os/mac/mach" class Pathname - include MachOShim + prepend MachOShim end diff --git a/Library/Homebrew/os/linux/elf.rb b/Library/Homebrew/os/linux/elf.rb new file mode 100644 index 0000000000000..1a53e50f3e05d --- /dev/null +++ b/Library/Homebrew/os/linux/elf.rb @@ -0,0 +1,160 @@ +module ELFShim + # See: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header + MAGIC_NUMBER_OFFSET = 0 + MAGIC_NUMBER_ASCII = "\x7fELF".freeze + + 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) + read(1, offset).unpack("C").first + end + + def read_uint16(offset) + read(2, offset).unpack("v").first + 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 + + def dynamic_elf? + return @dynamic_elf if defined? @dynamic_elf + + if which "readelf" + 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 + match.captures.compact.first + end.compact + @dylibs = ldd_paths.select do |ldd_path| + next true unless ldd_path.start_with? "/" + needed.include? File.basename(ldd_path) + end + end + + private + + def needed_libraries(path) + if DevelopmentTools.locate "readelf" + needed_libraries_using_readelf path + elsif DevelopmentTools.locate "patchelf" + needed_libraries_using_patchelf path + else + raise "patchelf must be installed: brew install patchelf" + end + end + + def needed_libraries_using_patchelf(path) + patchelf = DevelopmentTools.locate "patchelf" + if path.dylib? + command = [patchelf, "--print-soname", path.expand_path.to_s] + soname = Utils.popen_read(*command).chomp + raise ErrorDuringExecution, command unless $CHILD_STATUS.success? + end + command = [patchelf, "--print-needed", path.expand_path.to_s] + needed = Utils.popen_read(*command).split("\n") + raise ErrorDuringExecution, command unless $CHILD_STATUS.success? + [soname, needed] + end + + def needed_libraries_using_readelf(path) + soname = nil + needed = [] + command = ["readelf", "-d", path.expand_path.to_s] + lines = Utils.popen_read(*command).split("\n") + raise ErrorDuringExecution, command unless $CHILD_STATUS.success? + lines.each do |s| + filename = s[/\[(.*)\]/, 1] + next if filename.nil? + if s.include? "(SONAME)" + soname = filename + elsif s.include? "(NEEDED)" + needed << filename + end + end + [soname, needed] + end + end + + def metadata + @metadata ||= Metadata.new(self) + end + + def dylib_id + metadata.dylib_id + end + + def dynamically_linked_libraries(*) + metadata.dylibs + end +end From d79c5ade1abaad0fb1ea88f2b949def8dcfddb81 Mon Sep 17 00:00:00 2001 From: Shaun Jackman Date: Fri, 1 Dec 2017 16:43:00 -0800 Subject: [PATCH 2/2] Implement linkage for Linux --- Library/Homebrew/extend/pathname.rb | 4 ++++ Library/Homebrew/os/mac/linkage_checker.rb | 2 +- Library/Homebrew/os/mac/mach.rb | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Library/Homebrew/extend/pathname.rb b/Library/Homebrew/extend/pathname.rb index 82cf10be07a1d..32d57c1c4df4a 100644 --- a/Library/Homebrew/extend/pathname.rb +++ b/Library/Homebrew/extend/pathname.rb @@ -470,6 +470,10 @@ def inspect end } end + + def mach_o_bundle? + false + end end require "extend/os/pathname" diff --git a/Library/Homebrew/os/mac/linkage_checker.rb b/Library/Homebrew/os/mac/linkage_checker.rb index f6aa4c2f33365..941a5a847a2d9 100644 --- a/Library/Homebrew/os/mac/linkage_checker.rb +++ b/Library/Homebrew/os/mac/linkage_checker.rb @@ -23,7 +23,7 @@ def initialize(keg, formula = nil) def check_dylibs @keg.find do |file| next if file.symlink? || file.directory? - next unless file.dylib? || file.mach_o_executable? || file.mach_o_bundle? + next unless file.dylib? || file.binary_executable? || file.mach_o_bundle? # weakly loaded dylibs may not actually exist on disk, so skip them # when checking for broken linkage diff --git a/Library/Homebrew/os/mac/mach.rb b/Library/Homebrew/os/mac/mach.rb index 9b53c497975aa..9586188692780 100644 --- a/Library/Homebrew/os/mac/mach.rb +++ b/Library/Homebrew/os/mac/mach.rb @@ -103,6 +103,8 @@ def mach_o_executable? mach_data.any? { |m| m.fetch(:type) == :executable } end + alias binary_executable? mach_o_executable? + # @private def mach_o_bundle? mach_data.any? { |m| m.fetch(:type) == :bundle }