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 +4 -4
- data/lib/macho.rb +1 -1
- data/lib/macho/exceptions.rb +10 -0
- data/lib/macho/fat_file.rb +31 -15
- data/lib/macho/headers.rb +73 -6
- data/lib/macho/load_commands.rb +5 -0
- data/lib/macho/macho_file.rb +1 -0
- data/lib/macho/sections.rb +2 -0
- data/lib/macho/tools.rb +3 -2
- data/lib/macho/utils.rb +18 -4
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c4fdf0c3007545c3c9a84dacc832b803fc83edfe4aef0513eba857f14fcd2d1
|
4
|
+
data.tar.gz: 0dd36193221a6d9f25084b5d8dea8f17eb8165a1f304e2d0006433ade11d61b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a07521accccc3e50119811ca36392c9ecc579dd0d8a238e8dcec5473f75325a2017a367d58212bc8fd1287e78f0b043f19546cf37eaa95c5c7f65623e7d3e015
|
7
|
+
data.tar.gz: 16f243d60a944e725795bb77b6c2a301e5be7939df83c70c9929f23346792f7854c9b5ad626ea2bdf301155f882f43b934e12064efdc709723a6321fc13a83d5
|
data/lib/macho.rb
CHANGED
@@ -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.
|
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
|
data/lib/macho/exceptions.rb
CHANGED
@@ -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
|
data/lib/macho/fat_file.rb
CHANGED
@@ -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
|
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
|
-
|
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(
|
36
|
-
offset = Headers::FatHeader.bytesize + (machos.size *
|
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
|
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 <<
|
47
|
-
|
48
|
-
|
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 += (
|
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 <<
|
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
|
-
|
331
|
-
|
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 <<
|
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
|
data/lib/macho/headers.rb
CHANGED
@@ -6,11 +6,19 @@ module MachO
|
|
6
6
|
FAT_MAGIC = 0xcafebabe
|
7
7
|
|
8
8
|
# little-endian fat magic
|
9
|
-
#
|
10
|
-
# fat headers are always big-endian
|
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
|
-
#
|
490
|
-
#
|
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
|
-
#
|
536
|
+
# @note Always big endian.
|
509
537
|
# @see MachOStructure::FORMAT
|
510
538
|
# @api private
|
511
|
-
FORMAT = "
|
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
|
|
data/lib/macho/load_commands.rb
CHANGED
@@ -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
|
|
data/lib/macho/macho_file.rb
CHANGED
data/lib/macho/sections.rb
CHANGED
data/lib/macho/tools.rb
CHANGED
@@ -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
|
data/lib/macho/utils.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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-
|
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.
|
45
|
+
version: '2.2'
|
46
46
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
47
|
requirements:
|
48
48
|
- - ">="
|