ruby-macho 2.0.0 → 2.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
  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
  - - ">="