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