tracetool 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 24b271eb9816bfe2c8c9cf44af633851dfd06497
4
+ data.tar.gz: f1c65ec08ac3aeedc355ec8160d8507cf1e1b73e
5
+ SHA512:
6
+ metadata.gz: a2008ddb93477871d23e215cd4b7771e15b51942782f3e0a05b292d73e2c3e8b169f95fb4d2b3ce4a1c3c8bf7f007aeda8d68cf529a171999cb21ad9fdb187a2
7
+ data.tar.gz: 4509d9dd674ef506f88930e178a851da1af1167fe2985d490ecf0a5c3c083bc64d1c3b3f9699753b9492054875dda78a073b1600adc1040790aac82ff353b0ed
data/bin/tracetool ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative '../lib/tracetool'
@@ -0,0 +1,46 @@
1
+ require_relative '../utils/parser'
2
+
3
+ module Tracetool
4
+ module Android
5
+ # Parses java stack traces
6
+ class JavaTraceParser < Tracetool::BaseTraceParser
7
+ # Describes java stack entry
8
+ STACK_ENTRY_PATTERN = /^(\s+at (?<call_description>.+))|((?<error>.+?): (?<message>.+))$/
9
+ # Describes java method call
10
+ CALL_PATTERN = /(?<class>.+)\.(?<method>[^\(]+)\((((?<file>.+\.java):(?<line>\d+))|(?<location>.+))\)$/
11
+
12
+ def initialize(files)
13
+ super(STACK_ENTRY_PATTERN, CALL_PATTERN, files, true)
14
+ end
15
+ end
16
+ # Processes java traces
17
+ class JavaTraceScanner
18
+ RX_FIRST_EXCEPTION_LINE = /^.+$/
19
+ RX_OTHER_EXCEPTION_LINE = /at [^(]+\(([^:]+:\d+)|(Native Method)\)$/
20
+
21
+ def initialize(string)
22
+ @trace = string
23
+ end
24
+
25
+ def process(_ctx)
26
+ @trace
27
+ end
28
+
29
+ class << self
30
+ def match(string)
31
+ # Split into lines
32
+ first, *rest = string.split("\n")
33
+
34
+ return if rest.nil? || rest.empty?
35
+ return unless RX_FIRST_EXCEPTION_LINE.match(first)
36
+
37
+ rest.all? { |line| RX_OTHER_EXCEPTION_LINE.match(line) }
38
+ end
39
+
40
+ def [](string)
41
+ new(string) if match(string)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,156 @@
1
+ require_relative '../utils/parser'
2
+
3
+ module Tracetool
4
+ module Android
5
+ # Android traces scanner and mapper
6
+ class NativeTraceParser < Tracetool::BaseTraceParser
7
+ # Describes android stack entry
8
+ STACK_ENTRY_PATTERN =
9
+ %r{Stack frame #(?<frame>\d+) (?<address>\w+ [a-f\d]+) (?<lib>[/\w\d\.-]+)( )?(: (?<call_description>.+))?$}
10
+ # Describes android native method call (class::method and source file with line number)
11
+ CALL_PATTERN = /(Routine )?(?<method>.+) ((in)|(at)) (?<file>.+):(?<line>\d+)/
12
+
13
+ def initialize(files)
14
+ super(STACK_ENTRY_PATTERN, CALL_PATTERN, files, true)
15
+ end
16
+ end
17
+
18
+ # Methods for stack trace string normalization
19
+ module NativeTraceEnhancer
20
+ # Default header for android backtrace
21
+ NATIVE_DUMP_HEADER = <<-BACKTRACE.strip_indent
22
+ *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
23
+ Build fingerprint: UNKNOWN
24
+ pid: 0, tid: 0
25
+ signal 0 (UNKNOWN)
26
+ backtrace:
27
+ BACKTRACE
28
+
29
+ # Converts packed stack trace into ndk-stack compatible format
30
+ # @param [String] trace packed stack trace
31
+ # @return well formed stack trace
32
+ def unpack(trace)
33
+ dump_body = prepare(trace).map.with_index { |line, index| convert_line(line, index) }
34
+ add_header(dump_body.join("\n"))
35
+ end
36
+
37
+ # Add dummy header for stack trace body
38
+ def add_header(string)
39
+ NATIVE_DUMP_HEADER + sanitize(string)
40
+ end
41
+
42
+ private
43
+
44
+ def prepare(trace)
45
+ trace.gsub('>>><<<', '')[/<<<(.+)>>>/, 1].split(';')
46
+ end
47
+
48
+ def convert_line(line, index)
49
+ frame = index
50
+ addr = line[/^(-?\d+) (.*)$/, 1]
51
+ lib = line[/^(-?\d+) (.*)$/, 2].strip
52
+ ' #%02i pc %08x %s'.format(frame, addr, lib)
53
+ end
54
+
55
+ # If needed here we'll drop all unneeded leading characters from each
56
+ # stack trace line. This may be needed to add NATIVE_DUMP_HEADER
57
+ # correctly
58
+ def sanitize(string)
59
+ string
60
+ end
61
+ end
62
+
63
+ # Processes native traces
64
+ class NativeTraceScanner
65
+ # Initial sequence of asterisks which marks begining of trace body
66
+ TRACE_DELIMETER = '*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***'.freeze
67
+ RX_INITIAL_ASTERISKS = /#{TRACE_DELIMETER.gsub('*', '\*')}/
68
+ # Contains address line like
69
+ #
70
+ # ```
71
+ # pc 00000000004321ec libfoo.so
72
+ # ```
73
+ RX_PC_ADDRESS = /pc \d+/
74
+
75
+ # Format of packed trace.
76
+ # Consists of one or more trace blocks.
77
+ # * Each block starts with `<<<` and ends with `>>>`.
78
+ # * Each block contains one or more lines
79
+ # * Lines delimited with ;
80
+ # * Line consists of
81
+ # ** pointer address `/\d+/`
82
+ # ** library (so) name `/[^ ]+/`
83
+ # ** symbol name `/[^ ]+/`, if present
84
+ # ** symbol offset `/\d+/`
85
+ #
86
+ # Last two entries can be missing.
87
+ RX_PACKED_FORMAT = /^(<<<(\d+ [^ ]+ ([^ ]+ \d+)?;)+>>>)+$/
88
+
89
+ # @param [String] string well formed native android stack trace
90
+ # @see https://developer.android.com/ndk/guides/ndk-stack.html
91
+ def initialize(string)
92
+ @trace = string
93
+ end
94
+
95
+ # @param [OpenStruct] ctx context object containing `symbols` field with
96
+ # path to symbols dir
97
+ # @return [String] desymbolicated stack trace
98
+ def process(ctx)
99
+ symbols = File.join(ctx.symbols, 'local')
100
+ symbols = if ctx.arch
101
+ File.join(symbols, ctx.arch)
102
+ else
103
+ Dir[File.join(symbols, '*')].first || symbols
104
+ end
105
+ Pipe['ndk-stack', '-sym', symbols] << @trace
106
+ end
107
+
108
+ class << self
109
+ # Add methods for trace normalization
110
+ include Tracetool::Android::NativeTraceEnhancer
111
+ # Tells if provided string is a ndk trace
112
+ # @return truthy or falsey value
113
+ def match(string)
114
+ return false if string.empty?
115
+ packed?(string) || with_header?(string) || without_header?(string)
116
+ end
117
+
118
+ def packed?(string)
119
+ RX_PACKED_FORMAT.match(string)
120
+ end
121
+
122
+ def without_header?(string)
123
+ lines = string.split("\n")
124
+ return true if address_lines?(lines)
125
+
126
+ first, *rest = lines
127
+ first.include?('backtrace:') && address_lines?(rest)
128
+ end
129
+
130
+ def address_lines?(lines)
131
+ lines.all? do |line|
132
+ RX_PC_ADDRESS.match(line)
133
+ end
134
+ end
135
+
136
+ def with_header?(string)
137
+ RX_INITIAL_ASTERISKS.match(string)
138
+ end
139
+
140
+ # With given potential stack trace string
141
+ # create scanner if possible
142
+ # @param [String] string trace
143
+ # @return [NativeTraceScanner] or nil
144
+ def [](string)
145
+ if packed? string
146
+ new(unpack(string))
147
+ elsif with_header? string
148
+ new(string)
149
+ elsif without_header? string
150
+ new(add_header(string))
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,30 @@
1
+ require_relative 'android/java'
2
+ require_relative 'android/native'
3
+
4
+ # Tracetool root module
5
+ module Tracetool
6
+ # Android trace scan variant
7
+ module Android
8
+ # Desymbolicates android traces
9
+ class AndroidTraceScanner
10
+ # List of scanners
11
+ SCANNERS = [JavaTraceScanner, NativeTraceScanner].freeze
12
+
13
+ # Launches process of trace desymbolication
14
+ # @param [String] trace trace body
15
+ def process(trace, context)
16
+ # Find scanner which maches trace format
17
+ scanner = SCANNERS.map { |s| s[trace] }.compact.first
18
+ raise(ArgumentError, "#{trace}\n not android trace?") unless scanner
19
+ scanner.process(context)
20
+ end
21
+ end
22
+
23
+ class << self
24
+ # Desymbolicate android stack trace
25
+ def scan(string, opts = {})
26
+ AndroidTraceScanner.new .process(string, OpenStruct.new(opts))
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,18 @@
1
+ require_relative '../utils/parser'
2
+
3
+ module Tracetool
4
+ module IOS
5
+ # IOS traces scanner and source mapper
6
+ class IOSTraceParser < Tracetool::BaseTraceParser
7
+ # Describes IOS stack entry
8
+ STACK_ENTRY_PATTERN = /^(?<frame>\d+) (?<binary>[^ ]+) (?<call_description>.+)$/
9
+ # Describes source block
10
+ SOURCE_PATTERN =
11
+ /^((-?\[(?<class>[^ ]+) (?<method>.+)\])|(?<method>.+)) \(in (?<module>.+)\) \((?<file>.+):(?<line>\d+)\)$/
12
+
13
+ def initialize(files)
14
+ super(STACK_ENTRY_PATTERN, SOURCE_PATTERN, files, true)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,78 @@
1
+ module Tracetool
2
+ module IOS
3
+ # Converts context to atos arguments
4
+ class AtosContext
5
+ # If no arch specified will use `arm64`
6
+ DEFAULT_ARCH = 'arm64'.freeze
7
+
8
+ # List of required argument names
9
+ REQUIRED_ARGUMENTS = %i[load_address xarchive module_name].freeze
10
+
11
+ #
12
+ def initialize(ctx)
13
+ check_arguments(ctx)
14
+ @load_address = ctx.load_address
15
+ @binary_path = module_binary(ctx.xarchive, ctx.module_name)
16
+ @arch = ctx.arch || 'arm64'
17
+ end
18
+
19
+ def to_args
20
+ %w[-o -l -arch].zip([@binary_path, @load_address, @arch]).flatten
21
+ end
22
+
23
+ private
24
+
25
+ def module_binary(xarchive, module_name)
26
+ File.join(xarchive, 'dSYMs', "#{module_name}.app.dSYM", 'Contents', 'Resources', 'DWARF', module_name)
27
+ end
28
+
29
+ def check_arguments(ctx)
30
+ REQUIRED_ARGUMENTS.each do |a|
31
+ ctx[a] || raise(ArgumentError, "Missing `#{a}` value")
32
+ end
33
+ end
34
+ end
35
+
36
+ # launches atos
37
+ class IOSTraceScanner
38
+ # Stack trace line consists of numerous whitespace separated
39
+ # columns. First three always are:
40
+ #
41
+ # * frame #
42
+ # * binary name
43
+ # * address
44
+ # @param [String] trace string containing stack trace
45
+ # @return [Array] containing (%binary_name%, %address%) pairs
46
+ def parse(trace)
47
+ trace.split("\n").map do |line|
48
+ line.split(' ')[1..2] # Fetch binary name and address
49
+ end
50
+ end
51
+
52
+ def process(trace, context)
53
+ trace = parse(trace)
54
+ desym = run_atos(context, trace.map(&:last))
55
+ # Add useful columns to unpacked trace
56
+ mix(trace, desym.split("\n")).join("\n")
57
+ end
58
+
59
+ def run_atos(context, trace)
60
+ Pipe['atos', *AtosContext.new(context).to_args] << trace
61
+ end
62
+
63
+ private
64
+
65
+ def mix(trace, symbolicated)
66
+ trace.zip(symbolicated).map.with_index do |pair, i|
67
+ t, desym = pair
68
+ line = []
69
+ line << i
70
+ line << t.first
71
+ line << desym
72
+
73
+ line.join(' ')
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'ios/scanner'
2
+ require_relative 'ios/parser'
3
+
4
+ module Tracetool
5
+ # IOS trace unpacking routines
6
+ module IOS
7
+ def self.scan(string, opts)
8
+ IOSTraceScanner.new.process(string, OpenStruct.new(opts))
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,118 @@
1
+ require 'optparse'
2
+ require 'optparse/time'
3
+ require 'ostruct'
4
+
5
+ module Tracetool
6
+ # Tracetool cli args parser
7
+ class ParseArgs
8
+ # List of supported abis
9
+ ARCH_LIST = %i[armeabi-v7a armeabi x86 arm64 x86_64].freeze
10
+ #
11
+ # Return a structure describing the options.
12
+ #
13
+ def self.parse(args)
14
+ # The options specified on the command line will be collected in *options*.
15
+ # We set default values here.
16
+ opt_parser = ParseArgs.new(OpenStruct.new(sym_dir: Dir.pwd))
17
+ options = opt_parser.parse(args)
18
+ check(options)
19
+ check_ios(options)
20
+ options
21
+ rescue OptionParser::MissingArgument => x
22
+ puts ["Error occurred: #{x.message}", '', opt_parser.help].join("\n")
23
+ exit 2
24
+ end
25
+
26
+ def self.check_ios(options)
27
+ return unless options.platform == :ios
28
+ {
29
+ 'address' => options.address,
30
+ 'modulename' => options.modulename
31
+ }.each { |arg, check| raise(OptionParser::MissingArgument, arg) unless check }
32
+ end
33
+
34
+ def self.check(options)
35
+ {
36
+ 'platform' => options.platform,
37
+ 'arch' => options.arch
38
+ }.each { |arg, check| raise(OptionParser::MissingArgument, arg) unless check }
39
+ end
40
+
41
+ def initialize(defaults)
42
+ @options = defaults
43
+ @opt_parser = OptionParser.new do |opts|
44
+ opt_banner(opts)
45
+ opts.separator ''
46
+ opt_common(opts)
47
+ opts.separator ''
48
+ opt_ios(opts)
49
+ opts.separator ''
50
+ opt_default(opts)
51
+ end
52
+ end
53
+
54
+ def parse(args)
55
+ @opt_parser.parse!(args)
56
+ @options
57
+ end
58
+
59
+ def help
60
+ @opt_parser.help
61
+ end
62
+
63
+ def opt_banner(opts)
64
+ opts.banner = 'Usage: tracetool OPTION... [FILE]...'
65
+ opts.separator ''
66
+ opts.separator 'Examples:'
67
+ opts.separator "\techo '<<<...>>>' | tracetool --arch armeabi-v7a --platform android --symbols %build_dir%"
68
+ opts.separator "\ttracetool --platform ios --arch arm64 --address 0x100038000 --module ZombieCastaways dump"
69
+ end
70
+
71
+ def opt_common(opts)
72
+ opts.separator 'Common options:'
73
+ # Specify arch
74
+ opts.on('--arch ARCH', ARCH_LIST, "Specify arch #{ARCH_LIST.join(', ')}") do |arch|
75
+ @options.arch = arch
76
+ end
77
+
78
+ # Specify platform
79
+ opts.on('--platform PLATORM', %i[android ios], 'Specify platform (android, ios)') do |platform|
80
+ @options.platform = platform
81
+ end
82
+
83
+ # Symbols dir
84
+ opts.on('--symbols [SYMBOLS]', 'Symbols dir. Using current working dir if not specified') do |dir|
85
+ @options.sym_dir = dir
86
+ end
87
+ end
88
+
89
+ def opt_ios(opts)
90
+ opts.separator 'IOS specific options'
91
+ # Addres
92
+ opts.on('--address ADDRESS', 'Baseline address for ios builds') do |address|
93
+ @options.address = address
94
+ end
95
+
96
+ # Modulename -- execution entry point
97
+ opts.on('--module MODULENAME', 'Entry point for ios builds') do |modulename|
98
+ @options.modulename = modulename
99
+ end
100
+ end
101
+
102
+ def opt_default(opts)
103
+ opts.separator 'Base options:'
104
+ # No argument, shows at tail. This will print an options summary.
105
+ # Try it and see!
106
+ opts.on_tail('-h', '--help', 'Show this message') do
107
+ puts opts
108
+ exit
109
+ end
110
+
111
+ # Another typical switch to print the version.
112
+ opts.on_tail('--version', 'Show version') do
113
+ puts 'tracetool ' + Tracetool::Version.to_s
114
+ exit
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,34 @@
1
+ module Tracetool
2
+ # Utility methods for working with environment
3
+ module Env
4
+ class << self
5
+ # Finds executable in path
6
+ def which(cmd)
7
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
8
+ candidates = ENV['PATH'].split(File::PATH_SEPARATOR).flat_map do |path|
9
+ exts.map { |ext| File.join(path, "#{cmd}#{ext}") }
10
+ end
11
+
12
+ candidates.find { |exe| File.executable?(exe) && !File.directory?(exe) }
13
+ end
14
+
15
+ # Raises exception if can't find executable in path
16
+ # @raise [ArgumentError] if executable not found
17
+ def ensure_command(cmd)
18
+ raise(ArgumentError, "#{cmd} not found in PATH") unless which(cmd)
19
+ end
20
+
21
+ # Checks if `ndk-stack` can be found in path
22
+ # @raise [ArgumentError] if can't find ndk-stack
23
+ def ensure_ndk_stack
24
+ ensure_command('ndk-stack')
25
+ end
26
+
27
+ # Checks if `atos` can be found in path
28
+ # @raise [ArgumentError] if can't find atos
29
+ def ensure_atos
30
+ ensure_command('atos')
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,104 @@
1
+ module Tracetool
2
+ # Base trace parser logic
3
+ class BaseTraceParser
4
+ attr_reader :entry_pattern, :call_pattern
5
+
6
+ def initialize(entry_pattern, call_pattern, build_files, convert_numbers = false)
7
+ @build_files = build_files
8
+ @entry_pattern = entry_pattern
9
+ @call_pattern = call_pattern
10
+ @convert_numbers = convert_numbers
11
+ end
12
+
13
+ # Parse crash dump
14
+ # Each line should be parsed in Hash which SHOULD or MAY contains following
15
+ # entries:
16
+ #
17
+ # * SHOULD contain `orig` entry -- original trace entry
18
+ # * MAY contain following entries IF original entry matched pattern:
19
+ # ** `frame` - stack frame index
20
+ # ** `lib` - shared library name (android) or module name (ios)
21
+ # ** `call_description` - original call block
22
+ # ** MAY contain `call` entry if `call_description` was recognized:
23
+ # *** `method` - namespaced method name (with class and its namespace)
24
+ # *** `file` - file path relative to `symbols dir`
25
+ # *** `line` - line number in specified file
26
+ def parse(lines)
27
+ lines
28
+ .split("\n")
29
+ .select { |line| line_filter(line) }
30
+ .map { |line| scan_call(scan_line(line)) }
31
+ end
32
+
33
+ private
34
+
35
+ # Will drop other lines from unpacked result
36
+ def line_filter(line)
37
+ entry_pattern.match(line)
38
+ end
39
+
40
+ # Find basic entry elements:
41
+ # * frame
42
+ # * address
43
+ # * library
44
+ # * call description
45
+ def scan_line(line)
46
+ e = { orig: line }
47
+ entry_pattern.match(line) do |m|
48
+ e.update(extract_groups(m))
49
+ end
50
+ e
51
+ end
52
+
53
+ # Parse call description to extract:
54
+ # * method
55
+ # * file
56
+ # * line number
57
+ def scan_call(e)
58
+ if e[:call_description]
59
+ call_pattern.match(e[:call_description]) do |m|
60
+ call = extract_groups(m)
61
+ # Update file entry with expanded path
62
+ call[:file] = find_file(call[:file]) if call[:file]
63
+
64
+ e[:call] = call
65
+ end
66
+ end
67
+
68
+ e
69
+ end
70
+
71
+ # Find file with specified file name in symbols dir
72
+ # Can return multiple files if name was ambigous
73
+ def find_file(file)
74
+ # Find all matching files
75
+ # remove build_dir from path
76
+ # remove leading '/'
77
+ glob = File.join('**', File.basename(file))
78
+ files = @build_files.select { |f| File.fnmatch(glob, f) }
79
+
80
+ # If has only option return first
81
+ return files.first if files.size == 1
82
+ # Return original file if files empty
83
+ return file if files.empty?
84
+
85
+ # If got ambiguous files return all
86
+ files
87
+ end
88
+
89
+ def extract_groups(match)
90
+ groups = match
91
+ .names
92
+ .map { |name| [name.to_sym, match[name]] }
93
+ .flat_map { |name, value| [name, try_convert(value)] }
94
+ Hash[*groups]
95
+ end
96
+
97
+ def try_convert(value)
98
+ return value unless @convert_numbers
99
+ return value.to_i if /^\d+$/ =~ value
100
+
101
+ value
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,32 @@
1
+ require 'open3'
2
+ module Tracetool
3
+ # helper module for launching commands
4
+ module Pipe
5
+ # Executes shell command
6
+ class Executor
7
+ def initialize(cmd, *args)
8
+ @cmd = cmd
9
+ @args = args
10
+ end
11
+
12
+ def cmd
13
+ [@cmd, @args].flatten
14
+ end
15
+
16
+ def <<(args)
17
+ args = args.join("\n") if args.is_a? Array
18
+ IO.popen(cmd, 'r+') do |io|
19
+ io.write(args)
20
+ io.close_write
21
+ io.read.chomp
22
+ end
23
+ end
24
+ end
25
+
26
+ class << self
27
+ def[](cmd, *args)
28
+ Executor.new(cmd, args)
29
+ end
30
+ end
31
+ end
32
+ end
data/lib/tracetool.rb ADDED
@@ -0,0 +1,29 @@
1
+ require 'ostruct'
2
+ require 'powerpack/string'
3
+
4
+ require_relative 'version'
5
+
6
+ require_relative 'tracetool/android'
7
+ require_relative 'tracetool/ios'
8
+ require_relative 'tracetool/utils/cli'
9
+ require_relative 'tracetool/utils/env'
10
+ require_relative 'tracetool/utils/pipe'
11
+
12
+ opts = Tracetool::ParseArgs.parse(ARGV)
13
+
14
+ case opts.platform
15
+ when :android
16
+ stack = ARGF.read
17
+ puts Tracetool::Android.scan(stack,
18
+ symbols: opts.sym_dir,
19
+ arch: opts.arch.to_s)
20
+ when :ios
21
+ stack = ARGF.read
22
+ puts Tracetool::IOS.scan(stack,
23
+ arch: opts.arch.to_s,
24
+ xarchive: opts.sym_dir,
25
+ module_name: opts.modulename,
26
+ load_address: opts.address)
27
+ else
28
+ raise "Unknown(#{opts.platform})"
29
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,13 @@
1
+ module Tracetool
2
+ # Version constant
3
+ module Version
4
+ VERSION = [0, 3, 0].freeze
5
+
6
+ class << self
7
+ # @return [String] version string
8
+ def to_s
9
+ VERSION.join('.')
10
+ end
11
+ end
12
+ end
13
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tracetool
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - ilya.arkhanhelsky
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-11-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: powerpack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.1
27
+ description: Helper for unpacking Android and iOS traces
28
+ email: ilya.arkhanhelsky@vizor-games.com
29
+ executables:
30
+ - tracetool
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - bin/tracetool
35
+ - lib/tracetool.rb
36
+ - lib/tracetool/android.rb
37
+ - lib/tracetool/android/java.rb
38
+ - lib/tracetool/android/native.rb
39
+ - lib/tracetool/ios.rb
40
+ - lib/tracetool/ios/parser.rb
41
+ - lib/tracetool/ios/scanner.rb
42
+ - lib/tracetool/utils/cli.rb
43
+ - lib/tracetool/utils/env.rb
44
+ - lib/tracetool/utils/parser.rb
45
+ - lib/tracetool/utils/pipe.rb
46
+ - lib/version.rb
47
+ homepage: https://github.com/vizor-games/tracetool
48
+ licenses:
49
+ - MIT
50
+ metadata: {}
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubyforge_project:
67
+ rubygems_version: 2.6.13
68
+ signing_key:
69
+ specification_version: 4
70
+ summary: Tracetool
71
+ test_files: []