ruby-macho 0.0.3 → 0.0.5

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: ce4c78f7559d54d07390f535cfe67da8b747ca9d
4
- data.tar.gz: 5e1da197dae79a1d084781898e01c5576704b54e
3
+ metadata.gz: e6bf6b45bcf73b710246fe124075f4ca77c73070
4
+ data.tar.gz: 5713089fd72d423c6a25b90839e29a317072169b
5
5
  SHA512:
6
- metadata.gz: 5534f18bf42c95de58c33abb7a98521496cc1258a99049784a1030d145efaaf59755575db792eab1ad714da02a003eebee0c97c9f7c8f301df12b794fabb4c48
7
- data.tar.gz: e5f681c4c01f9105dfcb087d36d74870ea0fbef69336bfd5286ab8787d7e1c2555c049699510c38fa42c6ccb1bb876cbe177e3db7ebfec86334824721a7199e0
6
+ metadata.gz: a2aaa85277768159ee35ff7e44817afd1bad136884a2ced66dd471a39657e3827d4adbb7987fbca38c36aae25ef2ec3f8bfa5b9296d06013443f8f2e77db85fc
7
+ data.tar.gz: 20d89669d43b4fa964f878ade17d5130c58743a9bd742978e36cb485a32f4575d1d81957eb68719647340a9fc83120da611570e70da54a27ede7a4fe5adb02a2
data/lib/macho.rb CHANGED
@@ -1,13 +1,13 @@
1
- $:.unshift File.dirname(__FILE__)
2
-
3
1
  require "cstruct"
4
2
  require "macho/headers"
5
3
  require "macho/structure"
6
4
  require "macho/load_commands"
7
5
  require "macho/sections"
8
- require "macho/file"
6
+ require "macho/macho_file"
7
+ require "macho/fat_file"
9
8
  require "macho/exceptions"
10
9
  require "macho/utils"
10
+ require "macho/tools"
11
11
 
12
12
  module MachO
13
13
  # nothing to see here.
@@ -10,10 +10,15 @@ module MachO
10
10
  end
11
11
  end
12
12
 
13
- # raised when a file's magic bytes are those of a fat binary
14
13
  class FatBinaryError < MachOError
15
- def initialize(num)
16
- super "Unsupported fat binary (magic 0x#{"%02x" % num})"
14
+ def initialize
15
+ super "Fat binaries must be loaded with MachO::FatFile"
16
+ end
17
+ end
18
+
19
+ class MachOBinaryError < MachOError
20
+ def initialize
21
+ super "Normal binaries must be loaded with MachO::MachOFile"
17
22
  end
18
23
  end
19
24
 
