tracetool 0.4.0 → 0.4.1
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 +5 -5
- data/lib/tracetool/android.rb +2 -0
- data/lib/tracetool/android/java.rb +10 -6
- data/lib/tracetool/android/native.rb +17 -9
- data/lib/tracetool/ios/parser.rb +2 -1
- data/lib/tracetool/ios/scanner.rb +0 -1
- data/lib/tracetool/utils/cli.rb +2 -1
- data/lib/tracetool/utils/parser.rb +50 -19
- data/lib/tracetool/utils/string.rb +24 -0
- data/lib/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ebe6ba1b66d5457e530be1544b059cf42da169d948a651766ad57fd3c6dac99e
|
4
|
+
data.tar.gz: 7b1a2cdefc535a9bb851dc212b69eaa34d89720d051936ab7d34442384db150f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d7a9e46d22089eb1b2517edc7664e3bf26ae04e6f82e4b55aaad3e87d9119b293a9d1fdde0a8d072ebadf2ac619487c24ddf099a0184a968e638b90ba34008b3
|
7
|
+
data.tar.gz: 4a187b23b271f395919265da64dc8d328b7a1b4fa4bcaf8383d33ed8e07d6c8c5dd5253e044db2d70a3efa6e6c604271b49439f7f4d01f163a5fab04761b8c9c
|
data/lib/tracetool/android.rb
CHANGED
@@ -16,6 +16,7 @@ module Tracetool
|
|
16
16
|
# Find scanner which matches trace format
|
17
17
|
@scanner = SCANNERS.map { |s| s[trace] }.compact.first
|
18
18
|
raise(ArgumentError, "#{trace}\n not android trace?") unless @scanner
|
19
|
+
|
19
20
|
@scanner.process(context)
|
20
21
|
end
|
21
22
|
|
@@ -25,6 +26,7 @@ module Tracetool
|
|
25
26
|
# Or `nil`. If there was no scanning.
|
26
27
|
def parser(files)
|
27
28
|
return unless @scanner
|
29
|
+
|
28
30
|
@scanner.parser(files)
|
29
31
|
end
|
30
32
|
end
|
@@ -5,9 +5,9 @@ module Tracetool
|
|
5
5
|
# Parses java stack traces
|
6
6
|
class JavaTraceParser < Tracetool::BaseTraceParser
|
7
7
|
# Describes java stack entry
|
8
|
-
STACK_ENTRY_PATTERN = /^(\s+at (?<call_description>.+))|((?<error>.+?): (?<message>.+))
|
8
|
+
STACK_ENTRY_PATTERN = /^(\s+at (?<call_description>.+))|((?<error>.+?): (?<message>.+))$/.freeze
|
9
9
|
# Describes java method call
|
10
|
-
CALL_PATTERN = /(?<class>.+)\.(?<method>[^\(]+)\((((?<file>.+\.java):(?<line>\d+))|(?<location>.+))\)
|
10
|
+
CALL_PATTERN = /(?<class>.+)\.(?<method>[^\(]+)\((((?<file>.+\.java):(?<line>\d+))|(?<location>.+))\)$/.freeze
|
11
11
|
|
12
12
|
def initialize(files)
|
13
13
|
super(STACK_ENTRY_PATTERN, CALL_PATTERN, files, true)
|
@@ -15,8 +15,14 @@ module Tracetool
|
|
15
15
|
end
|
16
16
|
# Processes java traces
|
17
17
|
class JavaTraceScanner
|
18
|
-
|
19
|
-
|
18
|
+
# Usually java trace starts with
|
19
|
+
# com.something.SomeClass(: Some message)?
|
20
|
+
RX_FIRST_EXCEPTION_LINE = /^([a-zA-Z.]*)(:.*)?$/.freeze
|
21
|
+
|
22
|
+
# Rest is expanded as
|
23
|
+
# at com.other.OtherClass.someMethod(OtherClass.java:42)
|
24
|
+
# Source marker can be just "Native Method" or "Unknown Source"
|
25
|
+
RX_OTHER_EXCEPTION_LINE = /((at [a-zA-Z$.]+)|(Caused by:)|(\.\.\. [0-9]* more))(.+)?$/.freeze
|
20
26
|
|
21
27
|
def initialize(string)
|
22
28
|
@trace = string
|
@@ -38,8 +44,6 @@ module Tracetool
|
|
38
44
|
def match(string)
|
39
45
|
# Split into lines
|
40
46
|
first, *rest = string.split("\n")
|
41
|
-
|
42
|
-
return if rest.nil? || rest.empty?
|
43
47
|
return unless RX_FIRST_EXCEPTION_LINE.match(first)
|
44
48
|
|
45
49
|
rest.all? { |line| RX_OTHER_EXCEPTION_LINE.match(line) }
|
@@ -6,9 +6,13 @@ module Tracetool
|
|
6
6
|
class NativeTraceParser < Tracetool::BaseTraceParser
|
7
7
|
# Describes android stack entry
|
8
8
|
STACK_ENTRY_PATTERN =
|
9
|
-
%r{Stack frame #(?<frame>\d+) (?<address>\w+ [a-f\d]+) (?<lib>[/\w\d
|
9
|
+
%r{Stack frame #(?<frame>\d+) (?<address>\w+ [a-f\d]+) (?<lib>[/\w\d\.=-]+)( )?(:? (?<call_description>.+))?$}
|
10
|
+
.freeze
|
10
11
|
# Describes android native method call (class::method and source file with line number)
|
11
|
-
CALL_PATTERN =
|
12
|
+
CALL_PATTERN = [
|
13
|
+
/((Routine )?(?<method>.+) ((in)|(at)) (?<file>.+):(?<line>\d+))/,
|
14
|
+
/(?<method>.+?) \d+/
|
15
|
+
].freeze
|
12
16
|
|
13
17
|
def initialize(files)
|
14
18
|
super(STACK_ENTRY_PATTERN, CALL_PATTERN, files, true)
|
@@ -30,7 +34,9 @@ module Tracetool
|
|
30
34
|
# @param [String] trace packed stack trace
|
31
35
|
# @return well formed stack trace
|
32
36
|
def unpack(trace)
|
33
|
-
dump_body = prepare(trace)
|
37
|
+
dump_body = prepare(trace)
|
38
|
+
.map
|
39
|
+
.with_index { |line, index| convert_line(line, index) }
|
34
40
|
add_header(dump_body.join("\n"))
|
35
41
|
end
|
36
42
|
|
@@ -48,8 +54,9 @@ module Tracetool
|
|
48
54
|
def convert_line(line, index)
|
49
55
|
frame = index
|
50
56
|
addr = line[/^(-?\d+) (.*)$/, 1]
|
51
|
-
lib = line[/^(-?\d+) (.*)$/, 2].strip
|
52
|
-
' #%
|
57
|
+
lib = (line[/^(-?\d+) (.*)$/, 2] || '').strip # nil safe
|
58
|
+
' #%02<frame>i pc %08<addr>x %<lib>s'
|
59
|
+
.format(frame: frame, addr: addr, lib: lib)
|
53
60
|
end
|
54
61
|
|
55
62
|
# If needed here we'll drop all unneeded leading characters from each
|
@@ -63,14 +70,15 @@ module Tracetool
|
|
63
70
|
# Processes native traces
|
64
71
|
class NativeTraceScanner
|
65
72
|
# Initial sequence of asterisks which marks begining of trace body
|
66
|
-
TRACE_DELIMETER =
|
67
|
-
|
73
|
+
TRACE_DELIMETER =
|
74
|
+
'*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***'.freeze
|
75
|
+
RX_INITIAL_ASTERISKS = /#{TRACE_DELIMETER.gsub('*', '\*')}/.freeze
|
68
76
|
# Contains address line like
|
69
77
|
#
|
70
78
|
# ```
|
71
79
|
# pc 00000000004321ec libfoo.so
|
72
80
|
# ```
|
73
|
-
RX_PC_ADDRESS = /pc \d
|
81
|
+
RX_PC_ADDRESS = /pc \d+/.freeze
|
74
82
|
|
75
83
|
# Format of packed trace.
|
76
84
|
# Consists of one or more trace blocks.
|
@@ -84,7 +92,7 @@ module Tracetool
|
|
84
92
|
# ** symbol offset `/\d+/`
|
85
93
|
#
|
86
94
|
# Last two entries can be missing.
|
87
|
-
RX_PACKED_FORMAT = /^(<<<(
|
95
|
+
RX_PACKED_FORMAT = /^(<<<([-\d]+ [^ ]+ (.+)?;)+>>>)+$/.freeze
|
88
96
|
|
89
97
|
# @param [String] string well formed native android stack trace
|
90
98
|
# @see https://developer.android.com/ndk/guides/ndk-stack.html
|
data/lib/tracetool/ios/parser.rb
CHANGED
@@ -5,10 +5,11 @@ module Tracetool
|
|
5
5
|
# IOS traces scanner and source mapper
|
6
6
|
class IOSTraceParser < Tracetool::BaseTraceParser
|
7
7
|
# Describes IOS stack entry
|
8
|
-
STACK_ENTRY_PATTERN = /^(?<frame>\d+) (?<binary>[^ ]+) (?<call_description>.+)
|
8
|
+
STACK_ENTRY_PATTERN = /^(?<frame>\d+) (?<binary>[^ ]+) (?<call_description>.+)$/.freeze
|
9
9
|
# Describes source block
|
10
10
|
SOURCE_PATTERN =
|
11
11
|
/^((-?\[(?<class>[^ ]+) (?<method>.+)\])|(?<method>.+)) \(in (?<module>.+)\) \((?<file>.+):(?<line>\d+)\)$/
|
12
|
+
.freeze
|
12
13
|
|
13
14
|
def initialize(files)
|
14
15
|
super(STACK_ENTRY_PATTERN, SOURCE_PATTERN, files, true)
|
data/lib/tracetool/utils/cli.rb
CHANGED
@@ -27,9 +27,10 @@ module Tracetool
|
|
27
27
|
|
28
28
|
def self.check_ios(options)
|
29
29
|
return unless options.platform == :ios
|
30
|
+
|
30
31
|
{
|
31
32
|
'address' => options.address,
|
32
|
-
'module' =>
|
33
|
+
'module' => options.modulename
|
33
34
|
}.each { |arg, check| raise(OptionParser::MissingArgument, arg) unless check }
|
34
35
|
end
|
35
36
|
|
@@ -1,12 +1,16 @@
|
|
1
|
+
require_relative 'string'
|
2
|
+
|
1
3
|
module Tracetool
|
2
4
|
# Base trace parser logic
|
3
5
|
class BaseTraceParser
|
6
|
+
include StringUtils
|
7
|
+
|
4
8
|
attr_reader :entry_pattern, :call_pattern
|
5
9
|
|
6
10
|
def initialize(entry_pattern, call_pattern, build_files, convert_numbers = false)
|
7
11
|
@build_files = build_files
|
8
12
|
@entry_pattern = entry_pattern
|
9
|
-
@call_pattern = call_pattern
|
13
|
+
@call_pattern = call_pattern.is_a?(Array) ? call_pattern : [call_pattern]
|
10
14
|
@convert_numbers = convert_numbers
|
11
15
|
end
|
12
16
|
|
@@ -54,36 +58,63 @@ module Tracetool
|
|
54
58
|
# * method
|
55
59
|
# * file
|
56
60
|
# * line number
|
57
|
-
def scan_call(
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
61
|
+
def scan_call(call_info)
|
62
|
+
call_description = call_info[:call_description]
|
63
|
+
# TODO: Lazy check
|
64
|
+
match = call_description && call_pattern.map { |p| p.match(call_description) }.compact.first
|
65
|
+
if match
|
66
|
+
call = extract_groups(match)
|
67
|
+
# Update file entry with expanded path
|
68
|
+
call[:file] = find_file(call[:file]) if call[:file]
|
69
|
+
|
70
|
+
call_info[:call] = call
|
66
71
|
end
|
67
72
|
|
68
|
-
|
73
|
+
call_info
|
69
74
|
end
|
70
75
|
|
71
76
|
# Find file with specified file name in symbols dir
|
72
|
-
# Can return multiple files if name was
|
77
|
+
# Can return multiple files if name was ambiguous
|
73
78
|
def find_file(file)
|
74
|
-
|
75
|
-
#
|
76
|
-
|
77
|
-
|
78
|
-
files =
|
79
|
+
file_name = File.basename(file)
|
80
|
+
# Firstly we'll drop obvious mismatches where basename of file differs
|
81
|
+
candidates = @build_files.select { |path| File.basename(path) == file_name }
|
82
|
+
# In case when got ambiguous files return all try to find closest match
|
83
|
+
files = find_closest_files(file, candidates)
|
79
84
|
|
80
85
|
# If has only option return first
|
81
86
|
return files.first if files.size == 1
|
82
87
|
# Return original file if files empty
|
83
88
|
return file if files.empty?
|
84
89
|
|
85
|
-
#
|
86
|
-
|
90
|
+
files # Return all files if many matched
|
91
|
+
end
|
92
|
+
|
93
|
+
# Select from candidates list such files
|
94
|
+
# that ends with maximum substring of file
|
95
|
+
# @param [String] file file path to match
|
96
|
+
# @param [Array<String>] candidates list of candidates path
|
97
|
+
# @return [Array<String>] list of files with maximum length matches
|
98
|
+
def find_closest_files(file, candidates)
|
99
|
+
candidates.inject([[], 0]) do |acc, elem|
|
100
|
+
# Current element score is length of longest common postfix
|
101
|
+
elem_score = file.longest_common_postfix(elem).length
|
102
|
+
|
103
|
+
# Unpack accumulator as (list_of_matched_files, max_score)
|
104
|
+
matched, score = acc
|
105
|
+
# Will update if only have better score
|
106
|
+
if elem_score >= score
|
107
|
+
# Current score more than last known score, so now
|
108
|
+
# we drop all previous results and replace them with
|
109
|
+
# current element
|
110
|
+
matched = [] if elem_score > score
|
111
|
+
score = elem_score
|
112
|
+
# Update list of matched
|
113
|
+
matched << elem
|
114
|
+
end
|
115
|
+
|
116
|
+
[matched, score]
|
117
|
+
end.first
|
87
118
|
end
|
88
119
|
|
89
120
|
def extract_groups(match)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Tracetool
|
2
|
+
# Set of utility methods for working with strings
|
3
|
+
module StringUtils
|
4
|
+
# Extended string class
|
5
|
+
# rubocop:disable Style/ClassAndModuleChildren
|
6
|
+
class ::String
|
7
|
+
# Return longest common postfix
|
8
|
+
# @param [String] other other string to match
|
9
|
+
# @return [String] longest common postfix
|
10
|
+
def longest_common_postfix(other)
|
11
|
+
sidx = length - 1
|
12
|
+
oidx = other.length - 1
|
13
|
+
|
14
|
+
while sidx >= 0 && oidx >= 0 && (self[sidx] == other[oidx])
|
15
|
+
sidx -= 1
|
16
|
+
oidx -= 1
|
17
|
+
end
|
18
|
+
|
19
|
+
other[(oidx + 1)..-1]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
# rubocop:enable Style/ClassAndModuleChildren
|
23
|
+
end
|
24
|
+
end
|
data/lib/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tracetool
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ilya.arkhanhelsky
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-01-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: powerpack
|
@@ -43,6 +43,7 @@ files:
|
|
43
43
|
- lib/tracetool/utils/env.rb
|
44
44
|
- lib/tracetool/utils/parser.rb
|
45
45
|
- lib/tracetool/utils/pipe.rb
|
46
|
+
- lib/tracetool/utils/string.rb
|
46
47
|
- lib/tracetool_cli.rb
|
47
48
|
- lib/version.rb
|
48
49
|
homepage: https://github.com/vizor-games/tracetool
|
@@ -65,7 +66,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
65
66
|
version: '0'
|
66
67
|
requirements: []
|
67
68
|
rubyforge_project:
|
68
|
-
rubygems_version: 2.
|
69
|
+
rubygems_version: 2.7.8
|
69
70
|
signing_key:
|
70
71
|
specification_version: 4
|
71
72
|
summary: Tracetool
|