aboutsummaryrefslogtreecommitdiffstats
path: root/Library/Homebrew/os/linux/elf.rb
blob: 1a53e50f3e05d6f5f62f5f0e1c8585a819abb99d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
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