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 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