ruby-macho 0.1.3 → 0.1.5

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