Skip to content

Commit

Permalink
Merge pull request #3517 from sjackman/linkage
Browse files Browse the repository at this point in the history
Implement linkage for Linux
  • Loading branch information
MikeMcQuaid committed Dec 4, 2017
2 parents 0ad42eb + d79c5ad commit 4f5e938
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 19 deletions.
20 changes: 3 additions & 17 deletions Library/Homebrew/extend/os/linux/extend/pathname.rb
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion Library/Homebrew/extend/os/mac/extend/pathname.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
require "os/mac/mach"

class Pathname
include MachOShim
prepend MachOShim
end
4 changes: 4 additions & 0 deletions Library/Homebrew/extend/pathname.rb
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,10 @@ def inspect
end
}
end

def mach_o_bundle?
false
end
end

require "extend/os/pathname"
Expand Down
160 changes: 160 additions & 0 deletions Library/Homebrew/os/linux/elf.rb
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion Library/Homebrew/os/mac/linkage_checker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions Library/Homebrew/os/mac/mach.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down

0 comments on commit 4f5e938

Please sign in to comment.