ruby-macho 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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