ruby-macho 1.0.0 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5c4525869d67df12f84b98120b7a0e28cd2e554b
4
- data.tar.gz: 007fa710b4d119fe5bb477d4dea2c51664ddbb8d
3
+ metadata.gz: 633935fc4cb74f62b33362d5ecb5f4db5b6d4cbf
4
+ data.tar.gz: f69c9cedb248620ce5e8477f5f3ee02d9cf9a483
5
5
  SHA512:
6
- metadata.gz: c90ef356d11b1c19b28703ef5a45ef2f1f999a7836ec126b4106a17dcc39630b825a8cd860cbd70c6c31225182c348eb5d1b2f0579fe9ecc072ce39a0ffc0c7c
7
- data.tar.gz: a8bb43dd63755f1b1db1f1e3bfc23120d88f41020aa021dea91db9c8e2c7a2e14d7c5182bf2bb315c09236decba982202461a57f8b670ee78d57e1a40b04b874
6
+ metadata.gz: 957d16360147749264145ac81312e28e8fdd612d24802659642416f8bb2ad2a2e0d3998bc8be15d2adb24c41ef00ba8ec9be16e4c4f28fc6801cf07f358f9128
7
+ data.tar.gz: 327c34832cc7233b81023f49fbbb6ae34fbece07ea6cdb0c4e7f9b89237e5780d1acfd359a073c5638f3fd81eb23895d540caca07cd91c1706ca10cded96c6b3
data/README.md CHANGED
@@ -51,7 +51,7 @@ puts lc_vers.version_string # => "10.10.0"
51
51
  Attribution:
52
52
 
53
53
  * Constants were taken from Apple, Inc's
