dsym_uuid_extractor 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 414325b5c26358f6ea5948f1fd5c5e24fef16038
4
+ data.tar.gz: cbc44da0cda1c5acbdcd303d0da72e8c8329ac56
5
+ SHA512:
6
+ metadata.gz: bbfe84993a42b3809dbb62528a83cec3b9eda1fc1ba1a11111e2aa0e9474b4b204052dfafb36233ae5b1e841b4ad822c80e0906dd332254bb9faf7bb1c210d38
7
+ data.tar.gz: 706cf7cfda811dd7b24ec23d7c34e7762d3e4c58181dd760d96bf68a391cee7c0baa87d1a3d99cbc87c527546ff861efb91f7d874b1daa7a71f46b02d141e075
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /tags
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Marwan Tanager
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,23 @@
1
+ # dsym_uuid_extractor
2
+
3
+ This gem provides a ruby module to extract UUID/architecture info from a dSYM
4
+ file generated by Apple's Xcode or a similar environment.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'dsym_uuid_extractor'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ ## Usage
19
+
20
+ ```ruby
21
+ DsymUuidExtractor.run(dsym_file_path)
22
+ => {'4383705b-72ab-4b89-815e-dae35cce2e52'=>'arm64','048c3790-07fb-428d-a330-91fe1d30d2c1'=>'armv7s'}
23
+ ```
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "dsym_uuid_extractor"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'dsym_uuid_extractor/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "dsym_uuid_extractor"
8
+ spec.version = DsymUuidExtractor::VERSION
9
+ spec.authors = ["Marwan Tanager"]
10
+ spec.email = ["marwan.tanager@gmail.com"]
11
+
12
+ spec.summary = "dSYM UUID/arch extractor"
13
+ spec.description = "Extract UUIDs/architecture information from a standard dSYM file"
14
+ spec.homepage = "https://github.com/Instabug/dsym_uuid_extractor"
15
+
16
+ if spec.respond_to?(:metadata)
17
+ spec.metadata['allowed_push_host'] = "https://rubygems.org"
18
+ else
19
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
20
+ end
21
+
22
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.13"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ spec.add_development_dependency "rspec", "~> 3.0"
28
+ spec.add_dependency "ffi", "~> 1.9", ">= 1.9.10"
29
+ end
@@ -0,0 +1,21 @@
1
+ require 'dsym_uuid_extractor/version'
2
+ require 'dsym_uuid_extractor/macho_reader'
3
+
4
+ module DsymUuidExtractor
5
+ def self.run(file_path)
6
+ results = {}
7
+
8
+ reader = if MachOBinaryReader.is_binary_file?(file_path)
9
+ MachOBinaryReader.new(file_path: file_path)
10
+ else
11
+ MachOFileReader.new(file_path: file_path)
12
+ end
13
+
14
+ reader.each_file do |file|
15
+ file.extract_info
16
+ results[file.uuid] = file.arch
17
+ end
18
+
19
+ results
20
+ end
21
+ end
@@ -0,0 +1,265 @@
1
+ require 'ffi'
2
+
3
+ module DsymUuidExtractor
4
+ # Based on the details found in https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/MachORuntime/index.html
5
+
6
+ class MachOReader
7
+ # Constants
8
+ ## CPU Types
9
+ CPU_ARCH_ABI64 = 0x01000000
10
+ CPU_TYPE_I386 = 7
11
+ CPU_TYPE_X86_64 = CPU_TYPE_I386 | CPU_ARCH_ABI64
12
+ CPU_TYPE_ARM = 12
13
+ CPU_TYPE_ARM64 = CPU_TYPE_ARM | CPU_ARCH_ABI64
14
+ ## CPU Subtypes
15
+ CPU_SUBTYPE_ARM_V7 = 9
16
+ CPU_SUBTYPE_ARM_V7S = 11
17
+
18
+ # Readers
19
+ attr_reader :file
20
+
21
+ # Methods
22
+ def initialize(opts = {})
23
+ @file = File.open(opts[:file_path], mode: 'rb')
24
+
25
+ @file.seek(opts[:seek_to_pos]) if opts[:seek_to_pos]
26
+ end
27
+
28
+ def read_struct(struct_class, opts = {})
29
+ struct_size = struct_class.size
30
+ buffer = FFI::Buffer.new(:char, struct_size)
31
+ buffer = buffer.order(opts[:byte_order]) if opts[:byte_order]
32
+
33
+ buffer.put_bytes(0, @file.read(struct_size))
34
+
35
+ struct_class.new(buffer)
36
+ end
37
+
38
+ class EmptySymbolicationFile < StandardError; end
39
+ class MachOReaderError < StandardError; end
40
+ class UnkownCpuSubtype < MachOReaderError; end
41
+ class UnkownCpuType < MachOReaderError; end
42
+ class ArchIsNil < MachOReaderError; end
43
+ end
44
+
45
+ class MachOBinaryReader < MachOReader
46
+ ## Magic Numbers
47
+ FAT_MAGIC = 0xcafebabe
48
+ FAT_CIGAM = 0xbebafeca
49
+ # Struct Classes
50
+ class FatHeader < FFI::Struct
51
+ layout magic: :uint32,
52
+ nfat_arch: :uint32
53
+ end
54
+
55
+ class FatArch < FFI::Struct
56
+ layout cputype: :int32,
57
+ cpusubtype: :int32,
58
+ offset: :uint32,
59
+ size: :uint32,
60
+ align: :uint32
61
+ end
62
+
63
+ # Methods
64
+ def self.is_binary_file?(file_path)
65
+ fail EmptySymbolicationFile if File.zero?(file_path)
66
+
67
+ File.read(file_path, 4).unpack('L>')[0] == FAT_MAGIC ||
68
+ File.read(file_path, 4).unpack('L<')[0] == FAT_CIGAM
69
+ rescue EmptySymbolicationFile
70
+ # To be logged in CloudWatch...
71
+ false
72
+ end
73
+
74
+ def each_file
75
+ arch_count = fat_header[:nfat_arch]
76
+
77
+ seek_to_fat_arch
78
+ arch_offsets = arch_count.times.map do
79
+ fat_arch = read_struct(FatArch)
80
+
81
+ fat_arch[:offset]
82
+ end
83
+
84
+ arch_offsets.each do |arch_offset|
85
+ yield MachOFileReader.new(file_path: File.absolute_path(file.path), seek_to_pos: arch_offset)
86
+ end
87
+ end
88
+
89
+ def read_struct(struct_class, opts = {})
90
+ super(struct_class, opts.merge(byte_order: :big))
91
+ end
92
+
93
+ private
94
+
95
+ def fat_header
96
+ return @fat_header if @fat_header
97
+
98
+ seek_to_file_beginning
99
+ @fat_header = read_struct(FatHeader)
100
+ end
101
+
102
+ def seek_to_fat_arch
103
+ seek_to_file_beginning
104
+ file.seek(FatHeader.size, IO::SEEK_CUR)
105
+ end
106
+
107
+ def seek_to_file_beginning
108
+ file.seek(0)
109
+ end
110
+ end
111
+
112
+ class MachOFileReader < MachOReader
113
+ # Constants
114
+ ## Load Commands
115
+ LC_UUID = 0x1b
116
+ ## Magic Numbers
117
+ MH_MAGIC = 0xfeedface
118
+ MH_CIGAM = 0xcefaedfe
119
+ MH_MAGIC_64 = 0xfeedfacf
120
+ MH_CIGAM_64 = 0xcffaedfe
121
+ ## Byte Order
122
+ BYTE_ORDER = FFI::MemoryPointer.new(:char).order
123
+ BYTE_ORDER_INVERSE = BYTE_ORDER == :little ? :big : :little
124
+
125
+ # Struct Classes
126
+ class MachHeader < FFI::Struct
127
+ layout magic: :uint32,
128
+ cputype: :int32,
129
+ cpusubtype: :int32,
130
+ filetype: :uint32,
131
+ ncmds: :uint32,
132
+ sizeofcmds: :uint32,
133
+ flags: :uint32
134
+ end
135
+
136
+ class MachHeader64 < FFI::Struct
137
+ layout magic: :uint32,
138
+ cputype: :int32,
139
+ cpusubtype: :int32,
140
+ filetype: :uint32,
141
+ ncmds: :uint32,
142
+ sizeofcmds: :uint32,
143
+ flags: :uint32,
144
+ reserved: :uint32
145
+ end
146
+
147
+ class LoadCommand < FFI::Struct
148
+ layout cmd: :uint32,
149
+ cmdsize: :uint32
150
+ end
151
+
152
+ class UuidCommand < FFI::Struct
153
+ layout cmd: :uint32,
154
+ cmdsize: :uint32,
155
+ uuid: [:uint8, 16]
156
+ end
157
+
158
+ # Readers
159
+ attr_reader :arch, :uuid, :is_64_arch
160
+
161
+ # Methods
162
+ def self.is_macho_file?(file_path)
163
+ magic_number = new(file_path: file_path).magic_number
164
+
165
+ [MH_MAGIC, MH_MAGIC_64, MH_CIGAM, MH_CIGAM_64].include?(magic_number)
166
+ end
167
+
168
+ def initialize(opts = {})
169
+ super(opts)
170
+
171
+ @file_beginning_pos = file.pos
172
+ end
173
+
174
+ def magic_number
175
+ mach_header[:magic]
176
+ end
177
+
178
+ def extract_info
179
+ extract_arch
180
+ extract_uuid
181
+ end
182
+
183
+ def each_file
184
+ yield self
185
+ end
186
+
187
+ private
188
+
189
+ def mach_header
190
+ return @mach_header if @mach_header
191
+
192
+ seek_to_file_beginning
193
+ @mach_header = read_struct(MachHeader)
194
+ end
195
+
196
+ def extract_arch
197
+ @arch = case mach_header[:cputype]
198
+ when CPU_TYPE_I386
199
+ 'i386'
200
+ when CPU_TYPE_X86_64
201
+ 'x86_64'
202
+ when CPU_TYPE_ARM64
203
+ 'arm64'
204
+ when CPU_TYPE_ARM
205
+ case mach_header[:cpusubtype]
206
+ when CPU_SUBTYPE_ARM_V7
207
+ 'armv7'
208
+ when CPU_SUBTYPE_ARM_V7S
209
+ 'armv7s'
210
+ else
211
+ fail MachOReader::UnkownCpuSubtype, "Extracting arch failed; unknown cpusubtype '#{mach_header[:cputype]}'"
212
+ end
213
+ else
214
+ fail MachOReader::UnkownCpuType, "Extracting arch failed; unknown cputype '#{mach_header[:cputype]}'"
215
+ end
216
+
217
+ @is_64_arch = (mach_header[:cputype] & CPU_ARCH_ABI64 != 0)
218
+ end
219
+
220
+ def extract_uuid
221
+ seek_to_load_commands
222
+
223
+ mach_header[:ncmds].times do
224
+ load_command = read_struct(LoadCommand, byte_order: byte_order)
225
+
226
+ file.seek(-LoadCommand.size, IO::SEEK_CUR)
227
+
228
+ if load_command[:cmd] == LC_UUID
229
+ uuid_command = read_struct(UuidCommand, byte_order: byte_order)
230
+
231
+ # Copied from securerandom stdlib
232
+ uuid_unpacked = uuid_command[:uuid].to_a.pack('C' * 16).unpack('NnnnnN')
233
+ @uuid = (format('%08x-%04x-%04x-%04x-%04x%08x', *uuid_unpacked)).upcase
234
+
235
+ break
236
+ else
237
+ file.seek(load_command[:cmdsize], IO::SEEK_CUR)
238
+ end
239
+ end
240
+ end
241
+
242
+ def seek_to_file_beginning
243
+ file.seek(@file_beginning_pos)
244
+ end
245
+
246
+ def seek_to_load_commands
247
+ fail MachOReader::ArchIsNil, '@arch is nil; call #extract_arch first' if @arch.nil?
248
+
249
+ header_struct = is_64_arch ? MachHeader64 : MachHeader
250
+
251
+ seek_to_file_beginning
252
+ file.seek(header_struct.size, IO::SEEK_CUR)
253
+
254
+ true
255
+ end
256
+
257
+ def byte_order
258
+ @byte_order ||= if is_64_arch
259
+ mach_header[:magic] == MH_MAGIC_64 ? BYTE_ORDER : BYTE_ORDER_INVERSE
260
+ else
261
+ mach_header[:magic] == MH_MAGIC ? BYTE_ORDER : BYTE_ORDER_INVERSE
262
+ end
263
+ end
264
+ end
265
+ end
@@ -0,0 +1,3 @@
1
+ module DsymUuidExtractor
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dsym_uuid_extractor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Marwan Tanager
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-10-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.13'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.13'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: ffi
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.9'
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 1.9.10
65
+ type: :runtime
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - "~>"
70
+ - !ruby/object:Gem::Version
71
+ version: '1.9'
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 1.9.10
75
+ description: Extract UUIDs/architecture information from a standard dSYM file
76
+ email:
77
+ - marwan.tanager@gmail.com
78
+ executables: []
79
+ extensions: []
80
+ extra_rdoc_files: []
81
+ files:
82
+ - ".gitignore"
83
+ - ".rspec"
84
+ - Gemfile
85
+ - LICENSE.txt
86
+ - README.md
87
+ - Rakefile
88
+ - bin/console
89
+ - bin/setup
90
+ - dsym_uuid_extractor.gemspec
91
+ - lib/dsym_uuid_extractor.rb
92
+ - lib/dsym_uuid_extractor/macho_reader.rb
93
+ - lib/dsym_uuid_extractor/version.rb
94
+ homepage: https://github.com/Instabug/dsym_uuid_extractor
95
+ licenses: []
96
+ metadata:
97
+ allowed_push_host: https://rubygems.org
98
+ post_install_message:
99
+ rdoc_options: []
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ requirements: []
113
+ rubyforge_project:
114
+ rubygems_version: 2.4.5.1
115
+ signing_key:
116
+ specification_version: 4
117
+ summary: dSYM UUID/arch extractor
118
+ test_files: []