xref: /xnu-8019.80.24/tools/lldbmacros/ktrace.py (revision a325d9c4a84054e40bbe985afedcb50ab80993ea)
1from __future__ import print_function
2from xnu import *
3from utils import *
4from core.lazytarget import *
5from misc import *
6from kcdata import kcdata_item_iterator, KCObject, GetTypeForName, KCCompressedBufferObject
7from collections import namedtuple
8import heapq
9import os
10import plistlib
11import struct
12import subprocess
13import sys
14import tempfile
15import time
16
17# From the defines in bsd/sys/kdebug.h:
18
19KdebugClassNames = {
20    1: "MACH",
21    2: "NETWORK",
22    3: "FSYSTEM",
23    4: "BSD",
24    5: "IOKIT",
25    6: "DRIVERS",
26    7: "TRACE",
27    8: "DLIL",
28    9: "WORKQUEUE",
29    10: "CORESTORAGE",
30    11: "CG",
31    20: "MISC",
32    30: "SECURITY",
33    31: "DYLD",
34    32: "QT",
35    33: "APPS",
36    34: "LAUNCHD",
37    36: "PPT",
38    37: "PERF",
39    38: "IMPORTANCE",
40    39: "PERFCTRL",
41    40: "BANK",
42    41: "XPC",
43    42: "ATM",
44    43: "ARIADNE",
45    44: "DAEMON",
46    45: "ENERGYTRACE",
47    49: "IMG",
48    50: "CLPC",
49    128: "ANS",
50    129: "SIO",
51    130: "SEP",
52    131: "ISP",
53    132: "OSCAR",
54    133: "EMBEDDEDGFX"
55}
56
57def GetKdebugClassName(class_num):
58    return (KdebugClassNames[class_num] + ' ({})'.format(class_num) if class_num in KdebugClassNames else 'unknown ({})'.format(class_num))
59
60@lldb_type_summary(['typefilter_t'])
61@header('{0: <20s}'.format("class") + ' '.join(map('{:02x}'.format, xrange(0, 255, 8))))
62def GetKdebugTypefilter(typefilter):
63    """ Summarizes the provided typefilter.
64    """
65    classes = 256
66    subclasses_per_class = 256
67
68    # 8 bits at a time
69    subclasses_per_element = 64
70    cur_typefilter = cast(typefilter, 'uint64_t *')
71    subclasses_fmts = ' '.join(['{:02x}'] * 8)
72
73    elements_per_class = subclasses_per_class / subclasses_per_element
74
75    out_str = ''
76    for i in xrange(0, classes):
77        print_class = False
78        subclasses = [0] * elements_per_class
79
80        # check subclass ranges for set bits, remember those subclasses
81        for j in xrange(0, elements_per_class):
82            element = unsigned(cur_typefilter[i * elements_per_class + j])
83            if element != 0:
84                print_class = True
85            if print_class:
86                subclasses[j] = element
87
88        ## if any of the bits were set in a class, print the entire class
89        if print_class:
90            out_str += '{:<20s}'.format(GetKdebugClassName(i))
91            for element in subclasses:
92                # split up the 64-bit values into byte-sized pieces
93                bytes = [unsigned((element >> i) & 0xff) for i in (0, 8, 16, 24, 32, 40, 48, 56)]
94                out_str += subclasses_fmts.format(*bytes)
95                out_str += ' '
96
97            out_str += '\n'
98
99    return out_str
100
101@lldb_command('showkdebugtypefilter')
102def ShowKdebugTypefilter(cmd_args=None):
103    """ Show the current kdebug typefilter (or the typefilter at an address)
104
105        usage: showkdebugtypefilter [<address>]
106    """
107
108    if cmd_args:
109        typefilter = kern.GetValueFromAddress(cmd_args[0], 'typefilter_t')
110        if unsigned(typefilter) == 0:
111            raise ArgumentError('argument provided is NULL')
112
113        print(GetKdebugTypefilter.header)
114        print('-' * len(GetKdebugTypefilter.header))
115
116        print(GetKdebugTypefilter(typefilter))
117        return
118
119    typefilter = kern.globals.kdbg_typefilter
120    if unsigned(typefilter) == 0:
121        raise ArgumentError('no argument provided and active typefilter is not set')
122
123    print(GetKdebugTypefilter.header)
124    print('-' * len(GetKdebugTypefilter.header))
125    print(GetKdebugTypefilter(typefilter))
126
127def GetKdebugStatus():
128    """ Get a string summary of the kdebug subsystem.
129    """
130    out = ''
131
132    kdebug_flags = kern.globals.kd_ctrl_page_trace.kdebug_flags
133    out += 'kdebug flags: {}\n'.format(xnudefines.GetStateString(xnudefines.kdebug_flags_strings, kdebug_flags))
134    events = kern.globals.kd_data_page_trace.nkdbufs
135    buf_mb = events * (64 if kern.arch == 'x86_64' or kern.arch.startswith('arm64') else 32) / 1000000
136    out += 'events allocated: {:<d} ({:<d} MB)\n'.format(events, buf_mb)
137    out += 'enabled: {}\n'.format('yes' if kern.globals.kdebug_enable != 0 else 'no')
138    if kdebug_flags & xnudefines.KDBG_TYPEFILTER_CHECK:
139        out += 'typefilter:\n'
140        out += GetKdebugTypefilter.header + '\n'
141        out += '-' * len(GetKdebugTypefilter.header) + '\n'
142        typefilter = kern.globals.kdbg_typefilter
143        if unsigned(typefilter) != 0:
144            out += GetKdebugTypefilter(typefilter)
145
146    return out
147
148@lldb_command('showkdebug')
149def ShowKdebug(cmd_args=None):
150    """ Show the current kdebug state.
151
152        usage: showkdebug
153    """
154
155    print(GetKdebugStatus())
156
157@lldb_type_summary(['kperf_timer'])
158@header('{:<10s} {:<7s} {:<20s} {:<20s}'.format('period-ns', 'action', 'deadline', 'fire-time'))
159def GetKperfTimerSummary(timer):
160    """ Get a string summary of a kperf timer.
161
162        params:
163            timer: the kptimer object to get a summary of
164    """
165    try:
166        fire_time = timer.kt_fire_time
167    except:
168        fire_time = 0
169    return '{:<10d} {:<7d} {:<20d} {:<20d}\n'.format(
170        kern.GetNanotimeFromAbstime(timer.kt_period_abs), timer.kt_actionid,
171        timer.kt_cur_deadline, fire_time)
172
173@lldb_type_summary(['action'])
174@header('{:<10s} {:<20s} {:<20s}'.format('pid-filter', 'user-data', 'samplers'))
175def GetKperfActionSummary(action):
176    """ Get a string summary of a kperf action.
177
178        params:
179            action: the action object to get a summary of
180    """
181    samplers = xnudefines.GetStateString(xnudefines.kperf_samplers_strings, action.sample)
182    return '{:<10s} {:<20x} {:<20s}\n'.format(
183        '-' if action.pid_filter < 0 else str(action.pid_filter), action.userdata, samplers)
184
185def GetKperfKdebugFilterDescription():
186    kdebug_filter = kern.globals.kperf_kdebug_filter
187    desc = ''
188    for i in xrange(kdebug_filter.n_debugids):
189        filt_index = 1 if i >= 16 else 0
190        filt_type = (kdebug_filter.types[filt_index] >> ((i % 16) * 4)) & 0xf
191        debugid = kdebug_filter.debugids[i]
192        if filt_type < 2:
193            prefix = 'C'
194            width = 2
195            id = debugid >> 24
196        elif filt_type < 4:
197            prefix = 'S'
198            width = 4
199            id = debugid >> 16
200        else:
201            prefix = 'D'
202            width = 8
203            id = debugid
204
205        suffix = ''
206        if (filt_type % 2) == 1:
207            if debugid & xnudefines.DBG_FUNC_START:
208                suffix = 's'
209            elif debugid & xnudefines.DBG_FUNC_END:
210                suffix = 'r'
211            else:
212                suffix = 'n'
213
214        if i > 0:
215            desc += ','
216        desc += '{prefix}{id:0{width}x}{suffix}'.format(
217                prefix=prefix, id=id, width=width, suffix=suffix)
218
219    return desc
220
221def GetKperfStatus():
222    """ Get a string summary of the kperf subsystem.
223    """
224    out = ''
225
226    kperf_status = int(kern.globals.kperf_status)
227    out += 'sampling: '
228    if kperf_status == GetEnumValue('kperf_sampling::KPERF_SAMPLING_OFF'):
229        out += 'off\n'
230    elif kperf_status == GetEnumValue('kperf_sampling::KPERF_SAMPLING_SHUTDOWN'):
231        out += 'shutting down\n'
232    elif kperf_status == GetEnumValue('kperf_sampling::KPERF_SAMPLING_ON'):
233        out += 'on\n'
234    else:
235        out += 'unknown\n'
236
237    pet = kern.globals.kptimer.g_pet_active
238    pet_timer_id = kern.globals.kptimer.g_pet_active
239    if pet != 0:
240        pet_idle_rate = kern.globals.pet_idle_rate
241        out += 'legacy PET is active (timer = {:<d}, idle rate = {:<d})\n'.format(pet_timer_id, pet_idle_rate)
242    else:
243        out += 'legacy PET is off\n'
244
245    lw_pet = kern.globals.kppet.g_lightweight
246    if lw_pet != 0:
247        lw_pet_gen = kern.globals.kppet_gencount
248        out += 'lightweight PET is active (timer = {:<d}, generation count = {:<d})\n'.format(pet_timer_id, lw_pet_gen)
249    else:
250        out += 'lightweight PET is off\n'
251
252    actions = kern.globals.actionc
253    actions_arr = kern.globals.actionv
254
255    out += 'actions:\n'
256    out += '{:<5s} '.format('id') + GetKperfActionSummary.header + '\n'
257    for i in xrange(0, actions):
258        out += '{:<5d} '.format(i) + GetKperfActionSummary(actions_arr[i])
259
260    timers = kern.globals.kptimer.g_ntimers
261    timers_arr = kern.globals.kptimer.g_timers
262
263    out += 'timers:\n'
264    out += '{:<5s} '.format('id') + GetKperfTimerSummary.header + '\n'
265    for i in xrange(0, timers):
266        out += '{:<5d} '.format(i) + GetKperfTimerSummary(timers_arr[i])
267
268    return out
269
270
271def GetKtraceStatus():
272    """ Get a string summary of the ktrace subsystem.
273    """
274    out = ''
275
276    state = kern.globals.ktrace_state
277    if state == GetEnumValue('ktrace_state::KTRACE_STATE_OFF'):
278        out += 'ktrace is off\n'
279    else:
280        out += 'ktrace is active ('
281        if state == GetEnumValue('ktrace_state::KTRACE_STATE_FG'):
282            out += 'foreground)'
283        else:
284            out += 'background)'
285        out += '\n'
286        owner = kern.globals.ktrace_last_owner_execname
287        out += 'owned by: {0: <s} [%u]\n'.format(owner)
288        active_mask = kern.globals.ktrace_active_mask
289        out += 'active systems: {:<#x}\n'.format(active_mask)
290
291    return out
292
293
294def GetKtraceConfig():
295    kdebug_state = 0
296    if (kern.globals.kd_ctrl_page_trace.kdebug_flags & xnudefines.KDBG_BUFINIT) != 0:
297        kdebug_state = 1
298    if kern.globals.kdebug_enable:
299        kdebug_state = 3
300    kdebug_wrapping = True
301    if (kern.globals.kd_ctrl_page_trace.kdebug_flags & xnudefines.KDBG_NOWRAP):
302        kdebug_wrapping = False
303
304    kperf_state = 3 if (
305            unsigned(kern.globals.kperf_status) ==
306            GetEnumValue('kperf_sampling::KPERF_SAMPLING_ON')) else 0
307
308    action_count = kern.globals.actionc
309    actions = kern.globals.actionv
310    action_samplers = []
311    action_user_datas = []
312    action_pid_filters = []
313    for i in xrange(action_count):
314        action = actions[i]
315        action_samplers.append(unsigned(action.sample))
316        action_user_datas.append(unsigned(action.userdata))
317        action_pid_filters.append(unsigned(action.pid_filter))
318
319    timer_count = kern.globals.kptimer.g_ntimers
320    timers = kern.globals.kptimer.g_timers
321    timer_actions = []
322    timer_periods_ns = []
323
324    for i in xrange(timer_count):
325        timer = timers[i]
326        timer_actions.append(unsigned(timer.kt_actionid))
327        timer_periods_ns.append(
328            kern.GetNanotimeFromAbstime(unsigned(timer.kt_period_abs)))
329
330    pet_mode = 0
331    if kern.globals.kppet.g_lightweight:
332        pet_mode = 2
333    elif kern.globals.kptimer.g_pet_active:
334        pet_mode = 1
335
336    return {
337        'owner_name': str(kern.globals.ktrace_last_owner_execname),
338        'owner_kind': unsigned(kern.globals.ktrace_state),
339        'owner_pid': int(kern.globals.ktrace_owning_pid),
340
341        'kdebug_state': kdebug_state,
342        'kdebug_buffer_size': unsigned(kern.globals.kd_data_page_trace.nkdbufs),
343        'kdebug_typefilter': plistlib.Data(struct.pack('B', 0xff) * 4096 * 2), # XXX
344        'kdebug_procfilt_mode': 0, # XXX
345        'kdebug_procfilt': [], # XXX
346        'kdebug_wrapping': kdebug_wrapping,
347
348        'kperf_state': kperf_state,
349        'kperf_actions_sampler': action_samplers,
350        'kperf_actions_user_data': action_user_datas,
351        'kperf_actions_pid_filter': action_pid_filters,
352        'kperf_timers_action_id': timer_actions,
353        'kperf_timers_period_ns': timer_periods_ns,
354        'kperf_pet_mode': pet_mode,
355        'kperf_pet_timer_id': unsigned(kern.globals.kptimer.g_pet_timerid),
356        'kperf_pet_idle_rate': unsigned(kern.globals.kppet.g_idle_rate),
357
358        'kperf_kdebug_action_id': unsigned(kern.globals.kperf_kdebug_action),
359        'kperf_kdebug_filter': GetKperfKdebugFilterDescription(),
360
361        # XXX
362        'kpc_state': 0,
363        'kpc_classes': 0,
364        'kpc_thread_classes': 0,
365        'kpc_periods': [],
366        'kpc_action_ids': [],
367        'kpc_config': [],
368
369        'context_kind': 2, # backwards-facing
370        'reason': 'core file debugging',
371        'command': '(lldb) savekdebugtrace',
372        'trigger_kind': 2, # diagnostics trigger
373    }
374
375
376@lldb_command('showktrace')
377def ShowKtrace(cmd_args=None):
378    """ Show the current ktrace state, including subsystems.
379
380        usage: showktrace
381    """
382
383    print(GetKtraceStatus())
384    print(' ')
385    print('kdebug:')
386    print(GetKdebugStatus())
387    print(' ')
388    print('kperf:')
389    print(GetKperfStatus())
390
391
392class KDEvent(object):
393    """
394    Wrapper around kevent pointer that handles sorting logic.
395    """
396    def __init__(self, timestamp, kevent, k64):
397        self.kevent = kevent
398        self.timestamp = timestamp
399        self.k64 = k64
400
401    def get_kevent(self):
402        return self.kevent
403
404    def __eq__(self, other):
405        return self.timestamp == other.timestamp
406
407    def __lt__(self, other):
408        return self.timestamp < other.timestamp
409
410    def __gt__(self, other):
411        return self.timestamp > other.timestamp
412
413
414class KDCPU(object):
415    """
416    Represents all events from a single CPU.
417    """
418    def __init__(self, cpuid, k64, verbose):
419        self.cpuid = cpuid
420        self.iter_store = None
421        self.k64 = k64
422        self.verbose = verbose
423        self.timestamp_mask = ((1 << 48) - 1) if not self.k64 else ~0
424        self.last_timestamp = 0
425
426        kdstoreinfo = kern.globals.kd_data_page_trace.kdbip[cpuid]
427        self.kdstorep = kdstoreinfo.kd_list_head
428
429        if self.kdstorep.raw == xnudefines.KDS_PTR_NULL:
430            # Returns an empty iterrator. It will immediatelly stop at
431            # first call to __next__().
432            return
433
434        self.iter_store = self.get_kdstore(self.kdstorep)
435
436        # XXX Doesn't have the same logic to avoid un-mergeable events
437        #     (respecting barrier_min and bufindx) as the C code.
438
439        self.iter_idx = self.iter_store.kds_readlast
440
441    def get_kdstore(self, kdstorep):
442        """
443        See POINTER_FROM_KDSPTR.
444        """
445        buf = kern.globals.kd_data_page_trace.kd_bufs[kdstorep.buffer_index]
446        return addressof(buf.kdsb_addr[kdstorep.offset])
447
448    # Event iterator implementation returns KDEvent instance
449
450    def __iter__(self):
451        return self
452
453    def __next__(self):
454        # This CPU is out of events
455        if self.iter_store is None:
456            raise StopIteration
457
458        if self.iter_idx == self.iter_store.kds_bufindx:
459            self.iter_store = None
460            raise StopIteration
461
462        keventp = addressof(self.iter_store.kds_records[self.iter_idx])
463        timestamp = unsigned(keventp.timestamp) & self.timestamp_mask
464        if self.last_timestamp == 0 and self.verbose:
465            print('first event from CPU {} is at time {}'.format(
466                    self.cpuid, timestamp))
467        self.last_timestamp = timestamp
468
469        # check for writer overrun
470        if timestamp < self.iter_store.kds_timestamp:
471            raise StopIteration
472
473        # Advance iterator
474        self.iter_idx += 1
475
476        if self.iter_idx == xnudefines.EVENTS_PER_STORAGE_UNIT:
477            snext = self.iter_store.kds_next
478            if snext.raw == xnudefines.KDS_PTR_NULL:
479                # Terminate iteration in next loop. Current element is the
480                # last one in this CPU buffer.
481                self.iter_store = None
482            else:
483                self.iter_store = self.get_kdstore(snext)
484                self.iter_idx = self.iter_store.kds_readlast
485
486        return KDEvent(timestamp, keventp, self.k64)
487
488    # Python 2 compatibility
489    def next(self):
490        return self.__next__()
491
492
493def IterateKdebugEvents(verbose=False):
494    """
495    Yield events from the in-memory kdebug trace buffers.
496    """
497    ctrl = kern.globals.kd_ctrl_page_trace
498
499    if (ctrl.kdebug_flags & xnudefines.KDBG_BUFINIT) == 0:
500        return
501
502    barrier_min = ctrl.oldest_time
503
504    if (ctrl.kdebug_flags & xnudefines.KDBG_WRAPPED) != 0:
505        # TODO Yield a wrap event with the barrier_min timestamp.
506        pass
507
508    k64 = kern.ptrsize == 8
509    # Merge sort all events from all CPUs.
510    cpus = [KDCPU(cpuid, k64, verbose) for cpuid in range(ctrl.kdebug_cpus)]
511    last_timestamp = 0
512    warned = False
513    for event in heapq.merge(*cpus):
514        if event.timestamp < last_timestamp and not warned:
515            print('warning: events seem to be out-of-order')
516            warned = True
517        last_timestamp = event.timestamp
518        yield event.get_kevent()
519
520
521def GetKdebugEvent(event, k64):
522    """
523    Return a string representing a kdebug trace event.
524    """
525    if k64:
526        timestamp = event.timestamp & ((1 << 48) - 1)
527        cpuid = event.timestamp >> 48
528    else:
529        timestamp = event.timestamp
530        cpuid = event.cpuid
531
532    return '{:16} {:8} {:8x} {:16} {:16} {:16} {:16} {:4} {:8} {}'.format(
533            unsigned(timestamp), 0, unsigned(event.debugid),
534            unsigned(event.arg1), unsigned(event.arg2),
535            unsigned(event.arg3), unsigned(event.arg4), unsigned(cpuid),
536            unsigned(event.arg5), "")
537
538
539@lldb_command('showkdebugtrace')
540def ShowKdebugTrace(cmd_args=None):
541    """
542    List the events present in the kdebug trace buffers.
543
544    (lldb) showkdebugtrace
545
546    Caveats:
547        * Events from IOPs may be missing or cut-off -- they weren't informed
548          of this kind of buffer collection.
549    """
550    k64 = kern.ptrsize == 8
551    for event in IterateKdebugEvents(config['verbosity'] > vHUMAN):
552        print(GetKdebugEvent(event, k64))
553
554
555def binary_plist(o):
556    with tempfile.NamedTemporaryFile(delete=False) as f:
557        plistlib.writePlist(o, f)
558        name = f.name
559
560    subprocess.check_output(['plutil', '-convert', 'binary1', name])
561    with open(name, mode='rb') as f:
562        plist = f.read()
563
564    os.unlink(name)
565    return plist
566
567
568def align_next_chunk(f, offset, verbose):
569    trailing = offset % 8
570    padding = 0
571    if trailing != 0:
572        padding = 8 - trailing
573        f.write(b'\x00' * padding)
574        if verbose:
575            print('aligned next chunk with {} padding bytes'.format(padding))
576    return padding
577
578
579def GetKtraceMachine():
580    """
581    This is best effort -- several fields are only available to user space or
582    are difficult to determine from a core file.
583    """
584    master_cpu_data = GetCpuDataForCpuID(0)
585    if kern.arch == 'x86_64':
586        cpu_family = unsigned(kern.globals.cpuid_cpu_info.cpuid_cpufamily)
587    else:
588        cpu_family = 0 # XXX
589
590    k64 = kern.ptrsize == 8
591    page_size = 4 * 4096 if k64 else 4096
592
593    return {
594        'kern_version': str(kern.globals.version),
595        'boot_args': '', # XXX
596        'hw_memsize': unsigned(kern.globals.max_mem),
597        'hw_pagesize': page_size, # XXX
598        'vm_pagesize': page_size, # XXX
599        'os_name': 'Unknown', # XXX
600        'os_version': 'Unknown', # XXX
601        'os_build': str(kern.globals.osversion),
602        'arch': 'Unknown', # XXX
603        'hw_model': 'Unknown', # XXX
604        'cpu_type': unsigned(master_cpu_data.cpu_type),
605        'cpu_subtype': unsigned(master_cpu_data.cpu_subtype),
606        'cpu_family': cpu_family,
607        'active_cpus': unsigned(kern.globals.processor_avail_count),
608        'max_cpus': unsigned(kern.globals.machine_info.logical_cpu_max),
609        'apple_internal': True, # XXX
610    }
611
612
613CHUNKHDR_PACK = 'IHHQ'
614
615
616def append_chunk(f, file_offset, tag, major, minor, data, verbose):
617    f.write(struct.pack(CHUNKHDR_PACK, tag, major, minor, len(data)))
618    f.write(data)
619    offset = 16 + len(data)
620    return offset + align_next_chunk(f, file_offset + offset, verbose)
621
622
623@lldb_command('savekdebugtrace', 'N:M')
624def SaveKdebugTrace(cmd_args=None, cmd_options={}):
625    """
626    Save any valid ktrace events to a file.
627
628    (lldb) savekdebugtrace [-M] [-N <n-events>] <file-to-write>
629
630        -N <n-events>: only save the last <n-events> events
631        -M: ensure output is machine-friendly
632
633    Caveats:
634        * 32-bit kernels are unsupported.
635        * Fewer than the requested number of events may end up in the file.
636        * The machine and config chunks may be missing crucial information
637          required for tools to analyze them.
638        * Events from IOPs may be missing or cut-off -- they weren't informed
639          of this kind of buffer collection.
640    """
641
642    k64 = kern.ptrsize == 8
643
644    if len(cmd_args) != 1:
645        raise ArgumentError('error: path to trace file is required')
646
647    nevents = unsigned(kern.globals.kd_data_page_trace.nkdbufs)
648    if nevents == 0:
649        print('error: kdebug buffers are not set up')
650        return
651
652    limit_nevents = nevents
653    if '-N' in cmd_options:
654        limit_nevents = unsigned(cmd_options['-N'])
655        if limit_nevents > nevents:
656            limit_nevents = nevents
657    verbose = config['verbosity'] > vHUMAN
658    humanize = '-M' not in cmd_options
659
660    file_offset = 0
661    with open(cmd_args[0], 'w+b') as f:
662        FILE_MAGIC = 0x55aa0300
663        EVENTS_TAG = 0x00001e00
664        SSHOT_TAG = 0x8002
665        CONFIG_TAG = 0x8006
666        MACHINE_TAG = 0x8c00
667        CHUNKHDR_PACK = 'IHHQ'
668        FILEHDR_PACK = CHUNKHDR_PACK + 'IIQQIIII'
669        FILEHDR_SIZE = 40
670        FUTURE_SIZE = 8
671
672        numer, denom = GetTimebaseInfo()
673
674        # XXX The kernel doesn't have a solid concept of the wall time.
675        wall_abstime = 0
676        wall_secs = 0
677        wall_usecs = 0
678
679        # XXX 32-bit is NYI
680        event_size = 64 if k64 else 32
681
682        file_hdr = struct.pack(
683                FILEHDR_PACK, FILE_MAGIC, 0, 0, FILEHDR_SIZE,
684                numer, denom, wall_abstime, wall_secs, wall_usecs, 0, 0,
685                0x1 if k64 else 0)
686        f.write(file_hdr)
687        header_size_offset = file_offset + 8
688        file_offset += 16 + FILEHDR_SIZE # chunk header plus file header
689
690        if verbose:
691            print('writing machine chunk at offset 0x{:x}'.format(file_offset))
692        machine_data = GetKtraceMachine()
693        machine_bytes = binary_plist(machine_data)
694        file_offset += append_chunk(
695                f, file_offset, MACHINE_TAG, 0, 0, machine_bytes, verbose)
696
697        if verbose:
698            print('writing config chunk at offset 0x{:x}'.format(file_offset))
699        config_data = GetKtraceConfig()
700        config_bytes = binary_plist(config_data)
701        file_offset += append_chunk(
702                f, file_offset, CONFIG_TAG, 0, 0, config_bytes, verbose)
703
704        events_hdr = struct.pack(
705                CHUNKHDR_PACK, EVENTS_TAG, 0, 0, 0) # size will be filled in later
706        f.write(events_hdr)
707        file_offset += 16 # header size
708        event_size_offset = file_offset - FUTURE_SIZE
709        # Future events timestamp -- doesn't need to be set for merged events.
710        f.write(struct.pack('Q', 0))
711        file_offset += FUTURE_SIZE
712
713        if verbose:
714            print('events start at offset 0x{:x}'.format(file_offset))
715
716        process = LazyTarget().GetProcess()
717        error = lldb.SBError()
718
719        skip_nevents = nevents - limit_nevents if limit_nevents else 0
720        if skip_nevents > 0:
721            print('omitting {} events from the beginning'.format(skip_nevents))
722
723        written_nevents = 0
724        seen_nevents = 0
725        start_time = time.time()
726        update_every = 1000 if humanize else 25000
727        for event in IterateKdebugEvents(verbose):
728            seen_nevents += 1
729            if skip_nevents >= seen_nevents:
730                if seen_nevents % update_every == 0:
731                    sys.stderr.write('skipped {}/{} ({:4.2f}%) events'.format(
732                            seen_nevents, skip_nevents,
733                            float(seen_nevents) / skip_nevents * 100.0))
734                    sys.stderr.write('\r')
735
736                continue
737
738            event = process.ReadMemory(
739                    unsigned(event), event_size, error)
740            file_offset += event_size
741            f.write(event)
742            written_nevents += 1
743            # Periodically update the CLI with progress.
744            if written_nevents % update_every == 0:
745                sys.stderr.write('{}: wrote {}/{} ({:4.2f}%) events'.format(
746                        time.strftime('%H:%M:%S'), written_nevents,
747                        limit_nevents,
748                        float(written_nevents) / nevents * 100.0))
749                if humanize:
750                    sys.stderr.write('\r')
751                else:
752                    sys.stderr.write('\n')
753        sys.stderr.write('\n')
754        elapsed = time.time() - start_time
755        print('wrote {} ({}MB) events in {:.3f}s'.format(
756                written_nevents, written_nevents * event_size >> 20, elapsed))
757        if verbose:
758            print('events end at offset 0x{:x}'.format(file_offset))
759
760        # Normally, the chunk would need to be padded to 8, but events are
761        # already aligned.
762
763        kcdata = kern.globals.kc_panic_data
764        kcdata_addr = unsigned(kcdata.kcd_addr_begin)
765        kcdata_length = unsigned(kcdata.kcd_length)
766        if kcdata_addr != 0 and kcdata_length != 0:
767            print('writing stackshot')
768            if verbose:
769                print('stackshot is 0x{:x} bytes at offset 0x{:x}'.format(
770                        kcdata_length, file_offset))
771            ssdata = process.ReadMemory(kcdata_addr, kcdata_length, error)
772            magic = struct.unpack('I', ssdata[:4])
773            if magic[0] == GetTypeForName('KCDATA_BUFFER_BEGIN_COMPRESSED'):
774                if verbose:
775                    print('found compressed stackshot')
776                iterator = kcdata_item_iterator(ssdata)
777                for item in iterator:
778                    kcdata_buffer = KCObject.FromKCItem(item)
779                    if isinstance(kcdata_buffer, KCCompressedBufferObject):
780                        kcdata_buffer.ReadItems(iterator)
781                        decompressed = kcdata_buffer.Decompress(ssdata)
782                        ssdata = decompressed
783                        kcdata_length = len(ssdata)
784                        if verbose:
785                            print(
786                                    'compressed stackshot is 0x{:x} bytes long'.
787                                    format(kcdata_length))
788
789            file_offset += append_chunk(
790                    f, file_offset, SSHOT_TAG, 1, 0, ssdata, verbose)
791            if verbose:
792                print('stackshot ends at offset 0x{:x}'.format(file_offset))
793        else:
794            print('warning: no stackshot; trace file may not be usable')
795
796        # After the number of events is known, fix up the events chunk size.
797        events_data_size = unsigned(written_nevents * event_size) + FUTURE_SIZE
798        f.seek(event_size_offset)
799        f.write(struct.pack('Q', events_data_size))
800        if verbose:
801            print('wrote {:x} bytes at offset 0x{:x} for event size'.format(
802                    events_data_size, event_size_offset))
803
804        # Fix up the size of the header chunks, too.
805        f.seek(header_size_offset)
806        f.write(struct.pack('Q', FILEHDR_SIZE + 16 + len(machine_bytes)))
807        if verbose:
808            print((
809                    'wrote 0x{:x} bytes at offset 0x{:x} for ' +
810                    'file header size').format(
811                    len(machine_bytes), header_size_offset))
812
813    return
814