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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f149c2c7085b336a2447513e28f10d59afc88062
4
- data.tar.gz: 1f938c11b54535cc8b92a625235bff9a34572ceb
3
+ metadata.gz: 3c89073083ac031f2efa7e6a6f4d8f17185078c7
4
+ data.tar.gz: b49b996f5340a94e2baf58f10f05d21a251f6950
5
5
  SHA512:
6
- metadata.gz: 09cd628c568572c632897f1799e784a2668b41ef1c476a84db7c8a438b96832cd54f85050fd4aef593def43b82c90af4cdacdf5a607eb56b4c607390ffdcaddf
7
- data.tar.gz: f58bc28a331f6d9ec3d7c98e98d3d1d630788a31ec0fa7d82eef0bc43d254126726f8198581eb11c14f626078c92475fcf6de4742f5170008aa0553db9ffffc6
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://drone.io/github.com/woodruffw/ruby-macho/status.png)](https://drone.io/github.com/woodruffw/ruby-macho/latest)
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.open("/path/to/my/binary")
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
- puts "#{lc}: offset #{lc.offset}, size: #{lc.cmdsize}"
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?
@@ -11,6 +11,6 @@ require "#{File.dirname(__FILE__)}/macho/tools"
11
11
 
12
12
  # The primary namespace for ruby-macho.
13
13
  module MachO
14
- # release version
15
- VERSION = "0.2.2".freeze
14
+ # release version
15
+ VERSION = "0.2.3".freeze
16
16
  end
@@ -1,85 +1,116 @@
1
1
  module MachO
2
- # A generic Mach-O error in execution.
3
- class MachOError < RuntimeError
4
- end
2
+ # A generic Mach-O error in execution.
3
+ class MachOError < RuntimeError
4
+ end
5
5
 
