pykdebugparser 1.4.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 +6 -5
- pykdebugparser/trace_handlers/bsd.py +114 -0
- 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 +13 -0
- {pykdebugparser-1.4.0.dist-info → pykdebugparser-1.5.0.dist-info}/METADATA +1 -1
- pykdebugparser-1.5.0.dist-info/RECORD +25 -0
- pykdebugparser-1.4.0.dist-info/RECORD +0 -24
- {pykdebugparser-1.4.0.dist-info → pykdebugparser-1.5.0.dist-info}/WHEEL +0 -0
- {pykdebugparser-1.4.0.dist-info → pykdebugparser-1.5.0.dist-info}/entry_points.txt +0 -0
- {pykdebugparser-1.4.0.dist-info → pykdebugparser-1.5.0.dist-info}/licenses/LICENSE +0 -0
- {pykdebugparser-1.4.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
|
|
@@ -93,7 +92,9 @@ class PyKdebugParser:
|
|
|
93
92
|
if self.filter_process is not None:
|
|
94
93
|
trace_generator = filter(self._filter_process_callback, trace_generator)
|
|
95
94
|
if add_trace_class:
|
|
96
|
-
|
|
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)
|
|
97
98
|
if add_fs_class:
|
|
98
99
|
trace_generator = filter(lambda t: t.ktraces[0].eventid >> 24 != DBG_FSYSTEM, trace_generator)
|
|
99
100
|
return trace_generator
|
|
@@ -187,7 +188,7 @@ class PyKdebugParser:
|
|
|
187
188
|
formatted_data += f'{self._format_process(tid):<34}'
|
|
188
189
|
event_rep = str(trace)
|
|
189
190
|
if self.color:
|
|
190
|
-
event_rep = highlight(event_rep
|
|
191
|
+
event_rep = highlight(event_rep)
|
|
191
192
|
|
|
192
193
|
return formatted_data + event_rep
|
|
193
194
|
|
|
@@ -378,11 +378,18 @@ def serialize_stat_flags(flags: int) -> List[StatFlags]:
|
|
|
378
378
|
return stat_flags
|
|
379
379
|
|
|
380
380
|
|
|
381
|
+
# bsd/sys/errno.h: codes the kernel uses internally, which a syscall end event can carry.
|
|
382
|
+
KERNEL_ERRNOS = {-1: 'ERESTART', -2: 'EJUSTRETURN'}
|
|
383
|
+
|
|
384
|
+
|
|
381
385
|
def serialize_result(end_event, success_name='', fmt=lambda x: x) -> str:
|
|
382
386
|
error_code = end_event.values[0]
|
|
383
387
|
res = end_event.values[1]
|
|
388
|
+
signed = ctypes.c_int64(error_code).value
|
|
384
389
|
if error_code in errno.errorcode:
|
|
385
390
|
err = f'errno: {errno.errorcode[error_code]}({error_code})'
|
|
391
|
+
elif signed in KERNEL_ERRNOS:
|
|
392
|
+
err = f'errno: {KERNEL_ERRNOS[signed]}({signed})'
|
|
386
393
|
else:
|
|
387
394
|
err = f'errno: {error_code}'
|
|
388
395
|
success = f'{success_name}: {fmt(res)}' if success_name else ''
|
|
@@ -2823,6 +2830,55 @@ class BscGeteuid:
|
|
|
2823
2830
|
return f'geteuid(), uid: {self.uid}'
|
|
2824
2831
|
|
|
2825
2832
|
|
|
2833
|
+
# Darwin's signal numbers, which differ from Linux's.
|
|
2834
|
+
DARWIN_SIGNALS = (None, 'SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGILL', 'SIGTRAP', 'SIGABRT', 'SIGEMT', 'SIGFPE', 'SIGKILL',
|
|
2835
|
+
'SIGBUS', 'SIGSEGV', 'SIGSYS', 'SIGPIPE', 'SIGALRM', 'SIGTERM', 'SIGURG', 'SIGSTOP', 'SIGTSTP',
|
|
2836
|
+
'SIGCONT', 'SIGCHLD', 'SIGTTIN', 'SIGTTOU', 'SIGIO', 'SIGXCPU', 'SIGXFSZ', 'SIGVTALRM', 'SIGPROF',
|
|
2837
|
+
'SIGWINCH', 'SIGINFO', 'SIGUSR1', 'SIGUSR2')
|
|
2838
|
+
|
|
2839
|
+
|
|
2840
|
+
def format_wait_status(status: int) -> str:
|
|
2841
|
+
"""Describe a wait status (`W_EXITCODE(code, signal)`) as its exit code or terminating signal."""
|
|
2842
|
+
signal = status & 0x7f
|
|
2843
|
+
if not signal:
|
|
2844
|
+
return f'exit code: {(status >> 8) & 0xff}'
|
|
2845
|
+
name = DARWIN_SIGNALS[signal] if signal < len(DARWIN_SIGNALS) else str(signal)
|
|
2846
|
+
return f'signal: {name}' + (', core dumped' if status & 0x80 else '')
|
|
2847
|
+
|
|
2848
|
+
|
|
2849
|
+
@dataclass
|
|
2850
|
+
class ProcExit:
|
|
2851
|
+
"""A process exiting (`proc_exit`). An exec also ends the process image it replaces: xnu gives the new image a new
|
|
2852
|
+
proc and exits the old one, with the same pid and status 0."""
|
|
2853
|
+
ktraces: List
|
|
2854
|
+
pid: int
|
|
2855
|
+
status: int
|
|
2856
|
+
|
|
2857
|
+
def __str__(self):
|
|
2858
|
+
return f'proc_exit, pid: {self.pid}, {format_wait_status(self.status)}'
|
|
2859
|
+
|
|
2860
|
+
|
|
2861
|
+
@dataclass
|
|
2862
|
+
class BscExit:
|
|
2863
|
+
ktraces: List
|
|
2864
|
+
rval: int
|
|
2865
|
+
|
|
2866
|
+
def __str__(self):
|
|
2867
|
+
return f'exit({self.rval})'
|
|
2868
|
+
|
|
2869
|
+
|
|
2870
|
+
@dataclass
|
|
2871
|
+
class BscBsdthreadTerminate:
|
|
2872
|
+
ktraces: List
|
|
2873
|
+
stackaddr: int
|
|
2874
|
+
freesize: int
|
|
2875
|
+
port: int
|
|
2876
|
+
sem: int
|
|
2877
|
+
|
|
2878
|
+
def __str__(self):
|
|
2879
|
+
return f'bsdthread_terminate({hex(self.stackaddr)}, {hex(self.freesize)}, {self.port}, {self.sem})'
|
|
2880
|
+
|
|
2881
|
+
|
|
2826
2882
|
@dataclass
|
|
2827
2883
|
class BscWait4:
|
|
2828
2884
|
ktraces: List
|
|
@@ -2970,6 +3026,35 @@ class BscSync:
|
|
|
2970
3026
|
return 'sync()'
|
|
2971
3027
|
|
|
2972
3028
|
|
|
3029
|
+
@dataclass
|
|
3030
|
+
class BscPthreadKill:
|
|
3031
|
+
ktraces: List
|
|
3032
|
+
thread_port: int
|
|
3033
|
+
sig: int
|
|
3034
|
+
result: str
|
|
3035
|
+
|
|
3036
|
+
def __str__(self):
|
|
3037
|
+
rep = f'pthread_kill({self.thread_port}, {self.sig})'
|
|
3038
|
+
if self.result:
|
|
3039
|
+
rep += f', {self.result}'
|
|
3040
|
+
return rep
|
|
3041
|
+
|
|
3042
|
+
|
|
3043
|
+
@dataclass
|
|
3044
|
+
class BscSigreturn:
|
|
3045
|
+
ktraces: List
|
|
3046
|
+
uctx: int
|
|
3047
|
+
infostyle: int
|
|
3048
|
+
token: int
|
|
3049
|
+
result: str
|
|
3050
|
+
|
|
3051
|
+
def __str__(self):
|
|
3052
|
+
rep = f'sigreturn({hex(self.uctx)}, {self.infostyle}, {hex(self.token)})'
|
|
3053
|
+
if self.result:
|
|
3054
|
+
rep += f', {self.result}'
|
|
3055
|
+
return rep
|
|
3056
|
+
|
|
3057
|
+
|
|
2973
3058
|
@dataclass
|
|
2974
3059
|
class BscKill:
|
|
2975
3060
|
ktraces: List
|
|
@@ -5384,6 +5469,20 @@ def handle_geteuid(parser, events):
|
|
|
5384
5469
|
return BscGeteuid(events, events[-1].values[1])
|
|
5385
5470
|
|
|
5386
5471
|
|
|
5472
|
+
def handle_proc_exit(parser, events):
|
|
5473
|
+
args = events[0].values
|
|
5474
|
+
return ProcExit(events, args[0], ctypes.c_int32(args[1]).value)
|
|
5475
|
+
|
|
5476
|
+
|
|
5477
|
+
def handle_exit(parser, events):
|
|
5478
|
+
return BscExit(events, ctypes.c_int32(events[0].values[0]).value)
|
|
5479
|
+
|
|
5480
|
+
|
|
5481
|
+
def handle_bsdthread_terminate(parser, events):
|
|
5482
|
+
args = events[0].values
|
|
5483
|
+
return BscBsdthreadTerminate(events, args[0], args[1], args[2] & 0xffffffff, args[3] & 0xffffffff)
|
|
5484
|
+
|
|
5485
|
+
|
|
5387
5486
|
def handle_wait4(parser, events, no_cancel=False):
|
|
5388
5487
|
args = events[0].values
|
|
5389
5488
|
return BscWait4(events, args[0], args[1], args[2], args[3], serialize_result(events[-1], 'pid'), no_cancel)
|
|
@@ -5438,6 +5537,16 @@ def handle_sync(parser, events):
|
|
|
5438
5537
|
return BscSync(events)
|
|
5439
5538
|
|
|
5440
5539
|
|
|
5540
|
+
def handle_pthread_kill(parser, events):
|
|
5541
|
+
args = events[0].values
|
|
5542
|
+
return BscPthreadKill(events, args[0] & 0xffffffff, args[1], serialize_result(events[-1]))
|
|
5543
|
+
|
|
5544
|
+
|
|
5545
|
+
def handle_sigreturn(parser, events):
|
|
5546
|
+
args = events[0].values
|
|
5547
|
+
return BscSigreturn(events, args[0], args[1], args[2], serialize_result(events[-1]))
|
|
5548
|
+
|
|
5549
|
+
|
|
5441
5550
|
def handle_kill(parser, events):
|
|
5442
5551
|
return BscKill(events, events[0].values[0], events[0].values[1], serialize_result(events[-1]))
|
|
5443
5552
|
|
|
@@ -7133,6 +7242,8 @@ handlers = {
|
|
|
7133
7242
|
'BSC_fchflags': handle_fchflags,
|
|
7134
7243
|
'BSC_sync': handle_sync,
|
|
7135
7244
|
'BSC_kill': handle_kill,
|
|
7245
|
+
'BSC_pthread_kill': handle_pthread_kill,
|
|
7246
|
+
'BSC_sigreturn': handle_sigreturn,
|
|
7136
7247
|
'BSC_getppid': handle_getppid,
|
|
7137
7248
|
'BSC_sys_dup': handle_sys_dup,
|
|
7138
7249
|
'BSC_pipe': handle_pipe,
|
|
@@ -7472,6 +7583,9 @@ handlers = {
|
|
|
7472
7583
|
'BSC_sys_crossarch_trap': handle_sys_crossarch_trap,
|
|
7473
7584
|
'BSC_map_with_linking_np': handle_map_with_linking_np,
|
|
7474
7585
|
'BSC_wait4': handle_wait4,
|
|
7586
|
+
'proc_exit': handle_proc_exit,
|
|
7587
|
+
'BSC_exit': handle_exit,
|
|
7588
|
+
'BSC_bsdthread_terminate': handle_bsdthread_terminate,
|
|
7475
7589
|
'BSC_mknodat': handle_mknodat,
|
|
7476
7590
|
'BSC_mkfifoat': handle_mkfifoat,
|
|
7477
7591
|
'BSC_freadlink': handle_freadlink,
|
|
@@ -18,6 +18,10 @@ def to_rtld_flags(flags: int):
|
|
|
18
18
|
return [r for r in RtldFlag if r.value & flags]
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
def _image(path: str) -> str:
|
|
22
|
+
return f', image: "{path}"' if path else ''
|
|
23
|
+
|
|
24
|
+
|
|
21
25
|
@dataclass
|
|
22
26
|
class DyldUuidMapA:
|
|
23
27
|
ktraces: List
|
|
@@ -34,9 +38,11 @@ class DyldUuidMapB:
|
|
|
34
38
|
ktraces: List
|
|
35
39
|
fid_objno: int
|
|
36
40
|
fid_generation: int
|
|
41
|
+
path: str = ''
|
|
37
42
|
|
|
38
43
|
def __str__(self):
|
|
39
|
-
|
|
44
|
+
rep = f'DYLD_uuid_map_b, fid_objno: {self.fid_objno}, fid_generation: {hex(self.fid_generation)}'
|
|
45
|
+
return rep + f', path: "{self.path}"' if self.path else rep
|
|
40
46
|
|
|
41
47
|
|
|
42
48
|
@dataclass
|
|
@@ -55,9 +61,11 @@ class DyldUuidUnmapB:
|
|
|
55
61
|
ktraces: List
|
|
56
62
|
fid_objno: int
|
|
57
63
|
fid_generation: int
|
|
64
|
+
path: str = ''
|
|
58
65
|
|
|
59
66
|
def __str__(self):
|
|
60
|
-
|
|
67
|
+
rep = f'DYLD_uuid_unmap_b, fid_objno: {self.fid_objno}, fid_generation: {hex(self.fid_generation)}'
|
|
68
|
+
return rep + f', path: "{self.path}"' if self.path else rep
|
|
61
69
|
|
|
62
70
|
|
|
63
71
|
@dataclass
|
|
@@ -77,9 +85,11 @@ class DyldUuidSharedCacheB:
|
|
|
77
85
|
ktraces: List
|
|
78
86
|
fid_objno: int
|
|
79
87
|
fid_generation: int
|
|
88
|
+
path: str = ''
|
|
80
89
|
|
|
81
90
|
def __str__(self):
|
|
82
|
-
|
|
91
|
+
rep = f'DYLD_uuid_shared_cache_b, fid_objno: {self.fid_objno}, fid_generation: {hex(self.fid_generation)}'
|
|
92
|
+
return rep + f', path: "{self.path}"' if self.path else rep
|
|
83
93
|
|
|
84
94
|
|
|
85
95
|
@dataclass
|
|
@@ -106,9 +116,53 @@ class DyldFuncForAddImage:
|
|
|
106
116
|
ktraces: List
|
|
107
117
|
addr: int
|
|
108
118
|
func: int
|
|
119
|
+
image: str = ''
|
|
120
|
+
|
|
121
|
+
def __str__(self):
|
|
122
|
+
return f'DBG_DYLD_TIMING_FUNC_FOR_ADD_IMAGE, addr: {hex(self.addr)}{_image(self.image)}, func: {hex(self.func)}'
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass
|
|
126
|
+
class DyldFuncForRemoveImage:
|
|
127
|
+
ktraces: List
|
|
128
|
+
addr: int
|
|
129
|
+
func: int
|
|
130
|
+
image: str = ''
|
|
131
|
+
|
|
132
|
+
def __str__(self):
|
|
133
|
+
return (f'DBG_DYLD_TIMING_FUNC_FOR_REMOVE_IMAGE, addr: {hex(self.addr)}{_image(self.image)}, '
|
|
134
|
+
f'func: {hex(self.func)}')
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@dataclass
|
|
138
|
+
class DyldStaticInitializer:
|
|
139
|
+
ktraces: List
|
|
140
|
+
mh: int
|
|
141
|
+
func: int
|
|
142
|
+
image: str = ''
|
|
143
|
+
|
|
144
|
+
def __str__(self):
|
|
145
|
+
return f'DBG_DYLD_TIMING_STATIC_INITIALIZER, mh: {hex(self.mh)}{_image(self.image)}, func: {hex(self.func)}'
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dataclass
|
|
149
|
+
class DyldObjcInit:
|
|
150
|
+
ktraces: List
|
|
151
|
+
mh: int
|
|
152
|
+
image: str = ''
|
|
153
|
+
|
|
154
|
+
def __str__(self):
|
|
155
|
+
return f'DBG_DYLD_TIMING_OBJC_INIT, mh: {hex(self.mh)}{_image(self.image)}'
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@dataclass
|
|
159
|
+
class DyldTiming:
|
|
160
|
+
"""A dyld phase traced without arguments (applying fixups, mapping ObjC images, ...)."""
|
|
161
|
+
ktraces: List
|
|
162
|
+
name: str
|
|
109
163
|
|
|
110
164
|
def __str__(self):
|
|
111
|
-
return
|
|
165
|
+
return self.name
|
|
112
166
|
|
|
113
167
|
|
|
114
168
|
@dataclass
|
|
@@ -171,34 +225,55 @@ class Dladdr:
|
|
|
171
225
|
return f'dladdr({hex(self.addr)}), ret: {self.ret}'
|
|
172
226
|
|
|
173
227
|
|
|
174
|
-
|
|
228
|
+
# dyld traces each image (un)load as an `_a` event with its UUID and load address, followed on the same thread by a
|
|
229
|
+
# `_b` event with the id of its path string. The parser keeps the loaded images per process, so the events that only
|
|
230
|
+
# carry a mach header (initializers, ObjC init, ...) can name their image.
|
|
231
|
+
|
|
232
|
+
def _image_path(parser, events, mh):
|
|
233
|
+
return parser.dyld_images.get((parser.threads_pids.get(events[0].tid, -1), mh), '')
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _map_a(parser, events, cls):
|
|
175
237
|
args = events[0].values
|
|
176
|
-
|
|
238
|
+
parser.dyld_pending_load_addr[events[0].tid] = args[2]
|
|
239
|
+
return cls(events, UUID(bytes=events[0].data[:16]), args[2], args[3])
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _map_b(parser, events, cls, loaded):
|
|
243
|
+
args = events[0].values
|
|
244
|
+
path = parser.global_strings.get(args[1], '') if args[1] else ''
|
|
245
|
+
load_addr = parser.dyld_pending_load_addr.pop(events[0].tid, None)
|
|
246
|
+
if load_addr is not None:
|
|
247
|
+
key = (parser.threads_pids.get(events[0].tid, -1), load_addr)
|
|
248
|
+
if loaded and path:
|
|
249
|
+
parser.dyld_images[key] = path
|
|
250
|
+
elif not loaded:
|
|
251
|
+
parser.dyld_images.pop(key, None)
|
|
252
|
+
return cls(events, args[0] & 0xffffffff, args[0] >> 32, path)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def handle_uuid_map_a(parser, events):
|
|
256
|
+
return _map_a(parser, events, DyldUuidMapA)
|
|
177
257
|
|
|
178
258
|
|
|
179
259
|
def handle_uuid_map_b(parser, events):
|
|
180
|
-
|
|
181
|
-
return DyldUuidMapB(events, arg & 0xffffffff, arg >> 32)
|
|
260
|
+
return _map_b(parser, events, DyldUuidMapB, loaded=True)
|
|
182
261
|
|
|
183
262
|
|
|
184
263
|
def handle_uuid_unmap_a(parser, events):
|
|
185
|
-
|
|
186
|
-
return DyldUuidUnmapA(events, UUID(bytes=events[0].data[:16]), args[2], args[3])
|
|
264
|
+
return _map_a(parser, events, DyldUuidUnmapA)
|
|
187
265
|
|
|
188
266
|
|
|
189
267
|
def handle_uuid_unmap_b(parser, events):
|
|
190
|
-
|
|
191
|
-
return DyldUuidUnmapB(events, arg & 0xffffffff, arg >> 32)
|
|
268
|
+
return _map_b(parser, events, DyldUuidUnmapB, loaded=False)
|
|
192
269
|
|
|
193
270
|
|
|
194
271
|
def handle_uuid_shared_cache_a(parser, events):
|
|
195
|
-
|
|
196
|
-
return DyldUuidSharedCacheA(events, UUID(bytes=events[0].data[:16]), args[2], args[3])
|
|
272
|
+
return _map_a(parser, events, DyldUuidSharedCacheA)
|
|
197
273
|
|
|
198
274
|
|
|
199
275
|
def handle_uuid_shared_cache_b(parser, events):
|
|
200
|
-
|
|
201
|
-
return DyldUuidSharedCacheB(events, arg & 0xffffffff, arg >> 32)
|
|
276
|
+
return _map_b(parser, events, DyldUuidSharedCacheB, loaded=True)
|
|
202
277
|
|
|
203
278
|
|
|
204
279
|
def handle_timing_launch_executable(parser, events):
|
|
@@ -217,7 +292,26 @@ def handle_timing_map_image(parser, events):
|
|
|
217
292
|
|
|
218
293
|
def handle_timing_func_for_add_image(parser, events):
|
|
219
294
|
args = events[0].values
|
|
220
|
-
return DyldFuncForAddImage(events, args[1], args[2])
|
|
295
|
+
return DyldFuncForAddImage(events, args[1], args[2], _image_path(parser, events, args[1]))
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def handle_timing_func_for_remove_image(parser, events):
|
|
299
|
+
args = events[0].values
|
|
300
|
+
return DyldFuncForRemoveImage(events, args[1], args[2], _image_path(parser, events, args[1]))
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def handle_timing_static_initializer(parser, events):
|
|
304
|
+
args = events[0].values
|
|
305
|
+
return DyldStaticInitializer(events, args[1], args[2], _image_path(parser, events, args[1]))
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def handle_timing_objc_init(parser, events):
|
|
309
|
+
args = events[0].values
|
|
310
|
+
return DyldObjcInit(events, args[1], _image_path(parser, events, args[1]))
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def handle_timing(parser, events):
|
|
314
|
+
return DyldTiming(events, parser.trace_codes[events[0].eventid])
|
|
221
315
|
|
|
222
316
|
|
|
223
317
|
def handle_timing_bootstrap_start(parser, events):
|
|
@@ -257,6 +351,14 @@ handlers = {
|
|
|
257
351
|
'DBG_DYLD_TIMING_LAUNCH_EXECUTABLE': handle_timing_launch_executable,
|
|
258
352
|
'DBG_DYLD_TIMING_MAP_IMAGE': handle_timing_map_image,
|
|
259
353
|
'DBG_DYLD_TIMING_FUNC_FOR_ADD_IMAGE': handle_timing_func_for_add_image,
|
|
354
|
+
'DBG_DYLD_TIMING_FUNC_FOR_REMOVE_IMAGE': handle_timing_func_for_remove_image,
|
|
355
|
+
'DBG_DYLD_TIMING_STATIC_INITIALIZER': handle_timing_static_initializer,
|
|
356
|
+
'DBG_DYLD_TIMING_OBJC_INIT': handle_timing_objc_init,
|
|
357
|
+
'DBG_DYLD_TIMING_OBJC_MAP': handle_timing,
|
|
358
|
+
'DBG_DYLD_TIMING_APPLY_FIXUPS': handle_timing,
|
|
359
|
+
'DBG_DYLD_TIMING_ATTACH_CODESIGNATURE': handle_timing,
|
|
360
|
+
'DBG_DYLD_TIMING_BUILD_CLOSURE': handle_timing,
|
|
361
|
+
'DBG_DYLD_REMOTE_IMAGE_NOTIFIER': handle_timing,
|
|
260
362
|
'DBG_DYLD_TIMING_BOOTSTRAP_START': handle_timing_bootstrap_start,
|
|
261
363
|
'DBG_DYLD_TIMING_DLOPEN': handle_timing_dlopen,
|
|
262
364
|
'DBG_DYLD_TIMING_DLOPEN_PREFLIGHT': handle_timing_dlopen_preflight,
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
from typing import List
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
from pykdebugparser.trace_codes import default_trace_codes
|
|
5
|
+
|
|
6
|
+
DBG_DKRW = 0x302
|
|
7
|
+
# bsd/sys/kdebug.h: the code of a disk I/O event is these flags (`P_RdMetaAsync`, `P_WrDataDone`, ...).
|
|
8
|
+
DKIO_DONE = 0x01
|
|
3
9
|
|
|
4
10
|
|
|
5
11
|
@dataclass
|
|
@@ -12,11 +18,47 @@ class VfsLookup:
|
|
|
12
18
|
return f'lookup("{self.path}"), vnode id: {self.vnode_id}'
|
|
13
19
|
|
|
14
20
|
|
|
21
|
+
@dataclass
|
|
22
|
+
class DiskIo:
|
|
23
|
+
"""A disk I/O, from its issue (device, block, size) to its completion (bytes not transferred, error)."""
|
|
24
|
+
ktraces: List
|
|
25
|
+
name: str
|
|
26
|
+
dev: Optional[int]
|
|
27
|
+
blkno: Optional[int]
|
|
28
|
+
size: Optional[int]
|
|
29
|
+
resid: int
|
|
30
|
+
error: int
|
|
31
|
+
|
|
32
|
+
def __str__(self):
|
|
33
|
+
rep = self.name
|
|
34
|
+
if self.dev is not None:
|
|
35
|
+
rep += f', dev: {self.dev >> 24},{self.dev & 0xffffff}, blkno: {hex(self.blkno)}, size: {self.size}'
|
|
36
|
+
return rep + f', resid: {self.resid}, error: {self.error}'
|
|
37
|
+
|
|
38
|
+
|
|
15
39
|
def handle_vfs_lookup(parser, events):
|
|
16
40
|
node = parser.parse_vnode(events)
|
|
17
41
|
return VfsLookup(events, node.path, node.vnode_id)
|
|
18
42
|
|
|
19
43
|
|
|
44
|
+
def handle_disk_io(parser, events):
|
|
45
|
+
# xnu traces the issue as (buf, dev, blkno, size) and the completion, often on another thread, as
|
|
46
|
+
# (buf, vnode, resid, error); the buf address pairs them. Print one line when the I/O completes.
|
|
47
|
+
event = events[0]
|
|
48
|
+
buf = event.values[0]
|
|
49
|
+
if not (event.eventid >> 2) & DKIO_DONE:
|
|
50
|
+
parser.pending_disk_io[buf] = event
|
|
51
|
+
return None
|
|
52
|
+
start = parser.pending_disk_io.pop(buf, None)
|
|
53
|
+
resid, error = event.values[2], event.values[3]
|
|
54
|
+
if start is None:
|
|
55
|
+
return DiskIo(events, parser.trace_codes[event.eventid].removesuffix('Done'), None, None, None, resid, error)
|
|
56
|
+
args = start.values
|
|
57
|
+
return DiskIo([start, event], parser.trace_codes[start.eventid], args[1] & 0xffffffff, args[2], args[3], resid,
|
|
58
|
+
error)
|
|
59
|
+
|
|
60
|
+
|
|
20
61
|
handlers = {
|
|
21
62
|
'VFS_LOOKUP': handle_vfs_lookup,
|
|
22
63
|
}
|
|
64
|
+
handlers.update({name: handle_disk_io for eventid, name in default_trace_codes().items() if eventid >> 16 == DBG_DKRW})
|
|
@@ -2,10 +2,14 @@ import ctypes
|
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from enum import Enum
|
|
4
4
|
from functools import partial
|
|
5
|
-
from typing import List
|
|
5
|
+
from typing import List, Optional
|
|
6
6
|
|
|
7
|
+
from pykdebugparser.trace_codes import default_trace_codes
|
|
7
8
|
from pykdebugparser.trace_handlers import parse_enum
|
|
8
9
|
|
|
10
|
+
# bsd/sys/kdebug.h: (DBG_MACH << 8) | DBG_MACH_EXCP_SYNC_ARM
|
|
11
|
+
DBG_MACH_EXCP_SYNC_ARM = 0x103
|
|
12
|
+
|
|
9
13
|
|
|
10
14
|
class AsynchronousSystemTrapsReason(Enum):
|
|
11
15
|
AST_NONE = 0x00
|
|
@@ -59,10 +63,13 @@ class ExceptionSyndromeRegisterClass(Enum):
|
|
|
59
63
|
ESR_EC_TRAP_SIMD_FP = 0x07
|
|
60
64
|
ESR_EC_PTRAUTH_INSTR_TRAP = 0x09
|
|
61
65
|
ESR_EC_MCRR_MRRC_CP14_TRAP = 0x0c
|
|
66
|
+
ESR_EC_BTI_FAIL = 0x0d
|
|
62
67
|
ESR_EC_ILLEGAL_INSTR_SET = 0x0e
|
|
63
68
|
ESR_EC_SVC_32 = 0x11
|
|
64
69
|
ESR_EC_SVC_64 = 0x15
|
|
70
|
+
ESR_EC_HVC_64 = 0x16
|
|
65
71
|
ESR_EC_MSR_TRAP = 0x18
|
|
72
|
+
ESR_EC_PAC_FAIL = 0x1c
|
|
66
73
|
ESR_EC_IABORT_EL0 = 0x20
|
|
67
74
|
ESR_EC_IABORT_EL1 = 0x21
|
|
68
75
|
ESR_EC_PC_ALIGN = 0x22
|
|
@@ -281,6 +288,25 @@ class KernelUncategorizedExcArm:
|
|
|
281
288
|
return f'KernelUncategorizedExcArm, class: {esr_class}, far: {hex(self.far)}, pc: {hex(self.pc)}'
|
|
282
289
|
|
|
283
290
|
|
|
291
|
+
@dataclass
|
|
292
|
+
class ArmException:
|
|
293
|
+
"""An arm64 synchronous exception (`sleh.c`): its ESR, fault address and PC, for the classes without their own
|
|
294
|
+
trace class (breakpoints, `brk` traps, pointer authentication failures, alignment faults, ...)."""
|
|
295
|
+
ktraces: List
|
|
296
|
+
name: str
|
|
297
|
+
esr: int
|
|
298
|
+
far: int
|
|
299
|
+
pc: int
|
|
300
|
+
|
|
301
|
+
def __str__(self):
|
|
302
|
+
esr_class = self.esr >> 26
|
|
303
|
+
try:
|
|
304
|
+
esr_class = ExceptionSyndromeRegisterClass(esr_class).name
|
|
305
|
+
except ValueError:
|
|
306
|
+
pass
|
|
307
|
+
return f'{self.name}, class: {esr_class}, far: {hex(self.far)}, pc: {hex(self.pc)}'
|
|
308
|
+
|
|
309
|
+
|
|
284
310
|
@dataclass
|
|
285
311
|
class KernelDataAbortSameElExcArm:
|
|
286
312
|
ktraces: List
|
|
@@ -594,6 +620,58 @@ class SemaphoreTimedwait:
|
|
|
594
620
|
return f'semaphore_timedwait({hex(self.wait_name)}, {self.sec}, {self.nsec}), result: {self.result.name}'
|
|
595
621
|
|
|
596
622
|
|
|
623
|
+
@dataclass
|
|
624
|
+
class TaskForPid:
|
|
625
|
+
ktraces: List
|
|
626
|
+
target_tport: int
|
|
627
|
+
pid: int
|
|
628
|
+
t: int
|
|
629
|
+
result: KernReturn
|
|
630
|
+
|
|
631
|
+
def __str__(self):
|
|
632
|
+
return f'task_for_pid({self.target_tport}, {self.pid}, {hex(self.t)}), result: {self.result.name}'
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
@dataclass
|
|
636
|
+
class TaskNameForPid:
|
|
637
|
+
ktraces: List
|
|
638
|
+
target_tport: int
|
|
639
|
+
pid: int
|
|
640
|
+
tn: int
|
|
641
|
+
result: KernReturn
|
|
642
|
+
|
|
643
|
+
def __str__(self):
|
|
644
|
+
return f'task_name_for_pid({self.target_tport}, {self.pid}, {hex(self.tn)}), result: {self.result.name}'
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
@dataclass
|
|
648
|
+
class PidForTask:
|
|
649
|
+
ktraces: List
|
|
650
|
+
task: int
|
|
651
|
+
pid_addr: int
|
|
652
|
+
result: KernReturn
|
|
653
|
+
|
|
654
|
+
def __str__(self):
|
|
655
|
+
return f'pid_for_task({self.task}, {hex(self.pid_addr)}), result: {self.result.name}'
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
@dataclass
|
|
659
|
+
class TaskSuspension:
|
|
660
|
+
"""A task or thread suspended (by `task_suspend`, a debugger, ...) or resumed (osfmk `DBG_MACH_SUSPENSION`)."""
|
|
661
|
+
ktraces: List
|
|
662
|
+
name: str
|
|
663
|
+
target: str
|
|
664
|
+
target_id: int
|
|
665
|
+
user_stop_count: Optional[int] = None
|
|
666
|
+
pidsuspended: Optional[int] = None
|
|
667
|
+
|
|
668
|
+
def __str__(self):
|
|
669
|
+
rep = f'{self.name}, {self.target}: {self.target_id}'
|
|
670
|
+
if self.user_stop_count is not None:
|
|
671
|
+
rep += f', user_stop_count: {self.user_stop_count}, pidsuspended: {bool(self.pidsuspended)}'
|
|
672
|
+
return rep
|
|
673
|
+
|
|
674
|
+
|
|
597
675
|
@dataclass
|
|
598
676
|
class MachPortGetAttributes:
|
|
599
677
|
ktraces: List
|
|
@@ -1017,6 +1095,11 @@ def handle_user_instr_abort_lower_el_exc_arm(parser, events):
|
|
|
1017
1095
|
return UserInstrAbortLowerElExcArm(events, *events[0].values[:3])
|
|
1018
1096
|
|
|
1019
1097
|
|
|
1098
|
+
def handle_arm_exception(parser, events):
|
|
1099
|
+
args = events[0].values
|
|
1100
|
+
return ArmException(events, parser.trace_codes[events[0].eventid], args[0], args[1], args[2])
|
|
1101
|
+
|
|
1102
|
+
|
|
1020
1103
|
def handle_user_data_abort_lower_el_exc_arm(parser, events):
|
|
1021
1104
|
args = events[0].values
|
|
1022
1105
|
return UserDataAbortLowerElExcArm(events, args[0], args[1], args[2])
|
|
@@ -1057,6 +1140,41 @@ def handle_msc_mach_vm_map_trap(parser, events):
|
|
|
1057
1140
|
return MachVmMap(events, *events[0].values)
|
|
1058
1141
|
|
|
1059
1142
|
|
|
1143
|
+
def handle_msc_task_for_pid(parser, events):
|
|
1144
|
+
args = events[0].values
|
|
1145
|
+
return TaskForPid(events, args[0] & 0xffffffff, ctypes.c_int32(args[1]).value, args[2],
|
|
1146
|
+
parse_enum(KernReturn, events[-1].values[0]))
|
|
1147
|
+
|
|
1148
|
+
|
|
1149
|
+
def handle_msc_task_name_for_pid(parser, events):
|
|
1150
|
+
args = events[0].values
|
|
1151
|
+
return TaskNameForPid(events, args[0] & 0xffffffff, ctypes.c_int32(args[1]).value, args[2],
|
|
1152
|
+
parse_enum(KernReturn, events[-1].values[0]))
|
|
1153
|
+
|
|
1154
|
+
|
|
1155
|
+
def handle_msc_pid_for_task(parser, events):
|
|
1156
|
+
args = events[0].values
|
|
1157
|
+
return PidForTask(events, args[0] & 0xffffffff, args[1], parse_enum(KernReturn, events[-1].values[0]))
|
|
1158
|
+
|
|
1159
|
+
|
|
1160
|
+
def handle_task_suspend(parser, events):
|
|
1161
|
+
args = events[0].values
|
|
1162
|
+
return TaskSuspension(events, 'task_suspend', 'pid', args[0], args[1], args[2])
|
|
1163
|
+
|
|
1164
|
+
|
|
1165
|
+
def handle_task_resume(parser, events):
|
|
1166
|
+
return TaskSuspension(events, 'task_resume', 'pid', events[0].values[0])
|
|
1167
|
+
|
|
1168
|
+
|
|
1169
|
+
def handle_thread_suspend(parser, events):
|
|
1170
|
+
args = events[0].values
|
|
1171
|
+
return TaskSuspension(events, 'thread_suspend', 'tid', args[0], args[1], args[2])
|
|
1172
|
+
|
|
1173
|
+
|
|
1174
|
+
def handle_thread_resume(parser, events):
|
|
1175
|
+
return TaskSuspension(events, 'thread_resume', 'tid', events[0].values[0])
|
|
1176
|
+
|
|
1177
|
+
|
|
1060
1178
|
def handle_msc_mach_port_allocate_trap(parser, events):
|
|
1061
1179
|
args = events[0].values
|
|
1062
1180
|
return MachPortAllocate(events, args[0], parse_enum(MachPortRight, args[1]), args[2])
|
|
@@ -1306,6 +1424,13 @@ handlers = {
|
|
|
1306
1424
|
'MSC_semaphore_signal_trap': handle_msc_semaphore_signal_trap,
|
|
1307
1425
|
'MSC_semaphore_wait_trap': handle_msc_semaphore_wait_trap,
|
|
1308
1426
|
'MSC_semaphore_timedwait_trap': handle_msc_semaphore_timedwait_trap,
|
|
1427
|
+
'MSC_task_for_pid': handle_msc_task_for_pid,
|
|
1428
|
+
'MSC_task_name_for_pid': handle_msc_task_name_for_pid,
|
|
1429
|
+
'MSC_pid_for_task': handle_msc_pid_for_task,
|
|
1430
|
+
'MACH_task_suspend': handle_task_suspend,
|
|
1431
|
+
'MACH_task_resume': handle_task_resume,
|
|
1432
|
+
'MACH_thread_suspend': handle_thread_suspend,
|
|
1433
|
+
'MACH_thread_resume': handle_thread_resume,
|
|
1309
1434
|
'MSC_mach_port_get_attributes_trap': handle_msc_mach_port_get_attributes_trap,
|
|
1310
1435
|
'MSC_mach_port_guard_trap': handle_msc_mach_port_guard_trap,
|
|
1311
1436
|
'MSC_mach_port_unguard_trap': handle_msc_mach_port_unguard_trap,
|
|
@@ -1341,3 +1466,6 @@ handlers = {
|
|
|
1341
1466
|
'SCHED_CLUTCH_CPU_THREAD_SELECT': handle_sched_clutch_cpu_thread_select,
|
|
1342
1467
|
'SCHED_CLUTCH_TG_BUCKET_PRI': handle_sched_clutch_tg_bucket_pri,
|
|
1343
1468
|
}
|
|
1469
|
+
# Every other arm64 synchronous exception class shares the same arguments.
|
|
1470
|
+
handlers.update({name: handle_arm_exception for eventid, name in default_trace_codes().items()
|
|
1471
|
+
if eventid >> 16 == DBG_MACH_EXCP_SYNC_ARM and name.endswith('_Exc_ARM') and name not in handlers})
|
|
@@ -189,6 +189,23 @@ def handle_trace_string_threadname_prev(parser, events):
|
|
|
189
189
|
return event
|
|
190
190
|
|
|
191
191
|
|
|
192
|
+
# bsd/sys/kdebug.h
|
|
193
|
+
TRACE_LOST_EVENTS = 0x7020008
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@dataclass
|
|
197
|
+
class TraceLostEvents:
|
|
198
|
+
"""The kernel's trace buffer filled up and dropped events before this point."""
|
|
199
|
+
ktraces: List
|
|
200
|
+
|
|
201
|
+
def __str__(self):
|
|
202
|
+
return 'TRACE_LOST_EVENTS: the kernel dropped trace events here'
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def handle_trace_lost_events(parser, events):
|
|
206
|
+
return TraceLostEvents(events)
|
|
207
|
+
|
|
208
|
+
|
|
192
209
|
handlers = {
|
|
193
210
|
'TRACE_DATA_NEWTHREAD': handle_trace_data_newthread,
|
|
194
211
|
'TRACE_DATA_EXEC': handle_trace_data_exec,
|
|
@@ -200,4 +217,5 @@ handlers = {
|
|
|
200
217
|
'TRACE_STRING_PROC_EXIT': handle_trace_string_proc_exit,
|
|
201
218
|
'TRACE_STRING_THREADNAME': handle_trace_string_threadname,
|
|
202
219
|
'TRACE_STRING_THREADNAME_PREV': handle_trace_string_threadname_prev,
|
|
220
|
+
'TRACE_LOST_EVENTS': handle_trace_lost_events,
|
|
203
221
|
}
|
pykdebugparser/traces_parser.py
CHANGED
|
@@ -29,6 +29,9 @@ class RawTrace:
|
|
|
29
29
|
return rep
|
|
30
30
|
|
|
31
31
|
|
|
32
|
+
NO_RETURN_CALLS = ('BSC_exit', 'BSC_bsdthread_terminate')
|
|
33
|
+
|
|
34
|
+
|
|
32
35
|
class TracesParser:
|
|
33
36
|
def __init__(self, trace_codes_map, threads_pids, pids_names):
|
|
34
37
|
self.trace_codes = trace_codes_map
|
|
@@ -46,6 +49,13 @@ class TracesParser:
|
|
|
46
49
|
}
|
|
47
50
|
self.last_data_newthread = None
|
|
48
51
|
self.last_data_exec = None
|
|
52
|
+
# Calls that never return, parsed from their start event.
|
|
53
|
+
self.no_return_eventids = {eventid for eventid, name in trace_codes_map.items() if name in NO_RETURN_CALLS}
|
|
54
|
+
# Disk I/Os issued and not completed yet, by their buf address.
|
|
55
|
+
self.pending_disk_io = {}
|
|
56
|
+
# Images dyld loaded, by (pid, load address), and the load address of each thread's last `DYLD_uuid_*_a` event.
|
|
57
|
+
self.dyld_images = {}
|
|
58
|
+
self.dyld_pending_load_addr = {}
|
|
49
59
|
# Event ids to show as `RawTrace` when they have no handler.
|
|
50
60
|
self.raw_eventids = set()
|
|
51
61
|
self.handlers = {}
|
|
@@ -109,6 +119,9 @@ class TracesParser:
|
|
|
109
119
|
return list(self.vnode_generator([e for e in events if self.trace_codes.get(e.eventid) == 'VFS_LOOKUP']))
|
|
110
120
|
|
|
111
121
|
def _feed_start_event(self, event, state):
|
|
122
|
+
if event.eventid in self.no_return_eventids:
|
|
123
|
+
# The thread or process ends inside the call, so its end is never traced.
|
|
124
|
+
return self._feed_single_event(event, state)
|
|
112
125
|
tid_state = state.get(event.tid)
|
|
113
126
|
if tid_state is None:
|
|
114
127
|
# New tid
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
pykdebugparser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
pykdebugparser/__main__.py,sha256=WfgLv5SGr0hQlUeM68ai1g0AqtYDgDFqYlbvvm5rua4,3711
|
|
3
|
+
pykdebugparser/callstacks_parser.py,sha256=i2nfhx0olZZ65UV_ZV3Tw9E_6SWtre8CAbfRBSig4-E,1654
|
|
4
|
+
pykdebugparser/highlight.py,sha256=i-mjfyak0VTo8P_7EeZLIUsUH08Qs-1yAjHBJVWW448,6439
|
|
5
|
+
pykdebugparser/kd_buf_parser.py,sha256=CpifFeBnQ_MZSokjO44lzhXO-KNnZvO-teaPyQqSfio,10991
|
|
6
|
+
pykdebugparser/kevent.py,sha256=DudovIuES4QGxosYeEFJm3AW_FWGbJJBH1kcLnL7UFM,1779
|
|
7
|
+
pykdebugparser/os_log_event.py,sha256=OQUlHpS44oUNwma_bxvnK0SKh_RFeEUdH1RmyrM4nLc,12356
|
|
8
|
+
pykdebugparser/pykdebugparser.py,sha256=RipUcCh6dChgtdh_owvlprjLRMF1dvgic6ZyCqC5I8I,10320
|
|
9
|
+
pykdebugparser/trace.codes,sha256=S6ZN5l9FSGrlSon8nKoVaqRfLdCPl3HL93XSjuoCUnM,103995
|
|
10
|
+
pykdebugparser/trace_codes.py,sha256=4ImGc_jNpY-12cGXdwy6NMCEYcI24oYn1NBOaKNdWNo,1984
|
|
11
|
+
pykdebugparser/traces_parser.py,sha256=LyHVTi4P2OeOLNG2Tahu1x7roQXkC-Jx8LS-E4mBcAQ,5799
|
|
12
|
+
pykdebugparser/trace_handlers/__init__.py,sha256=r1dzmNTxiEtgENq9WFRVQX0g9f9dRxgAgVxjDXSc7s4,679
|
|
13
|
+
pykdebugparser/trace_handlers/bsd.py,sha256=XJjKxDzHSD2VkwEPOqizdTA6ZydFXq7piEVJKCAa0ZY,189267
|
|
14
|
+
pykdebugparser/trace_handlers/dyld.py,sha256=LDgWzOl90qXzcc_q8OvttV2gJ4JNodXFwMemSiV8_ps,10154
|
|
15
|
+
pykdebugparser/trace_handlers/fsystem.py,sha256=AE9L_00-11VyZAYd6dzkmTCP6EVFmXzyae2VikrsiJo,2082
|
|
16
|
+
pykdebugparser/trace_handlers/mach.py,sha256=_2YEgUfKYxaB7NfA2jCVxUbdKDrU21KJKhxtc5yP1M8,39172
|
|
17
|
+
pykdebugparser/trace_handlers/perf.py,sha256=CBffheZFlHa9IOVUZ-UcjNtKmjamv-un44bRJYYo9dw,5070
|
|
18
|
+
pykdebugparser/trace_handlers/trace.py,sha256=s3Kr0wDvWZL2JZVKhxcvA1uvqsdePlEWiXfqSI76znw,5797
|
|
19
|
+
pykdebugparser/trace_handlers/turnstile.py,sha256=8zZfba2Obq7a6qdhLIkItmZxRzzOe1PTTeHnrLuJUx0,8233
|
|
20
|
+
pykdebugparser-1.5.0.dist-info/licenses/LICENSE,sha256=KjZXYP_CGYzDTtVfcxPT6wlGRoQKHPSJu0vh55_2QSo,1071
|
|
21
|
+
pykdebugparser-1.5.0.dist-info/METADATA,sha256=biG_5LGv2RvIABkToh09hXR3eQ8Pm6BUlsI59SZPvdA,18995
|
|
22
|
+
pykdebugparser-1.5.0.dist-info/WHEEL,sha256=4YBfCYNH4wlLpv3pzq1hbEuIlXA4WJabKLFurZ7eTL0,109
|
|
23
|
+
pykdebugparser-1.5.0.dist-info/entry_points.txt,sha256=NrVIOgPR6v2MbpTZ3aKedP1jQ93nUQus6sgP4wAD0_E,63
|
|
24
|
+
pykdebugparser-1.5.0.dist-info/top_level.txt,sha256=FcHHXR9VKmqufFw0a6UP4c8NyS-u4ORY-yXQ6ZzP_28,15
|
|
25
|
+
pykdebugparser-1.5.0.dist-info/RECORD,,
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
pykdebugparser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
pykdebugparser/__main__.py,sha256=WfgLv5SGr0hQlUeM68ai1g0AqtYDgDFqYlbvvm5rua4,3711
|
|
3
|
-
pykdebugparser/callstacks_parser.py,sha256=i2nfhx0olZZ65UV_ZV3Tw9E_6SWtre8CAbfRBSig4-E,1654
|
|
4
|
-
pykdebugparser/kd_buf_parser.py,sha256=CpifFeBnQ_MZSokjO44lzhXO-KNnZvO-teaPyQqSfio,10991
|
|
5
|
-
pykdebugparser/kevent.py,sha256=DudovIuES4QGxosYeEFJm3AW_FWGbJJBH1kcLnL7UFM,1779
|
|
6
|
-
pykdebugparser/os_log_event.py,sha256=OQUlHpS44oUNwma_bxvnK0SKh_RFeEUdH1RmyrM4nLc,12356
|
|
7
|
-
pykdebugparser/pykdebugparser.py,sha256=87c7HT6NXl820A7bv4g2iMD3kWYKpq6ya49eT8p46nE,10238
|
|
8
|
-
pykdebugparser/trace.codes,sha256=S6ZN5l9FSGrlSon8nKoVaqRfLdCPl3HL93XSjuoCUnM,103995
|
|
9
|
-
pykdebugparser/trace_codes.py,sha256=4ImGc_jNpY-12cGXdwy6NMCEYcI24oYn1NBOaKNdWNo,1984
|
|
10
|
-
pykdebugparser/traces_parser.py,sha256=2nerqDgGLkaHWZaqHDE28gZvlRrbNcYANaBbcYwSlLk,5064
|
|
11
|
-
pykdebugparser/trace_handlers/__init__.py,sha256=r1dzmNTxiEtgENq9WFRVQX0g9f9dRxgAgVxjDXSc7s4,679
|
|
12
|
-
pykdebugparser/trace_handlers/bsd.py,sha256=2dg4wFdfiZ79jARUcB1VCfCPw2z8ni11eWFsMQtQk-E,185848
|
|
13
|
-
pykdebugparser/trace_handlers/dyld.py,sha256=ydyFhEkXKJzgo-1X5bH7UqiiLAenUa-KpBDfgSXMuAM,6839
|
|
14
|
-
pykdebugparser/trace_handlers/fsystem.py,sha256=yOILOEuQDdjPDKq6HhzsjI27z36fkT6TqgCBddTvENg,417
|
|
15
|
-
pykdebugparser/trace_handlers/mach.py,sha256=JL1v4rCCdGULsnVm3CYOLegL3JqNt3u7X-lJw1xzBRQ,35023
|
|
16
|
-
pykdebugparser/trace_handlers/perf.py,sha256=CBffheZFlHa9IOVUZ-UcjNtKmjamv-un44bRJYYo9dw,5070
|
|
17
|
-
pykdebugparser/trace_handlers/trace.py,sha256=Lu8s9XT1jb5avtrVXF6SQ6FUj93xYc0TwJd7yoIjV9U,5377
|
|
18
|
-
pykdebugparser/trace_handlers/turnstile.py,sha256=8zZfba2Obq7a6qdhLIkItmZxRzzOe1PTTeHnrLuJUx0,8233
|
|
19
|
-
pykdebugparser-1.4.0.dist-info/licenses/LICENSE,sha256=KjZXYP_CGYzDTtVfcxPT6wlGRoQKHPSJu0vh55_2QSo,1071
|
|
20
|
-
pykdebugparser-1.4.0.dist-info/METADATA,sha256=RcvGghbwZc-Xd7KHUzdlOdrnNH9i58vS0oHcqBWYedo,18995
|
|
21
|
-
pykdebugparser-1.4.0.dist-info/WHEEL,sha256=4YBfCYNH4wlLpv3pzq1hbEuIlXA4WJabKLFurZ7eTL0,109
|
|
22
|
-
pykdebugparser-1.4.0.dist-info/entry_points.txt,sha256=NrVIOgPR6v2MbpTZ3aKedP1jQ93nUQus6sgP4wAD0_E,63
|
|
23
|
-
pykdebugparser-1.4.0.dist-info/top_level.txt,sha256=FcHHXR9VKmqufFw0a6UP4c8NyS-u4ORY-yXQ6ZzP_28,15
|
|
24
|
-
pykdebugparser-1.4.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|