ruby-macho 2.0.0 → 2.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
  SHA256:
3
- metadata.gz: 6341c1a29aeae7169fea7f1e7fc11339331f21c2b07a3a19e7f68b5dd76af03d
4
- data.tar.gz: 9b5dd29c4a03c18bf6261e20641c95c584958ee142d4d64a37f9db8352065727
3
+ metadata.gz: 5c4fdf0c3007545c3c9a84dacc832b803fc83edfe4aef0513eba857f14fcd2d1
4
+ data.tar.gz: 0dd36193221a6d9f25084b5d8dea8f17eb8165a1f304e2d0006433ade11d61b8
5
5
  SHA512:
6
- metadata.gz: c3964af02f0b1abf51de625e1d69a8fd966c30d5bf93cfde410ac127fab57864ae6331441d2afa1acdd100c7187f5d05883ecd8736d88076507823b16627ab10
7
- data.tar.gz: c5a8ec50db69dccef0161dd96d6b7f23a47713a0fb8551d1bae10afa2690ed6837b52f9f3f49bd0826610b7c7a1841fadda2d1c53f6ab91f118732e36a3a4bd7
6
+ metadata.gz: a07521accccc3e50119811ca36392c9ecc579dd0d8a238e8dcec5473f75325a2017a367d58212bc8fd1287e78f0b043f19546cf37eaa95c5c7f65623e7d3e015
7
+ data.tar.gz: 16f243d60a944e725795bb77b6c2a301e5be7939df83c70c9929f23346792f7854c9b5ad626ea2bdf301155f882f43b934e12064efdc709723a6321fc13a83d5
@@ -12,7 +12,7 @@ require_relative "macho/tools"
12
12
  # The primary namespace for ruby-macho.
13
13
  module MachO
14
14
  # release version
15
- VERSION = "2.0.0".freeze
15
+ VERSION = "2.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
@@ -194,4 +194,14 @@ module MachO
194
194
  super "Unimplemented: #{thing}"
195
195
  end
196
196
  end
197
+
198
+ # Raised when attempting to create a {FatFile} from one or more {MachOFile}s
199
+ # whose offsets will not fit within the resulting 32-bit {Headers::FatArch#offset} fields.
200
+ class FatArchOffsetOverflowError < MachOError
201
+ # @param offset [Integer] the offending offset
202
+ def initialize(offset)
203
+ super "Offset #{offset} exceeds the 32-bit width of a fat_arch offset." \
204
+ " Consider merging with `fat64: true`"
205
+ end
206
+ end
197
207
  end
@@ -14,7 +14,7 @@ module MachO
14
14
  # @return [Headers::FatHeader] the file's header
15
15
  attr_reader :header
16
16
 
17
- # @return [Array<Headers::FatArch>] an array of fat architectures
17
+ # @return [Array<Headers::FatArch>, Array<Headers::FatArch64] an array of fat architectures
18
18
  attr_reader :fat_archs
19
19
 
20
20
  # @return [Array<MachOFile>] an array of Mach-O binaries
@@ -22,37 +22,49 @@ module MachO
22
22
 
23
23
  # Creates a new FatFile from the given (single-arch) Mach-Os
24
24
  # @param machos [Array<MachOFile>] the machos to combine
25
+ # @param fat64 [Boolean] whether to use {Headers::FatArch64}s to represent each slice
25
26
  # @return [FatFile] a new FatFile containing the give machos
26
27
  # @raise [ArgumentError] if less than one Mach-O is given
27
- def self.new_from_machos(*machos)
28
+ # @raise [FatArchOffsetOverflowError] if the Mach-Os are too big to be represented
29
+ # in a 32-bit {Headers::FatArch} and `fat64` is `false`.
30
+ def self.new_from_machos(*machos, fat64: false)
28
31
  raise ArgumentError, "expected at least one Mach-O" if machos.empty?
29
32
 
33
+ fa_klass, magic = if fat64
34
+ [Headers::FatArch64, Headers::FAT_MAGIC_64]
35
+ else
36
+ [Headers::FatArch, Headers::FAT_MAGIC]
37
+ end
38
+
30
39
  # put the smaller alignments further forwards in fat macho, so that we do less padding
31
40
  machos = machos.sort_by(&:segment_alignment)
32
41
 
