ruby-macho 0.0.3 → 0.0.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 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