ruby-macho 0.2.2 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +2 -2
- data/README.md +4 -9
- data/lib/macho.rb +2 -2
- data/lib/macho/exceptions.rb +104 -73
- data/lib/macho/fat_file.rb +273 -228
- data/lib/macho/headers.rb +342 -265
- data/lib/macho/load_commands.rb +1009 -999
- data/lib/macho/macho_file.rb +509 -529
- data/lib/macho/open.rb +23 -14
- data/lib/macho/sections.rb +157 -157
- data/lib/macho/structure.rb +33 -17
- data/lib/macho/tools.rb +55 -55
- data/lib/macho/utils.rb +42 -30
- metadata +3 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3c89073083ac031f2efa7e6a6f4d8f17185078c7
|
4
|
+
data.tar.gz: b49b996f5340a94e2baf58f10f05d21a251f6950
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 76490397fbbd8f1700dd460c973fa6cf4a6e55af84d9fca4dc27dbbb816e980f5641e6c6bb3db1eaa9530fd69f6e951674bbb88a3cbb85b3e386ef34f0f48a9f
|
7
|
+
data.tar.gz: 8beba2a551711b917b6a40744317ace5b8e92891b570f98e63f3948db1836ed689642e4adffe495076a0dcaee18ed539002b39f60011edbd406073a6a6bd4f05
|
data/LICENSE
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
The MIT License (MIT)
|
2
2
|
|
3
|
-
Copyright (c) 2015 William Woodruff <william @ tuffbizz.com>
|
3
|
+
Copyright (c) 2015, 2016 William Woodruff <william @ tuffbizz.com>
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
-
THE SOFTWARE.
|
21
|
+
THE SOFTWARE.
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@ ruby-macho
|
|
2
2
|
================
|
3
3
|
|
4
4
|
[![Gem Version](https://badge.fury.io/rb/ruby-macho.svg)](http://badge.fury.io/rb/ruby-macho)
|
5
|
-
[![Build Status](https://
|
5
|
+
[![Build Status](https://travis-ci.org/Homebrew/ruby-macho.svg?branch=master)](https://travis-ci.org/Homebrew/ruby-macho)
|
6
6
|
|
7
7
|
A Ruby library for examining and modifying Mach-O files.
|
8
8
|
|
@@ -21,14 +21,14 @@ A quick example of what ruby-macho can do:
|
|
21
21
|
```ruby
|
22
22
|
require 'macho'
|
23
23
|
|
24
|
-
file = MachO::MachOFile.
|
24
|
+
file = MachO::MachOFile.new("/path/to/my/binary")
|
25
25
|
|
26
26
|
# get the file's type (MH_OBJECT, MH_DYLIB, MH_EXECUTE, etc)
|
27
27
|
file.filetype # => "MH_EXECUTE"
|
28
28
|
|
29
29
|
# get all load commands in the file and print their offsets:
|
30
30
|
file.load_commands.each do |lc|
|
31
|
-
|
31
|
+
puts "#{lc}: offset #{lc.offset}, size: #{lc.cmdsize}"
|
32
32
|
end
|
33
33
|
|
34
34
|
# access a specific load command
|
@@ -38,17 +38,12 @@ puts lc_vers.version_string # => "10.10.0"
|
|
38
38
|
|
39
39
|
### What works?
|
40
40
|
|
41
|
-
* Reading data from x86/x86_64 Mach-O files
|
41
|
+
* Reading data from x86/x86_64/PPC Mach-O files
|
42
42
|
* Changing the IDs of Mach-O and Fat dylibs
|
43
43
|
* Changing install names in Mach-O and Fat files
|
44
44
|
|
45
|
-
### What might work?
|
46
|
-
|
47
|
-
* Reading *some* data from PPC Mach-O files.
|
48
|
-
|
49
45
|
### What doesn't work yet?
|
50
46
|
|
51
|
-
* Reading data from any other architecure's Mach-O files (probably).
|
52
47
|
* Adding, deleting, or modifying rpaths.
|
53
48
|
|
54
49
|
### What needs to be done?
|
data/lib/macho.rb
CHANGED
data/lib/macho/exceptions.rb
CHANGED
@@ -1,85 +1,116 @@
|
|
1
1
|
module MachO
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
# A generic Mach-O error in execution.
|
3
|
+
class MachOError < RuntimeError
|
4
|
+
end
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
6
|
+
# Raised when a file is not a Mach-O.
|
7
|
+
class NotAMachOError < MachOError
|
8
|
+
# @param error [String] the error in question
|
9
|
+
def initialize(error)
|
10
|
+
super error
|
11
|
+
end
|
12
|
+
end
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
14
|
+
# Raised when a file is too short to be a valid Mach-O file.
|
15
|
+
class TruncatedFileError < NotAMachOError
|
16
|
+
def initialize
|
17
|
+
super "File is too short to be a valid Mach-O"
|
18
|
+
end
|
19
|
+
end
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
21
|
+
# Raised when a file's magic bytes are not valid Mach-O magic.
|
22
|
+
class MagicError < NotAMachOError
|
23
|
+
# @param num [Fixnum] the unknown number
|
24
|
+
def initialize(num)
|
25
|
+
super "Unrecognized Mach-O magic: 0x#{"%02x" % num}"
|
26
|
+
end
|
27
|
+
end
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
29
|
+
# Raised when a file is a Java classfile instead of a fat Mach-O.
|
30
|
+
class JavaClassFileError < NotAMachOError
|
31
|
+
def initialize
|
32
|
+
super "File is a Java class file"
|
33
|
+
end
|
34
|
+
end
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
end
|
36
|
+
# Raised when a fat binary is loaded with MachOFile.
|
37
|
+
class FatBinaryError < MachOError
|
38
|
+
def initialize
|
39
|
+
super "Fat binaries must be loaded with MachO::FatFile"
|
40
|
+
end
|
41
|
+
end
|
43
42
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
43
|
+
# Raised when a Mach-O is loaded with FatFile.
|
44
|
+
class MachOBinaryError < MachOError
|
45
|
+
def initialize
|
46
|
+
super "Normal binaries must be loaded with MachO::MachOFile"
|
47
|
+
end
|
48
|
+
end
|
51
49
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
50
|
+
# Raised when the CPU type is unknown.
|
51
|
+
class CPUTypeError < MachOError
|
52
|
+
# @param cputype [Fixnum] the unknown CPU type
|
53
|
+
def initialize(cputype)
|
54
|
+
super "Unrecognized CPU type: 0x#{"%08x" % cputype}"
|
55
|
+
end
|
56
|
+
end
|
59
57
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end
|
58
|
+
# Raised when the CPU type/sub-type pair is unknown.
|
59
|
+
class CPUSubtypeError < MachOError
|
60
|
+
# @param cputype [Fixnum] the CPU type of the unknown pair
|
61
|
+
# @param cpusubtype [Fixnum] the CPU sub-type of the unknown pair
|
62
|
+
def initialize(cputype, cpusubtype)
|
63
|
+
super "Unrecognized CPU sub-type: 0x#{"%08x" % cpusubtype} (for CPU type: 0x#{"%08x" % cputype})"
|
64
|
+
end
|
65
|
+
end
|
69
66
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
67
|
+
# Raised when a mach-o file's filetype field is unknown.
|
68
|
+
class FiletypeError < MachOError
|
69
|
+
# @param num [Fixnum] the unknown number
|
70
|
+
def initialize(num)
|
71
|
+
super "Unrecognized Mach-O filetype code: 0x#{"%02x" % num}"
|
72
|
+
end
|
73
|
+
end
|
77
74
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
75
|
+
# Raised when an unknown load command is encountered.
|
76
|
+
class LoadCommandError < MachOError
|
77
|
+
# @param num [Fixnum] the unknown number
|
78
|
+
def initialize(num)
|
79
|
+
super "Unrecognized Mach-O load command: 0x#{"%02x" % num}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Raised when load commands are too large to fit in the current file.
|
84
|
+
class HeaderPadError < MachOError
|
85
|
+
# @param filename [String] the filename
|
86
|
+
def initialize(filename)
|
87
|
+
super "Updated load commands do not fit in the header of " +
|
88
|
+
"#{filename}. #{filename} needs to be relinked, possibly with " +
|
89
|
+
"-headerpad or -headerpad_max_install_names"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Raised when attempting to change a dylib name that doesn't exist.
|
94
|
+
class DylibUnknownError < MachOError
|
95
|
+
# @param dylib [String] the unknown shared library name
|
96
|
+
def initialize(dylib)
|
97
|
+
super "No such dylib name: #{dylib}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Raised when attempting to change an rpath that doesn't exist.
|
102
|
+
class RpathUnknownError < MachOError
|
103
|
+
# @param path [String] the unknown runtime path
|
104
|
+
def initialize(path)
|
105
|
+
super "No such runtime path: #{path}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Raised whenever unfinished code is called.
|
110
|
+
class UnimplementedError < MachOError
|
111
|
+
# @param thing [String] the thing that is unimplemented
|
112
|
+
def initialize(thing)
|
113
|
+
super "Unimplemented: #{thing}"
|
114
|
+
end
|
115
|
+
end
|
85
116
|
end
|
data/lib/macho/fat_file.rb
CHANGED
@@ -1,230 +1,275 @@
|
|
1
1
|
module MachO
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
2
|
+
# Represents a "Fat" file, which contains a header, a listing of available
|
3
|
+
# architectures, and one or more Mach-O binaries.
|
4
|
+
# @see https://en.wikipedia.org/wiki/Mach-O#Multi-architecture_binaries
|
5
|
+
# @see MachO::MachOFile
|
6
|
+
class FatFile
|
7
|
+
# @return [String] the filename loaded from, or nil if loaded from a binary string
|
8
|
+
attr_accessor :filename
|
9
|
+
|
10
|
+
# @return [MachO::FatHeader] the file's header
|
11
|
+
attr_reader :header
|
12
|
+
|
13
|
+
# @return [Array<MachO::FatArch>] an array of fat architectures
|
14
|
+
attr_reader :fat_archs
|
15
|
+
|
16
|
+
# @return [Array<MachO::MachOFile>] an array of Mach-O binaries
|
17
|
+
attr_reader :machos
|
18
|
+
|
19
|
+
# Creates a new FatFile instance from a binary string.
|
20
|
+
# @param bin [String] a binary string containing raw Mach-O data
|
21
|
+
# @return [MachO::FatFile] a new FatFile
|
22
|
+
def self.new_from_bin(bin)
|
23
|
+
instance = allocate
|
24
|
+
instance.initialize_from_bin(bin)
|
25
|
+
|
26
|
+
instance
|
27
|
+
end
|
28
|
+
|
29
|
+
# Creates a new FatFile from the given filename.
|
30
|
+
# @param filename [String] the fat file to load from
|
31
|
+
# @raise [ArgumentError] if the given file does not exist
|
32
|
+
def initialize(filename)
|
33
|
+
raise ArgumentError.new("#{filename}: no such file") unless File.file?(filename)
|
34
|
+
|
35
|
+
@filename = filename
|
36
|
+
@raw_data = File.open(@filename, "rb") { |f| f.read }
|
37
|
+
@header = get_fat_header
|
38
|
+
@fat_archs = get_fat_archs
|
39
|
+
@machos = get_machos
|
40
|
+
end
|
41
|
+
|
42
|
+
# @api private
|
43
|
+
def initialize_from_bin(bin)
|
44
|
+
@filename = nil
|
45
|
+
@raw_data = bin
|
46
|
+
@header = get_fat_header
|
47
|
+
@fat_archs = get_fat_archs
|
48
|
+
@machos = get_machos
|
49
|
+
end
|
50
|
+
|
51
|
+
# The file's raw fat data.
|
52
|
+
# @return [String] the raw fat data
|
53
|
+
def serialize
|
54
|
+
@raw_data
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [Boolean] true if the file is of type `MH_OBJECT`, false otherwise
|
58
|
+
def object?
|
59
|
+
machos.first.object?
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [Boolean] true if the file is of type `MH_EXECUTE`, false otherwise
|
63
|
+
def executable?
|
64
|
+
machos.first.executable?
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [Boolean] true if the file is of type `MH_FVMLIB`, false otherwise
|
68
|
+
def fvmlib?
|
69
|
+
machos.first.fvmlib?
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return [Boolean] true if the file is of type `MH_CORE`, false otherwise
|
73
|
+
def core?
|
74
|
+
machos.first.core?
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return [Boolean] true if the file is of type `MH_PRELOAD`, false otherwise
|
78
|
+
def preload?
|
79
|
+
machos.first.preload?
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [Boolean] true if the file is of type `MH_DYLIB`, false otherwise
|
83
|
+
def dylib?
|
84
|
+
machos.first.dylib?
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [Boolean] true if the file is of type `MH_DYLINKER`, false otherwise
|
88
|
+
def dylinker?
|
89
|
+
machos.first.dylinker?
|
90
|
+
end
|
91
|
+
|
92
|
+
# @return [Boolean] true if the file is of type `MH_BUNDLE`, false otherwise
|
93
|
+
def bundle?
|
94
|
+
machos.first.bundle?
|
95
|
+
end
|
96
|
+
|
97
|
+
# @return [Boolean] true if the file is of type `MH_DSYM`, false otherwise
|
98
|
+
def dsym?
|
99
|
+
machos.first.dsym?
|
100
|
+
end
|
101
|
+
|
102
|
+
# @return [Boolean] true if the file is of type `MH_KEXT_BUNDLE`, false otherwise
|
103
|
+
def kext?
|
104
|
+
machos.first.kext?
|
105
|
+
end
|
106
|
+
|
107
|
+
# @return [Fixnum] the file's magic number
|
108
|
+
def magic
|
109
|
+
header.magic
|
110
|
+
end
|
111
|
+
|
112
|
+
# @return [String] a string representation of the file's magic number
|
113
|
+
def magic_string
|
114
|
+
MH_MAGICS[magic]
|
115
|
+
end
|
116
|
+
|
117
|
+
# The file's type. Assumed to be the same for every Mach-O within.
|
118
|
+
# @return [String] the filetype
|
119
|
+
def filetype
|
120
|
+
machos.first.filetype
|
121
|
+
end
|
122
|
+
|
123
|
+
# The file's dylib ID. If the file is not a dylib, returns `nil`.
|
124
|
+
# @example
|
125
|
+
# file.dylib_id # => 'libBar.dylib'
|
126
|
+
# @return [String, nil] the file's dylib ID
|
127
|
+
def dylib_id
|
128
|
+
machos.first.dylib_id
|
129
|
+
end
|
130
|
+
|
131
|
+
# Changes the file's dylib ID to `new_id`. If the file is not a dylib, does nothing.
|
132
|
+
# @example
|
133
|
+
# file.dylib_id = 'libFoo.dylib'
|
134
|
+
# @param new_id [String] the new dylib ID
|
135
|
+
# @return [void]
|
136
|
+
# @raise [ArgumentError] if `new_id` is not a String
|
137
|
+
def dylib_id=(new_id)
|
138
|
+
if !new_id.is_a?(String)
|
139
|
+
raise ArgumentError.new("argument must be a String")
|
140
|
+
end
|
141
|
+
|
142
|
+
if !machos.all?(&:dylib?)
|
143
|
+
return nil
|
144
|
+
end
|
145
|
+
|
146
|
+
machos.each do |macho|
|
147
|
+
macho.dylib_id = new_id
|
148
|
+
end
|
149
|
+
|
150
|
+
synchronize_raw_data
|
151
|
+
end
|
152
|
+
|
153
|
+
# All shared libraries linked to the file's Mach-Os.
|
154
|
+
# @return [Array<String>] an array of all shared libraries
|
155
|
+
def linked_dylibs
|
156
|
+
# Individual architectures in a fat binary can link to different subsets
|
157
|
+
# of libraries, but at this point we want to have the full picture, i.e.
|
158
|
+
# the union of all libraries used by all architectures.
|
159
|
+
machos.map(&:linked_dylibs).flatten.uniq
|
160
|
+
end
|
161
|
+
|
162
|
+
# Changes all dependent shared library install names from `old_name` to `new_name`.
|
163
|
+
# In a fat file, this changes install names in all internal Mach-Os.
|
164
|
+
# @example
|
165
|
+
# file.change_install_name('/usr/lib/libFoo.dylib', '/usr/lib/libBar.dylib')
|
166
|
+
# @param old_name [String] the shared library name being changed
|
167
|
+
# @param new_name [String] the new name
|
168
|
+
# @todo incomplete
|
169
|
+
def change_install_name(old_name, new_name)
|
170
|
+
machos.each do |macho|
|
171
|
+
macho.change_install_name(old_name, new_name)
|
172
|
+
end
|
173
|
+
|
174
|
+
synchronize_raw_data
|
175
|
+
end
|
176
|
+
|
177
|
+
alias :change_dylib :change_install_name
|
178
|
+
|
179
|
+
# Extract a Mach-O with the given CPU type from the file.
|
180
|
+
# @example
|
181
|
+
# file.extract(:i386) # => MachO::MachOFile
|
182
|
+
# @param cputype [Symbol] the CPU type of the Mach-O being extracted
|
183
|
+
# @return [MachO::MachOFile, nil] the extracted Mach-O or nil if no Mach-O has the given CPU type
|
184
|
+
def extract(cputype)
|
185
|
+
machos.select { |macho| macho.cputype == cputype }.first
|
186
|
+
end
|
187
|
+
|
188
|
+
# Write all (fat) data to the given filename.
|
189
|
+
# @param filename [String] the file to write to
|
190
|
+
def write(filename)
|
191
|
+
File.open(filename, "wb") { |f| f.write(@raw_data) }
|
192
|
+
end
|
193
|
+
|
194
|
+
# Write all (fat) data to the file used to initialize the instance.
|
195
|
+
# @return [void]
|
196
|
+
# @raise [MachO::MachOError] if the instance was initialized without a file
|
197
|
+
# @note Overwrites all data in the file!
|
198
|
+
def write!
|
199
|
+
if filename.nil?
|
200
|
+
raise MachOError.new("cannot write to a default file when initialized from a binary string")
|
201
|
+
else
|
202
|
+
File.open(@filename, "wb") { |f| f.write(@raw_data) }
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
private
|
207
|
+
|
208
|
+
# Obtain the fat header from raw file data.
|
209
|
+
# @return [MachO::FatHeader] the fat header
|
210
|
+
# @raise [MachO::TruncatedFileError] if the file is too small to have a valid header
|
211
|
+
# @raise [MachO::MagicError] if the magic is not valid Mach-O magic
|
212
|
+
# @raise [MachO::MachOBinaryError] if the magic is for a non-fat Mach-O file
|
213
|
+
# @raise [MachO::JavaClassFileError] if the file is a Java classfile
|
214
|
+
# @private
|
215
|
+
def get_fat_header
|
216
|
+
# the smallest fat Mach-O header is 8 bytes
|
217
|
+
raise TruncatedFileError.new if @raw_data.size < 8
|
218
|
+
|
219
|
+
fh = FatHeader.new_from_bin(:big, @raw_data[0, FatHeader.bytesize])
|
220
|
+
|
221
|
+
raise MagicError.new(fh.magic) unless MachO.magic?(fh.magic)
|
222
|
+
raise MachOBinaryError.new unless MachO.fat_magic?(fh.magic)
|
223
|
+
|
224
|
+
# Rationale: Java classfiles have the same magic as big-endian fat
|
225
|
+
# Mach-Os. Classfiles encode their version at the same offset as
|
226
|
+
# `nfat_arch` and the lowest version number is 43, so we error out
|
227
|
+
# if a file claims to have over 30 internal architectures. It's
|
228
|
+
# technically possible for a fat Mach-O to have over 30 architectures,
|
229
|
+
# but this is extremely unlikely and in practice distinguishes the two
|
230
|
+
# formats.
|
231
|
+
raise JavaClassFileError.new if fh.nfat_arch > 30
|
232
|
+
|
233
|
+
fh
|
234
|
+
end
|
235
|
+
|
236
|
+
# Obtain an array of fat architectures from raw file data.
|
237
|
+
# @return [Array<MachO::FatArch>] an array of fat architectures
|
238
|
+
# @private
|
239
|
+
def get_fat_archs
|
240
|
+
archs = []
|
241
|
+
|
242
|
+
fa_off = FatHeader.bytesize
|
243
|
+
fa_len = FatArch.bytesize
|
244
|
+
header.nfat_arch.times do |i|
|
245
|
+
archs << FatArch.new_from_bin(:big, @raw_data[fa_off + (fa_len * i), fa_len])
|
246
|
+
end
|
247
|
+
|
248
|
+
archs
|
249
|
+
end
|
250
|
+
|
251
|
+
# Obtain an array of Mach-O blobs from raw file data.
|
252
|
+
# @return [Array<MachO::MachOFile>] an array of Mach-Os
|
253
|
+
# @private
|
254
|
+
def get_machos
|
255
|
+
machos = []
|
256
|
+
|
257
|
+
fat_archs.each do |arch|
|
258
|
+
machos << MachOFile.new_from_bin(@raw_data[arch.offset, arch.size])
|
259
|
+
end
|
260
|
+
|
261
|
+
machos
|
262
|
+
end
|
263
|
+
|
264
|
+
# @todo this needs to be redesigned. arch[:offset] and arch[:size] are
|
265
|
+
# already out-of-date, and the header needs to be synchronized as well.
|
266
|
+
# @private
|
267
|
+
def synchronize_raw_data
|
268
|
+
machos.each_with_index do |macho, i|
|
269
|
+
arch = fat_archs[i]
|
270
|
+
|
271
|
+
@raw_data[arch.offset, arch.size] = macho.serialize
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
230
275
|
end
|