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