ruby-macho 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 686c8eeee9cd964f7c1112259c64ba6f37650262
4
+ data.tar.gz: 9c7a05e36acc9b2120263e3847ca0dbb8e8f9fd9
5
+ SHA512:
6
+ metadata.gz: ff579faa1788421e587e995b4434ff2e8ef0f5491b33c4d599577ea70fff6ac7ce3b3e4eea166d5e5e94cd5128f3050adab90b38949fdc854419fbacfa059aa2
7
+ data.tar.gz: 107c76d944b6aa7987ac1c0299f2d49ee8e48eb8a4b8a9fa046ea9fbd6821586910c0892792c14c3aefb094b02d0a2a60c8d811cddc2c3f73309933f330c6b2c
data/lib/cstruct.rb ADDED
@@ -0,0 +1,346 @@
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
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
@@ -0,0 +1,17 @@
1
+ module INTHelpers
2
+ Change = Struct.new("Changes", :old, :new)
3
+
4
+ Rpath = Struct.new("Rpaths", :old, :new, :found) do
5
+ def found?
6
+ found
7
+ end
8
+ end
9
+
10
+ AddRpath = Struct.new("AddRpaths", :new)
11
+
12
+ DeleteRpath = Struct.new("DeleteRpaths", :old, :found) do
13
+ def found?
14
+ found
15
+ end
16
+ end
17
+ end
data/lib/macho.rb ADDED
@@ -0,0 +1,14 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require "cstruct"
4
+ require "macho/headers"
5
+ require "macho/structure"
6
+ require "macho/load_commands"
7
+ require "macho/sections"
8
+ require "macho/file"
9
+ require "macho/exceptions"
10
+ require "macho/utils"
11
+
12
+ module MachO
13
+ # nothing to see here.
14
+ end
@@ -0,0 +1,54 @@
1
+ module MachO
2
+ # generic toplevel error
3
+ class MachOError < RuntimeError
4
+ end
5
+
6
+ # raised when a file's magic bytes are not valid mach-o magic
7
+ class MagicError < MachOError
8
+ def initialize(num)
9
+ super "Unrecognized Mach-O magic: 0x#{"%02x" % num}"
10
+ end
11
+ end
12
+
13
+ # raised when a file's magic bytes are those of a fat binary
14
+ class FatBinaryError < MachOError
15
+ def initialize(num)
16
+ super "Unsupported fat binary (magic 0x#{"%02x" % num})"
17
+ end
18
+ end
19
+
20
+ class CPUTypeError < MachOError
21
+ def initialize(num)
22
+ super "Unrecognized CPU type: 0x#{"%02x" % num}"
23
+ end
24
+ end
25
+
26
+ class CPUSubtypeError < MachOError
27
+ def initialize(num)
28
+ super "Unrecognized CPU sub-type: 0x#{"%02x" % num}"
29
+ end
30
+ end
31
+
32
+ # raised when a mach-o file's filetype field is unknown
33
+ class FiletypeError < MachOError
34
+ def initialize(num)
35
+ super "Unrecognized Mach-O filetype code: 0x#{"%02x" % num}"
36
+ end
37
+ end
38
+
39
+ # raised when an unknown load command is encountered
40
+ class LoadCommandError < MachOError
41
+ def initialize(num)
42
+ super "Unrecognized Mach-O load command: 0x#{"%02x" % num}"
43
+ end
44
+ end
45
+
46
+ # raised when load commands are too large to fit in the current file
47
+ class HeaderPadError < MachOError
48
+ def initialize(filename)
49
+ super "Updated load commands do not fit in the header of " +
50
+ "#{filename}. #{filename} needs to be relinked, possibly with " +
51
+ "-headerpad or -headerpad_max_install_names"
52
+ end
53
+ end
54
+ end
data/lib/macho/file.rb ADDED
@@ -0,0 +1,351 @@
1
+ module MachO
2
+ class MachOFile
3
+ attr_reader :header, :load_commands
4
+
5
+ def initialize(filename)
6
+ raise ArgumentError.new("filename must be a String") unless filename.is_a? String
7
+
8
+ @filename = filename
9
+ @raw_data = open(@filename, "rb") { |f| f.read }
10
+ @header = get_mach_header
11
+ @load_commands = get_load_commands
12
+ end
13
+
14
+ def magic32?
15
+ Utils.magic32?(header[:magic])
16
+ end
17
+
18
+ def magic64?
19
+ Utils.magic64?(header[:magic])
20
+ end
21
+
22
+ # is the file executable?
23
+ def executable?
24
+ header[:filetype] == MH_EXECUTE
25
+ end
26
+
27
+ # is the file a dynamically bound shared object?
28
+ def dylib?
29
+ header[:filetype] == MH_DYLIB
30
+ end
31
+
32
+ # is the file a dynamically bound bundle?
33
+ def bundle?
34
+ header[:filetype] == MH_BUNDLE
35
+ end
36
+
37
+ def magic
38
+ header[:magic]
39
+ end
40
+
41
+ # string representation of the header's magic bytes
42
+ def magic_string
43
+ MH_MAGICS[header[:magic]]
44
+ end
45
+
46
+ # string representation of the header's filetype field
47
+ def filetype
48
+ MH_FILETYPES[header[:filetype]]
49
+ end
50
+
51
+ # string representation of the header's cputype field
52
+ def cputype
53
+ CPU_TYPES[header[:cputype]]
54
+ end
55
+
56
+ # string representation of the header's cpusubtype field
57
+ def cpusubtype
58
+ CPU_SUBTYPES[header[:cpusubtype]]
59
+ end
60
+
61
+ # number of load commands in the header
62
+ def ncmds
63
+ header[:ncmds]
64
+ end
65
+
66
+ # size of all load commands
67
+ def sizeofcmds
68
+ header[:sizeofcmds]
69
+ end
70
+
71
+ # various execution flags
72
+ def flags
73
+ header[:flags]
74
+ end
75
+
76
+ # get load commands by name
77
+ def command(name)
78
+ load_commands.select { |lc| lc.to_s == name }
79
+ end
80
+
81
+ alias :[] :command
82
+
83
+ # get all segment commands
84
+ def segments
85
+ if magic32?
86
+ command("LC_SEGMENT")
87
+ else
88
+ command("LC_SEGMENT_64")
89
+ end
90
+ end
91
+
92
+ # get the file's dylib id, if it is a dylib
93
+ def dylib_id
94
+ if !dylib?
95
+ return nil
96
+ end
97
+
98
+ dylib_id_cmd = command('LC_ID_DYLIB').first
99
+
100
+ cmdsize = dylib_id_cmd.cmdsize
101
+ offset = dylib_id_cmd.offset
102
+ stroffset = dylib_id_cmd.name
103
+
104
+ dylib_id = @raw_data.slice(offset + stroffset...offset + cmdsize).unpack("Z*").first
105
+
106
+ dylib_id
107
+ end
108
+
109
+ def dylib_id=(new_id)
110
+ if !new_id.is_a?(String)
111
+ raise ArgumentError.new("argument must be a String")
112
+ end
113
+
114
+ if !dylib?
115
+ return nil
116
+ end
117
+
118
+ if magic32?
119
+ cmd_round = 4
120
+ else
121
+ cmd_round = 8
122
+ end
123
+
124
+ new_sizeofcmds = header[:sizeofcmds]
125
+ dylib_id_cmd = command('LC_ID_DYLIB').first
126
+ old_id = dylib_id
127
+
128
+ new_pad = Utils.round(new_id.size, cmd_round) - new_id.size
129
+ old_pad = Utils.round(old_id.size, cmd_round) - old_id.size
130
+
131
+ # pad the old and new IDs with null bytes to meet command bounds
132
+ old_id << "\x00" * old_pad
133
+ new_id << "\x00" * new_pad
134
+
135
+ # calculate the new size of the DylibCommand and sizeofcmds in MH
136
+ new_size = DylibCommand.bytesize + new_id.size
137
+ new_sizeofcmds += new_size - dylib_id_cmd.cmdsize
138
+
139
+ # calculate the low file offset (offset to first section data)
140
+ low_fileoff = 2**64 # ULLONGMAX
141
+
142
+ segments.each do |seg|
143
+ sections(seg).each do |sect|
144
+ if sect.size != 0 && !sect.flag?(S_ZEROFILL) &&
145
+ !sect.flag?(S_THREAD_LOCAL_ZEROFILL) &&
146
+ sect.offset < low_fileoff
147
+
148
+ low_fileoff = sect.offset
149
+ end
150
+ end
151
+ end
152
+
153
+ if new_sizeofcmds + header.bytesize > low_fileoff
154
+ raise HeaderPadError.new(@filename)
155
+ end
156
+
157
+ # update sizeofcmds in mach_header
158
+ set_sizeofcmds(new_sizeofcmds)
159
+
160
+ # update cmdsize in the dylib_command
161
+ @raw_data[dylib_id_cmd.offset + 4, 4] = [new_size].pack("V")
162
+
163
+ # delete the old id
164
+ @raw_data.slice!(dylib_id_cmd.offset + dylib_id_cmd.name...dylib_id_cmd.offset + dylib_id_cmd.cmdsize)
165
+
166
+ # insert the new id
167
+ @raw_data.insert(dylib_id_cmd.offset + dylib_id_cmd.name, new_id)
168
+
169
+ # pad/unpad after new_sizeofcmds until offsets are corrected
170
+ null_pad = old_id.size - new_id.size
171
+
172
+ if null_pad < 0
173
+ @raw_data.slice!(new_sizeofcmds + header.bytesize, null_pad.abs)
174
+ else
175
+ @raw_data.insert(new_sizeofcmds + header.bytesize, "\x00" * null_pad)
176
+ end
177
+
178
+ # synchronize fields with the raw data
179
+ header = get_mach_header
180
+ load_commands = get_load_commands
181
+ end
182
+
183
+ # get a list of dylib paths linked to this file
184
+ def linked_dylibs
185
+ dylibs = []
186
+ dylib_cmds = command('LC_LOAD_DYLIB')
187
+
188
+ dylib_cmds.each do |dylib_cmd|
189
+ cmdsize = dylib_cmd.cmdsize
190
+ offset = dylib_cmd.offset
191
+ stroffset = dylib_cmd.name
192
+
193
+ dylib = @raw_data.slice(offset + stroffset...offset + cmdsize).unpack("Z*").first
194
+
195
+ dylibs << dylib
196
+ end
197
+
198
+ dylibs
199
+ end
200
+
201
+ # get all sections in a segment by name
202
+ def sections(segment)
203
+ sections = []
204
+
205
+ if !segment.is_a?(SegmentCommand) && !segment.is_a?(SegmentCommand64)
206
+ raise ArgumentError.new("not a valid segment")
207
+ end
208
+
209
+ if segment.nsects.zero?
210
+ return sections
211
+ end
212
+
213
+ offset = segment.offset + segment.class.bytesize
214
+
215
+ segment.nsects.times do
216
+ if segment.is_a? SegmentCommand
217
+ sections << Section.new_from_bin(@raw_data.slice(offset, Section.bytesize))
218
+ offset += Section.bytesize
219
+ else
220
+ sections << Section64.new_from_bin(@raw_data.slice(offset, Section64.bytesize))
221
+ offset += Section64.bytesize
222
+ end
223
+ end
224
+
225
+ sections
226
+ end
227
+
228
+ def write(filename)
229
+ File.open(filename, "wb") { |f| f.write(@raw_data) }
230
+ end
231
+
232
+ def write!
233
+ File.open(@filename, "wb") { |f| f.write(@raw_data) }
234
+ end
235
+
236
+ #######
237
+ private
238
+
239
+ def get_mach_header
240
+ magic = get_magic
241
+ cputype = get_cputype
242
+ cpusubtype = get_cpusubtype
243
+ filetype = get_filetype
244
+ ncmds = get_ncmds
245
+ sizeofcmds = get_sizeofcmds
246
+ flags = get_flags
247
+
248
+ if Utils.magic32?(magic)
249
+ header = MachHeader.new(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags)
250
+ else
251
+ # the reserved field is reserved, so just fill it with 0
252
+ header = MachHeader64.new(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags, 0)
253
+ end
254
+ end
255
+
256
+ def get_magic
257
+ magic = @raw_data[0..3].unpack("N").first
258
+
259
+ if !Utils.magic?(magic)
260
+ raise MagicError.new(magic)
261
+ end
262
+
263
+ # TODO: support fat (universal) binaries
264
+ if Utils.fat_magic?(magic)
265
+ raise FatBinaryError.new(magic)
266
+ end
267
+
268
+ magic
269
+ end
270
+
271
+ def get_cputype
272
+ cputype = @raw_data[4..7].unpack("V").first
273
+
274
+ if !CPU_TYPES.keys.include?(cputype)
275
+ raise CPUTypeError.new(cputype)
276
+ end
277
+
278
+ cputype
279
+ end
280
+
281
+ def get_cpusubtype
282
+ cpusubtype = @raw_data[8..11].unpack("V").first
283
+
284
+ # this mask isn't documented!
285
+ cpusubtype &= ~CPU_SUBTYPE_LIB64
286
+
287
+ if !CPU_SUBTYPES.keys.include?(cpusubtype)
288
+ raise CPUSubtypeError.new(cpusubtype)
289
+ end
290
+
291
+ cpusubtype
292
+ end
293
+
294
+ def get_filetype
295
+ filetype = @raw_data[12..15].unpack("V").first
296
+
297
+ if !MH_FILETYPES.keys.include?(filetype)
298
+ raise FiletypeError.new(filetype)
299
+ end
300
+
301
+ filetype
302
+ end
303
+
304
+ def get_ncmds
305
+ ncmds = @raw_data[16..19].unpack("V").first
306
+
307
+ ncmds
308
+ end
309
+
310
+ def get_sizeofcmds
311
+ sizeofcmds = @raw_data[20..23].unpack("V").first
312
+
313
+ sizeofcmds
314
+ end
315
+
316
+ # TODO: parse flags, maybe?
317
+ def get_flags
318
+ flags = @raw_data[24..27].unpack("V").first
319
+
320
+ flags
321
+ end
322
+
323
+ def get_load_commands
324
+ offset = header.bytesize
325
+ load_commands = []
326
+
327
+ header[:ncmds].times do
328
+ cmd = @raw_data.slice(offset, 4).unpack("V").first
329
+
330
+ if !LC_STRUCTURES.has_key?(cmd)
331
+ raise LoadCommandError.new(cmd)
332
+ end
333
+
334
+ # why do I do this? i don't like declaring constants below
335
+ # classes, and i need them to resolve...
336
+ klass = Object.const_get "MachO::#{LC_STRUCTURES[cmd]}"
337
+ command = klass.new_from_bin(offset, @raw_data.slice(offset, klass.bytesize))
338
+
339
+ load_commands << command
340
+ offset += command.cmdsize
341
+ end
342
+
343
+ load_commands
344
+ end
345
+
346
+ def set_sizeofcmds(size)
347
+ new_size = [size].pack("V")
348
+ @raw_data[20..23] = new_size
349
+ end
350
+ end
351
+ end