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.
@@ -1,21 +1,27 @@
1
- import struct
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 construct import Adapter, Struct, Const, Padding, Int32ul, Int64ul, Array, GreedyRange, Byte, FixedSized, \
7
- CString, Prefixed, GreedyBytes, Aligned, Bytes, Select
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
- class BplistAdapter(Adapter):
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
- Construct adapter ti build and parse plists.
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
- def _decode(self, obj, context, path):
44
- return plistlib.loads(obj)
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
- kd_header_v3 = Struct(
62
- 'tag' / Int32ul,
63
- 'sub_tag' / Int32ul,
64
- 'length' / Int64ul,
65
- 'timebase_numer' / Int32ul,
66
- 'timebase_denom' / Int32ul,
67
- 'timestamp' / Int64ul,
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
- kd_v3_threadmap = Struct(
79
- 'threadmap' / Prefixed(Int64ul, GreedyRange(kd_threadmap)),
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
- kd_v3_additional_data = GreedyRange(Struct(
83
- 'tag' / Bytes(8),
84
- 'data' / Select(Aligned(8, Prefixed(Int64ul, GreedyBytes)), Prefixed(Int64ul, GreedyBytes)),
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
- parsed_header = kd_header_v2.parse_stream(reader)
142
- self.set_thread_map(parsed_header.threadmap)
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 = reader.read(KEVENT_SIZE)
233
+ buf = read1(KEVENTS_READ_SIZE)
145
234
  if not buf:
146
235
  break
147
- yield from_kd_buf(buf)
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
- self.v3_header = Aligned(8, kd_header_v3).parse_stream(reader)
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
- threadmap = kd_v3_threadmap.parse_stream(reader).threadmap
162
- self.set_thread_map(threadmap)
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 = Int64ul.parse_stream(reader)
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 = kd_v3_additional_data.parse_stream(reader)
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, args_buf, tid, debugid, cpuid, unused = struct.unpack(KD_BUF_FORMAT, kd_buf)
28
- eventid = debugid & KDBG_EVENTID_MASK
29
- qual = debugid & KDBG_FUNC_MASK
30
- args = struct.unpack('<QQQQ', args_buf) # There are 4 arguments, 64 bit each.
31
- return Kevent(timestamp, args_buf, args, tid, debugid, eventid, qual)
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
@@ -3,7 +3,7 @@ from datetime import datetime, timezone
3
3
  import enum
4
4
  from typing import List, Dict
5
5
 
6
- from construct import Struct, Byte, Int32ul, BitStruct, Padding, Flag, BitsInteger, Int64ul
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.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.trace: FirehoseTracepointSingpostFlags,
103
+ FirehoseTracepointNamespace.signpost: FirehoseTracepointSingpostFlags,
104
104
  }
105
105
 
106
- firehose_tracepoint_id = Struct(
107
- 'namespace' / Byte,
108
- 'type_' / Byte,
109
- 'trace_flags' / BitStruct(
110
- Padding(2),
111
- 'has_large_offset' / Flag,
112
- 'has_unique_pid' / Flag,
113
- 'pc_style' / BitsInteger(3),
114
- 'has_current_aid' / Flag,
115
- ),
116
- 'flags' / Byte,
117
- 'code' / Int32ul,
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 = firehose_tracepoint_id.parse(Int64ul.build(trace_identifier))
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=trace_id.trace_flags.has_large_offset,
279
- has_unique_pid=trace_id.trace_flags.has_unique_pid,
280
- pc_style=FirehoseTracepointFlagsPcStyle(trace_id.trace_flags.pc_style),
281
- has_current_aid=trace_id.trace_flags.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,
@@ -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 = filter(lambda e: not isinstance(e, OsLogEvent), events_generator)
48
- if self.filter_tid is not None:
49
- events_generator = filter(lambda e: e.tid == self.filter_tid, events_generator)
50
- if self.filter_class or self.filter_subclass:
51
- events_generator = filter(lambda e: self._is_eventid_allowed(e.eventid), events_generator)
52
- return events_generator
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(filter(lambda sc: sc >> 8 == DBG_BSD, self.filter_subclass))
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
- ts = datetime.fromtimestamp((self.usecs_since_epoch + offset_usec) / 1000000, tz=self.timezone)
117
- time_string = ts.strftime('%Y-%m-%d %H:%M:%S.%f')
118
- return f'{time_string:<27}'
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)
@@ -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(option_name).name
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(args[0]), args[1], args[2], serialize_result(events[-1]))
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(args[1]), args[2], serialize_result(events[-1], 'return'),
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(args[0]), args[1], args[2], serialize_result(events[-1]))
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(args[0]), socket.SocketKind(args[1]), args[2],
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(args[0]), args[1], serialize_result(events[-1], 'priority'))
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(ctypes.c_int32(args[0]).value), args[1], serialize_result(events[-1]))
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(args[0]), socket.SocketKind(args[1]), args[2], args[3],
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(args[1]), args[2], args[3], serialize_result(events[-1]))
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(args[1]), args[2], args[3], serialize_result(events[-1]))
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(args[0]), args[1], args[2], args[3], serialize_result(events[-1]))
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(args[0]), socket.SocketKind(args[1]), args[2], args[3],
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(args[0]), args[1], nodes[0].path, name2, serialize_result(events[-1]))
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 AsynchronousSystemTrapsReason if r.value & flags]
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 ThreadState if s.value & flags]
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 VmProtection if p.value & flags]
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(self.type).name}'
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(args[1]), args[2])
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(args[2]), args[3])
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(events[0].values[3]))
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(events[-1].values[0]))
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(args[2]), args[3])
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(args[1]), args[2])
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(args[1]), args[2], args[3])
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(rets[3])
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(args[1] & 0xff)
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(args[1]), args[2], to_ast_reasons(args[3]))
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 SamplerAction if s.value & flags]
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 KperfTiState if s.value & flags]
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 CallstackFlag if c.value & flags]
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(events[0].values[2]))
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(events[0].values[2]))
241
+ return TurnstileComplete(events, *events[0].values[:2], parse_enum(TurnstileType, events[0].values[2]))
240
242
 
241
243
 
242
244
  handlers = {
@@ -39,26 +39,27 @@ class TracesParser:
39
39
  self.handlers.update(turnstile_handlers)
40
40
 
41
41
  def feed(self, event):
42
- if event.eventid in self.trace_codes:
43
- trace_name = self.trace_codes[event.eventid]
44
- if trace_name in trace_handlers:
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 = self.feed(event)
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
- if events[0].eventid not in self.trace_codes:
56
+ trace_name = self.trace_codes.get(events[0].eventid)
57
+ if trace_name is None:
57
58
  return None
58
- trace_name = self.trace_codes[events[0].eventid]
59
- if trace_name not in self.handlers:
59
+ handler = self.handlers.get(trace_name)
60
+ if handler is None:
60
61
  return None
61
- return self.handlers[trace_name](self, events)
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
- if event.tid not in state:
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
- state[event.tid][event.eventid] = []
97
- for eventid in state[event.tid]:
98
- state[event.tid][eventid].append(event)
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
- if event.tid not in state or event.eventid not in state[event.tid]:
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 eventid in state[event.tid]:
106
- state[event.tid][eventid].append(event)
108
+ for events in tid_state.values():
109
+ events.append(event)
107
110
 
108
- events = state[event.tid].pop(event.eventid)
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
- for eventid in state.get(event.tid, {}):
113
- state[event.tid][eventid].append(event)
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.2.8
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
- Requires-Python: >=3.8
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: construct
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,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (83.0.0)
2
+ Generator: setuptools (84.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any
@@ -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,,