pykdebugparser 1.3.0__py2.py3-none-any.whl → 1.5.0__py2.py3-none-any.whl
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.
- pykdebugparser/highlight.py +134 -0
- pykdebugparser/pykdebugparser.py +8 -5
- pykdebugparser/trace.codes +389 -15
- pykdebugparser/trace_handlers/bsd.py +603 -20
- pykdebugparser/trace_handlers/dyld.py +119 -17
- pykdebugparser/trace_handlers/fsystem.py +43 -1
- pykdebugparser/trace_handlers/mach.py +129 -1
- pykdebugparser/trace_handlers/trace.py +18 -0
- pykdebugparser/traces_parser.py +33 -1
- {pykdebugparser-1.3.0.dist-info → pykdebugparser-1.5.0.dist-info}/METADATA +1 -1
- pykdebugparser-1.5.0.dist-info/RECORD +25 -0
- pykdebugparser-1.3.0.dist-info/RECORD +0 -24
- {pykdebugparser-1.3.0.dist-info → pykdebugparser-1.5.0.dist-info}/WHEEL +0 -0
- {pykdebugparser-1.3.0.dist-info → pykdebugparser-1.5.0.dist-info}/entry_points.txt +0 -0
- {pykdebugparser-1.3.0.dist-info → pykdebugparser-1.5.0.dist-info}/licenses/LICENSE +0 -0
- {pykdebugparser-1.3.0.dist-info → pykdebugparser-1.5.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Syntax highlighting of trace lines, as pygments' C lexer and `stata-dark` true color formatter do it.
|
|
2
|
+
|
|
3
|
+
pygments lexes each line through a general state machine, which formats about 19k lines/s: too slow for a live
|
|
4
|
+
trace. Trace lines use a small part of C's syntax, so `highlight` tokenizes them with one regular expression that
|
|
5
|
+
follows the same rules, and produces the same escape sequences. A line with anything outside that part (a label at its
|
|
6
|
+
start, `;` or `{`, comments, character literals, string escapes, floating-point numbers, ...) goes to pygments, so the
|
|
7
|
+
result is always pygments' own.
|
|
8
|
+
"""
|
|
9
|
+
import re
|
|
10
|
+
from functools import lru_cache
|
|
11
|
+
|
|
12
|
+
from pygments import highlight as pygments_highlight
|
|
13
|
+
from pygments import lexers
|
|
14
|
+
from pygments.formatters import TerminalTrueColorFormatter
|
|
15
|
+
from pygments.token import Keyword, Name, Number, Operator, Punctuation, String, Whitespace
|
|
16
|
+
|
|
17
|
+
c_lexer = lexers.CLexer()
|
|
18
|
+
color_formatter = TerminalTrueColorFormatter(style='stata-dark')
|
|
19
|
+
|
|
20
|
+
# pygments.lexers.c_cpp.CFamilyLexer's 'keywords', 'types' and builtins, the names it does not lex as `Name`.
|
|
21
|
+
_KEYWORDS = {
|
|
22
|
+
'asm', 'auto', 'break', 'const', 'constexpr', 'continue', 'countof', 'default', 'defer', 'do', 'else', 'enum',
|
|
23
|
+
'extern', 'for', 'goto', 'if', 'register', 'restricted', 'return', 'sizeof', 'static', 'switch', 'typedef',
|
|
24
|
+
'typeof', 'typeof_unqual', 'volatile', 'while', 'thread_local', 'alignas', 'alignof', 'static_assert', '_Pragma',
|
|
25
|
+
'fortran',
|
|
26
|
+
}
|
|
27
|
+
_RESERVED = {'inline', '_inline', '__inline', 'naked', 'restrict', 'thread', '__int8', '__int16', '__int32',
|
|
28
|
+
'__int64', '__wchar_t', '__m128i', '__m128d', '__m128', '__m64'} | {
|
|
29
|
+
'__' + name for name in ('asm', 'based', 'except', 'stdcall', 'cdecl', 'fastcall', 'declspec', 'finally', 'try',
|
|
30
|
+
'leave', 'w64', 'unaligned', 'raise', 'noop', 'identifier', 'forceinline', 'assume',
|
|
31
|
+
'null')}
|
|
32
|
+
_TYPES = {'bool', 'int', 'long', 'float', 'short', 'double', 'char', 'unsigned', 'signed', 'void', '_BitInt',
|
|
33
|
+
'__int128'}
|
|
34
|
+
_BUILTINS = {'true', 'false', 'NULL', 'nullptr'}
|
|
35
|
+
# Names that start another lexer state, or a string with an affix (`L"..."`): left to pygments.
|
|
36
|
+
_STATEFUL = {'struct', 'union', 'case', 'L', 'u', 'U', 'u8'}
|
|
37
|
+
|
|
38
|
+
_INT_SUFFIX = r'(?:[uU][lL]{0,2}|[lL]{1,2}[uU]?)?'
|
|
39
|
+
_DEC = r'\d(?:\'?\d)*'
|
|
40
|
+
_HEX = r'[0-9a-fA-F](?:\'?[0-9a-fA-F])*'
|
|
41
|
+
# The alternatives are tried in the lexer's order at each token start. `unsupported` marks what pygments lexes
|
|
42
|
+
# differently from this tokenizer (comments and floating-point numbers); characters no alternative matches (`'`, `\\`,
|
|
43
|
+
# `#`, `$`, a string with an escape, ...) also send the line to pygments.
|
|
44
|
+
_TOKEN = re.compile(
|
|
45
|
+
r'(?P<unsupported>/[/*]'
|
|
46
|
+
r'|0[xX](?:' + _HEX + r'\.' + _HEX + r'|\.' + _HEX + r'|' + _HEX + r')[pP]'
|
|
47
|
+
r'|-?(?:' + _DEC + r'\.' + _DEC + r'|\.' + _DEC + r'|' + _DEC + r')[eE][+-]?' + _DEC +
|
|
48
|
+
r'|-?(?:' + _DEC + r'\.|\.' + _DEC + r'|' + _DEC + r'[fFlL])'
|
|
49
|
+
r')'
|
|
50
|
+
r'|(?P<string>")(?P<content>[^\\"\n]*)(?P<close>")'
|
|
51
|
+
r'|(?P<hex>-?0[xX]' + _HEX + _INT_SUFFIX + r')'
|
|
52
|
+
r'|(?P<bin>-?0[bB][01](?:\'?[01])*' + _INT_SUFFIX + r')'
|
|
53
|
+
r'|(?P<oct>-?0(?:\'?[0-7])+' + _INT_SUFFIX + r')'
|
|
54
|
+
r'|(?P<int>-?' + _DEC + _INT_SUFFIX + r')'
|
|
55
|
+
r'|(?P<op>[~!%^&*+=|?:<>/-])'
|
|
56
|
+
r'|(?P<punct>[()\[\],.])'
|
|
57
|
+
r'|(?P<name>[a-zA-Z_]\w*)'
|
|
58
|
+
r'|(?P<ws>[^\S\n]+)'
|
|
59
|
+
)
|
|
60
|
+
# Whole-line rules of the lexer: a label at the start of a line, and functions (their rules need a `;` or `{`).
|
|
61
|
+
_UNSUPPORTED = re.compile(r'^\s*[a-zA-Z_$]\w*\s*:(?!:)|[;{]')
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _style(ttype):
|
|
65
|
+
# The formatter uses the style of the token type, or of its closest parent that has one.
|
|
66
|
+
while str(ttype) not in color_formatter.style_string and ttype.parent is not None:
|
|
67
|
+
ttype = ttype.parent
|
|
68
|
+
return color_formatter.style_string.get(str(ttype), ('', ''))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
_GROUPS = {'hex': _style(Number.Hex), 'bin': _style(Number.Bin), 'oct': _style(Number.Oct),
|
|
72
|
+
'int': _style(Number.Integer), 'op': _style(Operator), 'punct': _style(Punctuation)}
|
|
73
|
+
_NAME_STYLES = {}
|
|
74
|
+
for _names, _ttype in ((_KEYWORDS, Keyword), (_RESERVED, Keyword.Reserved), (_TYPES, Keyword.Type),
|
|
75
|
+
(_BUILTINS, Name.Builtin)):
|
|
76
|
+
for _name in _names:
|
|
77
|
+
_NAME_STYLES[_name] = _style(_ttype)
|
|
78
|
+
_NAME = _style(Name)
|
|
79
|
+
_STRING = _style(String)
|
|
80
|
+
# Whitespace only gets a foreground color, which a space does not show: it can stay in the style before it.
|
|
81
|
+
_FG_RESET = '\x1b[39m'
|
|
82
|
+
assert _style(Whitespace)[1] == _FG_RESET
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _pygments(text: str) -> str:
|
|
86
|
+
return pygments_highlight(text, c_lexer, color_formatter).strip()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# Traces repeat (the same calls with the same arguments): in a busy `--bsc` trace, 77% of the lines were among the
|
|
90
|
+
# 16k most recent ones.
|
|
91
|
+
@lru_cache(maxsize=1 << 14)
|
|
92
|
+
def highlight(text: str) -> str:
|
|
93
|
+
"""`text` colored as `pygments.highlight(text, CLexer(), TerminalTrueColorFormatter('stata-dark'))` shows it.
|
|
94
|
+
|
|
95
|
+
pygments starts and ends the escape sequence of every token, even between tokens of the same color, and colors
|
|
96
|
+
spaces. Here a sequence starts only where the color changes, which renders the same in about half the bytes (the
|
|
97
|
+
terminal is the bottleneck of a busy trace).
|
|
98
|
+
"""
|
|
99
|
+
if _UNSUPPORTED.search(text):
|
|
100
|
+
return _pygments(text)
|
|
101
|
+
out = []
|
|
102
|
+
style = None # the (on, off) in effect
|
|
103
|
+
pos = 0
|
|
104
|
+
for match in _TOKEN.finditer(text):
|
|
105
|
+
if match.start() != pos:
|
|
106
|
+
return _pygments(text)
|
|
107
|
+
pos = match.end()
|
|
108
|
+
kind = match.lastgroup
|
|
109
|
+
if kind == 'ws':
|
|
110
|
+
out.append(match.group())
|
|
111
|
+
continue
|
|
112
|
+
if kind == 'unsupported':
|
|
113
|
+
return _pygments(text)
|
|
114
|
+
if kind == 'close':
|
|
115
|
+
token_style = _STRING
|
|
116
|
+
elif kind == 'name':
|
|
117
|
+
name = match.group()
|
|
118
|
+
if name in _STATEFUL:
|
|
119
|
+
return _pygments(text)
|
|
120
|
+
token_style = _NAME_STYLES.get(name, _NAME)
|
|
121
|
+
else:
|
|
122
|
+
token_style = _GROUPS[kind]
|
|
123
|
+
if style is None or token_style[0] != style[0]:
|
|
124
|
+
# Ending a foreground color is not needed when the next one replaces it.
|
|
125
|
+
if style is not None and style[1] != _FG_RESET:
|
|
126
|
+
out.append(style[1])
|
|
127
|
+
out.append(token_style[0])
|
|
128
|
+
style = token_style
|
|
129
|
+
out.append(match.group())
|
|
130
|
+
if pos != len(text):
|
|
131
|
+
return _pygments(text)
|
|
132
|
+
if style is not None:
|
|
133
|
+
out.append(style[1])
|
|
134
|
+
return ''.join(out).strip()
|
pykdebugparser/pykdebugparser.py
CHANGED
|
@@ -2,18 +2,17 @@ from datetime import datetime
|
|
|
2
2
|
import io
|
|
3
3
|
import math
|
|
4
4
|
|
|
5
|
-
from pygments import highlight, lexers, formatters
|
|
6
5
|
from termcolor import colored
|
|
7
6
|
|
|
8
7
|
from pykdebugparser.callstacks_parser import CallstacksParser
|
|
8
|
+
from pykdebugparser.highlight import highlight
|
|
9
9
|
from pykdebugparser.kd_buf_parser import KdBufParser
|
|
10
10
|
from pykdebugparser.kevent import DgbFuncQual
|
|
11
11
|
from pykdebugparser.trace_codes import default_trace_codes
|
|
12
|
+
from pykdebugparser.trace_handlers.trace import TRACE_LOST_EVENTS
|
|
12
13
|
from pykdebugparser.traces_parser import TracesParser
|
|
13
14
|
from pykdebugparser.os_log_event import OsLogEvent
|
|
14
15
|
|
|
15
|
-
c_lexer = lexers.CLexer()
|
|
16
|
-
color_formatter = formatters.TerminalTrueColorFormatter(style='stata-dark')
|
|
17
16
|
|
|
18
17
|
DBG_TRACE = 7
|
|
19
18
|
DBG_FSYSTEM = 3
|
|
@@ -27,6 +26,7 @@ class PyKdebugParser:
|
|
|
27
26
|
self.filter_class = []
|
|
28
27
|
self.filter_subclass = []
|
|
29
28
|
# Event ids (debugids without the function qualifier) to include, on top of the class/subclass filters.
|
|
29
|
+
# Those without a handler are shown by name and raw values.
|
|
30
30
|
self.filter_eventid = set()
|
|
31
31
|
self.show_timestamp = True
|
|
32
32
|
self.show_name = True
|
|
@@ -86,12 +86,15 @@ class PyKdebugParser:
|
|
|
86
86
|
self.filter_class.append(DBG_FSYSTEM)
|
|
87
87
|
|
|
88
88
|
traces_parser = TracesParser(trace_codes_map, self.threads_pids, self.pids_names)
|
|
89
|
+
traces_parser.raw_eventids = set(self.filter_eventid)
|
|
89
90
|
trace_generator = traces_parser.feed_generator(self.kevents(kdebug))
|
|
90
91
|
|
|
91
92
|
if self.filter_process is not None:
|
|
92
93
|
trace_generator = filter(self._filter_process_callback, trace_generator)
|
|
93
94
|
if add_trace_class:
|
|
94
|
-
|
|
95
|
+
# Lost events stay: they tell the reader the trace has a gap.
|
|
96
|
+
trace_generator = filter(lambda t: t.ktraces[0].eventid >> 24 != DBG_TRACE
|
|
97
|
+
or t.ktraces[0].eventid == TRACE_LOST_EVENTS, trace_generator)
|
|
95
98
|
if add_fs_class:
|
|
96
99
|
trace_generator = filter(lambda t: t.ktraces[0].eventid >> 24 != DBG_FSYSTEM, trace_generator)
|
|
97
100
|
return trace_generator
|
|
@@ -185,7 +188,7 @@ class PyKdebugParser:
|
|
|
185
188
|
formatted_data += f'{self._format_process(tid):<34}'
|
|
186
189
|
event_rep = str(trace)
|
|
187
190
|
if self.color:
|
|
188
|
-
event_rep = highlight(event_rep
|
|
191
|
+
event_rep = highlight(event_rep)
|
|
189
192
|
|
|
190
193
|
return formatted_data + event_rep
|
|
191
194
|
|