ruby-macho 0.0.8 → 0.0.9

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: 2b104665e169f8988913d6032d3755032d814a2f
4
- data.tar.gz: fc3f95ed644f84e913a78709227e178f33e9e995
3
+ metadata.gz: 119afdca4c1793c3bc3f8285c88a8126ab4e1e76
4
+ data.tar.gz: 26346fa156b237e2606e8b005c95330bc1b194ad
5
5
  SHA512:
6
- metadata.gz: 986523ca44a54d65a2d1624b31cbd612f520bab44ffaf405971052174224e9fedde4aabf1aa5d91fe451ee58cde5d892d95030bf6dad8f4688389fe8fc3f18dd
7
- data.tar.gz: b5e6ffe8212660e32ed089d7dba2294818257aeb523b8eac8a3d4d66b9a549a210b40037109c26ff2cd31e47c7921fbe3389153b86dff30816efab83a061cbd4
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
@@ -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
- attr_reader :header, :fat_archs, :machos
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
- # stub
58
- def change_dylib(old_path, new_path)
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(old_path, new_path)
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 :change_install_name :change_dylib
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
- # when we create machos within FatFile, we initialize them with slices
114
- # from @raw_data. this means creating new arrays that don't affect
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]
@@ -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
- attr_reader :header, :load_commands
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
- # is the file executable?
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
- # is the file a dynamically bound shared object?
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
- # is the file a dynamically bound bundle?
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 header's magic bytes
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 header's filetype field
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 header's cputype field
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 header's cpusubtype field
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
- # various execution flags
110
+ # @return [Fixnum] execution flags set by the linker
90
111
  def flags
91
112
  header[:flags]
92
113
  end
93
114
 
94
- # get load commands by name
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
- # get all segment commands
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
- # get the file's dylib id, if it is a dylib
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
- # get a list of dylib paths linked to this file
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
- # TODO: genericize change_dylib and dylib_id= for DRYness
221
- def change_dylib(old_path, new_path)
222
- idx = linked_dylibs.index(old_path)
223
- raise DylibUnknownError.new(old_path) if idx.nil?
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 = old_path.dup
238
- new_install_name = new_path.dup
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 :change_install_name :change_dylib
323
+ alias :change_dylib :change_install_name
287
324
 
288
- # get all sections in a segment by name
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.8
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-21 00:00:00.000000000 Z
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