+
+def GetInTransitPortSummary(port, disp, holding_port, holding_kmsg):
+ """ String-ify the in-transit dispostion of a port.
+ """
+ ## This should match the summary generated by GetIPCEntrySummary
+ ## "object" "name" "rite" "urefs" "nsets" "nmsgs" "destname" "destination"
+ format_str = "\t{: <#20x} {: <12} {: <8s} {: <8d} {: <8d} {: <8d} p:{: <#19x} k:{: <#19x}"
+ portname = 'intransit'
+
+ disp_str = GetPortDispositionString(disp)
+
+ out_str = format_str.format(unsigned(port), 'in-transit', disp_str, 0, 0, port.ip_messages.data.port.msgcount, unsigned(holding_port), unsigned(holding_kmsg))
+ return out_str
+
+
+def GetDispositionFromEntryType(entry_bits):
+ """ Translate an IPC entry type into an in-transit disposition. This allows
+ the GetInTransitPortSummary function to be re-used to string-ify IPC
+ entry types.
+ """
+ ebits = int(entry_bits)
+ if (ebits & 0x003f0000) == 0:
+ return 0
+
+ if (ebits & 0x00010000) != 0:
+ return 17 ## MACH_PORT_RIGHT_SEND
+ elif (ebits & 0x00020000) != 0:
+ return 16 ## MACH_PORT_RIGHT_RECEIVE
+ elif (ebits & 0x00040000) != 0:
+ return 18 ## MACH_PORT_RIGHT_SEND_ONCE
+ elif (ebits & 0x00080000) != 0:
+ return 100 ## MACH_PORT_RIGHT_PORT_SET
+ elif (ebits & 0x00100000) != 0:
+ return 101 ## MACH_PORT_RIGHT_DEAD_NAME
+ elif (ebits & 0x00200000) != 0:
+ return 102 ## MACH_PORT_RIGHT_LABELH
+ else:
+ return 0
+
+def GetDispositionFromVoucherPort(th_vport):
+ """ Translate a thread's voucher port into a 'disposition'
+ """
+ if unsigned(th_vport) > 0:
+ return 103 ## Voucher type
+ return 0
+
+
+g_kmsg_prog = 0
+g_progmeter = {
+ 0 : '*',
+ 1 : '-',
+ 2 : '\\',
+ 3 : '|',
+ 4 : '/',
+ 5 : '-',
+ 6 : '\\',
+ 7 : '|',
+ 8 : '/',
+}
+
+def PrintProgressForKmsg():
+ global g_kmsg_prog
+ global g_progmeter
+ sys.stderr.write(" {:<1s}\r".format(g_progmeter[g_kmsg_prog % 9]))
+ g_kmsg_prog += 1
+
+
+def CollectPortsForAnalysis(port, disposition):
+ """
+ """
+ p = Cast(port, 'struct ipc_port *')
+ yield (p, disposition)
+
+ # no-senders notification port
+ if unsigned(p.ip_nsrequest) != 0:
+ PrintProgressForKmsg()
+ yield (Cast(p.ip_nsrequest, 'struct ipc_port *'), -1)
+
+ # port-death notification port
+ if unsigned(p.ip_pdrequest) != 0:
+ PrintProgressForKmsg()
+ yield (Cast(p.ip_pdrequest, 'struct ipc_port *'), -2)
+
+ ## ports can have many send-possible notifications armed: go through the table!
+ if unsigned(p.ip_requests) != 0:
+ table = Cast(p.ip_requests, 'struct ipc_port_request *')
+ table_sz = int(table.name.size.its_size)
+ for i in range(table_sz):
+ if i == 0:
+ continue
+ ipr = table[i]
+ if unsigned(ipr.name.name) != 0:
+ ipr_bits = unsigned(ipr.notify.port) & 3
+ ipr_port = kern.GetValueFromAddress(int(ipr.notify.port) & ~3, 'struct ipc_port *')
+ ipr_disp = 0
+ if ipr_bits & 3: ## send-possible armed and requested
+ ipr_disp = -5
+ elif ipr_bits & 2: ## send-possible requested
+ ipr_disp = -4
+ elif ipr_bits & 1: ## send-possible armed
+ ipr_disp = -3
+ PrintProgressForKmsg()
+ yield (ipr_port, ipr_disp)
+ return
+
+def CollectKmsgPorts(task, task_port, kmsgp):
+ """ Look through a message, 'kmsgp' destined for 'task'
+ (enqueued on task_port). Collect any port descriptors,
+ remote, local, voucher, or other port references
+ into a (ipc_port_t, disposition) list.
+ """
+ kmsgh = dereference(kmsgp.ikm_header)
+
+ p_list = []
+
+ PrintProgressForKmsg()
+ if kmsgh.msgh_remote_port and unsigned(kmsgh.msgh_remote_port) != unsigned(task_port):
+ disp = kmsgh.msgh_bits & 0x1f
+ p_list += list(CollectPortsForAnalysis(kmsgh.msgh_remote_port, disp))
+
+ if kmsgh.msgh_local_port and unsigned(kmsgh.msgh_local_port) != unsigned(task_port) \
+ and unsigned(kmsgh.msgh_local_port) != unsigned(kmsgh.msgh_remote_port):
+ disp = (kmsgh.msgh_bits & 0x1f00) >> 8
+ p_list += list(CollectPortsForAnalysis(kmsgh.msgh_local_port, disp))
+
+ if kmsgp.ikm_voucher:
+ p_list += list(CollectPortsForAnalysis(kmsgp.ikm_voucher, 0))
+
+ if kmsgh.msgh_bits & 0x80000000:
+ ## Complex message - look for descriptors
+ PrintProgressForKmsg()
+ (body, dschead, dsc_list) = GetKmsgDescriptors(kmsgp)
+ for dsc in dsc_list:
+ PrintProgressForKmsg()
+ dsc_type = unsigned(dsc.type.type)
+ if dsc_type == 0 or dsc_type == 2: ## 0 == port, 2 == ool port
+ if dsc_type == 0:
+ ## its a port descriptor
+ dsc_disp = dsc.port.disposition
+ p_list += list(CollectPortsForAnalysis(dsc.port.name, dsc_disp))
+ else:
+ ## it's an ool_ports descriptor which is an array of ports
+ dsc_disp = dsc.ool_ports.disposition
+ dispdata = Cast(dsc.ool_ports.address, 'struct ipc_port *')
+ for pidx in range(dsc.ool_ports.count):
+ PrintProgressForKmsg()
+ p_list += list(CollectPortsForAnalysis(dispdata[pidx], dsc_disp))
+ return p_list
+
+def CollectKmsgPortRefs(task, task_port, kmsgp, p_refs):
+ """ Recursively collect all references to ports inside the kmsg 'kmsgp'
+ into the set 'p_refs'
+ """
+ p_list = CollectKmsgPorts(task, task_port, kmsgp)
+
+ ## Iterate over each ports we've collected, to see if they
+ ## have messages on them, and then recurse!
+ for p, pdisp in p_list:
+ ptype = (p.ip_object.io_bits & 0x7fff0000) >> 16
+ p_refs.add((p, pdisp, ptype))
+ if ptype != 0: ## don't bother with port sets
+ continue
+ ## If the port that's in-transit has messages already enqueued,
+ ## go through each of those messages and look for more ports!
+ if p.ip_messages.data.port.msgcount > 0:
+ p_kmsgp = Cast(p.ip_messages.data.port.messages.ikmq_base, 'ipc_kmsg_t')
+ kmsgheadp = p_kmsgp
+ while unsigned(p_kmsgp) > 0:
+ CollectKmsgPortRefs(task, p, p_kmsgp, p_refs)
+ p_kmsgp = p_kmsgp.ikm_next
+ if p_kmsgp == kmsgheadp:
+ break;
+
+
+def FindKmsgPortRefs(instr, task, task_port, kmsgp, qport):
+ """ Look through a message, 'kmsgp' destined for 'task'. If we find
+ any port descriptors, remote, local, voucher, or other port that
+ matches 'qport', return a short description
+ which should match the format of GetIPCEntrySummary.
+ """
+
+ out_str = instr
+ p_list = CollectKmsgPorts(task, task_port, kmsgp)
+
+ ## Run through all ports we've collected looking for 'qport'
+ for p, pdisp in p_list:
+ PrintProgressForKmsg()
+ if unsigned(p) == unsigned(qport):
+ ## the port we're looking for was found in this message!
+ if len(out_str) > 0:
+ out_str += '\n'
+ out_str += GetInTransitPortSummary(p, pdisp, task_port, kmsgp)
+
+ ptype = (p.ip_object.io_bits & 0x7fff0000) >> 16
+ if ptype != 0: ## don't bother with port sets
+ continue
+
+ ## If the port that's in-transit has messages already enqueued,
+ ## go through each of those messages and look for more ports!
+ if p.ip_messages.data.port.msgcount > 0:
+ p_kmsgp = Cast(p.ip_messages.data.port.messages.ikmq_base, 'ipc_kmsg_t')
+ kmsgheadp = p_kmsgp
+ while unsigned(p_kmsgp) > 0:
+ out_str = FindKmsgPortRefs(out_str, task, p, p_kmsgp, qport)
+ p_kmsgp = p_kmsgp.ikm_next
+ if p_kmsgp == kmsgheadp:
+ break
+ return out_str
+
+
+port_iteration_do_print_taskname = False
+registeredport_idx = -10
+excports_idx = -20
+intransit_idx = -1000
+taskports_idx = -2000
+thports_idx = -3000
+
+def IterateAllPorts(tasklist, func, ctx, include_psets, follow_busyports, should_log):
+ """ Iterate over all ports in the system, calling 'func'
+ for each entry in
+ """
+ global port_iteration_do_print_taskname
+ global intransit_idx, taskports_idx, thports_idx, registeredport_idx, excports_idx
+
+ ## XXX: also host special ports
+
+ entry_port_type_mask = 0x00070000
+ if include_psets:
+ entry_port_type_mask = 0x000f0000
+
+ if tasklist is None:
+ tasklist = kern.tasks
+ tasklist += kern.terminated_tasks
+
+ tidx = 1
+
+ for t in tasklist:
+ # Write a progress line. Using stderr avoids automatic newline when
+ # writing to stdout from lldb. Blank spaces at the end clear out long
+ # lines.
+ if should_log:
+ procname = ""
+ if not t.active:
+ procname = 'terminated: '
+ if t.halting:
+ procname += 'halting: '
+ t_p = Cast(t.bsd_info, 'proc *')
+ if unsigned(t_p) != 0:
+ procname += str(t_p.p_name)
+ elif unsigned(t.task_imp_base) != 0 and hasattr(t.task_imp_base, 'iit_procname'):
+ procname += str(t.task_imp_base.iit_procname)
+ sys.stderr.write(" checking {:s} ({}/{})...{:50s}\r".format(procname, tidx, len(tasklist), ''))
+ tidx += 1
+
+ port_iteration_do_print_taskname = True
+ space = t.itk_space
+ num_entries = int(space.is_table_size)
+ is_tableval = space.is_table
+ idx = 0
+ while idx < num_entries:
+ entry_val = GetObjectAtIndexFromArray(is_tableval, idx)
+ entry_bits= unsigned(entry_val.ie_bits)
+ entry_obj = 0
+ entry_str = ''
+ entry_name = "{:x}".format( (idx << 8 | entry_bits >> 24) )
+
+ entry_disp = GetDispositionFromEntryType(entry_bits)
+
+ ## If the entry in the table represents a port of some sort,
+ ## then make the callback provided
+ if int(entry_bits) & entry_port_type_mask:
+ eport = Cast(entry_val.ie_object, 'ipc_port_t')
+ ## Make the callback
+ func(t, space, ctx, idx, entry_val, eport, entry_disp)
+
+ ## if the port has pending messages, look through
+ ## each message for ports (and recurse)
+ if follow_busyports and unsigned(eport) > 0 and eport.ip_messages.data.port.msgcount > 0:
+ ## collect all port references from all messages
+ kmsgp = Cast(eport.ip_messages.data.port.messages.ikmq_base, 'ipc_kmsg_t')
+ kmsgheadp = kmsgp
+ while unsigned(kmsgp) > 0:
+ p_refs = set()
+ CollectKmsgPortRefs(t, eport, kmsgp, p_refs)
+ for (port, pdisp, ptype) in p_refs:
+ func(t, space, ctx, intransit_idx, None, port, pdisp)
+ kmsgp = kmsgp.ikm_next
+ if kmsgp == kmsgheadp:
+ break
+
+ idx = idx + 1
+ ## while (idx < num_entries)
+
+ ## Task ports (send rights)
+ if unsigned(t.itk_sself) > 0:
+ func(t, space, ctx, taskports_idx, 0, t.itk_sself, 17)
+ if unsigned(t.itk_host) > 0:
+ func(t, space, ctx, taskports_idx, 0, t.itk_host, 17)
+ if unsigned(t.itk_bootstrap) > 0:
+ func(t, space, ctx, taskports_idx, 0, t.itk_bootstrap, 17)
+ if unsigned(t.itk_seatbelt) > 0:
+ func(t, space, ctx, taskports_idx, 0, t.itk_seatbelt, 17)
+ if unsigned(t.itk_gssd) > 0:
+ func(t, space, ctx, taskports_idx, 0, t.itk_gssd, 17)
+ if unsigned(t.itk_debug_control) > 0:
+ func(t, space, ctx, taskports_idx, 0, t.itk_debug_control, 17)
+ if unsigned(t.itk_task_access) > 0:
+ func(t, space, ctx, taskports_idx, 0, t.itk_task_access, 17)
+
+ ## Task name port (not a send right, just a naked ref)
+ if unsigned(t.itk_nself) > 0:
+ func(t, space, ctx, taskports_idx, 0,t.itk_nself, 0)
+
+ ## task resume port is a receive right to resume the task
+ if unsigned(t.itk_resume) > 0:
+ func(t, space, ctx, taskports_idx, 0, t.itk_resume, 16)
+
+ ## registered task ports (all send rights)
+ tr_idx = 0
+ tr_max = sizeof(t.itk_registered) / sizeof(t.itk_registered[0])
+ while tr_idx < tr_max:
+ tport = t.itk_registered[tr_idx]
+ if unsigned(tport) > 0:
+ try:
+ func(t, space, ctx, registeredport_idx, 0, tport, 17)
+ except Exception, e:
+ print("\texception looking through registered port {:d}/{:d} in {:s}".format(tr_idx,tr_max,t))
+ pass
+ tr_idx += 1
+
+ ## Task exception ports
+ exidx = 0
+ exmax = sizeof(t.exc_actions) / sizeof(t.exc_actions[0])
+ while exidx < exmax: ## see: osfmk/mach/[arm|i386]/exception.h
+ export = t.exc_actions[exidx].port ## send right
+ if unsigned(export) > 0:
+ try:
+ func(t, space, ctx, excports_idx, 0, export, 17)
+ except Exception, e:
+ print("\texception looking through exception port {:d}/{:d} in {:s}".format(exidx,exmax,t))
+ pass
+ exidx += 1
+
+ ## XXX: any ports still valid after clearing IPC space?!
+
+ for thval in IterateQueue(t.threads, 'thread *', 'task_threads'):
+ ## XXX: look at block reason to see if it's in mach_msg_receive - then look at saved state / message
+
+ ## Thread port (send right)
+ if unsigned(thval.ith_sself) > 0:
+ thport = thval.ith_sself
+ func(t, space, ctx, thports_idx, 0, thport, 17) ## see: osfmk/mach/message.h
+ ## Thread special reply port (send-once right)
+ if unsigned(thval.ith_special_reply_port) > 0:
+ thport = thval.ith_special_reply_port
+ func(t, space, ctx, thports_idx, 0, thport, 18) ## see: osfmk/mach/message.h
+ ## Thread voucher port
+ if unsigned(thval.ith_voucher) > 0:
+ vport = thval.ith_voucher.iv_port
+ if unsigned(vport) > 0:
+ vdisp = GetDispositionFromVoucherPort(vport)
+ func(t, space, ctx, thports_idx, 0, vport, vdisp)
+ ## Thread exception ports
+ if unsigned(thval.exc_actions) > 0:
+ exidx = 0
+ while exidx < exmax: ## see: osfmk/mach/[arm|i386]/exception.h
+ export = thval.exc_actions[exidx].port ## send right
+ if unsigned(export) > 0:
+ try:
+ func(t, space, ctx, excports_idx, 0, export, 17)
+ except Exception, e:
+ print("\texception looking through exception port {:d}/{:d} in {:s}".format(exidx,exmax,t))
+ pass
+ exidx += 1
+ ## XXX: the message on a thread (that's currently being received)
+ ## for (thval in t.threads)
+ ## for (t in tasklist)
+
+
+# Macro: findportrights
+def FindPortRightsCallback(task, space, ctx, entry_idx, ipc_entry, ipc_port, port_disp):
+ """ Callback which uses 'ctx' as the (port,rights_types) tuple for which
+ a caller is seeking references. This should *not* be used from a
+ recursive call to IterateAllPorts.
+ """
+ global port_iteration_do_print_taskname
+
+ (qport, rights_type) = ctx
+ entry_name = ''
+ entry_str = ''
+ if unsigned(ipc_entry) != 0:
+ entry_bits = unsigned(ipc_entry.ie_bits)
+ entry_name = "{:x}".format( (entry_idx << 8 | entry_bits >> 24) )
+ if (int(entry_bits) & 0x001f0000) != 0 and unsigned(ipc_entry.ie_object) == unsigned(qport):
+ ## it's a valid entry, and it points to the port
+ entry_str = '\t' + GetIPCEntrySummary(ipc_entry, entry_name, rights_type)
+
+ procname = GetProcNameForTask(task)
+ if unsigned(ipc_port) != 0 and ipc_port.ip_messages.data.port.msgcount > 0:
+ sys.stderr.write(" checking {:s} busy-port {}:{:#x}...{:30s}\r".format(procname, entry_name, unsigned(ipc_port), ''))
+ ## Search through busy ports to find descriptors which could
+ ## contain the only reference to this port!
+ kmsgp = Cast(ipc_port.ip_messages.data.port.messages.ikmq_base, 'ipc_kmsg_t')
+ kmsgheadp = kmsgp
+ while unsigned(kmsgp):
+ entry_str = FindKmsgPortRefs(entry_str, task, ipc_port, kmsgp, qport)
+ kmsgp = kmsgp.ikm_next
+ if kmsgp == kmsgheadp:
+ break;
+ if len(entry_str) > 0:
+ sys.stderr.write("{:80s}\r".format(''))
+ if port_iteration_do_print_taskname:
+ print "Task: {0: <#x} {1: <s}".format(task, procname)
+ print '\t' + GetIPCEntrySummary.header
+ port_iteration_do_print_taskname = False
+ print entry_str
+
+@lldb_command('findportrights', 'R:S:')
+def FindPortRights(cmd_args=None, cmd_options={}):
+ """ Routine to locate and print all extant rights to a given port
+ Usage: findportrights [-R rights_type] [-S <ipc_space_t>] <ipc_port_t>
+ -S ipc_space : only search the specified ipc space
+ -R rights_type : only display rights matching the string 'rights_type'
+
+ types of rights:
+ 'Dead' : Dead name
+ 'Set' : Port set
+ 'S' : Send right
+ 'R' : Receive right
+ 'O' : Send-once right
+ types of notifications:
+ 'd' : Dead-Name notification requested
+ 's' : Send-Possible notification armed
+ 'r' : Send-Possible notification requested
+ 'n' : No-Senders notification requested
+ 'x' : Port-destroy notification requested
+ """
+ if not cmd_args:
+ raise ArgumentError("no port address provided")
+ port = kern.GetValueFromAddress(cmd_args[0], 'struct ipc_port *')
+
+ rights_type = 0
+ if "-R" in cmd_options:
+ rights_type = cmd_options["-R"]
+
+ tasklist = None
+ if "-S" in cmd_options:
+ space = kern.GetValueFromAddress(cmd_options["-S"], 'struct ipc_space *')
+ tasklist = [ space.is_task ]
+
+ ## Don't include port sets
+ ## Don't recurse on busy ports (we do that manually)
+ ## DO log progress
+ IterateAllPorts(tasklist, FindPortRightsCallback, (port, rights_type), False, False, True)
+ sys.stderr.write("{:120s}\r".format(' '))
+
+ print "Done."
+ return
+# EndMacro: findportrights
+
+# Macro: countallports
+
+def CountPortsCallback(task, space, ctx, entry_idx, ipc_entry, ipc_port, port_disp):
+ """ Callback which uses 'ctx' as the set of all ports found in the
+ iteration. This should *not* be used from a recursive
+ call to IterateAllPorts.
+ """
+ global intransit_idx
+
+ (p_set, p_intransit, p_bytask) = ctx
+
+ ## Add the port address to the set of all port addresses
+ p_set.add(unsigned(ipc_port))
+
+ if entry_idx == intransit_idx:
+ p_intransit.add(unsigned(ipc_port))
+
+ if task.active or (task.halting and not task.active):
+ pname = str(Cast(task.bsd_info, 'proc *').p_name)
+ if not pname in p_bytask.keys():
+ p_bytask[pname] = { 'transit':0, 'table':0, 'other':0 }
+ if entry_idx == intransit_idx:
+ p_bytask[pname]['transit'] += 1
+ elif entry_idx >= 0:
+ p_bytask[pname]['table'] += 1
+ else:
+ p_bytask[pname]['other'] += 1
+
+
+@lldb_command('countallports', 'P')
+def CountAllPorts(cmd_args=None, cmd_options={}):
+ """ Routine to search for all as many references to ipc_port structures in the kernel
+ that we can find.
+ Usage: countallports [-P]
+ -P : include port sets in the count (default: NO)
+ """
+ p_set = set()
+ p_intransit = set()
+ p_bytask = {}
+
+ find_psets = False
+ if "-P" in cmd_options:
+ find_psets = True
+
+ ## optionally include port sets
+ ## DO recurse on busy ports
+ ## DO log progress
+ IterateAllPorts(None, CountPortsCallback, (p_set, p_intransit, p_bytask), find_psets, True, True)
+ sys.stderr.write("{:120s}\r".format(' '))
+
+ print "Total ports found: {:d}".format(len(p_set))
+ print "In Transit: {:d}".format(len(p_intransit))
+ print "By Task:"
+ for pname in sorted(p_bytask.keys()):
+ count = p_bytask[pname]
+ print "\t{: <20s}: table={: <5d}, transit={: <5d}, other={: <5d}".format(pname, count['table'], count['transit'], count['other'])
+ return
+# EndMacro: countallports
+