33
42
  bin = +""
34
43
 
35
- bin << Headers::FatHeader.new(Headers::FAT_MAGIC, machos.size).serialize
36
- offset = Headers::FatHeader.bytesize + (machos.size * Headers::FatArch.bytesize)
44
+ bin << Headers::FatHeader.new(magic, machos.size).serialize
45
+ offset = Headers::FatHeader.bytesize + (machos.size * fa_klass.bytesize)
37
46
 
38
47
  macho_pads = {}
39
- macho_bins = {}
40
48
 
41
49
  machos.each do |macho|
42
- macho_offset = Utils.round(offset, 2**macho.segment_alignment)
50
+ macho_offset = Utils.round(offset, 2**macho.segment_alignment)
51
+
52
+ if !fat64 && macho_offset > (2**32 - 1)
53
+ raise FatArchOffsetOverflowError, macho_offset
54
+ end
55
+
43
56
  macho_pads[macho] = Utils.padding_for(offset, 2**macho.segment_alignment)
44
- macho_bins[macho] = macho.serialize
45
57
 
46
- bin << Headers::FatArch.new(macho.header.cputype, macho.header.cpusubtype,
47
- macho_offset, macho_bins[macho].bytesize,
48
- macho.segment_alignment).serialize
58
+ bin << fa_klass.new(macho.header.cputype, macho.header.cpusubtype,
59
+ macho_offset, macho.serialize.bytesize,
60
+ macho.segment_alignment).serialize
49
61
 
50
- offset += (macho_bins[macho].bytesize + macho_pads[macho])
62
+ offset += (macho.serialize.bytesize + macho_pads[macho])
51
63
  end
52
64
 
53
65
  machos.each do |macho|
54
66
  bin << Utils.nullpad(macho_pads[macho])
55
- bin << macho_bins[macho]
67
+ bin << macho.serialize
56
68
  end
57
69
 
58
70
  new_from_bin(bin)
@@ -278,6 +290,7 @@ module MachO
278
290
  # @note Overwrites all data in the file!
279
291
  def write!
280
292
  raise MachOError, "no initial file to write to" if filename.nil?
293
+
281
294
  File.open(@filename, "wb") { |f| f.write(@raw_data) }
282
295
  end
283
296
 
@@ -327,10 +340,12 @@ module MachO
327
340
  def populate_fat_archs
328
341
  archs = []
329
342
 
330
- fa_off = Headers::FatHeader.bytesize
331
- fa_len = Headers::FatArch.bytesize
343
+ fa_klass = Utils.fat_magic32?(header.magic) ? Headers::FatArch : Headers::FatArch64
344
+ fa_off = Headers::FatHeader.bytesize
345
+ fa_len = fa_klass.bytesize
346
+
332
347
  header.nfat_arch.times do |i|
333
- archs << Headers::FatArch.new_from_bin(:big, @raw_data[fa_off + (fa_len * i), fa_len])
348
+ archs << fa_klass.new_from_bin(:big, @raw_data[fa_off + (fa_len * i), fa_len])
334
349
  end
335
350
 
336
351
  archs
@@ -380,6 +395,7 @@ module MachO
380
395
 
381
396
  # Strict mode: Immediately re-raise. Otherwise: Retain, check later.
382
397
  raise error if strict
398
+
383
399
  errors << error
384
400
  end
385
401
  end
@@ -6,11 +6,19 @@ module MachO
6
6
  FAT_MAGIC = 0xcafebabe
7
7
 
8
8
  # little-endian fat magic
9
- # this is defined, but should never appear in ruby-macho code because
10
- # fat headers are always big-endian and therefore always unpacked as such.
9
+ # @note This is defined for completeness, but should never appear in ruby-macho code,
10
+ # since fat headers are always big-endian.
11
11
  # @api private
12
12
  FAT_CIGAM = 0xbebafeca
13
13
 
14
+ # 64-bit big-endian fat magic
15
+ FAT_MAGIC_64 = 0xcafebabf
16
+
17
+ # 64-bit little-endian fat magic
18
+ # @note This is defined for completeness, but should never appear in ruby-macho code,
19
+ # since fat headers are always big-endian.
20
+ FAT_CIGAM_64 = 0xbfbafeca
21
+
14
22
  # 32-bit big-endian magic
