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 +4 -4
- data/.yardopts +1 -0
- data/LICENSE +21 -0
- data/README.md +74 -0
- data/lib/macho.rb +1 -2
- data/lib/macho/fat_file.rb +7 -10
- data/lib/macho/headers.rb +74 -26
- data/lib/macho/macho_file.rb +32 -67
- data/lib/macho/tools.rb +42 -0
- metadata +6 -3
- data/lib/cstruct.rb +0 -318
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 83843cf163d14c552c318a7d2d4787cee484ad88
|
4
|
+
data.tar.gz: 73313735ad3e7647fdc5aab78e17e1065e8b1d44
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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"
|
data/lib/macho/fat_file.rb
CHANGED
@@ -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
|
-
|
127
|
-
|
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
|
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
|
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
|
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 <
|
238
|
-
|
239
|
-
|
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 <
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
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 <
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
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 <
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
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)
|
data/lib/macho/macho_file.rb
CHANGED
@@ -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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
108
|
+
header.sizeofcmds
|
109
109
|
end
|
110
110
|
|
111
111
|
# @return [Fixnum] execution flags set by the linker
|
112
112
|
def flags
|
113
|
-
header
|
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
|
-
|
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
|
-
|
197
|
-
raise DylibUnknownError.new(old_name) if
|
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
|
-
|
291
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
348
|
+
header.ncmds.times do
|
382
349
|
cmd = @raw_data.slice(offset, 4).unpack("V").first
|
383
350
|
|
384
|
-
|
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
|
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
|
data/lib/macho/tools.rb
ADDED
@@ -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.
|
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-
|
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
|
-
-
|
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
|