6
- # Raised when a file's magic bytes are not valid Mach-O magic.
7
- class MagicError < MachOError
8
- # @param num [Fixnum] the unknown number
9
- def initialize(num)
10
- super "Unrecognized Mach-O magic: 0x#{"%02x" % num}"
11
- end
12
- end
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
- # Raised when a fat binary is loaded with MachOFile.
15
- class FatBinaryError < MachOError
16
- def initialize
17
- super "Fat binaries must be loaded with MachO::FatFile"
18
- end
19
- end
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
- # Raised when a Mach-O is loaded with FatFile.
22
- class MachOBinaryError < MachOError
23
- def initialize
24
- super "Normal binaries must be loaded with MachO::MachOFile"
25
- end
26
- end
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
- # Raised when the CPU type is unknown.
29
- class CPUTypeError < MachOError
30
- # @param num [Fixnum] the unknown number
31
- def initialize(num)
32
- super "Unrecognized CPU type: 0x#{"%02x" % num}"
33
- end
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
- # Raised when the CPU subtype is unknown.
37
- class CPUSubtypeError < MachOError
38
- # @param num [Fixnum] the unknown number
39
- def initialize(num)
40
- super "Unrecognized CPU sub-type: 0x#{"%02x" % num}"
41
- end
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
- # Raised when a mach-o file's filetype field is unknown.
45
- class FiletypeError < MachOError
46
- # @param num [Fixnum] the unknown number
47
- def initialize(num)
48
- super "Unrecognized Mach-O filetype code: 0x#{"%02x" % num}"
49
- end
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
- # Raised when an unknown load command is encountered.
53
- class LoadCommandError < MachOError
54
- # @param num [Fixnum] the unknown number
55
- def initialize(num)
56
- super "Unrecognized Mach-O load command: 0x#{"%02x" % num}"
57
- end
58
- end
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
- # Raised when load commands are too large to fit in the current file.
61
- class HeaderPadError < MachOError
62
- # @param filename [String] the filename
63
- def initialize(filename)
64
- super "Updated load commands do not fit in the header of " +
65
- "#{filename}. #{filename} needs to be relinked, possibly with " +
66
- "-headerpad or -headerpad_max_install_names"
67
- end
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
- # Raised when attempting to change a dylib name that doesn't exist.
71
- class DylibUnknownError < MachOError
72
- # @param dylib [String] the unknown shared library name
73
- def initialize(dylib)
74
- super "No such dylib name: #{dylib}"
75
- end
76
- end
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
- # Raised when attempting to change an rpath that doesn't exist.
79
- class RpathUnknownError < MachOError
80
- # @param path [String] the unknown runtime path
81
- def initialize(path)
82
- super "No such runtime path: #{path}"
83
- end
84
- end
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
@@ -1,230 +1,275 @@
1
1
  module MachO
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 [MachO::FatHeader] the file's header
8
- attr_reader :header
9
-
10
- # @return [Array<MachO::FatArch>] an array of fat architectures
11
- attr_reader :fat_archs
12
-
13
- # @return [Array<MachO::MachOFile>] an array of Mach-O binaries
14
- attr_reader :machos
15
-
16
- # Creates a new FatFile from the given filename.
17
- # @param filename [String] the fat file to load from
18
- # @raise [ArgumentError] if the given filename does not exist
19
- def initialize(filename)
20
- raise ArgumentError.new("#{filetype}: no such file") unless File.exist?(filename)
21
-
22
- @filename = filename
23
- @raw_data = open(@filename, "rb") { |f| f.read }
24
- @header = get_fat_header
25
- @fat_archs = get_fat_archs
26
- @machos = get_machos
27
- end
28
-
29
- # The file's raw fat data.
30
- # @return [String] the raw fat data
31
- def serialize
32
- @raw_data
33
- end
34
-
35
- # @return [Boolean] true if the file is of type `MH_OBJECT`, false otherwise
36
- def object?
37
- machos.first.object?
38
- end
39
-
40
- # @return [Boolean] true if the file is of type `MH_EXECUTE`, false otherwise
41
- def executable?
42
- machos.first.executable?
43
- end
44
-
45
- # @return [Boolean] true if the file is of type `MH_FVMLIB`, false otherwise
46
- def fvmlib?
47
- machos.first.fvmlib?
48
- end
49
-
50
- # @return [Boolean] true if the file is of type `MH_CORE`, false otherwise
51
- def core?
52
- machos.first.core?
53
- end
54
-
55
- # @return [Boolean] true if the file is of type `MH_PRELOAD`, false otherwise
56
- def preload?
57
- machos.first.preload?
58
- end
59
-
60
- # @return [Boolean] true if the file is of type `MH_DYLIB`, false otherwise
61
- def dylib?
62
- machos.first.dylib?
63
- end
64
-
65
- # @return [Boolean] true if the file is of type `MH_DYLINKER`, false otherwise
66
- def dylinker?
67
- machos.first.dylinker?
68
- end
69
-
70
- # @return [Boolean] true if the file is of type `MH_BUNDLE`, false otherwise
71
- def bundle?
72
- machos.first.bundle?
73
- end
74
-
75
- # @return [Boolean] true if the file is of type `MH_DSYM`, false otherwise
76
- def dsym?
77
- machos.first.dsym?
78
- end
79
-
80
- # @return [Boolean] true if the file is of type `MH_KEXT_BUNDLE`, false otherwise
81
- def kext?
82
- machos.first.kext?
83
- end
84
-
85
- # @return [Fixnum] the file's magic number
86
- def magic
87
- header.magic
88
- end
89
-
90
- # @return [String] a string representation of the file's magic number
91
- def magic_string
92
- MH_MAGICS[magic]
93
- end
94
-
95
- # The file's type. Assumed to be the same for every Mach-O within.
96
- # @return [String] the filetype
97
- def filetype
98
- machos.first.filetype
99
- end
100
-
101
- # The file's dylib ID. If the file is not a dylib, returns `nil`.
102
- # @example
103
- # file.dylib_id # => 'libBar.dylib'
104
- # @return [String, nil] the file's dylib ID
105
- def dylib_id
106
- machos.first.dylib_id
107
- end
108
-
109
- # Changes the file's dylib ID to `new_id`. If the file is not a dylib, does nothing.
110
- # @example
111
- # file.dylib_id = 'libFoo.dylib'
112
- # @param new_id [String] the new dylib ID
113
- # @return [void]
114
- # @raise [ArgumentError] if `new_id` is not a String
115
- def dylib_id=(new_id)
116
- if !new_id.is_a?(String)
117
- raise ArgumentError.new("argument must be a String")
118
- end
119
-
120
- if !machos.all?(&:dylib?)
121
- return nil
122
- end
123
-
124
- machos.each do |macho|
125
- macho.dylib_id = new_id
126
- end
127
-
128
- synchronize_raw_data
129
- end
130
-
131
- # All shared libraries linked to the file's Mach-Os.
132
- # @return [Array<String>] an array of all shared libraries
133
- def linked_dylibs
134
- # can machos inside fat binaries have different dylibs?
135
- machos.flat_map(&:linked_dylibs).uniq
136
- end
137
-
138
- # Changes all dependent shared library install names from `old_name` to `new_name`.
139
- # In a fat file, this changes install names in all internal Mach-Os.
140
- # @example
141
- # file.change_install_name('/usr/lib/libFoo.dylib', '/usr/lib/libBar.dylib')
142
- # @param old_name [String] the shared library name being changed
143
- # @param new_name [String] the new name
144
- # @todo incomplete
145
- def change_install_name(old_name, new_name)
146
- machos.each do |macho|
147
- macho.change_install_name(old_name, new_name)
148
- end
149
-
150
- synchronize_raw_data
151
- end
152
-
153
- alias :change_dylib :change_install_name
154
-
155
- # Extract a Mach-O with the given CPU type from the file.
156
- # @example
157
- # file.extract("CPU_TYPE_I386") # => MachO::MachOFile
158
- # @param cputype [String] the CPU type of the Mach-O being extracted
159
- # @return [MachO::MachOFile, nil] the extracted Mach-O or nil if no Mach-O has the given CPU type
160
- def extract(cputype)
161
- machos.select { |macho| macho.cputype == cputype }.first
162
- end
163
-
164
- # Write all (fat) data to the given filename.
165
- # @param filename [String] the file to write to
166
- def write(filename)
167
- File.open(filename, "wb") { |f| f.write(@raw_data) }
168
- end
169
-
170
- # Write all (fat) data to the file used to initialize the instance.
171
- # @note Overwrites all data in the file!
172
- def write!
173
- File.open(@filename, "wb") { |f| f.write(@raw_data) }
174
- end
175
-
176
- private
177
-
178
- # Obtain the fat header from raw file data.
179
- # @return [MachO::FatHeader] the fat header
180
- # @raise [MachO::MagicError] if the magic is not valid Mach-O magic
181
- # @raise [MachO::MachOBinaryError] if the magic is for a non-fat Mach-O file
182
- # @private
183
- def get_fat_header
184
- magic, nfat_arch = @raw_data[0..7].unpack("N2")
185
-
186
- raise MagicError.new(magic) unless MachO.magic?(magic)
187
- raise MachOBinaryError.new unless MachO.fat_magic?(magic)
188
-
189
- FatHeader.new(magic, nfat_arch)
190
- end
191
-
192
- # Obtain an array of fat architectures from raw file data.
193
- # @return [Array<MachO::FatArch>] an array of fat architectures
194
- # @private
195
- def get_fat_archs
196
- archs = []
197
-
198
- header.nfat_arch.times do |i|
199
- fields = @raw_data[8 + (FatArch.bytesize * i), FatArch.bytesize].unpack("N5")
200
- archs << FatArch.new(*fields)
201
- end
202
-
203
- archs
204
- end
205
-
206
- # Obtain an array of Mach-O blobs from raw file data.
207
- # @return [Array<MachO::MachOFile>] an array of Mach-Os
208
- # @private
209
- def get_machos
210
- machos = []
211
-
212
- fat_archs.each do |arch|
213
- machos << MachOFile.new_from_bin(@raw_data[arch.offset, arch.size])
214
- end
215
-
216
- machos
217
- end
218
-
219
- # @todo this needs to be redesigned. arch[:offset] and arch[:size] are
220
- # already out-of-date, and the header needs to be synchronized as well.
221
- # @private
222
- def synchronize_raw_data
223
- machos.each_with_index do |macho, i|
224
- arch = fat_archs[i]
225
-
226
- @raw_data[arch.offset, arch.size] = macho.serialize
227
- end
228
- end
229
- end
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