@@ -0,0 +1,114 @@
1
+ module MachO
2
+ class FatFile
3
+ attr_reader :header, :fat_archs, :machos
4
+
5
+ def initialize(filename)
6
+ raise ArgumentError.new("filename must be a String") unless filename.is_a? String
7
+
8
+ @filename = filename
9
+ @raw_data = open(@filename, "rb") { |f| f.read }
10
+ @header = get_fat_header
11
+ @fat_archs = get_fat_archs
12
+ @machos = get_machos
13
+ end
14
+
15
+ def serialize
16
+ @raw_data
17
+ end
18
+
19
+ def dylib_id
20
+ if !machos.all?(&:dylib?)
21
+ return nil
22
+ end
23
+
24
+ ids = machos.map(&:dylib_id)
25
+
26
+ # this should never be the case, but let's be defensive
27
+ if !ids.uniq!.size == 1
28
+ return nil
29
+ end
30
+
31
+ ids.first
32
+ end
33
+
34
+ def dylib_id=(new_id)
35
+ if !new_id.is_a?(String)
36
+ raise ArgumentError.new("argument must be a String")
37
+ end
38
+
39
+ if !machos.all?(&:dylib?)
40
+ return nil
41
+ end
42
+
43
+ machos.each do |macho|
44
+ macho.dylib_id = new_id
45
+ end
46
+
47
+ synchronize_raw_data
48
+ end
49
+
50
+ def linked_dylibs
51
+ dylibs = machos.map(&:linked_dylibs)
52
+
53
+ # can machos inside fat binaries have different dylibs?
54
+ dylibs.uniq!
55
+ end
56
+
57
+ def write(filename)
58
+ File.open(filename, "wb") { |f| f.write(@raw_data) }
59
+ end
60
+
61
+ def write!
62
+ File.open(@filename, "wb") { |f| f.write(@raw_data) }
63
+ end
64
+
65
+ private
66
+
67
+ def get_fat_header
68
+ magic, nfat_arch = @raw_data[0..7].unpack("N2")
69
+
70
+ if !Utils.magic?(magic)
71
+ raise MagicError.new(magic)
72
+ end
73
+
74
+ if !Utils.fat_magic?(magic)
75
+ raise MachOBinaryError.new
76
+ end
77
+
78
+ FatHeader.new(magic, nfat_arch)
79
+ end
80
+
81
+ def get_fat_archs
82
+ archs = []
83
+
84
+ header[:nfat_arch].times do |i|
85
+ fields = @raw_data[8 + (FatArch.bytesize * i), FatArch.bytesize].unpack("N5")
86
+ archs << FatArch.new(*fields)
87
+ end
88
+
89
+ archs
90
+ end
91
+
92
+ def get_machos
93
+ machos = []
94
+
95
+ fat_archs.each do |arch|
96
+ machos << MachOFile.new_from_bin(@raw_data[arch[:offset], arch[:size]])
97
+ end
98
+
99
+ machos
100
+ end
101
+
102
+ # when we create machos within FatFile, we initialize them with slices
103
+ # from @raw_data. this means creating new arrays that don't affect
104
+ # @raw_data directly, so we need to synchronize it after changing
105
+ # anything within the machos.
106
+ def synchronize_raw_data
107
+ machos.each_with_index do |macho, i|
108
+ arch = fat_archs[i]
109
+
110
+ @raw_data[arch[:offset], arch[:size]] = macho.serialize
111
+ end
112
+ end
113
+ end
114
+ end
data/lib/macho/headers.rb CHANGED
@@ -79,7 +79,62 @@ module MachO
79
79
  MH_KEXT_BUNDLE => "MH_KEXT_BUNDLE"
80
80
  }
81
81
 
