ruby-macho 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +8 -9
- data/lib/macho.rb +2 -1
- data/lib/macho/exceptions.rb +83 -6
- data/lib/macho/fat_file.rb +130 -39
- data/lib/macho/headers.rb +131 -25
- data/lib/macho/load_commands.rb +518 -177
- data/lib/macho/macho_file.rb +254 -172
- data/lib/macho/open.rb +5 -5
- data/lib/macho/sections.rb +20 -9
- data/lib/macho/structure.rb +8 -16
- data/lib/macho/tools.rb +34 -15
- data/lib/macho/utils.rb +87 -39
- data/lib/macho/view.rb +23 -0
- metadata +4 -2
data/lib/macho/open.rb
CHANGED
@@ -7,17 +7,17 @@ module MachO
|
|
7
7
|
# @raise [MachO::TruncatedFileError] if the file is too small to have a valid header
|
8
8
|
# @raise [MachO::MagicError] if the file's magic is not valid Mach-O magic
|
9
9
|
def self.open(filename)
|
10
|
-
raise ArgumentError
|
11
|
-
raise TruncatedFileError
|
10
|
+
raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
|
11
|
+
raise TruncatedFileError unless File.stat(filename).size >= 4
|
12
12
|
|
13
13
|
magic = File.open(filename, "rb") { |f| f.read(4) }.unpack("N").first
|
14
14
|
|
15
|
-
if
|
15
|
+
if Utils.fat_magic?(magic)
|
16
16
|
file = FatFile.new(filename)
|
17
|
-
elsif
|
17
|
+
elsif Utils.magic?(magic)
|
18
18
|
file = MachOFile.new(filename)
|
19
19
|
else
|
20
|
-
raise MagicError
|
20
|
+
raise MagicError, magic
|
21
21
|
end
|
22
22
|
|
23
23
|
file
|
data/lib/macho/sections.rb
CHANGED
@@ -45,7 +45,7 @@ module MachO
|
|
45
45
|
:S_ATTR_DEBUG => 0x02000000,
|
46
46
|
:S_ATTR_SOME_INSTRUCTIONS => 0x00000400,
|
47
47
|
:S_ATTR_EXT_RELOC => 0x00000200,
|
48
|
-
:S_ATTR_LOC_RELOC => 0x00000100
|
48
|
+
:S_ATTR_LOC_RELOC => 0x00000100,
|
49
49
|
}.freeze
|
50
50
|
|
51
51
|
# association of section name symbols to names
|
@@ -62,7 +62,7 @@ module MachO
|
|
62
62
|
:SECT_OBJC_STRINGS => "__selector_strs",
|
63
63
|
:SECT_OBJC_REFS => "__selector_refs",
|
64
64
|
:SECT_ICON_HEADER => "__header",
|
65
|
-
:SECT_ICON_TIFF => "__tiff"
|
65
|
+
:SECT_ICON_TIFF => "__tiff",
|
66
66
|
}.freeze
|
67
67
|
|
68
68
|
# Represents a section of a segment for 32-bit architectures.
|
@@ -91,7 +91,7 @@ module MachO
|
|
91
91
|
# @return [Fixnum] the number of relocation entries
|
92
92
|
attr_reader :nreloc
|
93
93
|
|
94
|
-
# @return [Fixnum] flags for type and
|
94
|
+
# @return [Fixnum] flags for type and attributes of the section
|
95
95
|
attr_reader :flags
|
96
96
|
|
97
97
|
# @return [void] reserved (for offset or index)
|
@@ -100,12 +100,15 @@ module MachO
|
|
100
100
|
# @return [void] reserved (for count or sizeof)
|
101
101
|
attr_reader :reserved2
|
102
102
|
|
103
|
-
|
103
|
+
# @see MachOStructure::FORMAT
|
104
|
+
FORMAT = "a16a16L=9".freeze
|
105
|
+
|
106
|
+
# @see MachOStructure::SIZEOF
|
104
107
|
SIZEOF = 68
|
105
108
|
|
106
109
|
# @api private
|
107
110
|
def initialize(sectname, segname, addr, size, offset, align, reloff,
|
108
|
-
|
111
|
+
nreloc, flags, reserved1, reserved2)
|
109
112
|
@sectname = sectname
|
110
113
|
@segname = segname
|
111
114
|
@addr = addr
|
@@ -121,12 +124,17 @@ module MachO
|
|
121
124
|
|
122
125
|
# @return [String] the section's name, with any trailing NULL characters removed
|
123
126
|
def section_name
|
124
|
-
|
127
|
+
sectname.delete("\x00")
|
125
128
|
end
|
126
129
|
|
127
130
|
# @return [String] the parent segment's name, with any trailing NULL characters removed
|
128
131
|
def segment_name
|
129
|
-
|
132
|
+
segname.delete("\x00")
|
133
|
+
end
|
134
|
+
|
135
|
+
# @return [Boolean] true if the section has no contents (i.e, `size` is 0)
|
136
|
+
def empty?
|
137
|
+
size.zero?
|
130
138
|
end
|
131
139
|
|
132
140
|
# @example
|
@@ -145,12 +153,15 @@ module MachO
|
|
145
153
|
# @return [void] reserved
|
146
154
|
attr_reader :reserved3
|
147
155
|
|
148
|
-
|
156
|
+
# @see MachOStructure::FORMAT
|
157
|
+
FORMAT = "a16a16Q=2L=8".freeze
|
158
|
+
|
159
|
+
# @see MachOStructure::SIZEOF
|
149
160
|
SIZEOF = 80
|
150
161
|
|
151
162
|
# @api private
|
152
163
|
def initialize(sectname, segname, addr, size, offset, align, reloff,
|
153
|
-
|
164
|
+
nreloc, flags, reserved1, reserved2, reserved3)
|
154
165
|
super(sectname, segname, addr, size, offset, align, reloff,
|
155
166
|
nreloc, flags, reserved1, reserved2)
|
156
167
|
@reserved3 = reserved3
|
data/lib/macho/structure.rb
CHANGED
@@ -3,9 +3,13 @@ module MachO
|
|
3
3
|
# @abstract
|
4
4
|
class MachOStructure
|
5
5
|
# The String#unpack format of the data structure.
|
6
|
-
|
6
|
+
# @return [String] the unpacking format
|
7
|
+
# @api private
|
8
|
+
FORMAT = "".freeze
|
7
9
|
|
8
10
|
# The size of the data structure, in bytes.
|
11
|
+
# @return [Fixnum] the size, in bytes
|
12
|
+
# @api private
|
9
13
|
SIZEOF = 0
|
10
14
|
|
11
15
|
# @return [Fixnum] the size, in bytes, of the represented structure.
|
@@ -13,26 +17,14 @@ module MachO
|
|
13
17
|
self::SIZEOF
|
14
18
|
end
|
15
19
|
|
16
|
-
# @param endianness [Symbol] either
|
20
|
+
# @param endianness [Symbol] either `:big` or `:little`
|
17
21
|
# @param bin [String] the string to be unpacked into the new structure
|
18
22
|
# @return [MachO::MachOStructure] a new MachOStructure initialized with `bin`
|
19
23
|
# @api private
|
20
24
|
def self.new_from_bin(endianness, bin)
|
21
|
-
format = specialize_format(self::FORMAT, endianness)
|
22
|
-
|
23
|
-
self.new(*bin.unpack(format))
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
25
|
+
format = Utils.specialize_format(self::FORMAT, endianness)
|
27
26
|
|
28
|
-
|
29
|
-
# @param format [String] the format string being converted
|
30
|
-
# @param endianness [Symbol] either :big or :little
|
31
|
-
# @return [String] the converted string
|
32
|
-
# @api private
|
33
|
-
def self.specialize_format(format, endianness)
|
34
|
-
modifier = (endianness == :big) ? ">" : "<"
|
35
|
-
format.tr("=", modifier)
|
27
|
+
new(*bin.unpack(format))
|
36
28
|
end
|
37
29
|
end
|
38
30
|
end
|
data/lib/macho/tools.rb
CHANGED
@@ -12,12 +12,14 @@ module MachO
|
|
12
12
|
# Changes the dylib ID of a Mach-O or Fat binary, overwriting the source file.
|
13
13
|
# @param filename [String] the Mach-O or Fat binary being modified
|
14
14
|
# @param new_id [String] the new dylib ID for the binary
|
15
|
+
# @param options [Hash]
|
16
|
+
# @option options [Boolean] :strict (true) whether or not to fail loudly
|
17
|
+
# with an exception if the change cannot be performed
|
15
18
|
# @return [void]
|
16
|
-
|
17
|
-
def self.change_dylib_id(filename, new_id)
|
19
|
+
def self.change_dylib_id(filename, new_id, options = {})
|
18
20
|
file = MachO.open(filename)
|
19
21
|
|
20
|
-
file.
|
22
|
+
file.change_dylib_id(new_id, options)
|
21
23
|
file.write!
|
22
24
|
end
|
23
25
|
|
@@ -25,12 +27,14 @@ module MachO
|
|
25
27
|
# @param filename [String] the Mach-O or Fat binary being modified
|
26
28
|
# @param old_name [String] the old shared library name
|
27
29
|
# @param new_name [String] the new shared library name
|
30
|
+
# @param options [Hash]
|
31
|
+
# @option options [Boolean] :strict (true) whether or not to fail loudly
|
32
|
+
# with an exception if the change cannot be performed
|
28
33
|
# @return [void]
|
29
|
-
|
30
|
-
def self.change_install_name(filename, old_name, new_name)
|
34
|
+
def self.change_install_name(filename, old_name, new_name, options = {})
|
31
35
|
file = MachO.open(filename)
|
32
36
|
|
33
|
-
file.change_install_name(old_name, new_name)
|
37
|
+
file.change_install_name(old_name, new_name, options)
|
34
38
|
file.write!
|
35
39
|
end
|
36
40
|
|
@@ -38,28 +42,43 @@ module MachO
|
|
38
42
|
# @param filename [String] the Mach-O or Fat binary being modified
|
39
43
|
# @param old_path [String] the old runtime path
|
40
44
|
# @param new_path [String] the new runtime path
|
45
|
+
# @param options [Hash]
|
46
|
+
# @option options [Boolean] :strict (true) whether or not to fail loudly
|
47
|
+
# with an exception if the change cannot be performed
|
41
48
|
# @return [void]
|
42
|
-
|
43
|
-
|
44
|
-
|
49
|
+
def self.change_rpath(filename, old_path, new_path, options = {})
|
50
|
+
file = MachO.open(filename)
|
51
|
+
|
52
|
+
file.change_rpath(old_path, new_path, options)
|
53
|
+
file.write!
|
45
54
|
end
|
46
55
|
|
47
56
|
# Add a runtime path to a Mach-O or Fat binary, overwriting the source file.
|
48
57
|
# @param filename [String] the Mach-O or Fat binary being modified
|
49
58
|
# @param new_path [String] the new runtime path
|
59
|
+
# @param options [Hash]
|
60
|
+
# @option options [Boolean] :strict (true) whether or not to fail loudly
|
61
|
+
# with an exception if the change cannot be performed
|
50
62
|
# @return [void]
|
51
|
-
|
52
|
-
|
53
|
-
|
63
|
+
def self.add_rpath(filename, new_path, options = {})
|
64
|
+
file = MachO.open(filename)
|
65
|
+
|
66
|
+
file.add_rpath(new_path, options)
|
67
|
+
file.write!
|
54
68
|
end
|
55
69
|
|
56
70
|
# Delete a runtime path from a Mach-O or Fat binary, overwriting the source file.
|
57
71
|
# @param filename [String] the Mach-O or Fat binary being modified
|
58
72
|
# @param old_path [String] the old runtime path
|
73
|
+
# @param options [Hash]
|
74
|
+
# @option options [Boolean] :strict (true) whether or not to fail loudly
|
75
|
+
# with an exception if the change cannot be performed
|
59
76
|
# @return [void]
|
60
|
-
|
61
|
-
|
62
|
-
|
77
|
+
def self.delete_rpath(filename, old_path, options = {})
|
78
|
+
file = MachO.open(filename)
|
79
|
+
|
80
|
+
file.delete_rpath(old_path, options)
|
81
|
+
file.write!
|
63
82
|
end
|
64
83
|
end
|
65
84
|
end
|
data/lib/macho/utils.rb
CHANGED
@@ -1,48 +1,96 @@
|
|
1
1
|
module MachO
|
2
|
-
#
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
value
|
10
|
-
|
11
|
-
|
2
|
+
# A collection of utility functions used throughout ruby-macho.
|
3
|
+
module Utils
|
4
|
+
# Rounds a value to the next multiple of the given round.
|
5
|
+
# @param value [Fixnum] the number being rounded
|
6
|
+
# @param round [Fixnum] the number being rounded with
|
7
|
+
# @return [Fixnum] the rounded value
|
8
|
+
# @see http://www.opensource.apple.com/source/cctools/cctools-870/libstuff/rnd.c
|
9
|
+
def self.round(value, round)
|
10
|
+
round -= 1
|
11
|
+
value += round
|
12
|
+
value &= ~round
|
13
|
+
value
|
14
|
+
end
|
12
15
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
16
|
+
# Returns the number of bytes needed to pad the given size to the given alignment.
|
17
|
+
# @param size [Fixnum] the unpadded size
|
18
|
+
# @param alignment [Fixnum] the number to alignment the size with
|
19
|
+
# @return [Fixnum] the number of pad bytes required
|
20
|
+
def self.padding_for(size, alignment)
|
21
|
+
round(size, alignment) - size
|
22
|
+
end
|
18
23
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
+
# Converts an abstract (native-endian) String#unpack format to big or little.
|
25
|
+
# @param format [String] the format string being converted
|
26
|
+
# @param endianness [Symbol] either `:big` or `:little`
|
27
|
+
# @return [String] the converted string
|
28
|
+
def self.specialize_format(format, endianness)
|
29
|
+
modifier = endianness == :big ? ">" : "<"
|
30
|
+
format.tr("=", modifier)
|
31
|
+
end
|
24
32
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
33
|
+
# Packs tagged strings into an aligned payload.
|
34
|
+
# @param fixed_offset [Fixnum] the baseline offset for the first packed string
|
35
|
+
# @param alignment [Fixnum] the alignment value to use for packing
|
36
|
+
# @param strings [Hash] the labeled strings to pack
|
37
|
+
# @return [Array<String, Hash>] the packed string and labeled offsets
|
38
|
+
def self.pack_strings(fixed_offset, alignment, strings = {})
|
39
|
+
offsets = {}
|
40
|
+
next_offset = fixed_offset
|
41
|
+
payload = ""
|
30
42
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
43
|
+
strings.each do |key, string|
|
44
|
+
offsets[key] = next_offset
|
45
|
+
payload << string
|
46
|
+
payload << "\x00"
|
47
|
+
next_offset += string.bytesize + 1
|
48
|
+
end
|
36
49
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
50
|
+
payload << "\x00" * padding_for(fixed_offset + payload.bytesize, alignment)
|
51
|
+
[payload, offsets]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Compares the given number to valid Mach-O magic numbers.
|
55
|
+
# @param num [Fixnum] the number being checked
|
56
|
+
# @return [Boolean] true if `num` is a valid Mach-O magic number, false otherwise
|
57
|
+
def self.magic?(num)
|
58
|
+
MH_MAGICS.key?(num)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Compares the given number to valid Fat magic numbers.
|
62
|
+
# @param num [Fixnum] the number being checked
|
63
|
+
# @return [Boolean] true if `num` is a valid Fat magic number, false otherwise
|
64
|
+
def self.fat_magic?(num)
|
65
|
+
num == FAT_MAGIC
|
66
|
+
end
|
67
|
+
|
68
|
+
# Compares the given number to valid 32-bit Mach-O magic numbers.
|
69
|
+
# @param num [Fixnum] the number being checked
|
70
|
+
# @return [Boolean] true if `num` is a valid 32-bit magic number, false otherwise
|
71
|
+
def self.magic32?(num)
|
72
|
+
num == MH_MAGIC || num == MH_CIGAM
|
73
|
+
end
|
74
|
+
|
75
|
+
# Compares the given number to valid 64-bit Mach-O magic numbers.
|
76
|
+
# @param num [Fixnum] the number being checked
|
77
|
+
# @return [Boolean] true if `num` is a valid 64-bit magic number, false otherwise
|
78
|
+
def self.magic64?(num)
|
79
|
+
num == MH_MAGIC_64 || num == MH_CIGAM_64
|
80
|
+
end
|
81
|
+
|
82
|
+
# Compares the given number to valid little-endian magic numbers.
|
83
|
+
# @param num [Fixnum] the number being checked
|
84
|
+
# @return [Boolean] true if `num` is a valid little-endian magic number, false otherwise
|
85
|
+
def self.little_magic?(num)
|
86
|
+
num == MH_CIGAM || num == MH_CIGAM_64
|
87
|
+
end
|
42
88
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
89
|
+
# Compares the given number to valid big-endian magic numbers.
|
90
|
+
# @param num [Fixnum] the number being checked
|
91
|
+
# @return [Boolean] true if `num` is a valid big-endian magic number, false otherwise
|
92
|
+
def self.big_magic?(num)
|
93
|
+
num == MH_CIGAM || num == MH_CIGAM_64
|
94
|
+
end
|
47
95
|
end
|
48
96
|
end
|
data/lib/macho/view.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module MachO
|
2
|
+
# A representation of some unspecified Mach-O data.
|
3
|
+
class MachOView
|
4
|
+
# @return [String] the raw Mach-O data
|
5
|
+
attr_reader :raw_data
|
6
|
+
|
7
|
+
# @return [Symbol] the endianness of the data (`:big` or `:little`)
|
8
|
+
attr_reader :endianness
|
9
|
+
|
10
|
+
# @return [Fixnum] the offset of the relevant data (in {#raw_data})
|
11
|
+
attr_reader :offset
|
12
|
+
|
13
|
+
# Creates a new MachOView.
|
14
|
+
# @param raw_data [String] the raw Mach-O data
|
15
|
+
# @param endianness [Symbol] the endianness of the data
|
16
|
+
# @param offset [Fixnum] the offset of the relevant data
|
17
|
+
def initialize(raw_data, endianness, offset)
|
18
|
+
@raw_data = raw_data
|
19
|
+
@endianness = endianness
|
20
|
+
@offset = offset
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-macho
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- William Woodruff
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-08-07 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: A library for viewing and manipulating Mach-O files in Ruby.
|
14
14
|
email: william@tuffbizz.com
|
@@ -30,6 +30,7 @@ files:
|
|
30
30
|
- lib/macho/structure.rb
|
31
31
|
- lib/macho/tools.rb
|
32
32
|
- lib/macho/utils.rb
|
33
|
+
- lib/macho/view.rb
|
33
34
|
homepage: https://github.com/Homebrew/ruby-macho
|
34
35
|
licenses:
|
35
36
|
- MIT
|
@@ -55,3 +56,4 @@ signing_key:
|
|
55
56
|
specification_version: 4
|
56
57
|
summary: ruby-macho - Mach-O file analyzer.
|
57
58
|
test_files: []
|
59
|
+
has_rdoc:
|