ruby-macho 0.1.3 → 0.1.5

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: 18e0719447412b4c6fea5f4ca08f5f0e316e34c1
4
- data.tar.gz: e42a6a11cc91a374693cd13d6d5ff8b07d5dec9c
3
+ metadata.gz: 83843cf163d14c552c318a7d2d4787cee484ad88
4
+ data.tar.gz: 73313735ad3e7647fdc5aab78e17e1065e8b1d44
5
5
  SHA512:
6
- metadata.gz: 6a77b43c73461ed816155a184230f27cb4a8130117f41c9f209033eb89cc09c1581849eb58b5fec55a58a7e8d444e1c0da16576fda19463b9d7c7843b0f21082
7
- data.tar.gz: 80ac6b3e2c76001e7a61076b7a6cfc72be034f418636d1ac195ee7f24144749ad4453ec67b3d0382e325a9f99fe88e0618db92696d7881051d93f01a2ef1b8d6
6
+ metadata.gz: a2f3c64080a15318bcc1c78a341899b8d141c7368b017094bb20fe58ba824130a6ecc5a366a78759a6c251d61a5da62ccf6c35452f77317c58351d3497f316b2
7
+ data.tar.gz: a86b0a030d58848ce69c5eb4eac8eb5268f6cefd03904ca69a3c18ad83e60b40db446360719c45879777c62142877036af891699cc0685976d7043b7f7598f3b
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --no-private --markup-provider=redcarpet --markup=markdown - README.md LICENSE
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 William Woodruff <william @ tuffbizz.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,74 @@
1
+ ruby-macho
2
+ ================
3
+
4
+ [![Gem Version](https://badge.fury.io/rb/ruby-macho.svg)](http://badge.fury.io/rb/ruby-macho)
5
+ [![Build Status](https://drone.io/github.com/woodruffw/ruby-macho/status.png)](https://drone.io/github.com/woodruffw/ruby-macho/latest)
6
+
7
+ A Ruby library for examining and modifying Mach-O files.
8
+
9
+ ### What is a Mach-O file?
10
+
11
+ The [Mach-O file format](https://en.wikipedia.org/wiki/Mach-O) is used by OS X
12
+ and iOS (among others) as a general purpose binary format for object files,
13
+ executables, dynamic libraries, and so forth.
14
+
15
+ ### Documentation
16
+
17
+ Full documentation is available on [RubyDoc](http://www.rubydoc.info/gems/ruby-macho/).
18
+
19
+ A quick example of what ruby-macho can do:
20
+
21
+ ```ruby
22
+ require 'macho'
23
+
24
+ file = MachO::MachOFile.open("/path/to/my/binary")
25
+
26
+ # get the file's type (MH_OBJECT, MH_DYLIB, MH_EXECUTE, etc)
27
+ file.filetype # => "MH_EXECUTE"
28
+
29
+ # get all load commands in the file and print their offsets:
30
+ file.load_commands.each do |lc|
31
+ puts "#{lc}: offset #{lc.offset}, size: #{lc.cmdsize}"
32
+ end
33
+
34
+ # access a specific load command
35
+ lc_vers = file['LC_VERSION_MIN_MACOSX'].first
36
+ puts lc_vers.version_string # => "10.10.0"
37
+ ```
38
+
39
+ ### What works?
40
+
41
+ * Reading data from x86/x86_64 Mach-O files
42
+ * Changing the IDs of Mach-O dylibs
43
+ * Changing install names in Mach-O files
44
+
45
+ ### What might work?
46
+
47
+ * Reading *some* data from PPC Mach-O files.
48
+ * Reading data from "Fat" files.
49
+
50
+ ### What doesn't work yet?
51
+
52
+ * Reading data from any other architecure's Mach-O files (probably).
53
+ * Changing anything in "Fat" files (at least not correctly).
54
+
55
+ ### What needs to be done?
56
+
57
+ * Documentation.
58
+ * Rpath modification.
59
+ * Many, many things.
60
+
61
+ Attribution:
62
+
63
+ * `lib/macho/cstruct.rb` was taken from Sami Samhuri's
64
+ [compiler](https://github.com/samsonjs/compiler) repository.
65
+ (No license provided).
66
+ * Constants were taken from Apple, Inc's
67
+ [`loader.h` in `cctools/include/mach-o`](http://www.opensource.apple.com/source/cctools/cctools-870/include/mach-o/loader.h).
68
+ (Apple Public Source License 2.0).
69
+
70
+ ### License
71
+
72
+ `ruby-macho` is licensed under the MIT License.
73
+
74
+ For the exact terms, see the [license](LICENSE) file.
data/lib/macho.rb CHANGED
@@ -1,6 +1,5 @@
1
- require "#{File.dirname(__FILE__)}/cstruct"
2
- require "#{File.dirname(__FILE__)}/macho/headers"
3
1
  require "#{File.dirname(__FILE__)}/macho/structure"
2
+ require "#{File.dirname(__FILE__)}/macho/headers"
4
3
  require "#{File.dirname(__FILE__)}/macho/load_commands"
5
4
  require "#{File.dirname(__FILE__)}/macho/sections"
6
5
  require "#{File.dirname(__FILE__)}/macho/macho_file"
@@ -119,17 +119,14 @@ module MachO
119
119
 
120
120
  # Obtain the fat header from raw file data.
121
121
  # @return [MachO::FatHeader] the fat header
122
+ # @raise [MachO::MagicError] if the magic is not valid Mach-O magic
123
+ # @raise [MachO::MachOBinaryError] if the magic is for a non-fat Mach-O file
122
124
  # @private
123
125
  def get_fat_header
124
126
  magic, nfat_arch = @raw_data[0..7].unpack("N2")
125
127
 
126
- if !MachO.magic?(magic)
127
- raise MagicError.new(magic)
128
- end
129
-
130
- if !MachO.fat_magic?(magic)
131
- raise MachOBinaryError.new
132
- end
128
+ raise MagicError.new(magic) unless MachO.magic?(magic)
129
+ raise MachOBinaryError.new unless MachO.fat_magic?(magic)
133
130
 
134
131
  FatHeader.new(magic, nfat_arch)
135
132
  end
@@ -140,7 +137,7 @@ module MachO
140
137
  def get_fat_archs
141
138
  archs = []
142
139
 
143
- header[:nfat_arch].times do |i|
140
+ header.nfat_arch.times do |i|
144
141
  fields = @raw_data[8 + (FatArch.bytesize * i), FatArch.bytesize].unpack("N5")
145
142
  archs << FatArch.new(*fields)
146
143
  end
@@ -155,7 +152,7 @@ module MachO
155
152
  machos = []
156
153
 
157
154
  fat_archs.each do |arch|
158
- machos << MachOFile.new_from_bin(@raw_data[arch[:offset], arch[:size]])
155
+ machos << MachOFile.new_from_bin(@raw_data[arch.offset, arch.size])
159
156
  end
160
157
 
161
158
  machos
@@ -168,7 +165,7 @@ module MachO
168
165
  machos.each_with_index do |macho, i|
169
166
  arch = fat_archs[i]
170
167
 
171
- @raw_data[arch[:offset], arch[:size]] = macho.serialize
168
+ @raw_data[arch.offset, arch.size] = macho.serialize
172
169
  end
173
170
  end
174
171
  end
data/lib/macho/headers.rb CHANGED
@@ -234,29 +234,62 @@ module MachO
234
234
  }
235
235
 
236
236
  # Fat binary header structure
237
- class FatHeader < CStruct
238
- uint32 :magic
239
- uint32 :nfat_arch # number of FatArchs that follow
237
+ class FatHeader < MachOStructure
238
+ attr_reader :magic
239
+ attr_reader :nfat_arch # number of FatArchs that follow
240
+
241
+ @format = "VV"
242
+ @sizeof = 8
243
+
244
+ def initialize(magic, nfat_arch)
245
+ @magic = magic
246
+ @nfat_arch = nfat_arch
247
+ end
240
248
  end
241
249
 
242
250
  # Fat binary header architecture structure
243
- class FatArch < CStruct
244
- int32 :cputype
245
- int32 :cpusubtype
246
- uint32 :offset
247
- uint32 :size
248
- uint32 :align
251
+ class FatArch < MachOStructure
252
+ attr_reader :cputype
253
+ attr_reader :cpusubtype
254
+ attr_reader :offset
255
+ attr_reader :size
256
+ attr_reader :align
257
+
258
+ @format = "VVVVV"
259
+ @sizeof = 20
260
+
261
+ def initialize(cputype, cpusubtype, offset, size, align)
262
+ @cputype = cputype
263
+ @cpusubtype = cpusubtype
264
+ @offset = offset
265
+ @size = size
266
+ @align = align
267
+ end
249
268
  end
250
269
 
251
270
  # 32-bit Mach-O file header structure
252
- class MachHeader < CStruct
253
- uint32 :magic
254
- int32 :cputype
255
- int32 :cpusubtype
256
- uint32 :filetype
257
- uint32 :ncmds
258
- uint32 :sizeofcmds
259
- uint32 :flags
271
+ class MachHeader < MachOStructure
272
+ attr_reader :magic
273
+ attr_reader :cputype
274
+ attr_reader :cpusubtype
275
+ attr_reader :filetype
276
+ attr_reader :ncmds
277
+ attr_reader :sizeofcmds
278
+ attr_reader :flags
279
+
280
+ @format = "VVVVVVV"
281
+ @sizeof = 28
282
+
283
+ def initialize(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds,
284
+ flags)
285
+ @magic = magic
286
+ @cputype = cputype
287
+ @cpusubtype = cpusubtype
288
+ @filetype = filetype
289
+ @ncmds = ncmds
290
+ @sizeofcmds = sizeofcmds
291
+ @flags = flags
292
+ end
260
293
 
261
294
  # @example
262
295
  # puts "this mach-o has position-independent execution" if header.flag?(MH_PIE)
@@ -268,15 +301,30 @@ module MachO
268
301
  end
269
302
 
270
303
  # 64-bit Mach-O file header structure
271
- class MachHeader64 < CStruct
272
- uint32 :magic
273
- int32 :cputype
274
- int32 :cpusubtype
275
- uint32 :filetype
276
- uint32 :ncmds
277
- uint32 :sizeofcmds
278
- uint32 :flags
279
- uint32 :reserved
304
+ class MachHeader64 < MachOStructure
305
+ attr_reader :magic
306
+ attr_reader :cputype
307
+ attr_reader :cpusubtype
308
+ attr_reader :filetype
309
+ attr_reader :ncmds
310
+ attr_reader :sizeofcmds
311
+ attr_reader :flags
312
+ attr_reader :reserved
313
+
314
+ @format = "VVVVVVVV"
315
+ @sizeof = 32
316
+
317
+ def initialize(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds,
318
+ flags, reserved)
319
+ @magic = magic
320
+ @cputype = cputype
321
+ @cpusubtype = cpusubtype
322
+ @filetype = filetype
323
+ @ncmds = ncmds
324
+ @sizeofcmds = sizeofcmds
325
+ @flags = flags
326
+ @reserved = reserved
327
+ end
280
328
 
281
329
  # @example
282
330
  # puts "this mach-o has position-independent execution" if header.flag?(MH_PIE)
@@ -50,67 +50,67 @@ module MachO
50
50
 
51
51
  # @return [Boolean] true if the Mach-O has 32-bit magic, false otherwise
52
52
  def magic32?
53
- MachO.magic32?(header[:magic])
53
+ MachO.magic32?(header.magic)
54
54
  end
55
55
 
56
56
  # @return [Boolean] true if the Mach-O has 64-bit magic, false otherwise
57
57
  def magic64?
58
- MachO.magic64?(header[:magic])
58
+ MachO.magic64?(header.magic)
59
59
  end
60
60
 
61
61
  # @return [Boolean] true if the Mach-O is of type `MH_EXECUTE`, false otherwise
62
62
  def executable?
63
- header[:filetype] == MH_EXECUTE
63
+ header.filetype == MH_EXECUTE
64
64
  end
65
65
 
66
66
  # @return [Boolean] true if the Mach-O is of type `MH_DYLIB`, false otherwise
67
67
  def dylib?
68
- header[:filetype] == MH_DYLIB
68
+ header.filetype == MH_DYLIB
69
69
  end
70
70
 
71
71
  # @return [Boolean] true if the Mach-O is of type `MH_BUNDLE`, false otherwise
72
72
  def bundle?
73
- header[:filetype] == MH_BUNDLE
73
+ header.filetype == MH_BUNDLE
74
74
  end
75
75
 
76
76
  # @return [Fixnum] the Mach-O's magic number
77
77
  def magic
78
- header[:magic]
78
+ header.magic
79
79
  end
80
80
 
81
81
  # @return [String] a string representation of the Mach-O's magic number
82
82
  def magic_string
83
- MH_MAGICS[header[:magic]]
83
+ MH_MAGICS[header.magic]
84
84
  end
85
85
 
86
86
  # @return [String] a string representation of the Mach-O's filetype
87
87
  def filetype
88
- MH_FILETYPES[header[:filetype]]
88
+ MH_FILETYPES[header.filetype]
89
89
  end
90
90
 
91
91
  # @return [String] a string representation of the Mach-O's CPU type
92
92
  def cputype
93
- CPU_TYPES[header[:cputype]]
93
+ CPU_TYPES[header.cputype]
94
94
  end
95
95
 
96
96
  # @return [String] a string representation of the Mach-O's CPU subtype
97
97
  def cpusubtype
98
- CPU_SUBTYPES[header[:cpusubtype]]
98
+ CPU_SUBTYPES[header.cpusubtype]
99
99
  end
100
100
 
101
101
  # @return [Fixnum] the number of load commands in the Mach-O's header
102
102
  def ncmds
103
- header[:ncmds]
103
+ header.ncmds
104
104
  end
105
105
 
106
106
  # @return [Fixnum] the size of all load commands, in bytes
107
107
  def sizeofcmds
108
- header[:sizeofcmds]
108
+ header.sizeofcmds
109
109
  end
110
110
 
111
111
  # @return [Fixnum] execution flags set by the linker
112
112
  def flags
113
- header[:flags]
113
+ header.flags
114
114
  end
115
115
 
116
116
  # All load commands of a given name.
@@ -173,16 +173,7 @@ module MachO
173
173
  # All shared libraries linked to the Mach-O.
174
174
  # @return [Array<String>] an array of all shared libraries
175
175
  def linked_dylibs
176
- dylibs = []
177
- dylib_cmds = command("LC_LOAD_DYLIB")
178
-
179
- dylib_cmds.each do |dylib_cmd|
180
- dylib = dylib_cmd.name.to_s
181
-
182
- dylibs << dylib
183
- end
184
-
185
- dylibs
176
+ command("LC_LOAD_DYLIB").map(&:name).map(&:to_s)
186
177
  end
187
178
 
188
179
  # Changes the shared library `old_name` to `new_name`
@@ -193,13 +184,8 @@ module MachO
193
184
  # @return [void]
194
185
  # @raise [MachO::DylibUnknownError] if no shared library has the old name
195
186
  def change_install_name(old_name, new_name)
196
- idx = linked_dylibs.index(old_name)
197
- raise DylibUnknownError.new(old_name) if idx.nil?
198
-
199
- # this is a bit of a hack - since there is a 1-1 ordered association
200
- # between linked_dylibs and command('LC_LOAD_DYLIB'), we can use
201
- # their indices interchangeably to avoid having to loop.
202
- dylib_cmd = command("LC_LOAD_DYLIB")[idx]
187
+ dylib_cmd = command("LC_LOAD_DYLIB").find { |d| d.name.to_s == old_name }
188
+ raise DylibUnknownError.new(old_name) if dylib_cmd.nil?
203
189
 
204
190
  set_name_in_dylib(dylib_cmd, old_name, new_name)
205
191
  end
@@ -287,13 +273,8 @@ module MachO
287
273
  def get_magic
288
274
  magic = @raw_data[0..3].unpack("N").first
289
275
 
290
- if !MachO.magic?(magic)
291
- raise MagicError.new(magic)
292
- end
293
-
294
- if MachO.fat_magic?(magic)
295
- raise FatBinaryError.new
296
- end
276
+ raise MagicError.new(magic) unless MachO.magic?(magic)
277
+ raise FatBinaryError.new if MachO.fat_magic?(magic)
297
278
 
298
279
  magic
299
280
  end
@@ -305,9 +286,7 @@ module MachO
305
286
  def get_cputype
306
287
  cputype = @raw_data[4..7].unpack("V").first
307
288
 
308
- if !CPU_TYPES.has_key?(cputype)
309
- raise CPUTypeError.new(cputype)
310
- end
289
+ raise CPUTypeError.new(cputype) unless CPU_TYPES.key?(cputype)
311
290
 
312
291
  cputype
313
292
  end
@@ -318,13 +297,9 @@ module MachO
318
297
  # @private
319
298
  def get_cpusubtype
320
299
  cpusubtype = @raw_data[8..11].unpack("V").first
300
+ cpusubtype &= ~CPU_SUBTYPE_LIB64 # this mask isn't documented!
321
301
 
322
- # this mask isn't documented!
323
- cpusubtype &= ~CPU_SUBTYPE_LIB64
324
-
325
- if !CPU_SUBTYPES.has_key?(cpusubtype)
326
- raise CPUSubtypeError.new(cpusubtype)
327
- end
302
+ raise CPUSubtypeError.new(cpusubtype) unless CPU_SUBTYPES.key?(cpusubtype)
328
303
 
329
304
  cpusubtype
330
305
  end
@@ -336,9 +311,7 @@ module MachO
336
311
  def get_filetype
337
312
  filetype = @raw_data[12..15].unpack("V").first
338
313
 
339
- if !MH_FILETYPES.has_key?(filetype)
340
- raise FiletypeError.new(filetype)
341
- end
314
+ raise FiletypeError.new(filetype) unless MH_FILETYPES.key?(filetype)
342
315
 
343
316
  filetype
344
317
  end
@@ -347,27 +320,21 @@ module MachO
347
320
  # @return [Fixnum] the number of load commands
348
321
  # @private
349
322
  def get_ncmds
350
- ncmds = @raw_data[16..19].unpack("V").first
351
-
352
- ncmds
323
+ @raw_data[16..19].unpack("V").first
353
324
  end
354
325
 
355
326
  # The size of all load commands, in bytes.
356
327
  # return [Fixnum] the size of all load commands
357
328
  # @private
358
329
  def get_sizeofcmds
359
- sizeofcmds = @raw_data[20..23].unpack("V").first
360
-
361
- sizeofcmds
330
+ @raw_data[20..23].unpack("V").first
362
331
  end
363
332
 
364
333
  # The Mach-O header's flags.
365
334
  # @return [Fixnum] the flags
366
335
  # @private
367
336
  def get_flags
368
- flags = @raw_data[24..27].unpack("V").first
369
-
370
- flags
337
+ @raw_data[24..27].unpack("V").first
371
338
  end
372
339
 
373
340
  # All load commands in the file.
@@ -375,15 +342,13 @@ module MachO
375
342
  # @raise [MachO::LoadCommandError] if an unknown load command is encountered
376
343
  # @private
377
344
  def get_load_commands
378
- offset = header.bytesize
345
+ offset = header.class.bytesize
379
346
  load_commands = []
380
347
 
381
- header[:ncmds].times do
348
+ header.ncmds.times do
382
349
  cmd = @raw_data.slice(offset, 4).unpack("V").first
383
350
 
384
- if !LC_STRUCTURES.has_key?(cmd)
385
- raise LoadCommandError.new(cmd)
386
- end
351
+ raise LoadCommandError.new(cmd) unless LC_STRUCTURES.key?(cmd)
387
352
 
388
353
  # why do I do this? i don't like declaring constants below
389
354
  # classes, and i need them to resolve...
@@ -420,7 +385,7 @@ module MachO
420
385
  cmd_round = 8
421
386
  end
422
387
 
423
- new_sizeofcmds = header[:sizeofcmds]
388
+ new_sizeofcmds = header.sizeofcmds
424
389
  old_name = old_name.dup
425
390
  new_name = new_name.dup
426
391
 
@@ -449,7 +414,7 @@ module MachO
449
414
  end
450
415
  end
451
416
 
452
- if new_sizeofcmds + header.bytesize > low_fileoff
417
+ if new_sizeofcmds + header.class.bytesize > low_fileoff
453
418
  raise HeaderPadError.new(@filename)
454
419
  end
455
420
 
@@ -469,9 +434,9 @@ module MachO
469
434
  null_pad = old_name.size - new_name.size
470
435
 
471
436
  if null_pad < 0
472
- @raw_data.slice!(new_sizeofcmds + header.bytesize, null_pad.abs)
437
+ @raw_data.slice!(new_sizeofcmds + header.class.bytesize, null_pad.abs)
473
438
  else
474
- @raw_data.insert(new_sizeofcmds + header.bytesize, "\x00" * null_pad)
439
+ @raw_data.insert(new_sizeofcmds + header.class.bytesize, "\x00" * null_pad)
475
440
  end
476
441
 
477
442
  # synchronize fields with the raw data
@@ -0,0 +1,42 @@
1
+ module MachO
2
+ # A collection of convenient methods for common operations on Mach-O and Fat binaries.
3
+ module Tools
4
+ # @param filename [String] the Mach-O or Fat binary being read
5
+ # @return [Array<String>] an array of all dylibs linked to the binary
6
+ def self.dylibs(filename)
7
+ file = MachO.open(filename)
8
+
9
+ file.linked_dylibs
10
+ end
11
+
12
+ # Changes the dylib ID of a Mach-O or Fat binary, overwriting the source file.
13
+ # @param filename [String] the Mach-O or Fat binary being modified
14
+ # @param new_id [String] the new dylib ID for the binary
15
+ # @return [void]
16
+ def self.change_dylib_id(filename, new_id)
17
+ file = MachO.open(filename)
18
+
19
+ if File.is_a? MachO::MachOFile
20
+ file.dylib_id = new_id
21
+ file.write!
22
+ else
23
+ raise MachOError.new("changing dylib ids for fat binaries is incomplete")
24
+ end
25
+ end
26
+
27
+ # Changes a shared library install name in a Mach-O or Fat binary, overwriting the source file.
28
+ # @param filename [String] the Mach-O or Fat binary being modified
29
+ # @param old_name [String] the old shared library name
30
+ # @param new_name [String] the new shared library name
31
+ # @return [void]
32
+ def self.change_install_name(filename, old_name, new_name)
33
+ file = MachO.open(filename)
34
+
35
+ if File.is_a? MachO::MachOFile
36
+ file.change_install_name(old_name, new_name)
37
+ else
38
+ raise MachOError.new("changing install names for fat binaries is incomplete")
39
+ end
40
+ end
41
+ end
42
+ 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: 0.1.3
4
+ version: 0.1.5
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-27 00:00:00.000000000 Z
11
+ date: 2015-10-28 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
@@ -16,7 +16,9 @@ executables: []
16
16
  extensions: []
17
17
  extra_rdoc_files: []
18
18
  files:
19
- - lib/cstruct.rb
19
+ - ".yardopts"
20
+ - LICENSE
21
+ - README.md
20
22
  - lib/macho.rb
21
23
  - lib/macho/exceptions.rb
22
24
  - lib/macho/fat_file.rb
@@ -25,6 +27,7 @@ files:
25
27
  - lib/macho/macho_file.rb
26
28
  - lib/macho/sections.rb
27
29
  - lib/macho/structure.rb
30
+ - lib/macho/tools.rb
28
31
  - lib/macho/utils.rb
29
32
  homepage: https://github.com/woodruffw/ruby-macho
30
33
  licenses:
data/lib/cstruct.rb DELETED
@@ -1,318 +0,0 @@
1
- # Struct does some trickery with custom allocators so we can't
2
- # subclass it without writing C. Instead we define a CStruct class
3
- # that does something similar enough for our purpose. It is
4
- # subclassed just like any other class. A nice side-effect of this
5
- # syntax is that it is always clear that a CStruct is just a class and
6
- # instances of the struct are objects.
7
- #
8
- # Some light metaprogramming is used to make the following syntax possible:
9
- #
10
- # class MachHeader < CStruct
11
- # uint :magic
12
- # int :cputype
13
- # int :cpusubtype
14
- # ...
15
- # int :flags
16
- # end
17
- #
18
- # Inheritance works as you would expect.
19
- #
20
- # class LoadCommand < CStruct
21
- # uint32 :cmd
22
- # uint32 :cmdsize
23
- # end
24
- #
25
- # # inherits cmd and cmdsize as the first 2 fields
26
- # class SegmentCommand < LoadCommand
27
- # string :segname, 16
28
- # uint32 :vmaddr
29
- # uint32
30
- # end
31
- #
32
- # Nothing tricky or confusing there. Members of a CStruct class are
33
- # declared in the class definition. A different definition using a
34
- # more static approach probably wouldn't be very hard... if
35
- # performance is critical ... but then why are you using Ruby? ;-)
36
- #
37
- #
38
- # TODO support bit fields
39
- #
40
- # Bit fields should be supported by passing the number of bits a field
41
- # should occupy. Perhaps we could use the size 'pack' for the rest of
42
- # the field.
43
- #
44
- # class RelocationInfo < CStruct
45
- # int32 :address
46
- # uint32 :symbolnum, 24
47
- # pack :pcrel, 1
48
- # pack :length, 2
49
- # pack :extern, 1
50
- # pack :type, 4
51
- # end
52
-
53
- class CStruct
54
-
55
-
56
- ###################
57
- # Class Constants #
58
- ###################
59
-
60
- # Size in bytes.
61
- SizeMap = {
62
- :int8 => 1,
63
- :uint8 => 1,
64
- :int16 => 2,
65
- :uint16 => 2,
66
- :int32 => 4,
67
- :uint32 => 4,
68
- :int64 => 8,
69
- :uint64 => 8,
70
- :string => lambda { |*opts| opts.first }, # first opt is size
71
- # the last 3 are to make the language more C-like
72
- :int => 4,
73
- :uint => 4,
74
- :char => 1
75
- }
76
-
77
- # 32-bit
78
- PackMap = {
79
- :int8 => 'c',
80
- :uint8 => 'C',
81
- :int16 => 's',
82
- :uint16 => 'S',
83
- :int32 => 'i',
84
- :uint32 => 'I',
85
- :int64 => 'q',
86
- :uint64 => 'Q',
87
- :string => lambda do |str, *opts|
88
- len = opts.first
89
- str.ljust(len, "\0")[0, len]
90
- end,
91
- # a few C-like names
92
- :int => 'i',
93
- :uint => 'I',
94
- :char => 'C'
95
- }
96
-
97
- # Only needed when unpacking is different from packing, i.e. strings w/ lambdas in PackMap.
98
- UnpackMap = {
99
- :string => lambda do |str, *opts|
100
- len = opts.first
101
- val = str[0, len-1].sub(/\0*$/, '')
102
- str.slice!((len-1)..-1)
103
- val
104
- end
105
- }
106
-
107
- ##########################
108
- # Class Instance Methods #
109
- ##########################
110
-
111
- # Note: const_get and const_set are used so the constants are bound
112
- # at runtime, to the real class that has subclassed CStruct.
113
- # I figured Ruby would do this but I haven't looked at the
114
- # implementation of constants so it might be tricky.
115
- #
116
- # All of this could probably be avoided with Ruby 1.9 and
117
- # private class variables. That is definitely something to
118
- # experiment with.
119
-
120
- class <<self
121
-
122
- def inherited(subclass)
123
- subclass.instance_eval do
124
-
125
- # These "constants" are only constant references. Structs can
126
- # be modified. After the struct is defined it is still open,
127
- # but good practice would be not to change a struct after it
128
- # has been defined.
129
- #
130
- # To support inheritance properly we try to get these
131
- # constants from the enclosing scope (and clone them before
132
- # modifying them!), and default to empty, er, defaults.
133
-
134
- members = const_get(:Members).clone rescue []
135
- member_index = const_get(:MemberIndex).clone rescue {}
136
- member_sizes = const_get(:MemberSizes).clone rescue {}
137
- member_opts = const_get(:MemberOptions).clone rescue {}
138
-
139
- const_set(:Members, members)
140
- const_set(:MemberIndex, member_index)
141
- const_set(:MemberSizes, member_sizes)
142
- const_set(:MemberOptions, member_opts)
143
-
144
- end
145
- end
146
-
147
-
148
- # Define a method for each size name, and when that method is called it updates
149
- # the struct class accordingly.
150
- SizeMap.keys.each do |type|
151
-
152
- define_method(type) do |name, *args|
153
- name = name.to_sym
154
- const_get(:MemberIndex)[name] = const_get(:Members).size
155
- const_get(:MemberSizes)[name] = type
156
- const_get(:MemberOptions)[name] = args
157
- const_get(:Members) << name
158
- end
159
-
160
- end
161
-
162
-
163
- # Return the number of members.
164
- def size
165
- const_get(:Members).size
166
- end
167
- alias_method :length, :size
168
-
169
- # Return the number of bytes occupied in memory or on disk.
170
- def bytesize
171
- const_get(:Members).inject(0) { |size, name| size + sizeof(name) }
172
- end
173
-
174
- def sizeof(name)
175
- value = SizeMap[const_get(:MemberSizes)[name]]
176
- value.respond_to?(:call) ? value.call(*const_get(:MemberOptions)[name]) : value
177
- end
178
-
179
- def new_from_bin(bin)
180
- new_struct = new
181
- new_struct.unserialize(bin)
182
- end
183
-
184
- end
185
-
186
-
187
- ####################
188
- # Instance Methods #
189
- ####################
190
-
191
- attr_reader :values
192
-
193
- def initialize(*args)
194
- @values = args
195
- end
196
-
197
- def serialize
198
- vals = @values.clone
199
- membs = members.clone
200
- pack_pattern.map do |patt|
201
- name = membs.shift
202
- if patt.is_a?(String)
203
- [vals.shift].pack(patt)
204
- else
205
- patt.call(vals.shift, *member_options[name])
206
- end
207
- end.join
208
- end
209
-
210
- def unserialize(bin)
211
- bin = bin.clone
212
- @values = []
213
- membs = members.clone
214
- unpack_pattern.each do |patt|
215
- name = membs.shift
216
- if patt.is_a?(String)
217
- @values += bin.unpack(patt)
218
- bin.slice!(0, sizeof(name))
219
- else
220
- @values << patt.call(bin, *member_options[name])
221
- end
222
- end
223
- self
224
- end
225
-
226
- def pack_pattern
227
- members.map { |name| PackMap[member_sizes[name]] }
228
- end
229
-
230
- def unpack_pattern
231
- members.map { |name| UnpackMap[member_sizes[name]] || PackMap[member_sizes[name]] }
232
- end
233
-
234
- def [](name_or_idx)
235
- case name_or_idx
236
-
237
- when Numeric
238
- idx = name_or_idx
239
- @values[idx]
240
-
241
- when String, Symbol
242
- name = name_or_idx.to_sym
243
- @values[member_index[name]]
244
-
245
- else
246
- raise ArgumentError, "expected name or index, got #{name_or_idx.inspect}"
247
- end
248
- end
249
-
250
- def []=(name_or_idx, value)
251
- case name_or_idx
252
-
253
- when Numeric
254
- idx = name_or_idx
255
- @values[idx] = value
256
-
257
- when String, Symbol
258
- name = name_or_idx.to_sym
259
- @values[member_index[name]] = value
260
-
261
- else
262
- raise ArgumentError, "expected name or index, got #{name_or_idx.inspect}"
263
- end
264
- end
265
-
266
- def ==(other)
267
- puts @values.inspect
268
- puts other.values.inspect
269
- other.is_a?(self.class) && other.values == @values
270
- end
271
-
272
- # Some of these are just to quack like Ruby's built-in Struct. YAGNI, but can't hurt either.
273
-
274
- def each(&block)
275
- @values.each(&block)
276
- end
277
-
278
- def each_pair(&block)
279
- members.zip(@values).each(&block)
280
- end
281
-
282
- def size
283
- members.size
284
- end
285
- alias_method :length, :size
286
-
287
- def sizeof(name)
288
- self.class.sizeof(name)
289
- end
290
-
291
- def bytesize
292
- self.class.bytesize
293
- end
294
-
295
- alias_method :to_a, :values
296
-
297
-
298
- # A few convenience methods.
299
-
300
- def members
301
- self.class::Members
302
- end
303
-
304
- def member_index
305
- self.class::MemberIndex
306
- end
307
-
308
- def member_sizes
309
- self.class::MemberSizes
310
- end
311
-
312
- def member_options
313
- self.class::MemberOptions
314
- end
315
-
316
- # The last expression is returned, so return self instead of junk.
317
- self
318
- end