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 'pathname'
|
||||||
require 'bottles'
|
require 'bottles'
|
||||||
|
require 'mach'
|
||||||
|
|
||||||
# we enhance pathname to make our code more readable
|
# we enhance pathname to make our code more readable
|
||||||
class Pathname
|
class Pathname
|
||||||
|
include MachO
|
||||||
|
|
||||||
def install *sources
|
def install *sources
|
||||||
results = []
|
results = []
|
||||||
sources.each do |src|
|
sources.each do |src|
|
||||||
@ -268,6 +271,10 @@ class Pathname
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def text_executable?
|
||||||
|
%r[#!\s*(/.+)+] === open('r') { |f| f.readline }
|
||||||
|
end
|
||||||
|
|
||||||
def incremental_hash(hasher)
|
def incremental_hash(hasher)
|
||||||
incr_hash = hasher.new
|
incr_hash = hasher.new
|
||||||
self.open('r') do |f|
|
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