ruby-macho 0.0.8 → 0.0.9
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/cstruct.rb +0 -28
- data/lib/macho/fat_file.rb +53 -9
- data/lib/macho/macho_file.rb +68 -23
- data/lib/macho.rb +4 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 119afdca4c1793c3bc3f8285c88a8126ab4e1e76
|
4
|
+
data.tar.gz: 26346fa156b237e2606e8b005c95330bc1b194ad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 413afe10c917e3e7365711b821904f058d48a9c92a6fcf36d1fecd132b1bbd5971e2d6e547b1684fb46061a328cfaf71afc35788068a1480d33bbde24a0e6d1d
|
7
|
+
data.tar.gz: cf66bd02bac6b2956bbad64d2f0f82de46e339713ab9ddbfecea88902399f304a58fcb8588294025f647180077f4e054feb40ed85bbeaee941aa00aed26babca
|
data/lib/cstruct.rb
CHANGED
@@ -316,31 +316,3 @@ class CStruct
|
|
316
316
|
# The last expression is returned, so return self instead of junk.
|
317
317
|
self
|
318
318
|
end
|
319
|
-
|
320
|
-
|
321
|
-
# a small test
|
322
|
-
if $0 == __FILE__
|
323
|
-
class MachHeader < CStruct
|
324
|
-
uint :magic
|
325
|
-
int :cputype
|
326
|
-
int :cpusubtype
|
327
|
-
string :segname, 16
|
328
|
-
end
|
329
|
-
puts MachHeader::Members.inspect
|
330
|
-
puts MachHeader::MemberIndex.inspect
|
331
|
-
puts MachHeader::MemberSizes.inspect
|
332
|
-
puts "# of MachHeader members: " + MachHeader.size.to_s + ", size in bytes: " + MachHeader.bytesize.to_s
|
333
|
-
mh = MachHeader.new(0xfeedface, 7, 3, "foobar")
|
334
|
-
%w[magic cputype cpusubtype segname].each do |field|
|
335
|
-
puts "#{field}(#{MachHeader.sizeof(field.to_sym)}): #{mh[field.to_sym].inspect}"
|
336
|
-
end
|
337
|
-
puts mh.pack_pattern.inspect
|
338
|
-
binstr = mh.serialize
|
339
|
-
puts "values: " + mh.values.inspect
|
340
|
-
newmh = MachHeader.new_from_bin(binstr)
|
341
|
-
puts "new values: " + newmh.values.inspect
|
342
|
-
newbinstr = newmh.serialize
|
343
|
-
puts "serialized: " + binstr.inspect
|
344
|
-
puts "unserialized: " + newbinstr.inspect
|
345
|
-
puts "new == old ? " + (newbinstr == binstr).to_s
|
346
|
-
end
|
data/lib/macho/fat_file.rb
CHANGED
@@ -1,7 +1,22 @@
|
|
1
1
|
module MachO
|
2
|
+
# Represents a "Fat" file, which contains a header, a listing of available
|
3
|
+
# architectures, and one or more Mach-O binaries.
|
4
|
+
# @see https://en.wikipedia.org/wiki/Mach-O#Multi-architecture_binaries
|
5
|
+
# @see MachO::MachOFile
|
2
6
|
class FatFile
|
3
|
-
|
7
|
+
# @return [MachO::FatHeader] the file's header
|
8
|
+
attr_reader :header
|
4
9
|
|
10
|
+
# @return [Array<MachO::FatArch>] an array of fat architectures
|
11
|
+
attr_reader :fat_archs
|
12
|
+
|
13
|
+
# @return [Array<MachO::MachOFile>] an array of Mach-O binaries
|
14
|
+
attr_reader :machos
|
15
|
+
|
16
|
+
# Creates a new FatFile from the given filename.
|
17
|
+
# @param filename [String] the fat file to load from
|
18
|
+
# @raise [MachO::MagicError] if the file does not have a Mach-O magic number.
|
19
|
+
# @raise [MachO::MachOBinaryError] if the file is not a fat binary.
|
5
20
|
def initialize(filename)
|
6
21
|
raise ArgumentError.new("filename must be a String") unless filename.is_a? String
|
7
22
|
|
@@ -12,10 +27,16 @@ module MachO
|
|
12
27
|
@machos = get_machos
|
13
28
|
end
|
14
29
|
|
30
|
+
# The file's raw fat data.
|
31
|
+
# @return [String] the raw fat data
|
15
32
|
def serialize
|
16
33
|
@raw_data
|
17
34
|
end
|
18
35
|
|
36
|
+
# The file's dylib ID. If the file is not a dylib, returns `nil`.
|
37
|
+
# @example
|
38
|
+
# file.dylib_id # => 'libBar.dylib'
|
39
|
+
# @return [String] the file's dylib ID
|
19
40
|
def dylib_id
|
20
41
|
if !machos.all?(&:dylib?)
|
21
42
|
return nil
|
@@ -31,6 +52,10 @@ module MachO
|
|
31
52
|
ids.first
|
32
53
|
end
|
33
54
|
|
55
|
+
# Changes the file's dylib ID to `new_id`. If the file is not a dylib, does nothing.
|
56
|
+
# @example
|
57
|
+
# file.dylib_id = 'libFoo.dylib'
|
58
|
+
# @param new_id [String] the new dylib ID
|
34
59
|
def dylib_id=(new_id)
|
35
60
|
if !new_id.is_a?(String)
|
36
61
|
raise ArgumentError.new("argument must be a String")
|
@@ -47,6 +72,8 @@ module MachO
|
|
47
72
|
synchronize_raw_data
|
48
73
|
end
|
49
74
|
|
75
|
+
# All shared libraries linked to the file's Mach-Os.
|
76
|
+
# @return [Array<String>] an array of all shared libraries
|
50
77
|
def linked_dylibs
|
51
78
|
dylibs = machos.map(&:linked_dylibs).flatten
|
52
79
|
|
@@ -54,27 +81,40 @@ module MachO
|
|
54
81
|
dylibs.uniq!
|
55
82
|
end
|
56
83
|
|
57
|
-
#
|
58
|
-
|
84
|
+
# Changes all dependent shared library install names from `old_name` to `new_name`.
|
85
|
+
# In a fat file, this changes install names in all internal Mach-Os.
|
86
|
+
# @example
|
87
|
+
# file.change_install_name('/usr/lib/libFoo.dylib', '/usr/lib/libBar.dylib')
|
88
|
+
# @param old_name [String] the shared library name being changed
|
89
|
+
# @param new_name [String] the new name
|
90
|
+
# @todo incomplete
|
91
|
+
def change_install_name(old_name, new_name)
|
59
92
|
machos.each do |macho|
|
60
|
-
macho.change_install_name(
|
93
|
+
macho.change_install_name(old_name, new_name)
|
61
94
|
end
|
62
95
|
|
63
96
|
synchronize_raw_data
|
64
97
|
end
|
65
98
|
|
66
|
-
alias :
|
99
|
+
alias :change_dylib :change_install_name
|
67
100
|
|
101
|
+
# Write all (fat) data to the given filename.
|
102
|
+
# @param filename [String] the file to write to
|
68
103
|
def write(filename)
|
69
104
|
File.open(filename, "wb") { |f| f.write(@raw_data) }
|
70
105
|
end
|
71
106
|
|
107
|
+
# Write all (fat) data to the file used to initialize the instance.
|
108
|
+
# IMPORTANT: Overwrites all data in the file!
|
72
109
|
def write!
|
73
110
|
File.open(@filename, "wb") { |f| f.write(@raw_data) }
|
74
111
|
end
|
75
112
|
|
76
113
|
private
|
77
114
|
|
115
|
+
# Obtain the fat header from raw file data.
|
116
|
+
# @return [MachO::FatHeader] the fat header
|
117
|
+
# @private
|
78
118
|
def get_fat_header
|
79
119
|
magic, nfat_arch = @raw_data[0..7].unpack("N2")
|
80
120
|
|
@@ -89,6 +129,9 @@ module MachO
|
|
89
129
|
FatHeader.new(magic, nfat_arch)
|
90
130
|
end
|
91
131
|
|
132
|
+
# Obtain an array of fat architectures from raw file data.
|
133
|
+
# @return [Array<MachO::FatArch>] an array of fat architectures
|
134
|
+
# @private
|
92
135
|
def get_fat_archs
|
93
136
|
archs = []
|
94
137
|
|
@@ -100,6 +143,9 @@ module MachO
|
|
100
143
|
archs
|
101
144
|
end
|
102
145
|
|
146
|
+
# Obtain an array of Mach-O blobs from raw file data.
|
147
|
+
# @return [Array<MachO::MachOFile>] an array of Mach-Os
|
148
|
+
# @private
|
103
149
|
def get_machos
|
104
150
|
machos = []
|
105
151
|
|
@@ -110,10 +156,8 @@ module MachO
|
|
110
156
|
machos
|
111
157
|
end
|
112
158
|
|
113
|
-
#
|
114
|
-
#
|
115
|
-
# @raw_data directly, so we need to synchronize it after changing
|
116
|
-
# anything within the machos.
|
159
|
+
# @todo this needs to be redesigned. arch[:offset] and arch[:size] are
|
160
|
+
# already out-of-date, and the header needs to be synchronized as well.
|
117
161
|
def synchronize_raw_data
|
118
162
|
machos.each_with_index do |macho, i|
|
119
163
|
arch = fat_archs[i]
|
data/lib/macho/macho_file.rb
CHANGED
@@ -1,7 +1,19 @@
|
|
1
1
|
module MachO
|
2
|
+
# Represents a Mach-O file, which contains a header and load commands
|
3
|
+
# as well as binary executable instructions. Mach-O binaries are
|
4
|
+
# architecture specific.
|
5
|
+
# @see https://en.wikipedia.org/wiki/Mach-O
|
2
6
|
class MachOFile
|
3
|
-
|
7
|
+
# @return [MachO::MachHeader] if the Mach-O is 32-bit
|
8
|
+
# @return [MachO::MachHeader64] if the Mach-O is 64-bit
|
9
|
+
attr_reader :header
|
4
10
|
|
11
|
+
# @return [Array<MachO::LoadCommand>] an array of the file's load commands
|
12
|
+
attr_reader :load_commands
|
13
|
+
|
14
|
+
# Creates a new MachOFile instance from a binary string.
|
15
|
+
# @param bin [String] a binary string containing raw Mach-O data
|
16
|
+
# @return [MachO::MachOFile] a new MachOFile
|
5
17
|
def self.new_from_bin(bin)
|
6
18
|
instance = allocate
|
7
19
|
instance.initialize_from_bin(bin)
|
@@ -9,6 +21,9 @@ module MachO
|
|
9
21
|
instance
|
10
22
|
end
|
11
23
|
|
24
|
+
# Creates a new FatFile from the given filename.
|
25
|
+
# @param filename [String] the Mach-O file to load from
|
26
|
+
# @todo document all the exceptions propagated here
|
12
27
|
def initialize(filename)
|
13
28
|
raise ArgumentError.new("filename must be a String") unless filename.is_a? String
|
14
29
|
|
@@ -18,6 +33,7 @@ module MachO
|
|
18
33
|
@load_commands = get_load_commands
|
19
34
|
end
|
20
35
|
|
36
|
+
# @private
|
21
37
|
def initialize_from_bin(bin)
|
22
38
|
@filename = nil
|
23
39
|
@raw_data = bin
|
@@ -25,80 +41,91 @@ module MachO
|
|
25
41
|
@load_commands = get_load_commands
|
26
42
|
end
|
27
43
|
|
44
|
+
# The file's raw Mach-O data.
|
45
|
+
# @return [String] the raw Mach-O data
|
28
46
|
def serialize
|
29
47
|
@raw_data
|
30
48
|
end
|
31
49
|
|
50
|
+
# @return [Boolean] true if the Mach-O has 32-bit magic, false otherwise
|
32
51
|
def magic32?
|
33
52
|
MachO.magic32?(header[:magic])
|
34
53
|
end
|
35
54
|
|
55
|
+
# @return [Boolean] true if the Mach-O has 64-bit magic, false otherwise
|
36
56
|
def magic64?
|
37
57
|
MachO.magic64?(header[:magic])
|
38
58
|
end
|
39
59
|
|
40
|
-
#
|
60
|
+
# @return [Boolean] true if the Mach-O is of type `MH_EXECUTE`, false otherwise
|
41
61
|
def executable?
|
42
62
|
header[:filetype] == MH_EXECUTE
|
43
63
|
end
|
44
64
|
|
45
|
-
#
|
65
|
+
# @return [Boolean] true if the Mach-O is of type `MH_DYLIB`, false otherwise
|
46
66
|
def dylib?
|
47
67
|
header[:filetype] == MH_DYLIB
|
48
68
|
end
|
49
69
|
|
50
|
-
#
|
70
|
+
# @return [Boolean] true if the Mach-O is of type `MH_BUNDLE`, false otherwise
|
51
71
|
def bundle?
|
52
72
|
header[:filetype] == MH_BUNDLE
|
53
73
|
end
|
54
74
|
|
75
|
+
# @return [Fixnum] the Mach-O's magic number
|
55
76
|
def magic
|
56
77
|
header[:magic]
|
57
78
|
end
|
58
79
|
|
59
|
-
# string representation of the
|
80
|
+
# @return [String] a string representation of the Mach-O's magic number
|
60
81
|
def magic_string
|
61
82
|
MH_MAGICS[header[:magic]]
|
62
83
|
end
|
63
84
|
|
64
|
-
# string representation of the
|
85
|
+
# @return [String] a string representation of the Mach-O's filetype
|
65
86
|
def filetype
|
66
87
|
MH_FILETYPES[header[:filetype]]
|
67
88
|
end
|
68
89
|
|
69
|
-
# string representation of the
|
90
|
+
# @return [String] a string representation of the Mach-O's CPU type
|
70
91
|
def cputype
|
71
92
|
CPU_TYPES[header[:cputype]]
|
72
93
|
end
|
73
94
|
|
74
|
-
# string representation of the
|
95
|
+
# @return [String] a string representation of the Mach-O's CPU subtype
|
75
96
|
def cpusubtype
|
76
97
|
CPU_SUBTYPES[header[:cpusubtype]]
|
77
98
|
end
|
78
99
|
|
79
|
-
# number of load commands in the header
|
100
|
+
# @return [Fixnum] the number of load commands in the Mach-O's header
|
80
101
|
def ncmds
|
81
102
|
header[:ncmds]
|
82
103
|
end
|
83
104
|
|
84
|
-
# size of all load commands
|
105
|
+
# @return [Fixnum] the size of all load commands, in bytes
|
85
106
|
def sizeofcmds
|
86
107
|
header[:sizeofcmds]
|
87
108
|
end
|
88
109
|
|
89
|
-
#
|
110
|
+
# @return [Fixnum] execution flags set by the linker
|
90
111
|
def flags
|
91
112
|
header[:flags]
|
92
113
|
end
|
93
114
|
|
94
|
-
#
|
115
|
+
# All load commands of a given name.
|
116
|
+
# @example
|
117
|
+
# file.command("LC_LOAD_DYLIB")
|
118
|
+
# file["LC_LOAD_DYLIB"]
|
119
|
+
# @return [Array<MachO::LoadCommand>] an array of LoadCommands corresponding to `name`
|
95
120
|
def command(name)
|
96
121
|
load_commands.select { |lc| lc.to_s == name }
|
97
122
|
end
|
98
123
|
|
99
124
|
alias :[] :command
|
100
125
|
|
101
|
-
#
|
126
|
+
# All segment load commands in the Mach-O.
|
127
|
+
# @return [Array<MachO::SegmentCommand>] if the Mach-O is 32-bit
|
128
|
+
# @return [Array<MachO::SegmentCommand64>] if the Mach-O is 64-bit
|
102
129
|
def segments
|
103
130
|
if magic32?
|
104
131
|
command("LC_SEGMENT")
|
@@ -107,7 +134,10 @@ module MachO
|
|
107
134
|
end
|
108
135
|
end
|
109
136
|
|
110
|
-
#
|
137
|
+
# The Mach-O's dylib ID, or `nil` if not a dylib.
|
138
|
+
# @example
|
139
|
+
# file.dylib_id # => 'libBar.dylib'
|
140
|
+
# @return [String] the Mach-O's dylib ID
|
111
141
|
def dylib_id
|
112
142
|
if !dylib?
|
113
143
|
return nil
|
@@ -124,6 +154,11 @@ module MachO
|
|
124
154
|
dylib_id.delete("\x00")
|
125
155
|
end
|
126
156
|
|
157
|
+
# Changes the Mach-O's dylib ID to `new_id`. Does nothing if not a dylib.
|
158
|
+
# @example
|
159
|
+
# file.dylib_id = "libFoo.dylib"
|
160
|
+
# @return [void]
|
161
|
+
# @todo refactor
|
127
162
|
def dylib_id=(new_id)
|
128
163
|
if !new_id.is_a?(String)
|
129
164
|
raise ArgumentError.new("argument must be a String")
|
@@ -199,7 +234,8 @@ module MachO
|
|
199
234
|
load_commands = get_load_commands
|
200
235
|
end
|
201
236
|
|
202
|
-
#
|
237
|
+
# All shared libraries linked to the Mach-O.
|
238
|
+
# @return [Array<String>] an array of all shared libraries
|
203
239
|
def linked_dylibs
|
204
240
|
dylibs = []
|
205
241
|
dylib_cmds = command('LC_LOAD_DYLIB')
|
@@ -217,10 +253,11 @@ module MachO
|
|
217
253
|
dylibs
|
218
254
|
end
|
219
255
|
|
220
|
-
#
|
221
|
-
|
222
|
-
|
223
|
-
|
256
|
+
# @return [void]
|
257
|
+
# @todo refactor
|
258
|
+
def change_install_name(old_name, new_name)
|
259
|
+
idx = linked_dylibs.index(old_name)
|
260
|
+
raise DylibUnknownError.new(old_name) if idx.nil?
|
224
261
|
|
225
262
|
# this is a bit of a hack - since there is a 1-1 ordered association
|
226
263
|
# between linked_dylibs and command('LC_LOAD_DYLIB'), we can use
|
@@ -234,8 +271,8 @@ module MachO
|
|
234
271
|
end
|
235
272
|
|
236
273
|
new_sizeofcmds = header[:sizeofcmds]
|
237
|
-
old_install_name =
|
238
|
-
new_install_name =
|
274
|
+
old_install_name = old_name.dup
|
275
|
+
new_install_name = new_name.dup
|
239
276
|
|
240
277
|
old_pad = MachO.round(old_install_name.size, cmd_round) - old_install_name.size
|
241
278
|
new_pad = MachO.round(new_install_name.size, cmd_round) - new_install_name.size
|
@@ -283,9 +320,11 @@ module MachO
|
|
283
320
|
load_commands = get_load_commands
|
284
321
|
end
|
285
322
|
|
286
|
-
alias :
|
323
|
+
alias :change_dylib :change_install_name
|
287
324
|
|
288
|
-
#
|
325
|
+
# All sections of the segment `segment`.
|
326
|
+
# @return [Array<MachO::Section>] if the Mach-O is 32-bit
|
327
|
+
# @return [Array<MachO::Section64>] if the Mach-O is 64-bit
|
289
328
|
def sections(segment)
|
290
329
|
sections = []
|
291
330
|
|
@@ -312,10 +351,16 @@ module MachO
|
|
312
351
|
sections
|
313
352
|
end
|
314
353
|
|
354
|
+
# Write all Mach-O data to the given filename.
|
355
|
+
# @param filename [String] the file to write to
|
356
|
+
# @return [void]
|
315
357
|
def write(filename)
|
316
358
|
File.open(filename, "wb") { |f| f.write(@raw_data) }
|
317
359
|
end
|
318
360
|
|
361
|
+
# Write all Mach-O data to the file used to initialize the instance.
|
362
|
+
# @raise [MachOError] if the instance was created from a binary string
|
363
|
+
# @return [void]
|
319
364
|
def write!
|
320
365
|
if @filename.nil?
|
321
366
|
raise MachOError.new("cannot write to a default file when initialized from a binary string")
|
data/lib/macho.rb
CHANGED
@@ -10,6 +10,10 @@ require "#{File.dirname(__FILE__)}/macho/utils"
|
|
10
10
|
require "#{File.dirname(__FILE__)}/macho/tools"
|
11
11
|
|
12
12
|
module MachO
|
13
|
+
# Opens the given filename as a MachOFile or FatFile, depending on its magic.
|
14
|
+
# @param filename [String] the file being opened
|
15
|
+
# @return [MachO::MachOFile] if the file is a Mach-O
|
16
|
+
# @return [MachO::FatFile] if the file is a Fat file
|
13
17
|
def self.open(filename)
|
14
18
|
# open file and test magic instead of using exceptions for control?
|
15
19
|
begin
|
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: 0.0.
|
4
|
+
version: 0.0.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- William Woodruff
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-10-
|
11
|
+
date: 2015-10-23 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@tuffbizz.com
|