Skip to content

Commit 4f5e938

Browse files
authored
Merge pull request #3517 from sjackman/linkage
Implement linkage for Linux
2 parents 0ad42eb + d79c5ad commit 4f5e938

File tree

6 files changed

+171
-19
lines changed

6 files changed

+171
-19
lines changed
Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,5 @@
1-
class Pathname
2-
# @private
3-
def elf?
4-
# See: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header
5-
read(4) == "\x7fELF"
6-
end
1+
require "os/linux/elf"
72

8-
# @private
9-
def dynamic_elf?
10-
if which "readelf"
11-
popen_read("readelf", "-l", to_path).include?(" DYNAMIC ")
12-
elsif which "file"
13-
!popen_read("file", "-L", "-b", to_path)[/dynamic|shared/].nil?
14-
else
15-
raise StandardError, "Neither `readelf` nor `file` is available "\
16-
"to determine whether '#{self}' is dynamically or statically linked."
17-
end
18-
end
3+
class Pathname
4+
prepend ELFShim
195
end
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
require "os/mac/mach"
22

33
class Pathname
4-
include MachOShim
4+
prepend MachOShim
55
end

Library/Homebrew/extend/pathname.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,10 @@ def inspect
470470
end
471471
}
472472
end
473+
474+
def mach_o_bundle?
475+
false
476+
end
473477
end
474478

475479
require "extend/os/pathname"

