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 +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
|
+
[](http://badge.fury.io/rb/ruby-macho)
|
5
|
+
[](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
|