ruby-macho 2.5.1 → 4.0.0
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 +15 -2
- data/lib/macho/exceptions.rb +42 -10
- data/lib/macho/fat_file.rb +21 -4
- data/lib/macho/headers.rb +107 -103
- data/lib/macho/load_commands.rb +207 -627
- data/lib/macho/macho_file.rb +86 -21
- data/lib/macho/sections.rb +63 -54
- data/lib/macho/structure.rb +264 -22
- data/lib/macho/tools.rb +4 -0
- data/lib/macho/utils.rb +7 -0
- data/lib/macho/view.rb +10 -1
- data/lib/macho.rb +2 -2
- metadata +9 -8
data/lib/macho/structure.rb
CHANGED
@@ -1,42 +1,284 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module MachO
|
4
|
-
# A general purpose pseudo-structure.
|
4
|
+
# A general purpose pseudo-structure. Described in detail in docs/machostructure-dsl.md.
|
5
5
|
# @abstract
|
6
6
|
class MachOStructure
|
7
|
-
#
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
7
|
+
# Constants used for parsing MachOStructure fields
|
8
|
+
module Fields
|
9
|
+
# 1. All fields with empty strings and zeros aren't used
|
10
|
+
# to calculate the format and sizeof variables.
|
11
|
+
# 2. All fields with nil should provide those values manually
|
12
|
+
# via the :size parameter.
|
13
|
+
|
14
|
+
# association of field types to byte size
|
15
|
+
# @api private
|
16
|
+
BYTE_SIZE = {
|
17
|
+
# Binary slices
|
18
|
+
:string => nil,
|
19
|
+
:null_padded_string => nil,
|
20
|
+
:int32 => 4,
|
21
|
+
:uint32 => 4,
|
22
|
+
:uint64 => 8,
|
23
|
+
# Classes
|
24
|
+
:view => 0,
|
25
|
+
:lcstr => 4,
|
26
|
+
:two_level_hints_table => 0,
|
27
|
+
:tool_entries => 4,
|
28
|
+
}.freeze
|
29
|
+
|
30
|
+
# association of field types with ruby format codes
|
31
|
+
# Binary format codes can be found here:
|
32
|
+
# https://docs.ruby-lang.org/en/2.6.0/String.html#method-i-unpack
|
33
|
+
#
|
34
|
+
# The equals sign is used to manually change endianness using
|
35
|
+
# the Utils#specialize_format() method.
|
36
|
+
# @api private
|
37
|
+
FORMAT_CODE = {
|
38
|
+
# Binary slices
|
39
|
+
:string => "a",
|
40
|
+
:null_padded_string => "Z",
|
41
|
+
:int32 => "l=",
|
42
|
+
:uint32 => "L=",
|
43
|
+
:uint64 => "Q=",
|
44
|
+
# Classes
|
45
|
+
:view => "",
|
46
|
+
:lcstr => "L=",
|
47
|
+
:two_level_hints_table => "",
|
48
|
+
:tool_entries => "L=",
|
49
|
+
}.freeze
|
50
|
+
|
51
|
+
# A list of classes that must get initialized
|
52
|
+
# To add a new class append it here and add the init method to the def_class_reader method
|
53
|
+
# @api private
|
54
|
+
CLASSES_TO_INIT = %i[lcstr tool_entries two_level_hints_table].freeze
|
55
|
+
|
56
|
+
# A list of fields that don't require arguments in the initializer
|
57
|
+
# Used to calculate MachOStructure#min_args
|
58
|
+
# @api private
|
59
|
+
NO_ARG_REQUIRED = %i[two_level_hints_table].freeze
|
20
60
|
end
|
21
61
|
|
22
|
-
#
|
23
|
-
|
24
|
-
|
25
|
-
#
|
26
|
-
|
27
|
-
format = Utils.specialize_format(self::FORMAT, endianness)
|
62
|
+
# map of field names to indices
|
63
|
+
@field_idxs = {}
|
64
|
+
|
65
|
+
# array of fields sizes
|
66
|
+
@size_list = []
|
28
67
|
|
29
|
-
|
68
|
+
# array of field format codes
|
69
|
+
@fmt_list = []
|
70
|
+
|
71
|
+
# minimum number of required arguments
|
72
|
+
@min_args = 0
|
73
|
+
|
74
|
+
# @param args [Array[Value]] list of field parameters
|
75
|
+
def initialize(*args)
|
76
|
+
raise ArgumentError, "Invalid number of arguments" if args.size < self.class.min_args
|
77
|
+
|
78
|
+
@values = args
|
30
79
|
end
|
31
80
|
|
32
81
|
# @return [Hash] a hash representation of this {MachOStructure}.
|
33
82
|
def to_h
|
34
83
|
{
|
35
84
|
"structure" => {
|
36
|
-
"format" => self.class
|
85
|
+
"format" => self.class.format,
|
37
86
|
"bytesize" => self.class.bytesize,
|
38
87
|
},
|
39
88
|
}
|
40
89
|
end
|
90
|
+
|
91
|
+
class << self
|
92
|
+
attr_reader :min_args
|
93
|
+
|
94
|
+
# @param endianness [Symbol] either `:big` or `:little`
|
95
|
+
# @param bin [String] the string to be unpacked into the new structure
|
96
|
+
# @return [MachO::MachOStructure] the resulting structure
|
97
|
+
# @api private
|
98
|
+
def new_from_bin(endianness, bin)
|
99
|
+
format = Utils.specialize_format(self.format, endianness)
|
100
|
+
|
101
|
+
new(*bin.unpack(format))
|
102
|
+
end
|
103
|
+
|
104
|
+
def format
|
105
|
+
@format ||= @fmt_list.join
|
106
|
+
end
|
107
|
+
|
108
|
+
def bytesize
|
109
|
+
@bytesize ||= @size_list.sum
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
# @param subclass [Class] subclass type
|
115
|
+
# @api private
|
116
|
+
def inherited(subclass) # rubocop:disable Lint/MissingSuper
|
117
|
+
# Clone all class instance variables
|
118
|
+
field_idxs = @field_idxs.dup
|
119
|
+
size_list = @size_list.dup
|
120
|
+
fmt_list = @fmt_list.dup
|
121
|
+
min_args = @min_args.dup
|
122
|
+
|
123
|
+
# Add those values to the inheriting class
|
124
|
+
subclass.class_eval do
|
125
|
+
@field_idxs = field_idxs
|
126
|
+
@size_list = size_list
|
127
|
+
@fmt_list = fmt_list
|
128
|
+
@min_args = min_args
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# @param name [Symbol] name of internal field
|
133
|
+
# @param type [Symbol] type of field in terms of binary size
|
134
|
+
# @param options [Hash] set of additonal options
|
135
|
+
# Expected options
|
136
|
+
# :size [Integer] size in bytes
|
137
|
+
# :mask [Integer] bitmask
|
138
|
+
# :unpack [String] string format
|
139
|
+
# :default [Value] default value
|
140
|
+
# :to_s [Boolean] flag for generating #to_s
|
141
|
+
# :endian [Symbol] optionally specify :big or :little endian
|
142
|
+
# :padding [Symbol] optionally specify :null padding
|
143
|
+
# @api private
|
144
|
+
def field(name, type, **options)
|
145
|
+
raise ArgumentError, "Invalid field type #{type}" unless Fields::FORMAT_CODE.key?(type)
|
146
|
+
|
147
|
+
# Get field idx for size_list and fmt_list
|
148
|
+
idx = if @field_idxs.key?(name)
|
149
|
+
@field_idxs[name]
|
150
|
+
else
|
151
|
+
@min_args += 1 unless options.key?(:default) || Fields::NO_ARG_REQUIRED.include?(type)
|
152
|
+
@field_idxs[name] = @field_idxs.size
|
153
|
+
@size_list << nil
|
154
|
+
@fmt_list << nil
|
155
|
+
@field_idxs.size - 1
|
156
|
+
end
|
157
|
+
|
158
|
+
# Update string type if padding is specified
|
159
|
+
type = :null_padded_string if type == :string && options[:padding] == :null
|
160
|
+
|
161
|
+
# Add to size_list and fmt_list
|
162
|
+
@size_list[idx] = Fields::BYTE_SIZE[type] || options[:size]
|
163
|
+
@fmt_list[idx] = if options[:endian]
|
164
|
+
Utils.specialize_format(Fields::FORMAT_CODE[type], options[:endian])
|
165
|
+
else
|
166
|
+
Fields::FORMAT_CODE[type]
|
167
|
+
end
|
168
|
+
@fmt_list[idx] += options[:size].to_s if options.key?(:size)
|
169
|
+
|
170
|
+
# Generate methods
|
171
|
+
if Fields::CLASSES_TO_INIT.include?(type)
|
172
|
+
def_class_reader(name, type, idx)
|
173
|
+
elsif options.key?(:mask)
|
174
|
+
def_mask_reader(name, idx, options[:mask])
|
175
|
+
elsif options.key?(:unpack)
|
176
|
+
def_unpack_reader(name, idx, options[:unpack])
|
177
|
+
elsif options.key?(:default)
|
178
|
+
def_default_reader(name, idx, options[:default])
|
179
|
+
else
|
180
|
+
def_reader(name, idx)
|
181
|
+
end
|
182
|
+
|
183
|
+
def_to_s(name) if options[:to_s]
|
184
|
+
end
|
185
|
+
|
186
|
+
#
|
187
|
+
# Method Generators
|
188
|
+
#
|
189
|
+
|
190
|
+
# Generates a reader method for classes that need to be initialized.
|
191
|
+
# These classes are defined in the Fields::CLASSES_TO_INIT array.
|
192
|
+
# @param name [Symbol] name of internal field
|
193
|
+
# @param type [Symbol] type of field in terms of binary size
|
194
|
+
# @param idx [Integer] the index of the field value in the @values array
|
195
|
+
# @api private
|
196
|
+
def def_class_reader(name, type, idx)
|
197
|
+
case type
|
198
|
+
when :lcstr
|
199
|
+
define_method(name) do
|
200
|
+
instance_variable_defined?("@#{name}") ||
|
201
|
+
instance_variable_set("@#{name}", LoadCommands::LoadCommand::LCStr.new(self, @values[idx]))
|
202
|
+
|
203
|
+
instance_variable_get("@#{name}")
|
204
|
+
end
|
205
|
+
when :two_level_hints_table
|
206
|
+
define_method(name) do
|
207
|
+
instance_variable_defined?("@#{name}") ||
|
208
|
+
instance_variable_set("@#{name}", LoadCommands::TwolevelHintsCommand::TwolevelHintsTable.new(view, htoffset, nhints))
|
209
|
+
|
210
|
+
instance_variable_get("@#{name}")
|
211
|
+
end
|
212
|
+
when :tool_entries
|
213
|
+
define_method(name) do
|
214
|
+
instance_variable_defined?("@#{name}") ||
|
215
|
+
instance_variable_set("@#{name}", LoadCommands::BuildVersionCommand::ToolEntries.new(view, @values[idx]))
|
216
|
+
|
217
|
+
instance_variable_get("@#{name}")
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# Generates a reader method for fields that need to be bitmasked.
|
223
|
+
# @param name [Symbol] name of internal field
|
224
|
+
# @param idx [Integer] the index of the field value in the @values array
|
225
|
+
# @param mask [Integer] the bitmask
|
226
|
+
# @api private
|
227
|
+
def def_mask_reader(name, idx, mask)
|
228
|
+
define_method(name) do
|
229
|
+
instance_variable_defined?("@#{name}") ||
|
230
|
+
instance_variable_set("@#{name}", @values[idx] & ~mask)
|
231
|
+
|
232
|
+
instance_variable_get("@#{name}")
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# Generates a reader method for fields that need further unpacking.
|
237
|
+
# @param name [Symbol] name of internal field
|
238
|
+
# @param idx [Integer] the index of the field value in the @values array
|
239
|
+
# @param unpack [String] the format code used for futher binary unpacking
|
240
|
+
# @api private
|
241
|
+
def def_unpack_reader(name, idx, unpack)
|
242
|
+
define_method(name) do
|
243
|
+
instance_variable_defined?("@#{name}") ||
|
244
|
+
instance_variable_set("@#{name}", @values[idx].unpack(unpack))
|
245
|
+
|
246
|
+
instance_variable_get("@#{name}")
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# Generates a reader method for fields that have default values.
|
251
|
+
# @param name [Symbol] name of internal field
|
252
|
+
# @param idx [Integer] the index of the field value in the @values array
|
253
|
+
# @param default [Value] the default value
|
254
|
+
# @api private
|
255
|
+
def def_default_reader(name, idx, default)
|
256
|
+
define_method(name) do
|
257
|
+
instance_variable_defined?("@#{name}") ||
|
258
|
+
instance_variable_set("@#{name}", @values.size > idx ? @values[idx] : default)
|
259
|
+
|
260
|
+
instance_variable_get("@#{name}")
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# Generates an attr_reader like method for a field.
|
265
|
+
# @param name [Symbol] name of internal field
|
266
|
+
# @param idx [Integer] the index of the field value in the @values array
|
267
|
+
# @api private
|
268
|
+
def def_reader(name, idx)
|
269
|
+
define_method(name) do
|
270
|
+
@values[idx]
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
# Generates the to_s method based on the named field.
|
275
|
+
# @param name [Symbol] name of the field
|
276
|
+
# @api private
|
277
|
+
def def_to_s(name)
|
278
|
+
define_method(:to_s) do
|
279
|
+
send(name).to_s
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
41
283
|
end
|
42
284
|
end
|
data/lib/macho/tools.rb
CHANGED
@@ -51,6 +51,8 @@ module MachO
|
|
51
51
|
# @param options [Hash]
|
52
52
|
# @option options [Boolean] :strict (true) whether or not to fail loudly
|
53
53
|
# with an exception if the change cannot be performed
|
54
|
+
# @option options [Boolean] :uniq (false) whether or not to change duplicate
|
55
|
+
# rpaths simultaneously
|
54
56
|
# @return [void]
|
55
57
|
def self.change_rpath(filename, old_path, new_path, options = {})
|
56
58
|
file = MachO.open(filename)
|
@@ -80,6 +82,8 @@ module MachO
|
|
80
82
|
# @param options [Hash]
|
81
83
|
# @option options [Boolean] :strict (true) whether or not to fail loudly
|
82
84
|
# with an exception if the change cannot be performed
|
85
|
+
# @option options [Boolean] :uniq (false) whether or not to delete duplicate
|
86
|
+
# rpaths simultaneously
|
83
87
|
# @return [void]
|
84
88
|
def self.delete_rpath(filename, old_path, options = {})
|
85
89
|
file = MachO.open(filename)
|
data/lib/macho/utils.rb
CHANGED
@@ -121,5 +121,12 @@ module MachO
|
|
121
121
|
def self.big_magic?(num)
|
122
122
|
[Headers::MH_MAGIC, Headers::MH_MAGIC_64].include? num
|
123
123
|
end
|
124
|
+
|
125
|
+
# Compares the given number to the known magic number for a compressed Mach-O slice.
|
126
|
+
# @param num [Integer] the number being checked
|
127
|
+
# @return [Boolean] whether `num` is a valid compressed header magic number
|
128
|
+
def self.compressed_magic?(num)
|
129
|
+
num == Headers::COMPRESSED_MAGIC
|
130
|
+
end
|
124
131
|
end
|
125
132
|
end
|
data/lib/macho/view.rb
CHANGED
@@ -3,6 +3,9 @@
|
|
3
3
|
module MachO
|
4
4
|
# A representation of some unspecified Mach-O data.
|
5
5
|
class MachOView
|
6
|
+
# @return [MachOFile] that this view belongs to
|
7
|
+
attr_reader :macho_file
|
8
|
+
|
6
9
|
# @return [String] the raw Mach-O data
|
7
10
|
attr_reader :raw_data
|
8
11
|
|
@@ -13,10 +16,12 @@ module MachO
|
|
13
16
|
attr_reader :offset
|
14
17
|
|
15
18
|
# Creates a new MachOView.
|
19
|
+
# @param macho_file [MachOFile] the file this view slice is from
|
16
20
|
# @param raw_data [String] the raw Mach-O data
|
17
21
|
# @param endianness [Symbol] the endianness of the data
|
18
22
|
# @param offset [Integer] the offset of the relevant data
|
19
|
-
def initialize(raw_data, endianness, offset)
|
23
|
+
def initialize(macho_file, raw_data, endianness, offset)
|
24
|
+
@macho_file = macho_file
|
20
25
|
@raw_data = raw_data
|
21
26
|
@endianness = endianness
|
22
27
|
@offset = offset
|
@@ -29,5 +34,9 @@ module MachO
|
|
29
34
|
"offset" => offset,
|
30
35
|
}
|
31
36
|
end
|
37
|
+
|
38
|
+
def inspect
|
39
|
+
"#<#{self.class}:0x#{(object_id << 1).to_s(16)} @endianness=#{@endianness.inspect}, @offset=#{@offset.inspect}, length=#{@raw_data.length}>"
|
40
|
+
end
|
32
41
|
end
|
33
42
|
end
|
data/lib/macho.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "open3"
|
4
4
|
|
5
|
+
require_relative "macho/utils"
|
5
6
|
require_relative "macho/structure"
|
6
7
|
require_relative "macho/view"
|
7
8
|
require_relative "macho/headers"
|
@@ -10,13 +11,12 @@ require_relative "macho/sections"
|
|
10
11
|
require_relative "macho/macho_file"
|
11
12
|
require_relative "macho/fat_file"
|
12
13
|
require_relative "macho/exceptions"
|
13
|
-
require_relative "macho/utils"
|
14
14
|
require_relative "macho/tools"
|
15
15
|
|
16
16
|
# The primary namespace for ruby-macho.
|
17
17
|
module MachO
|
18
18
|
# release version
|
19
|
-
VERSION = "
|
19
|
+
VERSION = "4.0.0"
|
20
20
|
|
21
21
|
# Opens the given filename as a MachOFile or FatFile, depending on its magic.
|
22
22
|
# @param filename [String] the file being opened
|
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:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- William Woodruff
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-07-25 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@yossarian.net
|
@@ -33,8 +33,9 @@ files:
|
|
33
33
|
homepage: https://github.com/Homebrew/ruby-macho
|
34
34
|
licenses:
|
35
35
|
- MIT
|
36
|
-
metadata:
|
37
|
-
|
36
|
+
metadata:
|
37
|
+
rubygems_mfa_required: 'true'
|
38
|
+
post_install_message:
|
38
39
|
rdoc_options: []
|
39
40
|
require_paths:
|
40
41
|
- lib
|
@@ -42,15 +43,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
42
43
|
requirements:
|
43
44
|
- - ">="
|
44
45
|
- !ruby/object:Gem::Version
|
45
|
-
version: '2.
|
46
|
+
version: '2.6'
|
46
47
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
48
|
requirements:
|
48
49
|
- - ">="
|
49
50
|
- !ruby/object:Gem::Version
|
50
51
|
version: '0'
|
51
52
|
requirements: []
|
52
|
-
rubygems_version: 3.
|
53
|
-
signing_key:
|
53
|
+
rubygems_version: 3.4.10
|
54
|
+
signing_key:
|
54
55
|
specification_version: 4
|
55
56
|
summary: ruby-macho - Mach-O file analyzer.
|
56
57
|
test_files: []
|