82
- # TODO: declare values for flags in MachHeader/MachHeader64
82
+ # values for flags field in MachHeader/MachHeader64
83
+ MH_NOUNDEFS = 0x1
84
+ MH_INCRLINK = 0x2
85
+ MH_DYLDLINK = 0x4
86
+ MH_BINDATLOAD = 0x8
87
+ MH_PREBOUND = 0x10
88
+ MH_SPLIT_SEGS = 0x20
89
+ MH_LAZY_INIT = 0x40
90
+ MH_TWOLEVEL = 0x80
91
+ MH_FORCE_FLAT = 0x100
92
+ MH_NOMULTIDEFS = 0x200
93
+ MH_NOPREFIXBINDING = 0x400
94
+ MH_PREBINDABLE = 0x800
95
+ MH_ALLMODSBOUND = 0x1000
96
+ MH_SUBSECTIONS_VIA_SYMBOLS = 0x2000
97
+ MH_CANONICAL = 0x4000
98
+ MH_WEAK_DEFINES = 0x8000
99
+ MH_BINDS_TO_WEAK = 0x10000
100
+ MH_ALLOW_STACK_EXECUTION = 0x20000
101
+ MH_ROOT_SAFE = 0x40000
102
+ MH_SETUID_SAFE = 0x80000
103
+ MH_NO_REEXPORTED_DYLIBS = 0x100000
104
+ MH_PIE = 0x200000
105
+ MH_DEAD_STRIPPABLE_DYLIB = 0x400000
106
+ MH_HAS_TLV_DESCRIPTORS = 0x800000
107
+ MH_NO_HEAP_EXECUTION = 0x1000000
108
+ MH_APP_EXTENSION_SAFE = 0x02000000
109
+
110
+ MH_FLAGS = {
111
+ MH_NOUNDEFS => "MH_NOUNDEFS",
112
+ MH_INCRLINK => "MH_INCRLINK",
113
+ MH_DYLDLINK => "MH_DYLDLINK",
114
+ MH_BINDATLOAD => "MH_BINDATLOAD",
115
+ MH_PREBOUND => "MH_PREBOUND",
116
+ MH_SPLIT_SEGS => "MH_SPLIT_SEGS",
117
+ MH_LAZY_INIT => "MH_LAZY_INIT",
118
+ MH_TWOLEVEL => "MH_TWOLEVEL",
119
+ MH_FORCE_FLAT => "MH_FORCE_FLAT",
120
+ MH_NOMULTIDEFS => "MH_NOMULTIDEFS",
121
+ MH_NOPREFIXBINDING => "MH_NOPREFIXBINDING",
122
+ MH_PREBINDABLE => "MH_PREBINDABLE",
123
+ MH_ALLMODSBOUND => "MH_ALLMODSBOUND",
124
+ MH_SUBSECTIONS_VIA_SYMBOLS => "MH_SUBSECTIONS_VIA_SYMBOLS",
125
+ MH_CANONICAL => "MH_CANONICAL",
126
+ MH_WEAK_DEFINES => "MH_WEAK_DEFINES",
127
+ MH_BINDS_TO_WEAK => "MH_BINDS_TO_WEAK",
128
+ MH_ALLOW_STACK_EXECUTION => "MH_ALLOW_STACK_EXECUTION",
129
+ MH_ROOT_SAFE => "MH_ROOT_SAFE",
130
+ MH_SETUID_SAFE => "MH_SETUID_SAFE",
131
+ MH_NO_REEXPORTED_DYLIBS => "MH_NO_REEXPORTED_DYLIBS",
132
+ MH_PIE => "MH_PIE",
133
+ MH_DEAD_STRIPPABLE_DYLIB => "MH_DEAD_STRIPPABLE_DYLIB",
134
+ MH_HAS_TLV_DESCRIPTORS => "MH_HAS_TLV_DESCRIPTORS",
135
+ MH_NO_HEAP_EXECUTION => "MH_NO_HEAP_EXECUTION",
136
+ MH_APP_EXTENSION_SAFE => "MH_APP_EXTENSION_SAFE"
137
+ }
83
138
 
84
139
  # 'Fat' binaries envelop Mach-O binaries so include them for completeness,
85
140
  # Fat binary header structure
@@ -106,6 +161,10 @@ module MachO
106
161
  uint32 :ncmds
107
162
  uint32 :sizeofcmds
108
163
  uint32 :flags
164
+
165
+ def flag?(flag)
166
+ flags & flag == flag
167
+ end
109
168
  end
110
169
 
111
170
  # 64-bit Mach-O file header structure
@@ -118,5 +177,9 @@ module MachO
118
177
  uint32 :sizeofcmds
119
178
  uint32 :flags
120
179
  uint32 :reserved
180
+
181
+ def flag?(flag)
182
+ flags & flag == flag
183
+ end
121
184
  end
122
185
  end
@@ -152,6 +152,17 @@ module MachO
152
152
  LC_LINKER_OPTIMIZATION_HINT => "LinkeditDataCommand"
153
153
  }
154
154
 
155
+ # currently known segment names
156
+ # we don't use these anywhere right now, but they're good to have
157
+ SEG_PAGEZERO = "__PAGEZERO"
158
+ SEG_TEXT = "__TEXT"
159
+ SEG_DATA = "__DATA"
160
+ SEG_OBJC = "__OBJC"
161
+ SEG_ICON = "__ICON"
162
+ SEG_LINKEDIT = "__LINKEDIT"
163
+ SEG_UNIXSTACK = "__UNIXSTACK"
164
+ SEG_IMPORT = "__IMPORT"
165
+
155
166
  # Mach-O load command structure
