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