54
- [`loader.h` in `cctools/include/mach-o`](http://www.opensource.apple.com/source/cctools/cctools-870/include/mach-o/loader.h).
54
+ [`loader.h` in `cctools/include/mach-o`](https://www.opensource.apple.com/source/cctools/cctools-870/include/mach-o/loader.h).
55
55
  (Apple Public Source License 2.0).
56
56
 
57
57
  ### License
@@ -12,15 +12,15 @@ require "#{File.dirname(__FILE__)}/macho/tools"
12
12
  # The primary namespace for ruby-macho.
13
13
  module MachO
14
14
  # release version
15
- VERSION = "1.0.0".freeze
15
+ VERSION = "1.1.0".freeze
16
16
 
17
17
  # Opens the given filename as a MachOFile or FatFile, depending on its magic.
18
18
  # @param filename [String] the file being opened
19
- # @return [MachO::MachOFile] if the file is a Mach-O
20
- # @return [MachO::FatFile] if the file is a Fat file
19
+ # @return [MachOFile] if the file is a Mach-O
20
+ # @return [FatFile] if the file is a Fat file
21
21
  # @raise [ArgumentError] if the given file does not exist
22
- # @raise [MachO::TruncatedFileError] if the file is too small to have a valid header
23
- # @raise [MachO::MagicError] if the file's magic is not valid Mach-O magic
22
+ # @raise [TruncatedFileError] if the file is too small to have a valid header
23
+ # @raise [MagicError] if the file's magic is not valid Mach-O magic
24
24
  def self.open(filename)
25
25
  raise ArgumentError, "#{filename}: no such file" unless File.file?(filename)
26
26
  raise TruncatedFileError unless File.stat(filename).size >= 4
@@ -80,7 +80,8 @@ module MachO
80
80
  # @param cputype [Fixnum] the CPU type of the unknown pair
81
81
  # @param cpusubtype [Fixnum] the CPU sub-type of the unknown pair
82
82
  def initialize(cputype, cpusubtype)
83
- super "Unrecognized CPU sub-type: 0x#{"%08x" % cpusubtype} (for CPU type: 0x#{"%08x" % cputype})"
83
+ super "Unrecognized CPU sub-type: 0x#{"%08x" % cpusubtype}" \
84
+ " (for CPU type: 0x#{"%08x" % cputype})"
84
85
  end
85
86
  end
86
87
 
@@ -108,13 +109,15 @@ module MachO
108
109
  end
109
110
  end
110
111
 
111
- # Raised when the number of arguments used to create a load command manually is wrong.
112
+ # Raised when the number of arguments used to create a load command manually
113
+ # is wrong.
112
114
  class LoadCommandCreationArityError < MachOError
113
115
  # @param cmd_sym [Symbol] the load command's symbol
114
116
  # @param expected_arity [Fixnum] the number of arguments expected
115
117
  # @param actual_arity [Fixnum] the number of arguments received
116
118
  def initialize(cmd_sym, expected_arity, actual_arity)
117
- super "Expected #{expected_arity} arguments for #{cmd_sym} creation, got #{actual_arity}"
119
+ super "Expected #{expected_arity} arguments for #{cmd_sym} creation," \
120
+ " got #{actual_arity}"
118
121
  end
119
122
  end
120
123
 
@@ -130,7 +133,8 @@ module MachO
130
133
  class LCStrMalformedError < MachOError
131
134
  # @param lc [MachO::LoadCommand] the load command containing the string
132
135
  def initialize(lc)
133
- super "Load command #{lc.type} at offset #{lc.view.offset} contains a malformed string"
136
+ super "Load command #{lc.type} at offset #{lc.view.offset} contains a" \
137
+ " malformed string"
134
138
  end
135
139
  end
136
140
 
@@ -1,24 +1,50 @@
1
+ require "forwardable"
2
+
1
3
  module MachO
2
4
  # Represents a "Fat" file, which contains a header, a listing of available
3
5
  # architectures, and one or more Mach-O binaries.
4
6
  # @see https://en.wikipedia.org/wiki/Mach-O#Multi-architecture_binaries
5
- # @see MachO::MachOFile
7
+ # @see MachOFile
6
8
  class FatFile
9
+ extend Forwardable
10
+
7
11
  # @return [String] the filename loaded from, or nil if loaded from a binary string
8
12
  attr_accessor :filename
9
13
 
10
- # @return [MachO::Headers::FatHeader] the file's header
14
+ # @return [Headers::FatHeader] the file's header
11
15
  attr_reader :header
12
16
 
13
- # @return [Array<MachO::Headers::FatArch>] an array of fat architectures
17
+ # @return [Array<Headers::FatArch>] an array of fat architectures
14
18
  attr_reader :fat_archs
15
19
 
16
- # @return [Array<MachO::MachOFile>] an array of Mach-O binaries
20
+ # @return [Array<MachOFile>] an array of Mach-O binaries
17
21
  attr_reader :machos
18
22
 
23
+ # Creates a new FatFile from the given (single-arch) Mach-Os
24
+ # @param machos [Array<MachOFile>] the machos to combine
25
+ # @return [FatFile] a new FatFile containing the give machos
26
+ def self.new_from_machos(*machos)
27
+ header = Headers::FatHeader.new(Headers::FAT_MAGIC, machos.size)
28
+ offset = Headers::FatHeader.bytesize + (machos.size * Headers::FatArch.bytesize)
29
+ fat_archs = []
30
+ machos.each do |macho|
31
+ fat_archs << Headers::FatArch.new(macho.header.cputype,
32
+ macho.header.cpusubtype,
33
+ offset, macho.serialize.bytesize,
34
+ macho.alignment)
35
+ offset += macho.serialize.bytesize
36
+ end
37
+
38
+ bin = header.serialize
39
+ bin << fat_archs.map(&:serialize).join
40
+ bin << machos.map(&:serialize).join
41
+
42
+ new_from_bin(bin)
43
+ end
44
+
19
45
  # Creates a new FatFile instance from a binary string.
20
46
  # @param bin [String] a binary string containing raw Mach-O data
21
- # @return [MachO::FatFile] a new FatFile
47
+ # @return [FatFile] a new FatFile
22
48
  def self.new_from_bin(bin)
23
49
  instance = allocate
24
50
  instance.initialize_from_bin(bin)
@@ -38,7 +64,7 @@ module MachO
38
64
  end
39
65
 
40
66
  # Initializes a new FatFile instance from a binary string.
41
- # @see MachO::FatFile.new_from_bin
67
+ # @see new_from_bin
42
68
  # @api private
43
69
  def initialize_from_bin(bin)
44
70
  @filename = nil
@@ -52,72 +78,43 @@ module MachO
52
78
  @raw_data
53
79
  end
54
80
 
55
- # @return [Boolean] true if the file is of type `MH_OBJECT`, false otherwise
56
- def object?
57
- machos.first.object?
58
- end
59
-
60
- # @return [Boolean] true if the file is of type `MH_EXECUTE`, false otherwise
61
- def executable?
62
- machos.first.executable?
63
- end
64
-
65
- # @return [Boolean] true if the file is of type `MH_FVMLIB`, false otherwise
66
- def fvmlib?
67
- machos.first.fvmlib?
68
- end
69
-
70
- # @return [Boolean] true if the file is of type `MH_CORE`, false otherwise
71
- def core?
72
- machos.first.core?
73
- end
74
-
75
- # @return [Boolean] true if the file is of type `MH_PRELOAD`, false otherwise
76
- def preload?
77
- machos.first.preload?
78
- end
79
-
80
- # @return [Boolean] true if the file is of type `MH_DYLIB`, false otherwise
81
- def dylib?
82
- machos.first.dylib?
83
- end
84
-
85
- # @return [Boolean] true if the file is of type `MH_DYLINKER`, false otherwise
86
- def dylinker?
87
- machos.first.dylinker?
88
- end
89
-
90
- # @return [Boolean] true if the file is of type `MH_BUNDLE`, false otherwise
91
- def bundle?
92
- machos.first.bundle?
93
- end
94
-
95
- # @return [Boolean] true if the file is of type `MH_DSYM`, false otherwise
96
- def dsym?
97
- machos.first.dsym?
98
- end
99
-
100
- # @return [Boolean] true if the file is of type `MH_KEXT_BUNDLE`, false otherwise
101
- def kext?
102
- machos.first.kext?
103
- end
104
-
105
- # @return [Fixnum] the file's magic number
106
- def magic
107
- header.magic
108
- end
81
+ # @!method object?
82
+ # @return (see MachO::MachOFile#object?)
83
+ # @!method executable?
84
+ # @return (see MachO::MachOFile#executable?)
85
+ # @!method fvmlib?
86
+ # @return (see MachO::MachOFile#fvmlib?)
87
+ # @!method core?
88
+ # @return (see MachO::MachOFile#core?)
89
+ # @!method preload?
90
+ # @return (see MachO::MachOFile#preload?)
91
+ # @!method dylib?
92
+ # @return (see MachO::MachOFile#dylib?)
93
+ # @!method dylinker?
94
+ # @return (see MachO::MachOFile#dylinker?)
95
+ # @!method bundle?
96
+ # @return (see MachO::MachOFile#bundle?)
97
+ # @!method dsym?
98
+ # @return (see MachO::MachOFile#dsym?)
99
+ # @!method kext?
100
+ # @return (see MachO::MachOFile#kext?)
101
+ # @!method filetype
102
+ # @return (see MachO::MachOFile#filetype)
103
+ # @!method dylib_id
104
+ # @return (see MachO::MachOFile#dylib_id)
105
+ def_delegators :canonical_macho, :object?, :executable?, :fvmlib?,
106
+ :core?, :preload?, :dylib?, :dylinker?, :bundle?,
107
+ :dsym?, :kext?, :filetype, :dylib_id
108
+
109
+ # @!method magic
110
+ # @return (see MachO::Headers::FatHeader#magic)
111
+ def_delegators :header, :magic
109
112
 
110
113
  # @return [String] a string representation of the file's magic number
111
114
  def magic_string
112
115
  Headers::MH_MAGICS[magic]
113
116
  end
114
117
 
115
- # The file's type. Assumed to be the same for every Mach-O within.
116
- # @return [Symbol] the filetype
117
- def filetype
118
- machos.first.filetype
119
- end
120
-
121
118
  # Populate the instance's fields with the raw Fat Mach-O data.
122
119
  # @return [void]
123
120
  # @note This method is public, but should (almost) never need to be called.
@@ -128,21 +125,13 @@ module MachO
128
125
  end
129
126
 
130
127
  # All load commands responsible for loading dylibs in the file's Mach-O's.
131
- # @return [Array<MachO::LoadCommands::DylibCommand>] an array of DylibCommands
128
+ # @return [Array<LoadCommands::DylibCommand>] an array of DylibCommands
132
129
  def dylib_load_commands
133
130
  machos.map(&:dylib_load_commands).flatten
134
131
  end
135
132
 
136
- # The file's dylib ID. If the file is not a dylib, returns `nil`.
137
- # @example
138
- # file.dylib_id # => 'libBar.dylib'
139
- # @return [String, nil] the file's dylib ID
140
- # @see MachO::MachOFile#linked_dylibs
141
- def dylib_id
142
- machos.first.dylib_id
143
- end
144
-
145
- # Changes the file's dylib ID to `new_id`. If the file is not a dylib, does nothing.
133
+ # Changes the file's dylib ID to `new_id`. If the file is not a dylib,
134
+ # does nothing.
146
135
  # @example
147
136
  # file.change_dylib_id('libFoo.dylib')
148
137
  # @param new_id [String] the new dylib ID
@@ -151,7 +140,7 @@ module MachO
151
140
  # if false, fail only if all slices fail.
152
141
  # @return [void]
153
142
  # @raise [ArgumentError] if `new_id` is not a String
154
- # @see MachO::MachOFile#linked_dylibs
143
+ # @see MachOFile#linked_dylibs
155
144
  def change_dylib_id(new_id, options = {})
156
145
  raise ArgumentError, "argument must be a String" unless new_id.is_a?(String)
157
146
  return unless machos.all?(&:dylib?)
@@ -167,7 +156,7 @@ module MachO
167
156
 
168
157
  # All shared libraries linked to the file's Mach-Os.
169
158
  # @return [Array<String>] an array of all shared libraries
170
- # @see MachO::MachOFile#linked_dylibs
159
+ # @see MachOFile#linked_dylibs
171
160
  def linked_dylibs
172
161
  # Individual architectures in a fat binary can link to different subsets
173
162
  # of libraries, but at this point we want to have the full picture, i.e.
@@ -175,8 +164,9 @@ module MachO
175
164
  machos.map(&:linked_dylibs).flatten.uniq
176
165
  end
177
166
 
178
- # Changes all dependent shared library install names from `old_name` to `new_name`.
179
- # In a fat file, this changes install names in all internal Mach-Os.
167
+ # Changes all dependent shared library install names from `old_name` to
168
+ # `new_name`. In a fat file, this changes install names in all internal
169
+ # Mach-Os.
180
170
  # @example
181
171
  # file.change_install_name('/usr/lib/libFoo.dylib', '/usr/lib/libBar.dylib')
182
172
  # @param old_name [String] the shared library name being changed
@@ -185,7 +175,7 @@ module MachO
185
175
  # @option options [Boolean] :strict (true) if true, fail if one slice fails.
186
176
  # if false, fail only if all slices fail.
187
177
  # @return [void]
188
- # @see MachO::MachOFile#change_install_name
178
+ # @see MachOFile#change_install_name
189
179
  def change_install_name(old_name, new_name, options = {})
190
180
  each_macho(options) do |macho|
191
181
  macho.change_install_name(old_name, new_name, options)
@@ -198,7 +188,7 @@ module MachO
198
188
 
199
189
  # All runtime paths associated with the file's Mach-Os.
200
190
  # @return [Array<String>] an array of all runtime paths
201
- # @see MachO::MachOFile#rpaths
191
+ # @see MachOFile#rpaths
202
192
  def rpaths
203
193
  # Can individual architectures have different runtime paths?
204
194
  machos.map(&:rpaths).flatten.uniq
@@ -211,7 +201,7 @@ module MachO
211
201
  # @option options [Boolean] :strict (true) if true, fail if one slice fails.
212
202
  # if false, fail only if all slices fail.
213
203
  # @return [void]
214
- # @see MachO::MachOFile#change_rpath
204
+ # @see MachOFile#change_rpath
215
205
  def change_rpath(old_path, new_path, options = {})
216
206
  each_macho(options) do |macho|
217
207
  macho.change_rpath(old_path, new_path, options)
@@ -226,7 +216,7 @@ module MachO
226
216
  # @option options [Boolean] :strict (true) if true, fail if one slice fails.
227
217
  # if false, fail only if all slices fail.
228
218
  # @return [void]
229
- # @see MachO::MachOFile#add_rpath
219
+ # @see MachOFile#add_rpath
230
220
  def add_rpath(path, options = {})
231
221
  each_macho(options) do |macho|
232
222
  macho.add_rpath(path, options)
@@ -241,7 +231,7 @@ module MachO
241
231
  # @option options [Boolean] :strict (true) if true, fail if one slice fails.
242
232
  # if false, fail only if all slices fail.
243
233
  # @return void
244
- # @see MachO::MachOFile#delete_rpath
234
+ # @see MachOFile#delete_rpath
245
235
  def delete_rpath(path, options = {})
246
236
  each_macho(options) do |macho|
247
237
  macho.delete_rpath(path, options)
@@ -254,20 +244,21 @@ module MachO
254
244
  # @example
255
245
  # file.extract(:i386) # => MachO::MachOFile
256
246
  # @param cputype [Symbol] the CPU type of the Mach-O being extracted
257
- # @return [MachO::MachOFile, nil] the extracted Mach-O or nil if no Mach-O has the given CPU type
247
+ # @return [MachOFile, nil] the extracted Mach-O or nil if no Mach-O has the given CPU type
258
248
  def extract(cputype)
259
249
  machos.select { |macho| macho.cputype == cputype }.first
260
250
  end
261
251
 
262
252
  # Write all (fat) data to the given filename.
263
253
  # @param filename [String] the file to write to
254
+ # @return [void]
264
255
  def write(filename)
265
256
  File.open(filename, "wb") { |f| f.write(@raw_data) }
266
257
  end
267
258
 
268
259
  # Write all (fat) data to the file used to initialize the instance.
269
260
  # @return [void]
270
- # @raise [MachO::MachOError] if the instance was initialized without a file
261
+ # @raise [MachOError] if the instance was initialized without a file
271
262
  # @note Overwrites all data in the file!
272
263
  def write!
273
264
  if filename.nil?
@@ -280,11 +271,12 @@ module MachO
280
271
  private
281
272
 
282
273
  # Obtain the fat header from raw file data.
283
- # @return [MachO::Headers::FatHeader] the fat header
284
- # @raise [MachO::TruncatedFileError] if the file is too small to have a valid header
285
- # @raise [MachO::MagicError] if the magic is not valid Mach-O magic
286
- # @raise [MachO::MachOBinaryError] if the magic is for a non-fat Mach-O file
287
- # @raise [MachO::JavaClassFileError] if the file is a Java classfile
274
+ # @return [Headers::FatHeader] the fat header
275
+ # @raise [TruncatedFileError] if the file is too small to have a
276
+ # valid header
277
+ # @raise [MagicError] if the magic is not valid Mach-O magic
278
+ # @raise [MachOBinaryError] if the magic is for a non-fat Mach-O file
279
+ # @raise [JavaClassFileError] if the file is a Java classfile
288
280
  # @api private
289
281
  def populate_fat_header
290
282
  # the smallest fat Mach-O header is 8 bytes
@@ -308,7 +300,7 @@ module MachO
308
300
  end
309
301
 
310
302
  # Obtain an array of fat architectures from raw file data.
311
- # @return [Array<MachO::Headers::FatArch>] an array of fat architectures
303
+ # @return [Array<Headers::FatArch>] an array of fat architectures
312
304
  # @api private
313
305
  def populate_fat_archs
314
306
  archs = []
@@ -323,7 +315,7 @@ module MachO
323
315
  end
324
316
 
325
317
  # Obtain an array of Mach-O blobs from raw file data.
326
- # @return [Array<MachO::MachOFile>] an array of Mach-Os
318
+ # @return [Array<MachOFile>] an array of Mach-Os
327
319
  # @api private
328
320
  def populate_machos
329
321
  machos = []
@@ -351,7 +343,7 @@ module MachO
351
343
  # @option options [Boolean] :strict (true) whether or not to fail loudly
352
344
  # with an exception if at least one Mach-O raises an exception. If false,
353
345
  # only raises an exception if *all* Mach-Os raise exceptions.
354
- # @raise [MachO::RecoverableModificationError] under the conditions of
346
+ # @raise [RecoverableModificationError] under the conditions of
355
347
  # the `:strict` option above.
356
348
  # @api private
357
349
  def each_macho(options = {})
@@ -373,5 +365,13 @@ module MachO
373
365
  # Non-strict mode: Raise first error if *all* Mach-O slices failed.
374
366
  raise errors.first if errors.size == machos.size
375
367
  end
368
+
369
+ # Return a single-arch Mach-O that represents this fat Mach-O for purposes
370
+ # of delegation.
371
+ # @return [MachOFile] the Mach-O file
372
+ # @api private
373
+ def canonical_macho
374
+ machos.first
375
+ end
376
376
  end
377
377
  end
@@ -470,6 +470,11 @@ module MachO
470
470
  @magic = magic
471
471
  @nfat_arch = nfat_arch
472
472
  end
473
+
474
+ # @return [String] the serialized fields of the fat header
475
+ def serialize
476
+ [magic, nfat_arch].pack(FORMAT)
477
+ end
473
478
  end
474
479
 
475
480
  # Fat binary header architecture structure. A Fat binary has one or more of
@@ -508,6 +513,11 @@ module MachO
508
513
  @size = size
509
514
  @align = align
510
515
  end
516
+
517
+ # @return [String] the serialized fields of the fat arch
518
+ def serialize
519
+ [cputype, cpusubtype, offset, size, align].pack(FORMAT)
520
+ end
511
521
  end
512
522
 
513
523
  # 32-bit Mach-O file header structure
@@ -564,6 +574,71 @@ module MachO
564
574
  return false if flag.nil?
565
575
  flags & flag == flag
566
576
  end
577
+
578
+ # @return [Boolean] whether or not the file is of type `MH_OBJECT`
579
+ def object?
580
+ filetype == Headers::MH_OBJECT
581
+ end
582
+
583
+ # @return [Boolean] whether or not the file is of type `MH_EXECUTE`
584
+ def executable?
585
+ filetype == Headers::MH_EXECUTE
586
+ end
587
+
588
+ # @return [Boolean] whether or not the file is of type `MH_FVMLIB`
589
+ def fvmlib?
590
+ filetype == Headers::MH_FVMLIB
591
+ end
592
+
593
+ # @return [Boolean] whether or not the file is of type `MH_CORE`
594
+ def core?
595
+ filetype == Headers::MH_CORE
596
+ end
597
+
598
+ # @return [Boolean] whether or not the file is of type `MH_PRELOAD`
599
+ def preload?
600
+ filetype == Headers::MH_PRELOAD
601
+ end
602
+
603
+ # @return [Boolean] whether or not the file is of type `MH_DYLIB`
604
+ def dylib?
605
+ filetype == Headers::MH_DYLIB
606
+ end
607
+
608
+ # @return [Boolean] whether or not the file is of type `MH_DYLINKER`
609
+ def dylinker?
610
+ filetype == Headers::MH_DYLINKER
611
+ end
612
+
613
+ # @return [Boolean] whether or not the file is of type `MH_BUNDLE`
614
+ def bundle?
615
+ filetype == Headers::MH_BUNDLE
616
+ end
617
+
618
+ # @return [Boolean] whether or not the file is of type `MH_DSYM`
619
+ def dsym?
620
+ filetype == Headers::MH_DSYM
621
+ end
622
+
623
+ # @return [Boolean] whether or not the file is of type `MH_KEXT_BUNDLE`
624
+ def kext?
625
+ filetype == Headers::MH_KEXT_BUNDLE
626
+ end
627
+
628
+ # @return [Boolean] true if the Mach-O has 32-bit magic, false otherwise
629
+ def magic32?
630
+ Utils.magic32?(magic)
631
+ end
632
+
633
+ # @return [Boolean] true if the Mach-O has 64-bit magic, false otherwise
634
+ def magic64?
635
+ Utils.magic64?(magic)
636
+ end
637
+
638
+ # @return [Fixnum] the file's internal alignment
639
+ def alignment
640
+ magic32? ? 4 : 8
641
+ end
567
642
  end
568
643
 
569
644
  # 64-bit Mach-O file header structure