156
167
  # this is the most generic load command - only cmd ID and size are
157
168
  # represented, and no actual data. used when a more specific class
@@ -2,6 +2,13 @@ module MachO
2
2
  class MachOFile
3
3
  attr_reader :header, :load_commands
4
4
 
5
+ def self.new_from_bin(bin)
6
+ instance = allocate
7
+ instance.initialize_from_bin(bin)
8
+
9
+ instance
10
+ end
11
+
5
12
  def initialize(filename)
6
13
  raise ArgumentError.new("filename must be a String") unless filename.is_a? String
7
14
 
@@ -11,6 +18,17 @@ module MachO
11
18
  @load_commands = get_load_commands
12
19
  end
13
20
 
21
+ def initialize_from_bin(bin)
22
+ @filename = nil
23
+ @raw_data = bin
24
+ @header = get_mach_header
25
+ @load_commands = get_load_commands
26
+ end
27
+
28
+ def serialize
29
+ @raw_data
30
+ end
31
+
14
32
  def magic32?
15
33
  Utils.magic32?(header[:magic])
16
34
  end
@@ -231,10 +249,13 @@ module MachO
231
249
  end
232
250
 
233
251
  def write!
234
- File.open(@filename, "wb") { |f| f.write(@raw_data) }
252
+ if @filename.nil?
253
+ raise MachOError.new("cannot write to a default file when initialized from a binary string")
254
+ else
255
+ File.open(@filename, "wb") { |f| f.write(@raw_data) }
256
+ end
235
257
  end
236
258
 
237
- #######
238
259
  private
239
260
 
240
261
  def get_mach_header
@@ -247,10 +268,10 @@ module MachO
247
268
  flags = get_flags
248
269
 
249
270
  if Utils.magic32?(magic)
250
- header = MachHeader.new(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags)
271
+ MachHeader.new(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags)
251
272
  else
252
- # the reserved field is reserved, so just fill it with 0
253
- header = MachHeader64.new(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags, 0)
273
+ # the reserved field is...reserved, so just fill it with 0
274
+ MachHeader64.new(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags, 0)
254
275
  end
255
276
  end
256
277
 
@@ -261,9 +282,8 @@ module MachO
261
282
  raise MagicError.new(magic)
262
283
  end
263
284
 
264
- # TODO: support fat (universal) binaries
265
285
  if Utils.fat_magic?(magic)
266
- raise FatBinaryError.new(magic)
286
+ raise FatBinaryError.new
267
287
  end
268
288
 
269
289
  magic
@@ -272,7 +292,7 @@ module MachO
272
292
  def get_cputype
273
293
  cputype = @raw_data[4..7].unpack("V").first
274
294
 
275
- if !CPU_TYPES.keys.include?(cputype)
295
+ if !CPU_TYPES.has_key?(cputype)
276
296
  raise CPUTypeError.new(cputype)
277
297
  end
278
298
 
@@ -285,7 +305,7 @@ module MachO
285
305
  # this mask isn't documented!
286
306
  cpusubtype &= ~CPU_SUBTYPE_LIB64
287
307
 
288
- if !CPU_SUBTYPES.keys.include?(cpusubtype)
308
+ if !CPU_SUBTYPES.has_key?(cpusubtype)
289
309
  raise CPUSubtypeError.new(cpusubtype)
290
310
  end
291
311
 
@@ -295,7 +315,7 @@ module MachO
295
315
  def get_filetype
296
316
  filetype = @raw_data[12..15].unpack("V").first
297
317
 
298
- if !MH_FILETYPES.keys.include?(filetype)
318
+ if !MH_FILETYPES.has_key?(filetype)
299
319
  raise FiletypeError.new(filetype)
300
320
  end
301
321
 
@@ -314,7 +334,6 @@ module MachO
314
334
  sizeofcmds
315
335
  end
316
336
 