Library/Homebrew/os/linux/elf.rb

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
module ELFShim
2+
# See: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header
3+
MAGIC_NUMBER_OFFSET = 0
4+
MAGIC_NUMBER_ASCII = "\x7fELF".freeze
5+
6+
OS_ABI_OFFSET = 0x07
7+
OS_ABI_SYSTEM_V = 0
8+
OS_ABI_LINUX = 3
9+
10+
TYPE_OFFSET = 0x10
11+
TYPE_EXECUTABLE = 2
12+
TYPE_SHARED = 3
13+
14+
ARCHITECTURE_OFFSET = 0x12
15+
ARCHITECTURE_I386 = 0x3
16+
ARCHITECTURE_POWERPC = 0x14
17+
ARCHITECTURE_ARM = 0x28
18+
ARCHITECTURE_X86_64 = 0x62
19+
ARCHITECTURE_AARCH64 = 0xB7
20+
21+
def read_uint8(offset)
22+
read(1, offset).unpack("C").first
23+
end
24+
25+
def read_uint16(offset)
26+
read(2, offset).unpack("v").first
27+
end
28+
29+
def elf?
30+
return @elf if defined? @elf
31+
return @elf = false unless read(MAGIC_NUMBER_ASCII.size, MAGIC_NUMBER_OFFSET) == MAGIC_NUMBER_ASCII
32+
33+
# Check that this ELF file is for Linux or System V.
34+
# OS_ABI is often set to 0 (System V), regardless of the target platform.
35+
@elf = [OS_ABI_LINUX, OS_ABI_SYSTEM_V].include? read_uint8(OS_ABI_OFFSET)
36+
end
37+
38+
def arch
39+
return :dunno unless elf?
40+
41+
@arch ||= case read_uint16(ARCHITECTURE_OFFSET)
42+
when ARCHITECTURE_I386 then :i386
43+
when ARCHITECTURE_X86_64 then :x86_64
44+
when ARCHITECTURE_POWERPC then :powerpc
45+
when ARCHITECTURE_ARM then :arm
46+
when ARCHITECTURE_AARCH64 then :arm64
47+
else :dunno
48+
end
49+
end
50+
51+
def elf_type
52+
return :dunno unless elf?
53+
54+
@elf_type ||= case read_uint16(TYPE_OFFSET)
55+
when TYPE_EXECUTABLE then :executable
56+
when TYPE_SHARED then :dylib
57+
else :dunno
58+
end
59+
end
60+
61+
def dylib?
62+
elf_type == :dylib
63+
end
64+
65+
def binary_executable?
66+
elf_type == :executable
67+
end
68+
69+
def dynamic_elf?
70+
return @dynamic_elf if defined? @dynamic_elf
71+
72+
if which "readelf"
73+
Utils.popen_read("readelf", "-l", to_path).include?(" DYNAMIC ")
74+
elsif which "file"
75+
!Utils.popen_read("file", "-L", "-b", to_path)[/dynamic|shared/].nil?
76+
else
77+
raise "Please install either readelf (from binutils) or file."
78+
end
79+
end
80+
81+
class Metadata
82+
attr_reader :path, :dylib_id, :dylibs
83+
84+
def initialize(path)
85+
@path = path
86+
@dylibs = []
87+
@dylib_id, needed = needed_libraries path
88+
return if needed.empty?
89+
90+
ldd = DevelopmentTools.locate "ldd"
91+
ldd_output = Utils.popen_read(ldd, path.expand_path.to_s).split("\n")
92+
return unless $CHILD_STATUS.success?
93+
94+
ldd_paths = ldd_output.map do |line|
95+
match = line.match(/\t.+ => (.+) \(.+\)|\t(.+) => not found/)
96+
next unless match
97+
match.captures.compact.first
98+
end.compact
99+
@dylibs = ldd_paths.select do |ldd_path|
100+
next true unless ldd_path.start_with? "/"
101+
needed.include? File.basename(ldd_path)
102+
end
103+
end
104+
105+
private
106+
107+
def needed_libraries(path)
108+
if DevelopmentTools.locate "readelf"
109+
needed_libraries_using_readelf path
110+
elsif DevelopmentTools.locate "patchelf"
111+
needed_libraries_using_patchelf path
112+
else
113+
raise "patchelf must be installed: brew install patchelf"
114+
end
115+
end
116+
117+
def needed_libraries_using_patchelf(path)
118+
patchelf = DevelopmentTools.locate "patchelf"
119+
if path.dylib?
120+
command = [patchelf, "--print-soname", path.expand_path.to_s]
121+
soname = Utils.popen_read(*command).chomp
122+
raise ErrorDuringExecution, command unless $CHILD_STATUS.success?
123+
end
124+
command = [patchelf, "--print-needed", path.expand_path.to_s]
125+
needed = Utils.popen_read(*command).split("\n")
126+
raise ErrorDuringExecution, command unless $CHILD_STATUS.success?
127+
[soname, needed]
128+
end
129+
130+
def needed_libraries_using_readelf(path)
131+
soname = nil
132+
needed = []
133+
command = ["readelf", "-d", path.expand_path.to_s]
134+
lines = Utils.popen_read(*command).split("\n")
135+
raise ErrorDuringExecution, command unless $CHILD_STATUS.success?
136+
lines.each do |s|
137+
filename = s[/\[(.*)\]/, 1]
138+
next if filename.nil?
139+
if s.include? "(SONAME)"
140+
soname = filename
141+
elsif s.include? "(NEEDED)"
142+
needed << filename
143+
end
144+
end
145+
[soname, needed]
146+
end
147+
end
148+
149+
def metadata
150+
@metadata ||= Metadata.new(self)
151+
end
152+
153+
def dylib_id
154+
metadata.dylib_id
155+
end
156+
157+
def dynamically_linked_libraries(*)
158+
metadata.dylibs
159+
end
160+
end

Library/Homebrew/os/mac/linkage_checker.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def initialize(keg, formula = nil)
2323
def check_dylibs
2424
@keg.find do |file|
2525
next if file.symlink? || file.directory?
26-
next unless file.dylib? || file.mach_o_executable? || file.mach_o_bundle?
26+
next unless file.dylib? || file.binary_executable? || file.mach_o_bundle?
2727

2828
# weakly loaded dylibs may not actually exist on disk, so skip them
2929
# when checking for broken linkage

Library/Homebrew/os/mac/mach.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ def mach_o_executable?
103103
mach_data.any? { |m| m.fetch(:type) == :executable }
104104
end
105105

106+
alias binary_executable? mach_o_executable?
107+
106108
# @private
107109
def mach_o_bundle?
108110
mach_data.any? { |m| m.fetch(:type) == :bundle }

0 commit comments

Comments
 (0)