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