317
- # TODO: parse flags, maybe?
318
337
  def get_flags
319
338
  flags = @raw_data[24..27].unpack("V").first
320
339
 
@@ -334,7 +353,7 @@ module MachO
334
353
 
335
354
  # why do I do this? i don't like declaring constants below
336
355
  # classes, and i need them to resolve...
337
- klass = Object.const_get "MachO::#{LC_STRUCTURES[cmd]}"
356
+ klass = MachO.const_get "#{LC_STRUCTURES[cmd]}"
338
357
  command = klass.new_from_bin(offset, @raw_data.slice(offset, klass.bytesize))
339
358
 
340
359
  load_commands << command
@@ -41,6 +41,21 @@ module MachO
41
41
  S_ATTR_EXT_RELOC = 0x00000200
42
42
  S_ATTR_LOC_RELOC = 0x00000100
43
43
 
44
+ # currently known section names
45
+ # we don't use these anywhere right now, but they're good to have
46
+ SECT_TEXT = "__text"
47
+ SECT_FVMLIB_INIT0 = "__fvmlib_init0"
48
+ SECT_FVMLIB_INIT1 = "__fvmlib_init1"
49
+ SECT_DATA = "__data"
50
+ SECT_BSS = "__bss"
51
+ SECT_COMMON = "__common"
52
+ SECT_OBJC_SYMBOLS = "__symbol_table"
53
+ SECT_OBJC_MODULES = "__module_info"
54
+ SECT_OBJC_STRINGS = "__selector_strs"
55
+ SECT_OBJC_REFS = "__selector_refs"
56
+ SECT_ICON_HEADER = "__header"
57
+ SECT_ICON_TIFF = "__tiff"
58
+
44
59
  class Section < MachOStructure
45
60
  attr_reader :sectname, :segname, :addr, :size, :offset, :align, :reloff
46
61
  attr_reader :nreloc, :flags, :reserved1, :reserved2
data/lib/macho/utils.rb CHANGED
@@ -9,8 +9,7 @@ module MachO
9
9
  end
10
10
 
11
11
  def self.magic?(num)
12
- num == FAT_MAGIC || num == FAT_CIGAM || num == MH_MAGIC ||
13
- num == MH_CIGAM || num == MH_MAGIC_64 || num == MH_CIGAM_64
12
+ MH_MAGICS.has_key?(num)
14
13
  end
15
14
 
16
15
  def self.fat_magic?(num)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-macho
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - William Woodruff
@@ -17,16 +17,15 @@ extensions: []
17
17
  extra_rdoc_files: []
18
18
  files:
19
19
  - lib/cstruct.rb
20
- - lib/int_helpers.rb
21
20
  - lib/macho.rb
22
21
  - lib/macho/exceptions.rb
23
- - lib/macho/file.rb
22
+ - lib/macho/fat_file.rb
24
23
  - lib/macho/headers.rb
25
24
  - lib/macho/load_commands.rb
25
+ - lib/macho/macho_file.rb
26
26
  - lib/macho/sections.rb
27
27
  - lib/macho/structure.rb
28
28
  - lib/macho/utils.rb
29
- - lib/otool_helpers.rb
30
29
  homepage: https://github.com/woodruffw/ruby-macho
31
30
  licenses:
32
31
  - MIT
data/lib/int_helpers.rb DELETED
@@ -1,17 +0,0 @@
1
- module INTHelpers
2
- Change = Struct.new("Changes", :old, :new)
3
-
4
- Rpath = Struct.new("Rpaths", :old, :new, :found) do
5
- def found?
6
- found
7
- end
8
- end
9
-
10
- AddRpath = Struct.new("AddRpaths", :new)
11
-
12
- DeleteRpath = Struct.new("DeleteRpaths", :old, :found) do
13
- def found?
14
- found
15
- end
16
- end
17
- end
data/lib/otool_helpers.rb DELETED
@@ -1,3 +0,0 @@
1
- module OtoolHelpers
2
-
3
- end