pykdebugparser 1.2.8__py2.py3-none-any.whl → 1.3.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/kd_buf_parser.py +160 -57
- pykdebugparser/kevent.py +20 -5
- pykdebugparser/os_log_event.py +24 -21
- pykdebugparser/pykdebugparser.py +44 -12
- pykdebugparser/trace_codes.py +23 -1
- pykdebugparser/trace_handlers/__init__.py +23 -0
- pykdebugparser/trace_handlers/bsd.py +19 -16
- pykdebugparser/trace_handlers/mach.py +26 -14
- pykdebugparser/trace_handlers/perf.py +13 -3
- pykdebugparser/trace_handlers/turnstile.py +4 -2
- pykdebugparser/traces_parser.py +25 -20
- {pykdebugparser-1.2.8.dist-info → pykdebugparser-1.3.0.dist-info}/METADATA +4 -4
- pykdebugparser-1.3.0.dist-info/RECORD +24 -0
- {pykdebugparser-1.2.8.dist-info → pykdebugparser-1.3.0.dist-info}/WHEEL +1 -1
- pykdebugparser-1.2.8.dist-info/RECORD +0 -24
- {pykdebugparser-1.2.8.dist-info → pykdebugparser-1.3.0.dist-info}/entry_points.txt +0 -0
- {pykdebugparser-1.2.8.dist-info → pykdebugparser-1.3.0.dist-info}/licenses/LICENSE +0 -0
- {pykdebugparser-1.2.8.dist-info → pykdebugparser-1.3.0.dist-info}/top_level.txt +0 -0
pykdebugparser/kd_buf_parser.py
CHANGED
|
@@ -1,21 +1,27 @@
|
|
|
1
|
-
import
|
|
2
|
-
from collections import namedtuple
|
|
1
|
+
from dataclasses import dataclass, field
|
|
3
2
|
import io
|
|
4
3
|
import plistlib
|
|
4
|
+
import struct
|
|
5
|
+
from collections import namedtuple
|
|
6
|
+
from typing import Annotated, Any
|
|
5
7
|
|
|
6
|
-
from
|
|
7
|
-
|
|
8
|
+
from bytestruct import GREEDY_BYTES, U64_LE, BinaryParseError, BinaryStruct, ByteReader, Bytes, CodecBase, \
|
|
9
|
+
PaddedString, Prefixed, TruncatedDataError, u32, u64
|
|
8
10
|
|
|
9
|
-
from pykdebugparser.kevent import from_kd_buf, KD_BUF_FORMAT
|
|
11
|
+
from pykdebugparser.kevent import from_kd_buf, from_kd_bufs, KD_BUF_FORMAT
|
|
10
12
|
from pykdebugparser.os_log_event import OsLogEvent
|
|
11
13
|
|
|
12
14
|
KEVENT_SIZE = struct.calcsize(KD_BUF_FORMAT)
|
|
15
|
+
# Upper bound for a single bulk read of kevents.
|
|
16
|
+
KEVENTS_READ_SIZE = KEVENT_SIZE * 1024
|
|
13
17
|
|
|
14
18
|
ProcessData = namedtuple('ProcessData', ['pid', 'name'])
|
|
15
19
|
|
|
16
20
|
RAW_VERSION_SIZE = 4
|
|
17
21
|
RAW_VERSION2_BYTES = b'\x00\x02\xaa\x55'
|
|
18
22
|
RAW_VERSION3_BYTES = b'\x00\x03\xaa\x55'
|
|
23
|
+
# Header flag of a 64-bit kernel.
|
|
24
|
+
KDBG_LP64 = 0x1
|
|
19
25
|
TRACEV3_STACKSHOT_END = b'stackshot_out_fl'
|
|
20
26
|
TRACEV3_THREADMAP_TAG = b'\x00\x1d\x00\x00\x00\x00\x00\x00'
|
|
21
27
|
TRACEV3_EVENTS_TAG = b'\x00\x1e\x00\x00\x00\x00\x00\x00'
|
|
@@ -28,61 +34,131 @@ TRACEV3_LOG_STRINGS = b'\x12\x80\x00\x00\x00\x00\x00\x00'
|
|
|
28
34
|
TRACEV3_KERNEL_EXTENSIONS = b'\x05\x80\x00\x00\x00\x00\x00\x00'
|
|
29
35
|
TRACEV3_IMAGES = b'\x04\x80\x00\x00\x01\x00\x00\x00'
|
|
30
36
|
|
|
31
|
-
kd_threadmap = Struct(
|
|
32
|
-
'tid' / Int64ul,
|
|
33
|
-
'pid' / Int32ul,
|
|
34
|
-
'process' / FixedSized(0x14, CString('utf8')),
|
|
35
|
-
)
|
|
36
37
|
|
|
38
|
+
class Bplist(CodecBase[Any]):
|
|
39
|
+
"""
|
|
40
|
+
Codec for a plist (all remaining bytes).
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def read(self, reader: ByteReader) -> Any:
|
|
44
|
+
return plistlib.loads(GREEDY_BYTES.read(reader))
|
|
45
|
+
|
|
46
|
+
def build(self, value: Any) -> bytes:
|
|
47
|
+
return plistlib.dumps(value)
|
|
37
48
|
|
|
38
|
-
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class KdThreadmap(BinaryStruct, byte_order='<'):
|
|
52
|
+
tid: u64
|
|
53
|
+
pid: u32
|
|
54
|
+
process: Annotated[str, PaddedString(0x14)]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class KdHeaderV2(BinaryStruct, byte_order='<'):
|
|
39
59
|
"""
|
|
40
|
-
|
|
60
|
+
Header of a trace version 2 (`RAW_VERSION2`), as written by kperf's `kperf_buffer_readdata`.
|
|
61
|
+
The fields up to `tod_usecs` follow XNU's `RAW_header`; kperf always writes the time of day as zero.
|
|
41
62
|
"""
|
|
63
|
+
number_of_threads: u32
|
|
64
|
+
tod_secs: u64
|
|
65
|
+
tod_usecs: u32
|
|
66
|
+
flags: u32
|
|
67
|
+
tick_frequency: u64
|
|
68
|
+
# Never written by kperf, so it holds whatever the buffer contained before.
|
|
69
|
+
_unused: Annotated[bytes, Bytes(0x100)] = field(default=bytes(0x100), init=False, repr=False, compare=False)
|
|
42
70
|
|
|
43
|
-
|
|
44
|
-
|
|
71
|
+
@property
|
|
72
|
+
def is_64bit(self) -> bool:
|
|
73
|
+
return bool(self.flags & KDBG_LP64)
|
|
45
74
|
|
|
46
|
-
def _encode(self, obj, context, path):
|
|
47
|
-
return plistlib.dumps(obj)
|
|
48
75
|
|
|
76
|
+
@dataclass
|
|
77
|
+
class KdHeaderV3(BinaryStruct, byte_order='<'):
|
|
78
|
+
tag: u32
|
|
79
|
+
sub_tag: u32
|
|
80
|
+
length: u64
|
|
81
|
+
timebase_numer: u32
|
|
82
|
+
timebase_denom: u32
|
|
83
|
+
timestamp: u64
|
|
84
|
+
walltime_secs: u64
|
|
85
|
+
walltime_usecs: u32
|
|
86
|
+
timezone_minuteswest: u32
|
|
87
|
+
timezone_dst: u32
|
|
88
|
+
flags: u32
|
|
89
|
+
tag2: u32
|
|
90
|
+
cpu_info: Annotated[Any, Prefixed(U64_LE, Bplist())]
|
|
49
91
|
|
|
50
|
-
kd_header_v2 = Struct(
|
|
51
|
-
'number_of_treads' / Int32ul,
|
|
52
|
-
Padding(8),
|
|
53
|
-
Padding(4),
|
|
54
|
-
'is_64bit' / Int32ul,
|
|
55
|
-
'tick_frequency' / Int64ul,
|
|
56
|
-
Padding(0x100),
|
|
57
|
-
'threadmap' / Array(lambda ctx: ctx.number_of_treads, kd_threadmap),
|
|
58
|
-
'_pad' / GreedyRange(Const(0, Byte)),
|
|
59
|
-
)
|
|
60
92
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
'walltime_secs' / Int64ul,
|
|
69
|
-
'walltime_usecs' / Int32ul,
|
|
70
|
-
'timezone_minuteswest' / Int32ul,
|
|
71
|
-
'timezone_dst' / Int32ul,
|
|
72
|
-
'flags' / Int32ul,
|
|
73
|
-
'tag2' / Int32ul,
|
|
74
|
-
'cpu_info' / Prefixed(Int64ul, BplistAdapter(GreedyBytes)),
|
|
93
|
+
@dataclass
|
|
94
|
+
class KdV3Block(BinaryStruct, byte_order='<'):
|
|
95
|
+
"""
|
|
96
|
+
An additional data block of a trace version 3, aligned to 8 bytes (unless it is the last one).
|
|
97
|
+
"""
|
|
98
|
+
tag: Annotated[bytes, Bytes(8)]
|
|
99
|
+
data: Annotated[bytes, Prefixed(U64_LE, GREEDY_BYTES)]
|
|
75
100
|
|
|
76
|
-
)
|
|
77
101
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
102
|
+
def read_exactly(reader: io.IOBase, size: int) -> bytes:
|
|
103
|
+
"""
|
|
104
|
+
Read exactly `size` bytes from a stream.
|
|
105
|
+
:param reader: Stream to read from.
|
|
106
|
+
:param size: Number of bytes to read.
|
|
107
|
+
:return: The data read.
|
|
108
|
+
:raises TruncatedDataError: The stream ended first.
|
|
109
|
+
"""
|
|
110
|
+
data = reader.read(size)
|
|
111
|
+
if len(data) != size:
|
|
112
|
+
raise TruncatedDataError(f'expected {size} bytes, found {len(data)}')
|
|
113
|
+
return data
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def skip_zeros(reader: io.IOBase):
|
|
117
|
+
"""
|
|
118
|
+
Skip zero bytes in a stream, leaving it at the first non-zero byte (or at its end).
|
|
119
|
+
Reads a byte at a time and seeks back once, so a stream never consumes data past the zeros.
|
|
120
|
+
:param reader: Stream to skip in.
|
|
121
|
+
"""
|
|
122
|
+
while True:
|
|
123
|
+
position = reader.tell()
|
|
124
|
+
if reader.read(1) != b'\x00':
|
|
125
|
+
reader.seek(position, 0)
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def parse_threadmap(data: bytes) -> list:
|
|
130
|
+
"""
|
|
131
|
+
Parse consecutive threadmap entries, stopping at the first one that can't be parsed.
|
|
132
|
+
:param data: Threadmap entries.
|
|
133
|
+
:return: Parsed entries.
|
|
134
|
+
"""
|
|
135
|
+
reader = ByteReader(data)
|
|
136
|
+
threadmap = []
|
|
137
|
+
while reader.remaining:
|
|
138
|
+
try:
|
|
139
|
+
threadmap.append(KdThreadmap.read(reader))
|
|
140
|
+
except BinaryParseError:
|
|
141
|
+
break
|
|
142
|
+
return threadmap
|
|
81
143
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
144
|
+
|
|
145
|
+
def parse_v3_blocks(data: bytes) -> list:
|
|
146
|
+
"""
|
|
147
|
+
Parse the additional data blocks of a trace version 3, stopping at the first one that can't be parsed.
|
|
148
|
+
:param data: Additional data blocks.
|
|
149
|
+
:return: Parsed blocks.
|
|
150
|
+
"""
|
|
151
|
+
reader = ByteReader(data)
|
|
152
|
+
blocks = []
|
|
153
|
+
while True:
|
|
154
|
+
try:
|
|
155
|
+
block = KdV3Block.read(reader)
|
|
156
|
+
except BinaryParseError:
|
|
157
|
+
break
|
|
158
|
+
blocks.append(block)
|
|
159
|
+
# The last block may be unpadded.
|
|
160
|
+
reader.skip(min(-(KdV3Block.SIZE + 8 + len(block.data)) % 8, reader.remaining))
|
|
161
|
+
return blocks
|
|
86
162
|
|
|
87
163
|
|
|
88
164
|
def seek_until(reader, data: bytes):
|
|
@@ -138,13 +214,37 @@ class KdBufParser:
|
|
|
138
214
|
:param reader: Stream to parse.
|
|
139
215
|
:return: Generator for parsed kevents.
|
|
140
216
|
"""
|
|
141
|
-
|
|
142
|
-
self.set_thread_map(
|
|
217
|
+
header = KdHeaderV2.parse(read_exactly(reader, KdHeaderV2.SIZE))
|
|
218
|
+
self.set_thread_map([KdThreadmap.parse(read_exactly(reader, KdThreadmap.SIZE))
|
|
219
|
+
for _ in range(header.number_of_threads)])
|
|
220
|
+
skip_zeros(reader)
|
|
221
|
+
read1 = getattr(reader, 'read1', None)
|
|
222
|
+
if read1 is None:
|
|
223
|
+
# Without `read1` a bigger `read` might block until more data than currently available arrives.
|
|
224
|
+
while True:
|
|
225
|
+
buf = reader.read(KEVENT_SIZE)
|
|
226
|
+
if not buf:
|
|
227
|
+
break
|
|
228
|
+
yield from_kd_buf(buf)
|
|
229
|
+
return
|
|
230
|
+
# `read1` returns whatever is available, so kevents are decoded in bulk without delaying live streams.
|
|
231
|
+
leftover = b''
|
|
143
232
|
while True:
|
|
144
|
-
buf =
|
|
233
|
+
buf = read1(KEVENTS_READ_SIZE)
|
|
145
234
|
if not buf:
|
|
146
235
|
break
|
|
147
|
-
|
|
236
|
+
if leftover:
|
|
237
|
+
buf = leftover + buf
|
|
238
|
+
end = len(buf) - len(buf) % KEVENT_SIZE
|
|
239
|
+
if end != len(buf):
|
|
240
|
+
leftover = buf[end:]
|
|
241
|
+
buf = buf[:end]
|
|
242
|
+
else:
|
|
243
|
+
leftover = b''
|
|
244
|
+
yield from from_kd_bufs(buf)
|
|
245
|
+
if leftover:
|
|
246
|
+
# A truncated kevent, fail just like a non-bulk read would.
|
|
247
|
+
yield from_kd_buf(leftover)
|
|
148
248
|
|
|
149
249
|
def parse_v3(self, reader: io.IOBase):
|
|
150
250
|
"""
|
|
@@ -152,18 +252,21 @@ class KdBufParser:
|
|
|
152
252
|
:param reader: Stream to parse.
|
|
153
253
|
:return: Generator for parsed kevents.
|
|
154
254
|
"""
|
|
155
|
-
|
|
255
|
+
header = read_exactly(reader, KdHeaderV3.SIZE + U64_LE.size)
|
|
256
|
+
header += read_exactly(reader, U64_LE.unpack_from(header, KdHeaderV3.SIZE)[0])
|
|
257
|
+
read_exactly(reader, -len(header) % 8)
|
|
258
|
+
self.v3_header = KdHeaderV3.parse(header)
|
|
156
259
|
# Align the reader to 8 bytes from the beginning of the stream
|
|
157
260
|
reader.read(8 - RAW_VERSION_SIZE)
|
|
158
261
|
# The threadmap tag appears randomly in the stackshot.
|
|
159
262
|
seek_until(reader, TRACEV3_STACKSHOT_END)
|
|
160
263
|
seek_until(reader, TRACEV3_THREADMAP_TAG)
|
|
161
|
-
|
|
162
|
-
self.set_thread_map(
|
|
264
|
+
threadmap_size = U64_LE.parse(read_exactly(reader, U64_LE.size))
|
|
265
|
+
self.set_thread_map(parse_threadmap(read_exactly(reader, threadmap_size)))
|
|
163
266
|
|
|
164
267
|
while True:
|
|
165
268
|
seek_until(reader, TRACEV3_EVENTS_TAG)
|
|
166
|
-
size =
|
|
269
|
+
size = U64_LE.parse(read_exactly(reader, U64_LE.size))
|
|
167
270
|
reader.read(8) # All zeros, unknown.
|
|
168
271
|
for _ in range(size // KEVENT_SIZE):
|
|
169
272
|
buf = reader.read(KEVENT_SIZE)
|
|
@@ -172,7 +275,7 @@ class KdBufParser:
|
|
|
172
275
|
break
|
|
173
276
|
reader.seek(-8, 1)
|
|
174
277
|
|
|
175
|
-
additional_data =
|
|
278
|
+
additional_data = parse_v3_blocks(reader.read())
|
|
176
279
|
|
|
177
280
|
self.trace_codes = ''
|
|
178
281
|
self.kernel_extensions = {'Binaries': []}
|
pykdebugparser/kevent.py
CHANGED
|
@@ -5,6 +5,9 @@ import struct
|
|
|
5
5
|
KDBG_EVENTID_MASK = 0xfffffffc
|
|
6
6
|
KDBG_FUNC_MASK = 0x00000003
|
|
7
7
|
KD_BUF_FORMAT = '<Q32sQIIQ'
|
|
8
|
+
# Same layout as `KD_BUF_FORMAT`, with the 32 bytes of arguments unpacked as 4 64-bit values.
|
|
9
|
+
KD_BUF_STRUCT = struct.Struct('<QQQQQQIIQ')
|
|
10
|
+
KD_BUF_SIZE = KD_BUF_STRUCT.size
|
|
8
11
|
Kevent = namedtuple('Kevent', ['timestamp', 'data', 'values', 'tid', 'debugid', 'eventid', 'func_qualifier'])
|
|
9
12
|
|
|
10
13
|
|
|
@@ -24,8 +27,20 @@ def from_kd_buf(kd_buf: bytes) -> Kevent:
|
|
|
24
27
|
:param kd_buf: Buffer of kd_buf kevent's struct.
|
|
25
28
|
:return: Parsed kevent.
|
|
26
29
|
"""
|
|
27
|
-
timestamp,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
timestamp, arg0, arg1, arg2, arg3, tid, debugid, cpuid, unused = KD_BUF_STRUCT.unpack(kd_buf)
|
|
31
|
+
return Kevent(timestamp, kd_buf[8:40], (arg0, arg1, arg2, arg3), tid, debugid, debugid & KDBG_EVENTID_MASK,
|
|
32
|
+
debugid & KDBG_FUNC_MASK)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def from_kd_bufs(kd_bufs: bytes):
|
|
36
|
+
"""
|
|
37
|
+
Create Kevent objects from consecutive kd_buf kevent's structs.
|
|
38
|
+
:param kd_bufs: Buffer of kd_buf kevent's structs, its length must be a multiple of the struct size.
|
|
39
|
+
:return: Generator of parsed kevents.
|
|
40
|
+
"""
|
|
41
|
+
new_kevent = tuple.__new__ # Skips the namedtuple's Python-level __new__, the result is identical.
|
|
42
|
+
offset = 8
|
|
43
|
+
for timestamp, arg0, arg1, arg2, arg3, tid, debugid, cpuid, unused in KD_BUF_STRUCT.iter_unpack(kd_bufs):
|
|
44
|
+
yield new_kevent(Kevent, (timestamp, kd_bufs[offset:offset + 32], (arg0, arg1, arg2, arg3), tid, debugid,
|
|
45
|
+
debugid & KDBG_EVENTID_MASK, debugid & KDBG_FUNC_MASK))
|
|
46
|
+
offset += KD_BUF_SIZE
|
pykdebugparser/os_log_event.py
CHANGED
|
@@ -3,7 +3,7 @@ from datetime import datetime, timezone
|
|
|
3
3
|
import enum
|
|
4
4
|
from typing import List, Dict
|
|
5
5
|
|
|
6
|
-
from
|
|
6
|
+
from bytestruct import U64_LE, BinaryStruct, u8, u32
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class OsLogType(enum.Enum):
|
|
@@ -81,7 +81,7 @@ class FirehoseTracepointSignpostType(enum.IntFlag):
|
|
|
81
81
|
scope_system = 0xc0
|
|
82
82
|
|
|
83
83
|
|
|
84
|
-
class FirehoseTracepointSingpostFlags(enum.
|
|
84
|
+
class FirehoseTracepointSingpostFlags(enum.IntFlag):
|
|
85
85
|
has_private_data = 1
|
|
86
86
|
has_subsystem = 2
|
|
87
87
|
has_rules = 4
|
|
@@ -100,22 +100,24 @@ tracepoint_types = {
|
|
|
100
100
|
|
|
101
101
|
tracepoint_flags = {
|
|
102
102
|
FirehoseTracepointNamespace.log: FirehoseTracepointLogFlags,
|
|
103
|
-
FirehoseTracepointNamespace.
|
|
103
|
+
FirehoseTracepointNamespace.signpost: FirehoseTracepointSingpostFlags,
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
106
|
+
# `trace_flags` bits.
|
|
107
|
+
TRACE_FLAGS_HAS_LARGE_OFFSET = 0x20
|
|
108
|
+
TRACE_FLAGS_HAS_UNIQUE_PID = 0x10
|
|
109
|
+
TRACE_FLAGS_PC_STYLE_SHIFT = 1
|
|
110
|
+
TRACE_FLAGS_PC_STYLE_MASK = 0x7
|
|
111
|
+
TRACE_FLAGS_HAS_CURRENT_AID = 0x01
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@dataclass
|
|
115
|
+
class FirehoseTracepointId(BinaryStruct, byte_order='<'):
|
|
116
|
+
namespace: u8
|
|
117
|
+
type_: u8
|
|
118
|
+
trace_flags: u8
|
|
119
|
+
flags: u8
|
|
120
|
+
code: u32
|
|
119
121
|
|
|
120
122
|
|
|
121
123
|
@dataclass
|
|
@@ -262,7 +264,8 @@ class OsLogEvent:
|
|
|
262
264
|
|
|
263
265
|
@classmethod
|
|
264
266
|
def parse_trace_identifier(cls, trace_identifier):
|
|
265
|
-
trace_id =
|
|
267
|
+
trace_id = FirehoseTracepointId.parse(U64_LE.build(trace_identifier))
|
|
268
|
+
trace_flags = trace_id.trace_flags
|
|
266
269
|
trace_namespace = FirehoseTracepointNamespace(trace_id.namespace)
|
|
267
270
|
if trace_namespace in tracepoint_types:
|
|
268
271
|
type_ = tracepoint_types[trace_namespace](trace_id.type_)
|
|
@@ -275,10 +278,10 @@ class OsLogEvent:
|
|
|
275
278
|
return TraceIdentifier(
|
|
276
279
|
namespace=trace_namespace,
|
|
277
280
|
type_=type_,
|
|
278
|
-
has_large_offset=
|
|
279
|
-
has_unique_pid=
|
|
280
|
-
pc_style=FirehoseTracepointFlagsPcStyle(
|
|
281
|
-
has_current_aid=
|
|
281
|
+
has_large_offset=bool(trace_flags & TRACE_FLAGS_HAS_LARGE_OFFSET),
|
|
282
|
+
has_unique_pid=bool(trace_flags & TRACE_FLAGS_HAS_UNIQUE_PID),
|
|
283
|
+
pc_style=FirehoseTracepointFlagsPcStyle((trace_flags >> TRACE_FLAGS_PC_STYLE_SHIFT) & TRACE_FLAGS_PC_STYLE_MASK),
|
|
284
|
+
has_current_aid=bool(trace_flags & TRACE_FLAGS_HAS_CURRENT_AID),
|
|
282
285
|
flags=tracepoint_flags[trace_namespace](
|
|
283
286
|
trace_id.flags) if trace_namespace in tracepoint_flags else None,
|
|
284
287
|
code=trace_id.code,
|
pykdebugparser/pykdebugparser.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
import io
|
|
3
|
+
import math
|
|
3
4
|
|
|
4
5
|
from pygments import highlight, lexers, formatters
|
|
5
6
|
from termcolor import colored
|
|
@@ -25,6 +26,8 @@ class PyKdebugParser:
|
|
|
25
26
|
self.filter_process = None
|
|
26
27
|
self.filter_class = []
|
|
27
28
|
self.filter_subclass = []
|
|
29
|
+
# Event ids (debugids without the function qualifier) to include, on top of the class/subclass filters.
|
|
30
|
+
self.filter_eventid = set()
|
|
28
31
|
self.show_timestamp = True
|
|
29
32
|
self.show_name = True
|
|
30
33
|
self.show_func_qual = True
|
|
@@ -41,15 +44,29 @@ class PyKdebugParser:
|
|
|
41
44
|
self.pids_names = {}
|
|
42
45
|
self.dyld_addresses = []
|
|
43
46
|
self.dyld_uuids = []
|
|
47
|
+
self._timestamp_cache = None
|
|
44
48
|
|
|
45
49
|
def kevents(self, kdebug: io.IOBase):
|
|
46
50
|
events_generator = KdBufParser(self.threads_pids, self.pids_names).parse(kdebug)
|
|
47
|
-
events_generator
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
return self._filter_kevents(events_generator, self.filter_tid is not None,
|
|
52
|
+
bool(self.filter_class or self.filter_subclass or self.filter_eventid))
|
|
53
|
+
|
|
54
|
+
def _filter_kevents(self, events_generator, by_tid, by_class):
|
|
55
|
+
# A single generator instead of a `filter` chain, avoiding a lambda call per kevent per filter.
|
|
56
|
+
filter_class = self.filter_class
|
|
57
|
+
filter_subclass = self.filter_subclass
|
|
58
|
+
filter_eventid = set(self.filter_eventid)
|
|
59
|
+
for event in events_generator:
|
|
60
|
+
if isinstance(event, OsLogEvent):
|
|
61
|
+
continue
|
|
62
|
+
if by_tid and event.tid != self.filter_tid:
|
|
63
|
+
continue
|
|
64
|
+
if by_class:
|
|
65
|
+
eventid = event.eventid
|
|
66
|
+
if not ((eventid >> 24 in filter_class) or (eventid >> 16 in filter_subclass)
|
|
67
|
+
or eventid in filter_eventid):
|
|
68
|
+
continue
|
|
69
|
+
yield event
|
|
53
70
|
|
|
54
71
|
def formatted_kevents(self, kdebug: io.IOBase, trace_codes=None):
|
|
55
72
|
trace_codes_map = default_trace_codes() if trace_codes is None else trace_codes
|
|
@@ -58,11 +75,12 @@ class PyKdebugParser:
|
|
|
58
75
|
def traces(self, kdebug: io.IOBase, trace_codes=None):
|
|
59
76
|
trace_codes_map = default_trace_codes() if trace_codes is None else trace_codes
|
|
60
77
|
|
|
61
|
-
has_filters = self.filter_class or self.filter_subclass
|
|
78
|
+
has_filters = self.filter_class or self.filter_subclass or self.filter_eventid
|
|
62
79
|
add_trace_class = has_filters and DBG_TRACE not in self.filter_class
|
|
63
80
|
if add_trace_class:
|
|
64
81
|
self.filter_class.append(DBG_TRACE)
|
|
65
|
-
has_bsd = DBG_BSD in self.filter_class or any(
|
|
82
|
+
has_bsd = (DBG_BSD in self.filter_class or any(sc >> 8 == DBG_BSD for sc in self.filter_subclass)
|
|
83
|
+
or any(eventid >> 24 == DBG_BSD for eventid in self.filter_eventid))
|
|
66
84
|
add_fs_class = has_filters and has_bsd and DBG_FSYSTEM not in self.filter_class
|
|
67
85
|
if add_fs_class:
|
|
68
86
|
self.filter_class.append(DBG_FSYSTEM)
|
|
@@ -113,9 +131,22 @@ class PyKdebugParser:
|
|
|
113
131
|
offset_usec = (
|
|
114
132
|
((timestamp - self.mach_absolute_time) * self.numer) / (self.denom * 1000)
|
|
115
133
|
)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
134
|
+
posix_time = (self.usecs_since_epoch + offset_usec) / 1000000
|
|
135
|
+
# Split to seconds and microseconds exactly like `datetime.fromtimestamp` does (round half to even), so
|
|
136
|
+
# the date and time part can be formatted only once per second.
|
|
137
|
+
fraction, seconds = math.modf(posix_time)
|
|
138
|
+
microseconds = round(fraction * 1000000)
|
|
139
|
+
if microseconds >= 1000000:
|
|
140
|
+
microseconds -= 1000000
|
|
141
|
+
seconds += 1.0
|
|
142
|
+
elif microseconds < 0:
|
|
143
|
+
microseconds += 1000000
|
|
144
|
+
seconds -= 1.0
|
|
145
|
+
cache = self._timestamp_cache
|
|
146
|
+
if cache is None or cache[0] != seconds or cache[1] is not self.timezone:
|
|
147
|
+
prefix = datetime.fromtimestamp(seconds, tz=self.timezone).strftime('%Y-%m-%d %H:%M:%S.')
|
|
148
|
+
cache = self._timestamp_cache = (seconds, self.timezone, prefix)
|
|
149
|
+
return f'{cache[2]}{microseconds:06d}'.ljust(27)
|
|
119
150
|
|
|
120
151
|
def _format_process(self, tid):
|
|
121
152
|
pid = self.threads_pids.get(tid, -1)
|
|
@@ -184,4 +215,5 @@ class PyKdebugParser:
|
|
|
184
215
|
return event_rep
|
|
185
216
|
|
|
186
217
|
def _is_eventid_allowed(self, event_id):
|
|
187
|
-
return (event_id >> 24 in self.filter_class) or (event_id >> 16 in self.filter_subclass)
|
|
218
|
+
return ((event_id >> 24 in self.filter_class) or (event_id >> 16 in self.filter_subclass)
|
|
219
|
+
or event_id in self.filter_eventid)
|
pykdebugparser/trace_codes.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Mapping
|
|
1
|
+
from typing import Iterable, Mapping, Optional, Set
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
4
|
|
|
@@ -28,3 +28,25 @@ def default_trace_codes() -> Mapping[int, str]:
|
|
|
28
28
|
"""
|
|
29
29
|
with open(Path(__file__).resolve().parent.joinpath('trace.codes'), 'r') as fd:
|
|
30
30
|
return from_trace_codes_text(fd.read())
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def syscall_eventids(names: Iterable[str], trace_codes: Optional[Mapping[int, str]] = None) -> Set[int]:
|
|
34
|
+
"""
|
|
35
|
+
Get the event ids of BSD syscalls by name, for `PyKdebugParser.filter_eventid`.
|
|
36
|
+
A name also matches its `_nocancel` variant (e.g. `open` gives `BSC_open` and `BSC_open_nocancel`).
|
|
37
|
+
:param names: Syscall names, such as `open` or `openat`.
|
|
38
|
+
:param trace_codes: Mapping between code and event name; the default trace codes if omitted.
|
|
39
|
+
:return: The event ids.
|
|
40
|
+
:raises ValueError: A name is not a known syscall.
|
|
41
|
+
"""
|
|
42
|
+
codes = {name: code for code, name in (default_trace_codes() if trace_codes is None else trace_codes).items()}
|
|
43
|
+
eventids = set()
|
|
44
|
+
unknown = []
|
|
45
|
+
for name in names:
|
|
46
|
+
matches = {codes[event] for event in (f'BSC_{name}', f'BSC_{name}_nocancel') if event in codes}
|
|
47
|
+
if not matches:
|
|
48
|
+
unknown.append(name)
|
|
49
|
+
eventids |= matches
|
|
50
|
+
if unknown:
|
|
51
|
+
raise ValueError(f'unknown syscalls: {", ".join(unknown)}')
|
|
52
|
+
return eventids
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class UnknownEnumValue(int):
|
|
2
|
+
"""
|
|
3
|
+
A raw value that its enum doesn't define (e.g. a combination of flags or a value added in a newer OS).
|
|
4
|
+
It has `name` and `value` like an enum member, so it is formatted as the raw number instead of failing the parse.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
@property
|
|
8
|
+
def name(self) -> str:
|
|
9
|
+
return str(int(self))
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def value(self) -> int:
|
|
13
|
+
return int(self)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def parse_enum(enum_type, value):
|
|
17
|
+
"""
|
|
18
|
+
Convert a raw value to an enum member, falling back to `UnknownEnumValue` for values the enum doesn't define.
|
|
19
|
+
"""
|
|
20
|
+
try:
|
|
21
|
+
return enum_type(value)
|
|
22
|
+
except ValueError:
|
|
23
|
+
return UnknownEnumValue(value)
|
|
@@ -7,6 +7,8 @@ from signal import Signals
|
|
|
7
7
|
import socket
|
|
8
8
|
from typing import List
|
|
9
9
|
|
|
10
|
+
from pykdebugparser.trace_handlers import parse_enum
|
|
11
|
+
|
|
10
12
|
IOC_REQUEST_PARAMS = {
|
|
11
13
|
0x20000000: 'IOC_VOID',
|
|
12
14
|
0x40000000: 'IOC_OUT',
|
|
@@ -270,7 +272,7 @@ class SocketOptionName(enum.Enum):
|
|
|
270
272
|
|
|
271
273
|
def sockopt_format_level_and_option(level, option_name):
|
|
272
274
|
if level == socket.SOL_SOCKET:
|
|
273
|
-
return 'SOL_SOCKET', SocketOptionName
|
|
275
|
+
return 'SOL_SOCKET', parse_enum(SocketOptionName, option_name).name
|
|
274
276
|
else:
|
|
275
277
|
return level, option_name
|
|
276
278
|
|
|
@@ -5151,7 +5153,7 @@ def handle_getgid(parser, events):
|
|
|
5151
5153
|
|
|
5152
5154
|
def handle_sigprocmask(parser, events):
|
|
5153
5155
|
args = events[0].values
|
|
5154
|
-
return BscSigprocmap(events, SigprocmaskFlags
|
|
5156
|
+
return BscSigprocmap(events, parse_enum(SigprocmaskFlags, args[0]), args[1], args[2], serialize_result(events[-1]))
|
|
5155
5157
|
|
|
5156
5158
|
|
|
5157
5159
|
def handle_getlogin(parser, events):
|
|
@@ -5282,7 +5284,7 @@ def handle_sys_dup2(parser, events):
|
|
|
5282
5284
|
|
|
5283
5285
|
def handle_sys_fcntl(parser, events, no_cancel=False):
|
|
5284
5286
|
args = events[0].values
|
|
5285
|
-
return BscSysFcntl(events, args[0], FcntlCmd
|
|
5287
|
+
return BscSysFcntl(events, args[0], parse_enum(FcntlCmd, args[1]), args[2], serialize_result(events[-1], 'return'),
|
|
5286
5288
|
no_cancel)
|
|
5287
5289
|
|
|
5288
5290
|
|
|
@@ -5297,13 +5299,13 @@ def handle_fsync(parser, events, no_cancel=False):
|
|
|
5297
5299
|
|
|
5298
5300
|
def handle_setpriority(parser, events):
|
|
5299
5301
|
args = events[0].values
|
|
5300
|
-
return BscSetpriority(events, PriorityWhich
|
|
5302
|
+
return BscSetpriority(events, parse_enum(PriorityWhich, args[0]), args[1], args[2], serialize_result(events[-1]))
|
|
5301
5303
|
|
|
5302
5304
|
|
|
5303
5305
|
def handle_socket(parser, events):
|
|
5304
5306
|
args = events[0].values
|
|
5305
|
-
return BscSocket(events, socket.AddressFamily
|
|
5306
|
-
serialize_result(events[-1], 'fd'))
|
|
5307
|
+
return BscSocket(events, parse_enum(socket.AddressFamily, args[0]), parse_enum(socket.SocketKind, args[1]),
|
|
5308
|
+
args[2], serialize_result(events[-1], 'fd'))
|
|
5307
5309
|
|
|
5308
5310
|
|
|
5309
5311
|
def handle_connect(parser, events, no_cancel=False):
|
|
@@ -5313,7 +5315,7 @@ def handle_connect(parser, events, no_cancel=False):
|
|
|
5313
5315
|
|
|
5314
5316
|
def handle_getpriority(parser, events):
|
|
5315
5317
|
args = events[0].values
|
|
5316
|
-
return BscGetpriority(events, PriorityWhich
|
|
5318
|
+
return BscGetpriority(events, parse_enum(PriorityWhich, args[0]), args[1], serialize_result(events[-1], 'priority'))
|
|
5317
5319
|
|
|
5318
5320
|
|
|
5319
5321
|
def handle_bind(parser, events):
|
|
@@ -5342,7 +5344,7 @@ def handle_gettimeofday(parser, events):
|
|
|
5342
5344
|
|
|
5343
5345
|
def handle_getrusage(parser, events):
|
|
5344
5346
|
args = events[0].values
|
|
5345
|
-
return BscGetrusage(events, RusageWho
|
|
5347
|
+
return BscGetrusage(events, parse_enum(RusageWho, ctypes.c_int32(args[0]).value), args[1], serialize_result(events[-1]))
|
|
5346
5348
|
|
|
5347
5349
|
|
|
5348
5350
|
def handle_getsockopt(parser, events):
|
|
@@ -5415,8 +5417,8 @@ def handle_shutdown(parser, events):
|
|
|
5415
5417
|
|
|
5416
5418
|
def handle_socketpair(parser, events):
|
|
5417
5419
|
args = events[0].values
|
|
5418
|
-
return BscSocketpair(events, socket.AddressFamily
|
|
5419
|
-
serialize_result(events[-1]))
|
|
5420
|
+
return BscSocketpair(events, parse_enum(socket.AddressFamily, args[0]), parse_enum(socket.SocketKind, args[1]),
|
|
5421
|
+
args[2], args[3], serialize_result(events[-1]))
|
|
5420
5422
|
|
|
5421
5423
|
|
|
5422
5424
|
def handle_mkdir(parser, events):
|
|
@@ -5517,12 +5519,12 @@ def handle_mount(parser, events):
|
|
|
5517
5519
|
|
|
5518
5520
|
def handle_csops(parser, events):
|
|
5519
5521
|
args = events[0].values
|
|
5520
|
-
return BscCsops(events, args[0], CsopsOps
|
|
5522
|
+
return BscCsops(events, args[0], parse_enum(CsopsOps, args[1]), args[2], args[3], serialize_result(events[-1]))
|
|
5521
5523
|
|
|
5522
5524
|
|
|
5523
5525
|
def handle_csops_audittoken(parser, events):
|
|
5524
5526
|
args = events[0].values
|
|
5525
|
-
return BscCsopsAudittoken(events, args[0], CsopsOps
|
|
5527
|
+
return BscCsopsAudittoken(events, args[0], parse_enum(CsopsOps, args[1]), args[2], args[3], serialize_result(events[-1]))
|
|
5526
5528
|
|
|
5527
5529
|
|
|
5528
5530
|
def handle_waitid(parser, events, no_cancel=False):
|
|
@@ -5969,7 +5971,7 @@ def handle_semwait_signal(parser, events, no_cancel=False):
|
|
|
5969
5971
|
|
|
5970
5972
|
def handle_proc_info(parser, events):
|
|
5971
5973
|
args = events[0].values
|
|
5972
|
-
return BscProcInfo(events, ProcInfoCall
|
|
5974
|
+
return BscProcInfo(events, parse_enum(ProcInfoCall, args[0]), args[1], args[2], args[3], serialize_result(events[-1]))
|
|
5973
5975
|
|
|
5974
5976
|
|
|
5975
5977
|
def handle_sendfile(parser, events):
|
|
@@ -6199,8 +6201,8 @@ def handle_peeloff(parser, events):
|
|
|
6199
6201
|
|
|
6200
6202
|
def handle_socket_delegate(parser, events):
|
|
6201
6203
|
args = events[0].values
|
|
6202
|
-
return BscSocketDelegate(events, socket.AddressFamily
|
|
6203
|
-
serialize_result(events[-1], 'fd'))
|
|
6204
|
+
return BscSocketDelegate(events, parse_enum(socket.AddressFamily, args[0]),
|
|
6205
|
+
parse_enum(socket.SocketKind, args[1]), args[2], args[3], serialize_result(events[-1], 'fd'))
|
|
6204
6206
|
|
|
6205
6207
|
|
|
6206
6208
|
def handle_telemetry(parser, events):
|
|
@@ -6543,7 +6545,8 @@ def handle_fs_snapshot(parser, events):
|
|
|
6543
6545
|
nodes = parser.parse_vnodes(events)
|
|
6544
6546
|
name2 = nodes[1].path if len(nodes) > 1 else ''
|
|
6545
6547
|
args = events[0].values
|
|
6546
|
-
return BscFsSnapshot(events, FsSnapshotOp
|
|
6548
|
+
return BscFsSnapshot(events, parse_enum(FsSnapshotOp, args[0]), args[1], nodes[0].path, name2,
|
|
6549
|
+
serialize_result(events[-1]))
|
|
6547
6550
|
|
|
6548
6551
|
|
|
6549
6552
|
def handle_terminate_with_payload(parser, events):
|
|
@@ -4,6 +4,8 @@ from enum import Enum
|
|
|
4
4
|
from functools import partial
|
|
5
5
|
from typing import List
|
|
6
6
|
|
|
7
|
+
from pykdebugparser.trace_handlers import parse_enum
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
class AsynchronousSystemTrapsReason(Enum):
|
|
9
11
|
AST_NONE = 0x00
|
|
@@ -31,11 +33,15 @@ class AsynchronousSystemTrapsReason(Enum):
|
|
|
31
33
|
AST_UNQUIESCE = 0x200000
|
|
32
34
|
|
|
33
35
|
|
|
36
|
+
# Pairs of (member, value), accessing `.value` through the enum descriptor is slow.
|
|
37
|
+
_ASYNCHRONOUS_SYSTEM_TRAPS_REASON_VALUES = [(m, m.value) for m in AsynchronousSystemTrapsReason]
|
|
38
|
+
|
|
39
|
+
|
|
34
40
|
def to_ast_reasons(flags: int):
|
|
35
41
|
if not flags:
|
|
36
42
|
return [AsynchronousSystemTrapsReason.AST_NONE]
|
|
37
43
|
else:
|
|
38
|
-
return [r for r in
|
|
44
|
+
return [r for r, value in _ASYNCHRONOUS_SYSTEM_TRAPS_REASON_VALUES if value & flags]
|
|
39
45
|
|
|
40
46
|
|
|
41
47
|
# osfmk/arm64/proc_reg.h
|
|
@@ -86,8 +92,11 @@ class ThreadState(Enum):
|
|
|
86
92
|
TH_IDLE = 0x80
|
|
87
93
|
|
|
88
94
|
|
|
95
|
+
_THREAD_STATE_VALUES = [(m, m.value) for m in ThreadState]
|
|
96
|
+
|
|
97
|
+
|
|
89
98
|
def to_thread_state(flags: int):
|
|
90
|
-
return [s for s in
|
|
99
|
+
return [s for s, value in _THREAD_STATE_VALUES if value & flags]
|
|
91
100
|
|
|
92
101
|
|
|
93
102
|
class InterruptType(Enum):
|
|
@@ -134,11 +143,14 @@ class VmProtection(Enum):
|
|
|
134
143
|
VM_PROT_STRIP_READ = 0x80
|
|
135
144
|
|
|
136
145
|
|
|
146
|
+
_VM_PROTECTION_VALUES = [(m, m.value) for m in VmProtection]
|
|
147
|
+
|
|
148
|
+
|
|
137
149
|
def to_vm_prot(flags: int):
|
|
138
150
|
if not flags:
|
|
139
151
|
return [VmProtection.VM_PROT_NONE]
|
|
140
152
|
else:
|
|
141
|
-
return [p for p in
|
|
153
|
+
return [p for p, value in _VM_PROTECTION_VALUES if value & flags]
|
|
142
154
|
|
|
143
155
|
|
|
144
156
|
# osfmk/mach/port.h
|
|
@@ -309,7 +321,7 @@ class Interrupt:
|
|
|
309
321
|
type: int
|
|
310
322
|
|
|
311
323
|
def __str__(self):
|
|
312
|
-
return f'INTERRUPT, pc: {hex(self.pc)}, is_user: {self.is_user}, type: {InterruptType
|
|
324
|
+
return f'INTERRUPT, pc: {hex(self.pc)}, is_user: {self.is_user}, type: {parse_enum(InterruptType, self.type).name}'
|
|
313
325
|
|
|
314
326
|
|
|
315
327
|
@dataclass
|
|
@@ -1047,7 +1059,7 @@ def handle_msc_mach_vm_map_trap(parser, events):
|
|
|
1047
1059
|
|
|
1048
1060
|
def handle_msc_mach_port_allocate_trap(parser, events):
|
|
1049
1061
|
args = events[0].values
|
|
1050
|
-
return MachPortAllocate(events, args[0], MachPortRight
|
|
1062
|
+
return MachPortAllocate(events, args[0], parse_enum(MachPortRight, args[1]), args[2])
|
|
1051
1063
|
|
|
1052
1064
|
|
|
1053
1065
|
def handle_msc_mach_port_deallocate_trap(parser, events):
|
|
@@ -1056,11 +1068,11 @@ def handle_msc_mach_port_deallocate_trap(parser, events):
|
|
|
1056
1068
|
|
|
1057
1069
|
def handle_msc_mach_port_mod_refs_trap(parser, events):
|
|
1058
1070
|
args = events[0].values
|
|
1059
|
-
return MachPortModRefs(events, args[0], args[1], MachPortRight
|
|
1071
|
+
return MachPortModRefs(events, args[0], args[1], parse_enum(MachPortRight, args[2]), args[3])
|
|
1060
1072
|
|
|
1061
1073
|
|
|
1062
1074
|
def handle_msc_mach_port_insert_right_trap(parser, events):
|
|
1063
|
-
return MachPortInsertRight(events, *events[0].values[:3], MachMsgTypeName
|
|
1075
|
+
return MachPortInsertRight(events, *events[0].values[:3], parse_enum(MachMsgTypeName, events[0].values[3]))
|
|
1064
1076
|
|
|
1065
1077
|
|
|
1066
1078
|
def handle_msc_mach_port_insert_member_trap(parser, events):
|
|
@@ -1105,12 +1117,12 @@ def handle_msc_semaphore_wait_trap(parser, events):
|
|
|
1105
1117
|
|
|
1106
1118
|
def handle_msc_semaphore_timedwait_trap(parser, events):
|
|
1107
1119
|
args = events[0].values
|
|
1108
|
-
return SemaphoreTimedwait(events, args[0], args[1] & 0xffffffff, args[2], KernReturn
|
|
1120
|
+
return SemaphoreTimedwait(events, args[0], args[1] & 0xffffffff, args[2], parse_enum(KernReturn, events[-1].values[0]))
|
|
1109
1121
|
|
|
1110
1122
|
|
|
1111
1123
|
def handle_msc_mach_port_get_attributes_trap(parser, events):
|
|
1112
1124
|
args = events[0].values
|
|
1113
|
-
return MachPortGetAttributes(events, *args[:2], MachPortFlavor
|
|
1125
|
+
return MachPortGetAttributes(events, *args[:2], parse_enum(MachPortFlavor, args[2]), args[3])
|
|
1114
1126
|
|
|
1115
1127
|
|
|
1116
1128
|
def handle_msc_mach_port_guard_trap(parser, events):
|
|
@@ -1135,7 +1147,7 @@ def handle_msc_thread_get_special_reply_port(parser, events):
|
|
|
1135
1147
|
|
|
1136
1148
|
def handle_msc_thread_switch(parser, events):
|
|
1137
1149
|
args = events[0].values
|
|
1138
|
-
return ThreadSwitch(events, args[0], SwitchOption
|
|
1150
|
+
return ThreadSwitch(events, args[0], parse_enum(SwitchOption, args[1]), args[2])
|
|
1139
1151
|
|
|
1140
1152
|
|
|
1141
1153
|
def handle_msc_host_create_mach_voucher_trap(parser, events):
|
|
@@ -1176,7 +1188,7 @@ def handle_msc_mk_timer_cancel(parser, events):
|
|
|
1176
1188
|
|
|
1177
1189
|
def handle_msc_mk_timer_arm_leeway(parser, events):
|
|
1178
1190
|
args = events[0].values
|
|
1179
|
-
return MkTimerArmLeeway(events, args[0], MkTimerFlags
|
|
1191
|
+
return MkTimerArmLeeway(events, args[0], parse_enum(MkTimerFlags, args[1]), args[2], args[3])
|
|
1180
1192
|
|
|
1181
1193
|
|
|
1182
1194
|
def handle_msc_iokit_user_client(parser, events):
|
|
@@ -1200,7 +1212,7 @@ def handle_mach_vmfault(parser, events):
|
|
|
1200
1212
|
pid = None
|
|
1201
1213
|
caller_prot = None
|
|
1202
1214
|
if result == 0:
|
|
1203
|
-
fault_type = DbgVmFaultType
|
|
1215
|
+
fault_type = parse_enum(DbgVmFaultType, rets[3])
|
|
1204
1216
|
real_events = [e for e in events[1:-1] if 0x1320008 <= e.eventid <= 0x1320014]
|
|
1205
1217
|
if real_events:
|
|
1206
1218
|
vm_fault_real = parser.parse_event_list(real_events)
|
|
@@ -1212,7 +1224,7 @@ def handle_mach_vmfault(parser, events):
|
|
|
1212
1224
|
def handle_real_fault_address(addr_type, parser, events):
|
|
1213
1225
|
args = events[0].values
|
|
1214
1226
|
caller_prot = to_vm_prot((args[1] >> 8) & 0xff)
|
|
1215
|
-
fault_type = DbgVmFaultType
|
|
1227
|
+
fault_type = parse_enum(DbgVmFaultType, args[1] & 0xff)
|
|
1216
1228
|
return addr_type(events, args[0], args[1] >> 16, caller_prot, fault_type, args[2], args[3])
|
|
1217
1229
|
|
|
1218
1230
|
|
|
@@ -1233,7 +1245,7 @@ def handle_mach_mkrunnable(parser, events):
|
|
|
1233
1245
|
|
|
1234
1246
|
def handle_mach_idle(parser, events):
|
|
1235
1247
|
args = events[-1].values
|
|
1236
|
-
return MachIdle(events, args[0], ProcessState
|
|
1248
|
+
return MachIdle(events, args[0], parse_enum(ProcessState, args[1]), args[2], to_ast_reasons(args[3]))
|
|
1237
1249
|
|
|
1238
1250
|
|
|
1239
1251
|
def handle_mach_block(parser, events):
|
|
@@ -21,8 +21,12 @@ class SamplerAction(Enum):
|
|
|
21
21
|
SAMPLER_TK_INFO = 0x2000
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
# Pairs of (member, value), accessing `.value` through the enum descriptor is slow.
|
|
25
|
+
_SAMPLER_ACTION_VALUES = [(m, m.value) for m in SamplerAction]
|
|
26
|
+
|
|
27
|
+
|
|
24
28
|
def to_sampler_action(flags: int):
|
|
25
|
-
return [s for s in
|
|
29
|
+
return [s for s, value in _SAMPLER_ACTION_VALUES if value & flags]
|
|
26
30
|
|
|
27
31
|
|
|
28
32
|
class KperfTiState(Enum):
|
|
@@ -35,8 +39,11 @@ class KperfTiState(Enum):
|
|
|
35
39
|
KPERF_TI_IDLE = 0x40
|
|
36
40
|
|
|
37
41
|
|
|
42
|
+
_KPERF_TI_STATE_VALUES = [(m, m.value) for m in KperfTiState]
|
|
43
|
+
|
|
44
|
+
|
|
38
45
|
def to_kperf_ti_state(flags: int):
|
|
39
|
-
return [s for s in
|
|
46
|
+
return [s for s, value in _KPERF_TI_STATE_VALUES if value & flags]
|
|
40
47
|
|
|
41
48
|
|
|
42
49
|
class CallstackFlag(Enum):
|
|
@@ -51,8 +58,11 @@ class CallstackFlag(Enum):
|
|
|
51
58
|
CALLSTACK_FIXUP_PC = 0x100
|
|
52
59
|
|
|
53
60
|
|
|
61
|
+
_CALLSTACK_FLAG_VALUES = [(m, m.value) for m in CallstackFlag]
|
|
62
|
+
|
|
63
|
+
|
|
54
64
|
def to_callstack_flags(flags: int):
|
|
55
|
-
return [c for c in
|
|
65
|
+
return [c for c, value in _CALLSTACK_FLAG_VALUES if value & flags]
|
|
56
66
|
|
|
57
67
|
|
|
58
68
|
@dataclass
|
|
@@ -2,6 +2,8 @@ from dataclasses import dataclass
|
|
|
2
2
|
from enum import Enum
|
|
3
3
|
from typing import List
|
|
4
4
|
|
|
5
|
+
from pykdebugparser.trace_handlers import parse_enum
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
class TurnstileType(Enum):
|
|
7
9
|
TURNSTILE_NONE = 0
|
|
@@ -232,11 +234,11 @@ def handle_thread_user_promotion_change(parser, events):
|
|
|
232
234
|
|
|
233
235
|
|
|
234
236
|
def handle_turnstile_turnstile_prepare(parser, events):
|
|
235
|
-
return TurnstilePrepare(events, *events[0].values[:2], TurnstileType
|
|
237
|
+
return TurnstilePrepare(events, *events[0].values[:2], parse_enum(TurnstileType, events[0].values[2]))
|
|
236
238
|
|
|
237
239
|
|
|
238
240
|
def handle_turnstile_turnstile_complete(parser, events):
|
|
239
|
-
return TurnstileComplete(events, *events[0].values[:2], TurnstileType
|
|
241
|
+
return TurnstileComplete(events, *events[0].values[:2], parse_enum(TurnstileType, events[0].values[2]))
|
|
240
242
|
|
|
241
243
|
|
|
242
244
|
handlers = {
|
pykdebugparser/traces_parser.py
CHANGED
|
@@ -39,26 +39,27 @@ class TracesParser:
|
|
|
39
39
|
self.handlers.update(turnstile_handlers)
|
|
40
40
|
|
|
41
41
|
def feed(self, event):
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return self.qualifiers_actions[event.func_qualifier](event, self.on_going_traces)
|
|
42
|
+
trace_name = self.trace_codes.get(event.eventid)
|
|
43
|
+
if trace_name is not None and trace_name in trace_handlers:
|
|
44
|
+
return self.qualifiers_actions[event.func_qualifier](event, self.on_going_traces)
|
|
46
45
|
|
|
47
46
|
return self.qualifiers_actions[event.func_qualifier](event, self.on_going_events)
|
|
48
47
|
|
|
49
48
|
def feed_generator(self, generator):
|
|
49
|
+
feed = self.feed
|
|
50
50
|
for event in generator:
|
|
51
|
-
ret =
|
|
51
|
+
ret = feed(event)
|
|
52
52
|
if ret is not None:
|
|
53
53
|
yield ret
|
|
54
54
|
|
|
55
55
|
def parse_event_list(self, events):
|
|
56
|
-
|
|
56
|
+
trace_name = self.trace_codes.get(events[0].eventid)
|
|
57
|
+
if trace_name is None:
|
|
57
58
|
return None
|
|
58
|
-
|
|
59
|
-
if
|
|
59
|
+
handler = self.handlers.get(trace_name)
|
|
60
|
+
if handler is None:
|
|
60
61
|
return None
|
|
61
|
-
return
|
|
62
|
+
return handler(self, events)
|
|
62
63
|
|
|
63
64
|
@staticmethod
|
|
64
65
|
def vnode_generator(events):
|
|
@@ -89,26 +90,30 @@ class TracesParser:
|
|
|
89
90
|
return list(self.vnode_generator([e for e in events if self.trace_codes.get(e.eventid) == 'VFS_LOOKUP']))
|
|
90
91
|
|
|
91
92
|
def _feed_start_event(self, event, state):
|
|
92
|
-
|
|
93
|
+
tid_state = state.get(event.tid)
|
|
94
|
+
if tid_state is None:
|
|
93
95
|
# New tid
|
|
94
|
-
state[event.tid] = {}
|
|
96
|
+
tid_state = state[event.tid] = {}
|
|
95
97
|
|
|
96
|
-
|
|
97
|
-
for
|
|
98
|
-
|
|
98
|
+
tid_state[event.eventid] = []
|
|
99
|
+
for events in tid_state.values():
|
|
100
|
+
events.append(event)
|
|
99
101
|
|
|
100
102
|
def _feed_end_event(self, event, state):
|
|
101
|
-
|
|
103
|
+
tid_state = state.get(event.tid)
|
|
104
|
+
if tid_state is None or event.eventid not in tid_state:
|
|
102
105
|
# Event end without start.
|
|
103
106
|
return
|
|
104
107
|
|
|
105
|
-
for
|
|
106
|
-
|
|
108
|
+
for events in tid_state.values():
|
|
109
|
+
events.append(event)
|
|
107
110
|
|
|
108
|
-
events =
|
|
111
|
+
events = tid_state.pop(event.eventid)
|
|
109
112
|
return self.parse_event_list(events)
|
|
110
113
|
|
|
111
114
|
def _feed_single_event(self, event, state):
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
tid_state = state.get(event.tid)
|
|
116
|
+
if tid_state:
|
|
117
|
+
for events in tid_state.values():
|
|
118
|
+
events.append(event)
|
|
114
119
|
return self.parse_event_list([event])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pykdebugparser
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Python parser for kdebug events
|
|
5
5
|
Author: Matan Perelman
|
|
6
6
|
Author-email: matan1008@gmail.com
|
|
@@ -28,17 +28,17 @@ License: MIT License
|
|
|
28
28
|
|
|
29
29
|
Project-URL: Homepage, https://github.com/matan1008/pykdebugparser
|
|
30
30
|
Project-URL: Source, https://github.com/matan1008/pykdebugparser
|
|
31
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
32
31
|
Classifier: Programming Language :: Python :: 3.9
|
|
33
32
|
Classifier: Programming Language :: Python :: 3.10
|
|
34
33
|
Classifier: Programming Language :: Python :: 3.11
|
|
35
34
|
Classifier: Programming Language :: Python :: 3.12
|
|
36
35
|
Classifier: Programming Language :: Python :: 3.13
|
|
37
36
|
Classifier: Programming Language :: Python :: 3.14
|
|
38
|
-
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
38
|
+
Requires-Python: >=3.9
|
|
39
39
|
Description-Content-Type: text/markdown
|
|
40
40
|
License-File: LICENSE
|
|
41
|
-
Requires-Dist:
|
|
41
|
+
Requires-Dist: bytestruct>=0.1.0
|
|
42
42
|
Requires-Dist: pygments
|
|
43
43
|
Requires-Dist: click
|
|
44
44
|
Requires-Dist: termcolor
|
|
@@ -0,0 +1,24 @@
|
|
|
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=z8R20sBeohiQUV2QvQjqNOhFyg6PGsM6tri3joGbccw,10108
|
|
8
|
+
pykdebugparser/trace.codes,sha256=smXNbWkJyxXEZpaeXTkeFU7zrBTAMeXxw-_bIV70LTw,90621
|
|
9
|
+
pykdebugparser/trace_codes.py,sha256=4ImGc_jNpY-12cGXdwy6NMCEYcI24oYn1NBOaKNdWNo,1984
|
|
10
|
+
pykdebugparser/traces_parser.py,sha256=Xq6nDvc5mSv25efaNSWIVc9ZO_dq8UPkbel-MqTXZ4Y,4442
|
|
11
|
+
pykdebugparser/trace_handlers/__init__.py,sha256=r1dzmNTxiEtgENq9WFRVQX0g9f9dRxgAgVxjDXSc7s4,679
|
|
12
|
+
pykdebugparser/trace_handlers/bsd.py,sha256=QVXPjWqm4bHhvbHpE32AwieaLyvwkxvzn6T1oT-4sBg,173654
|
|
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.3.0.dist-info/licenses/LICENSE,sha256=KjZXYP_CGYzDTtVfcxPT6wlGRoQKHPSJu0vh55_2QSo,1071
|
|
20
|
+
pykdebugparser-1.3.0.dist-info/METADATA,sha256=McG8-jtDn654bLjLmCSCJULcJj4lBXJGfTJSwAyQLwU,18995
|
|
21
|
+
pykdebugparser-1.3.0.dist-info/WHEEL,sha256=4YBfCYNH4wlLpv3pzq1hbEuIlXA4WJabKLFurZ7eTL0,109
|
|
22
|
+
pykdebugparser-1.3.0.dist-info/entry_points.txt,sha256=NrVIOgPR6v2MbpTZ3aKedP1jQ93nUQus6sgP4wAD0_E,63
|
|
23
|
+
pykdebugparser-1.3.0.dist-info/top_level.txt,sha256=FcHHXR9VKmqufFw0a6UP4c8NyS-u4ORY-yXQ6ZzP_28,15
|
|
24
|
+
pykdebugparser-1.3.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=H87PsFFRBKdilIW2IDuR2MtTLb0LBnoDEVyhXTrNeEc,7255
|
|
5
|
-
pykdebugparser/kevent.py,sha256=-wz8HnSwpW2IbcEg3In3R5JF2EOYt-9Q77HudXKdBZM,951
|
|
6
|
-
pykdebugparser/os_log_event.py,sha256=jNBp64RRAsqKSpJwf6Jnf0-ZiWDIMsCJ1FyBgfkjN_M,12251
|
|
7
|
-
pykdebugparser/pykdebugparser.py,sha256=_fFvSzWn6t5WWuvCBM4aqr052pahq-fDjW-HzsWx1HY,8507
|
|
8
|
-
pykdebugparser/trace.codes,sha256=smXNbWkJyxXEZpaeXTkeFU7zrBTAMeXxw-_bIV70LTw,90621
|
|
9
|
-
pykdebugparser/trace_codes.py,sha256=cixU1NW149TvMk8_dKJHHrpyUr9JXyVsD815mqG7mVc,938
|
|
10
|
-
pykdebugparser/traces_parser.py,sha256=sUYPynanzyrlJZ82umbp-Jzp_pvtNQ8U8KyBgtK5LvE,4382
|
|
11
|
-
pykdebugparser/trace_handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
pykdebugparser/trace_handlers/bsd.py,sha256=CaVVORps_mce02T8aewQA747KQfLhD_Yk4uNMFS2qN0,173383
|
|
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=jXcNblxJf4eM5_hyvR02jYk9zzFuN6iDIvUc3Mbn4qY,34486
|
|
16
|
-
pykdebugparser/trace_handlers/perf.py,sha256=OX79TBNN4rFTmm2vwt17HphGzC9LLnimkcbtQqZeT7Y,4749
|
|
17
|
-
pykdebugparser/trace_handlers/trace.py,sha256=Lu8s9XT1jb5avtrVXF6SQ6FUj93xYc0TwJd7yoIjV9U,5377
|
|
18
|
-
pykdebugparser/trace_handlers/turnstile.py,sha256=E8sRF3mvNMKLj465YfMpFgfF-5b6K_EbC7te8p0W0Mc,8155
|
|
19
|
-
pykdebugparser-1.2.8.dist-info/licenses/LICENSE,sha256=KjZXYP_CGYzDTtVfcxPT6wlGRoQKHPSJu0vh55_2QSo,1071
|
|
20
|
-
pykdebugparser-1.2.8.dist-info/METADATA,sha256=Y-_cCRLJp7PBpFr0u0i4YREvSr0W_9p3RAeVPyHcrkU,18986
|
|
21
|
-
pykdebugparser-1.2.8.dist-info/WHEEL,sha256=qWEHOWeXof9kJLXAXJ1hukQ_MvVtaV9UUAew6N7Y99E,109
|
|
22
|
-
pykdebugparser-1.2.8.dist-info/entry_points.txt,sha256=NrVIOgPR6v2MbpTZ3aKedP1jQ93nUQus6sgP4wAD0_E,63
|
|
23
|
-
pykdebugparser-1.2.8.dist-info/top_level.txt,sha256=FcHHXR9VKmqufFw0a6UP4c8NyS-u4ORY-yXQ6ZzP_28,15
|
|
24
|
-
pykdebugparser-1.2.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|