15
23
  # @api private
16
24
  MH_MAGIC = 0xfeedface
@@ -31,6 +39,7 @@ module MachO
31
39
  # @api private
32
40
  MH_MAGICS = {
33
41
  FAT_MAGIC => "FAT_MAGIC",
42
+ FAT_MAGIC_64 => "FAT_MAGIC_64",
34
43
  MH_MAGIC => "MH_MAGIC",
35
44
  MH_CIGAM => "MH_CIGAM",
36
45
  MH_MAGIC_64 => "MH_MAGIC_64",
@@ -41,6 +50,11 @@ module MachO
41
50
  # @api private
42
51
  CPU_ARCH_ABI64 = 0x01000000
43
52
 
53
+ # mask for CPUs with 64-bit architectures (when running a 32-bit ABI?)
54
+ # @see https://github.com/Homebrew/ruby-macho/issues/113
55
+ # @api private
56
+ CPU_ARCH_ABI32 = 0x02000000
57
+
44
58
  # any CPU (unused?)
45
59
  # @api private
46
60
  CPU_TYPE_ANY = -1
@@ -69,6 +83,10 @@ module MachO
69
83
  # @api private
70
84
  CPU_TYPE_ARM64 = (CPU_TYPE_ARM | CPU_ARCH_ABI64)
71
85
 
86
+ # 64-bit ARM compatible CPUs (running in 32-bit mode?)
87
+ # @see https://github.com/Homebrew/ruby-macho/issues/113
88
+ CPU_TYPE_ARM64_32 = (CPU_TYPE_ARM | CPU_ARCH_ABI32)
89
+
72
90
  # PowerPC compatible CPUs
73
91
  # @api private
74
92
  CPU_TYPE_POWERPC = 0x12
@@ -85,6 +103,7 @@ module MachO
85
103
  CPU_TYPE_X86_64 => :x86_64,
86
104
  CPU_TYPE_ARM => :arm,
87
105
  CPU_TYPE_ARM64 => :arm64,
106
+ CPU_TYPE_ARM64_32 => :arm64_32,
88
107
  CPU_TYPE_POWERPC => :ppc,
89
108
  CPU_TYPE_POWERPC64 => :ppc64,
90
109
  }.freeze
@@ -218,6 +237,10 @@ module MachO
218
237
  # @api private
219
238
  CPU_SUBTYPE_ARM64_V8 = 1
220
239
 
240
+ # the v8 sub-type for `CPU_TYPE_ARM64_32`
241
+ # @api private
242
+ CPU_SUBTYPE_ARM64_32_V8 = 1
243
+
221
244
  # the lowest common sub-type for `CPU_TYPE_MC88000`
222
245
  # @api private
223
246
  CPU_SUBTYPE_MC88000_ALL = 0
@@ -328,6 +351,9 @@ module MachO
328
351
  CPU_SUBTYPE_ARM64_ALL => :arm64,
329
352
  CPU_SUBTYPE_ARM64_V8 => :arm64v8,
330
353
  }.freeze,
