Pathname: add Mach-O module
The MachO module contains methods for learning about Mach-O binaries, and can be used where one might normally shell out to file(1). Signed-off-by: Jack Nagel <jacknagel@gmail.com>
This commit is contained in:
parent
b6e0dfed23
commit
a786178382
@ -1,8 +1,11 @@
|
||||
require 'pathname'
|
||||
require 'bottles'
|
||||
require 'mach'
|
||||
|
||||
# we enhance pathname to make our code more readable
|
||||
class Pathname
|
||||
include MachO
|
||||
|
||||
def install *sources
|
||||
results = []
|
||||
sources.each do |src|
|
||||
@ -268,6 +271,10 @@ class Pathname
|
||||
end
|
||||
end
|
||||
|
||||
def text_executable?
|
||||
%r[#!\s*(/.+)+] === open('r') { |f| f.readline }
|
||||
end
|
||||
|
||||
def incremental_hash(hasher)
|
||||
incr_hash = hasher.new
|
||||
self.open('r') do |f|
|
||||
|
||||
93
Library/Homebrew/mach.rb
Normal file
93
Library/Homebrew/mach.rb
Normal file
@ -0,0 +1,93 @@
|
||||
module MachO
|
||||
# Mach-O binary methods, see:
|
||||
# /usr/include/mach-o/loader.h
|
||||
# /usr/include/mach-o/fat.h
|
||||
|
||||
def mach_data
|
||||
@mach_data ||= begin
|
||||
offsets = []
|
||||
mach_data = []
|
||||
|
||||
header = read(8).unpack("N2")
|
||||
case header[0]
|
||||
when 0xcafebabe # universal
|
||||
header[1].times do |i|
|
||||
# header[1] is the number of struct fat_arch in the file.
|
||||
# Each struct fat_arch is 20 bytes, and the 'offset' member
|
||||
# begins 8 bytes into the struct, with an additional 8 byte
|
||||
# offset due to the struct fat_header at the beginning of
|
||||
# the file.
|
||||
offsets << read(4, 20*i + 16).unpack("N")[0]
|
||||
end
|
||||
when 0xcefaedfe, 0xcffaedfe, 0xfeedface, 0xfeedfacf # Single arch
|
||||
offsets << 0
|
||||
else
|
||||
raise "Not a Mach-O binary."
|
||||
end
|
||||
|
||||
offsets.each do |offset|
|
||||
arch = case read(8, offset).unpack("N2")
|
||||
when [0xcefaedfe, 0x07000000] then :i386
|
||||
when [0xcffaedfe, 0x07000001] then :x86_64
|
||||
when [0xfeedface, 0x00000012] then :ppc7400
|
||||
when [0xfeedfacf, 0x01000012] then :ppc64
|
||||
else :dunno
|
||||
end
|
||||
|
||||
type = case read(4, offset + 12).unpack("N")[0]
|
||||
when 0x00000002, 0x02000000 then :executable
|
||||
when 0x00000006, 0x06000000 then :dylib
|
||||
else :dunno
|
||||
end
|
||||
|
||||
mach_data << { :arch => arch, :type => type }
|
||||
end
|
||||
mach_data
|
||||
rescue
|
||||
# read() will raise if it sees EOF, which should only happen if the
|
||||
# file is < 8 bytes. Otherwise, we raise if the file is not a Mach-O
|
||||
# binary. In both cases, we want to return an empty array.
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def archs
|
||||
mach_data.map{ |m| m.fetch :arch }
|
||||
end
|
||||
|
||||
def arch
|
||||
case archs.length
|
||||
when 0 then :dunno
|
||||
when 1 then archs.first
|
||||
else :universal
|
||||
end
|
||||
end
|
||||
|
||||
def universal?
|
||||
arch == :universal
|
||||
end
|
||||
|
||||
def i386?
|
||||
arch == :i386
|
||||
end
|
||||
|
||||
def x86_64?
|
||||
arch == :x86_64
|
||||
end
|
||||
|
||||
def ppc7400?
|
||||
arch == :ppc7400
|
||||
end
|
||||
|
||||
def ppc64?
|
||||
arch == :ppc64
|
||||
end
|
||||
|
||||
def dylib?
|
||||
mach_data.map{ |m| m.fetch :type }.include? :dylib
|
||||
end
|
||||
|
||||
def mach_o_executable?
|
||||
mach_data.map{ |m| m.fetch :type }.include? :executable
|
||||
end
|
||||
end
|
||||
BIN
Library/Homebrew/test/mach/a.out
Executable file
BIN
Library/Homebrew/test/mach/a.out
Executable file
Binary file not shown.
BIN
Library/Homebrew/test/mach/fat.dylib
Normal file
BIN
Library/Homebrew/test/mach/fat.dylib
Normal file
Binary file not shown.
BIN
Library/Homebrew/test/mach/i386.dylib
Normal file
BIN
Library/Homebrew/test/mach/i386.dylib
Normal file
Binary file not shown.
BIN
Library/Homebrew/test/mach/x86_64.dylib
Normal file
BIN
Library/Homebrew/test/mach/x86_64.dylib
Normal file
Binary file not shown.
140
Library/Homebrew/test/test_mach.rb
Normal file
140
Library/Homebrew/test/test_mach.rb
Normal file
@ -0,0 +1,140 @@
|
||||
require 'testing_env'
|
||||
|
||||
require 'extend/ARGV' # needs to be after test/unit to avoid conflict with OptionsParser
|
||||
ARGV.extend(HomebrewArgvExtension)
|
||||
|
||||
class MachOPathnameTests < Test::Unit::TestCase
|
||||
def test_fat_dylib
|
||||
pn = Pathname.new("#{TEST_FOLDER}/mach/fat.dylib")
|
||||
assert pn.universal?
|
||||
assert !pn.i386?
|
||||
assert !pn.x86_64?
|
||||
assert !pn.ppc7400?
|
||||
assert !pn.ppc64?
|
||||
assert pn.dylib?
|
||||
assert !pn.mach_o_executable?
|
||||
assert !pn.text_executable?
|
||||
assert pn.arch == :universal
|
||||
assert_match /Mach-O (64-bit )?dynamically linked shared library/,
|
||||
`/usr/bin/file -h '#{pn}'`.chomp
|
||||
end
|
||||
|
||||
def test_i386_dylib
|
||||
pn = Pathname.new("#{TEST_FOLDER}/mach/i386.dylib")
|
||||
assert !pn.universal?
|
||||
assert pn.i386?
|
||||
assert !pn.x86_64?
|
||||
assert !pn.ppc7400?
|
||||
assert !pn.ppc64?
|
||||
assert pn.dylib?
|
||||
assert !pn.mach_o_executable?
|
||||
assert !pn.text_executable?
|
||||
assert_match /Mach-O (64-bit )?dynamically linked shared library/,
|
||||
`/usr/bin/file -h '#{pn}'`.chomp
|
||||
end
|
||||
|
||||
def test_x86_64_dylib
|
||||
pn = Pathname.new("#{TEST_FOLDER}/mach/x86_64.dylib")
|
||||
assert !pn.universal?
|
||||
assert !pn.i386?
|
||||
assert pn.x86_64?
|
||||
assert !pn.ppc7400?
|
||||
assert !pn.ppc64?
|
||||
assert pn.dylib?
|
||||
assert !pn.mach_o_executable?
|
||||
assert !pn.text_executable?
|
||||
assert_match /Mach-O (64-bit )?dynamically linked shared library/,
|
||||
`/usr/bin/file -h '#{pn}'`.chomp
|
||||
end
|
||||
|
||||
def test_mach_o_executable
|
||||
pn = Pathname.new("#{TEST_FOLDER}/mach/a.out")
|
||||
assert pn.universal?
|
||||
assert !pn.i386?
|
||||
assert !pn.x86_64?
|
||||
assert !pn.ppc7400?
|
||||
assert !pn.ppc64?
|
||||
assert !pn.dylib?
|
||||
assert pn.mach_o_executable?
|
||||
assert !pn.text_executable?
|
||||
assert_match /Mach-O (64-bit )?executable/,
|
||||
`/usr/bin/file -h '#{pn}'`.chomp
|
||||
end
|
||||
|
||||
def test_non_mach_o
|
||||
pn = Pathname.new("#{TEST_FOLDER}/tarballs/testball-0.1.tbz")
|
||||
assert !pn.universal?
|
||||
assert !pn.i386?
|
||||
assert !pn.x86_64?
|
||||
assert !pn.ppc7400?
|
||||
assert !pn.ppc64?
|
||||
assert !pn.dylib?
|
||||
assert !pn.mach_o_executable?
|
||||
assert !pn.text_executable?
|
||||
assert pn.arch == :dunno
|
||||
assert_no_match /Mach-O (64-bit )?dynamically linked shared library/,
|
||||
`/usr/bin/file -h '#{pn}'`.chomp
|
||||
assert_no_match /Mach-O [^ ]* ?executable/,
|
||||
`/usr/bin/file -h '#{pn}'`.chomp
|
||||
end
|
||||
end
|
||||
|
||||
class TextExecutableTests < Test::Unit::TestCase
|
||||
TMPDIR = HOMEBREW_PREFIX/'tmp'
|
||||
|
||||
def setup
|
||||
FileUtils.mkdir_p TMPDIR
|
||||
end
|
||||
|
||||
def test_simple_shebang
|
||||
pn = Pathname.new('foo')
|
||||
pn.write '#!/bin/sh'
|
||||
assert !pn.universal?
|
||||
assert !pn.i386?
|
||||
assert !pn.x86_64?
|
||||
assert !pn.ppc7400?
|
||||
assert !pn.ppc64?
|
||||
assert !pn.dylib?
|
||||
assert !pn.mach_o_executable?
|
||||
assert pn.text_executable?
|
||||
assert_equal [], pn.archs
|
||||
assert pn.arch == :dunno
|
||||
assert_match /text executable/, `/usr/bin/file -h '#{pn}'`.chomp
|
||||
end
|
||||
|
||||
def test_shebang_with_options
|
||||
pn = Pathname.new('bar')
|
||||
pn.write '#! /usr/bin/perl -w'
|
||||
assert !pn.universal?
|
||||
assert !pn.i386?
|
||||
assert !pn.x86_64?
|
||||
assert !pn.ppc7400?
|
||||
assert !pn.ppc64?
|
||||
assert !pn.dylib?
|
||||
assert !pn.mach_o_executable?
|
||||
assert pn.text_executable?
|
||||
assert_equal [], pn.archs
|
||||
assert pn.arch == :dunno
|
||||
assert_match /text executable/, `/usr/bin/file -h '#{pn}'`.chomp
|
||||
end
|
||||
|
||||
def test_malformed_shebang
|
||||
pn = Pathname.new('baz')
|
||||
pn.write '#! '
|
||||
assert !pn.universal?
|
||||
assert !pn.i386?
|
||||
assert !pn.x86_64?
|
||||
assert !pn.ppc7400?
|
||||
assert !pn.ppc64?
|
||||
assert !pn.dylib?
|
||||
assert !pn.mach_o_executable?
|
||||
assert !pn.text_executable?
|
||||
assert_equal [], pn.archs
|
||||
assert pn.arch == :dunno
|
||||
assert_match /text executable/, `/usr/bin/file -h '#{pn}'`.chomp
|
||||
end
|
||||
|
||||
def teardown
|
||||
TMPDIR.rmtree
|
||||
end
|
||||
end
|
||||
Loading…
x
Reference in New Issue
Block a user