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