354
+ CPU_TYPE_ARM64_32 => {
355
+ CPU_SUBTYPE_ARM64_32_V8 => :arm64_32v8,
356
+ }.freeze,
331
357
  CPU_TYPE_POWERPC => {
332
358
  CPU_SUBTYPE_POWERPC_ALL => :ppc,
333
359
  CPU_SUBTYPE_POWERPC_601 => :ppc601,
@@ -486,8 +512,10 @@ module MachO
486
512
  end
487
513
  end
488
514
 
489
- # Fat binary header architecture structure. A Fat binary has one or more of
490
- # these, representing one or more internal Mach-O blobs.
515
+ # 32-bit fat binary header architecture structure. A 32-bit fat Mach-O has one or more of
516
+ # these, indicating one or more internal Mach-O blobs.
517
+ # @note "32-bit" indicates the fact that this structure stores 32-bit offsets, not that the
518
+ # Mach-Os that it points to necessarily *are* 32-bit.
491
519
  # @see MachO::Headers::FatHeader
492
520
  class FatArch < MachOStructure
493
521
  # @return [Integer] the CPU type of the Mach-O
@@ -505,10 +533,10 @@ module MachO
505
533
  # @return [Integer] the alignment, as a power of 2
506
534
  attr_reader :align
507
535
 
508
- # always big-endian
536
+ # @note Always big endian.
509
537
  # @see MachOStructure::FORMAT
510
538
  # @api private
511
- FORMAT = "N5".freeze
539
+ FORMAT = "L>5".freeze
512
540
 
513
541
  # @see MachOStructure::SIZEOF
514
542
  # @api private
@@ -542,6 +570,43 @@ module MachO
542
570
  end
543
571
  end
544
572
 
573
+ # 64-bit fat binary header architecture structure. A 64-bit fat Mach-O has one or more of
574
+ # these, indicating one or more internal Mach-O blobs.
575
+ # @note "64-bit" indicates the fact that this structure stores 64-bit offsets, not that the
576
+ # Mach-Os that it points to necessarily *are* 64-bit.
577
+ # @see MachO::Headers::FatHeader
578
+ class FatArch64 < FatArch
579
+ # @return [void]
580
+ attr_reader :reserved
581
+
582
+ # @note Always big endian.
583
+ # @see MachOStructure::FORMAT
584
+ # @api private
585
+ FORMAT = "L>2Q>2L>2".freeze
586
+
587
+ # @see MachOStructure::SIZEOF
588
+ # @api private
589
+ SIZEOF = 32
590
+
591
+ # @api private
592
+ def initialize(cputype, cpusubtype, offset, size, align, reserved = 0)
593
+ super(cputype, cpusubtype, offset, size, align)
594
+ @reserved = reserved
595
+ end
596
+
597
+ # @return [String] the serialized fields of the fat arch
598
+ def serialize
599
+ [cputype, cpusubtype, offset, size, align, reserved].pack(FORMAT)
600
+ end
601
+
602
+ # @return [Hash] a hash representation of this {FatArch64}
603
+ def to_h
604
+ {
605
+ "reserved" => reserved,
606
+ }.merge super
607
+ end
608
+ end
609
+
545
610
  # 32-bit Mach-O file header structure
546
611
  class MachHeader < MachOStructure
547
612
  # @return [Integer] the magic number
@@ -593,7 +658,9 @@ module MachO
593
658
  # @return [Boolean] true if `flag` is present in the header's flag section
594
659
  def flag?(flag)
595
660
  flag = MH_FLAGS[flag]
661
+
596
662
  return false if flag.nil?
663
+
597
664
  flags & flag == flag
598
665
  end
599
666
 
@@ -242,6 +242,7 @@ module MachO
242
242
  # @api private
243
243
  def serialize(context)
244
244
  raise LoadCommandNotSerializableError, LOAD_COMMANDS[cmd] unless serializable?
245
+
245
246
  format = Utils.specialize_format(FORMAT, context.endianness)
246
247
  [cmd, SIZEOF].pack(format)
247
248
  end
@@ -298,7 +299,9 @@ module MachO
298
299
  lc_end = view.offset + lc.cmdsize - 1
299
300
  raw_string = view.raw_data.slice(lc_str_abs..lc_end)
300
301
  @string, null_byte, _padding = raw_string.partition("\x00")
302
+
301
303
  raise LCStrMalformedError, lc if null_byte.empty?
304
+
302
305
  @string_offset = lc_str
303
306
  else
304
307
  @string = lc_str
@@ -473,7 +476,9 @@ module MachO
473
476
  # @return [Boolean] true if `flag` is present in the segment's flag field
474
477
  def flag?(flag)
475
478
  flag = SEGMENT_FLAGS[flag]
479
+
476
480
  return false if flag.nil?
481
+
477
482
  flags & flag == flag
478
483
  end
479
484
 
@@ -431,6 +431,7 @@ module MachO
431
431
  # @note Overwrites all data in the file!
432
432
  def write!
433
433
  raise MachOError, "no initial file to write to" if @filename.nil?
434
+
434
435
  File.open(@filename, "wb") { |f| f.write(@raw_data) }
435
436
  end
436
437
 
@@ -150,7 +150,9 @@ module MachO
150
150
  # @return [Boolean] whether the flag is present in the section's {flags}
151
151
  def flag?(flag)
152
152
  flag = SECTION_FLAGS[flag]
153
+
153
154
  return false if flag.nil?
155
+
154
156
  flags & flag == flag
155
157
  end
156
158
 
@@ -89,8 +89,9 @@ module MachO
89
89
  # Merge multiple Mach-Os into one universal (Fat) binary.
90
90
  # @param filename [String] the fat binary to create
91
91
  # @param files [Array<String>] the files to merge
92
+ # @param fat64 [Boolean] whether to use {Headers::FatArch64}s to represent each slice
92
93
  # @return [void]
93
- def self.merge_machos(filename, *files)
94
+ def self.merge_machos(filename, *files, fat64: false)
94
95
  machos = files.map do |file|
95
96
  macho = MachO.open(file)
96
97
  case macho
@@ -101,7 +102,7 @@ module MachO
101
102
  end
102
103
  end.flatten
103
104
 
104
- fat_macho = MachO::FatFile.new_from_machos(*machos)
105
+ fat_macho = MachO::FatFile.new_from_machos(*machos, :fat64 => fat64)
105
106
  fat_macho.write(filename)
106
107
  end
107
108
  end
@@ -75,35 +75,49 @@ module MachO
75
75
  # @param num [Integer] the number being checked
76
76
  # @return [Boolean] whether `num` is a valid Fat magic number
77
77
  def self.fat_magic?(num)
78
+ [Headers::FAT_MAGIC, Headers::FAT_MAGIC_64].include? num
79
+ end
80
+
81
+ # Compares the given number to valid 32-bit Fat magic numbers.
82
+ # @param num [Integer] the number being checked
83
+ # @return [Boolean] whether `num` is a valid 32-bit fat magic number
84
+ def self.fat_magic32?(num)
78
85
  num == Headers::FAT_MAGIC
79
86
  end
80
87
 
88
+ # Compares the given number to valid 64-bit Fat magic numbers.
89
+ # @param num [Integer] the number being checked
90
+ # @return [Boolean] whether `num` is a valid 64-bit fat magic number
91
+ def self.fat_magic64?(num)
92
+ num == Headers::FAT_MAGIC_64
93
+ end
94
+
81
95
  # Compares the given number to valid 32-bit Mach-O magic numbers.
82
96
  # @param num [Integer] the number being checked
83
97
  # @return [Boolean] whether `num` is a valid 32-bit magic number
84
98
  def self.magic32?(num)
85
- num == Headers::MH_MAGIC || num == Headers::MH_CIGAM
99
+ [Headers::MH_MAGIC, Headers::MH_CIGAM].include? num
86
100
  end
87
101
 
88
102
  # Compares the given number to valid 64-bit Mach-O magic numbers.
89
103
  # @param num [Integer] the number being checked
90
104
  # @return [Boolean] whether `num` is a valid 64-bit magic number
91
105
  def self.magic64?(num)
92
- num == Headers::MH_MAGIC_64 || num == Headers::MH_CIGAM_64
106
+ [Headers::MH_MAGIC_64, Headers::MH_CIGAM_64].include? num
93
107
  end
94
108
 
95
109
  # Compares the given number to valid little-endian magic numbers.
96
110
  # @param num [Integer] the number being checked
97
111
  # @return [Boolean] whether `num` is a valid little-endian magic number
98
112
  def self.little_magic?(num)
99
- num == Headers::MH_CIGAM || num == Headers::MH_CIGAM_64
113
+ [Headers::MH_CIGAM, Headers::MH_CIGAM_64].include? num
100
114
  end
101
115
 
102
116
  # Compares the given number to valid big-endian magic numbers.
103
117
  # @param num [Integer] the number being checked
104
118
  # @return [Boolean] whether `num` is a valid big-endian magic number
105
119
  def self.big_magic?(num)
106
- num == Headers::MH_CIGAM || num == Headers::MH_CIGAM_64
120
+ [Headers::MH_MAGIC, Headers::MH_MAGIC_64].include? num
107
121
  end
108
122
  end
109
123
  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: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - William Woodruff
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-03 00:00:00.000000000 Z
11
+ date: 2018-09-26 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
@@ -42,7 +42,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
42
42
  requirements:
43
43
  - - ">="
44
44
  - !ruby/object:Gem::Version
45
- version: '2.1'
45
+ version: '2.2'
46
46
  required_rubygems_version: !ruby/object:Gem::Requirement
47
47
  requirements:
48
48
  - - ">="