|
| 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 |
0 commit comments