| kdp-reenter Schedule reentry into the debugger and continue.
| kdp-reboot Restart remote target
|
+| zstack Print zalloc caller stack (zone leak debugging)
+| findoldest Find oldest zone leak debugging record
+| countpcs Print how often a pc occurs in the zone leak log
+|
+|
| Type "help <macro>" for more specific help on a particular macro.
| Type "show user <macro>" to see what the macro is really doing.
end
define showcurrentthreads
set $kgm_prp = (struct processor *)processor_list
while $kgm_prp != 0
+ printf "Processor 0x%08x State %d (cpu_id %x)\n", $kgm_prp, ($kgm_prp)->state, ($kgm_prp)->cpu_num
if ($kgm_prp)->active_thread != 0
set $kgm_actp = ($kgm_prp)->active_thread
showtaskheader
define showcurrentstacks
set $kgm_prp = processor_list
while $kgm_prp != 0
+ printf "Processor 0x%08x State %d (cpu_id %x)\n", $kgm_prp, ($kgm_prp)->state, ($kgm_prp)->cpu_num
if ($kgm_prp)->active_thread != 0
set $kgm_actp = ($kgm_prp)->active_thread
showtaskheader
_kgm_update_loop
end
else
+ if ($kgm_mtype == 7)
set $newact = (struct thread *) $arg0
#This needs to identify 64-bit processes as well
set $newiss = (x86_saved_state32_t) ($newact->machine.pcb->iss.uss.ss_32)
_kgm_flush_loop
_kgm_update_loop
end
+ else
+ echo showuserstack not supported on this architecture\n
+ end
end
end
document showuserstack
flushstack
set $pc = $newact->machine->pcb.save_srr0
else
+ if ($kgm_mtype == 7)
set $kgm_cstatep = (struct x86_kernel_state32 *) \
($newact->kernel_stack + 0x4000 \
- sizeof(struct x86_kernel_state32))
loadcontext $kgm_cstatep
flushstack
+ else
+ echo switchtocorethread not supported on this architecture\n
+ end
end
showcontext_int
end
set $cr = $kgm_contextp.save_cr
set $ctr = $kgm_contextp.save_ctr
else
+ if ($kgm_mtype == 7)
set $kgm_contextp = (struct x86_kernel_state32 *) $arg0
set $ebx = $kgm_contextp->k_ebx
set $ebp = $kgm_contextp->k_ebp
set $esi = $kgm_contextp->k_esi
set $eip = $kgm_contextp->k_eip
set $pc = $kgm_contextp->k_eip
+ else
+ echo loadcontext not supported on this architecture\n
+ end
end
end
flushstack
set $pc = $kdpstatep->eip
update
+ else
+ echo resetcorectx not supported on this architecture\n
end
end
showcontext_int
set $kgm_obj = (OSObject *) $arg1
set $kgm_vt = *((void **) $arg1)
+ if ($kgm_mtype == 12)
+ set $kgm_vt = $kgm_vt - 2 * sizeof(void *)
+ end
+
if ($kgm_show_object_addrs)
printf "`object %p, vt ", $arg1
output /a (unsigned) $kgm_vt
printf " <object %p, ", $kgm_re
printf "vtable "
set $kgm_vt = (unsigned) *(void**) $kgm_re
+ if ($kgm_mtype == 12)
+ set $kgm_vt = $kgm_vt - 2 * sizeof(void *)
+ end
output /a $kgm_vt
if ($kgm_vt != _ZTV15IORegistryEntry)
_if_present mca_threshold_status_present
printf "\n%d error banks, ", mca_error_bank_count
printf "family code 0x%x, ", mca_family
- printf "machine-check exception taken: %d\n", mca_exception_taken
+ printf "machine-check dump state: %d\n", mca_dump_state
set $kgm_cpu = 0
while cpu_data_ptr[$kgm_cpu] != 0
set $kgm_mcp = cpu_data_ptr[$kgm_cpu]->cpu_mca_state
Syntax: showMCAstate
| Print machine-check register state after MC exception.
end
+
+define _pt_step
+ #
+ # Step to lower-level page table and print attributes
+ # $kgm_pt_paddr: current page table entry physical address
+ # $kgm_pt_index: current page table entry index (0..511)
+ # returns
+ # $kgm_pt_paddr: next level page table entry physical address
+ # or null if invalid
+ # For $kgm_pt_verbose = 0: print nothing
+ # 1: print basic information
+ # 2: print basic information and hex table dump
+ # The trickery with kdp_src_high32 is required for accesses above 4GB.
+ #
+ set $kgm_entryp = $kgm_pt_paddr + 8*$kgm_pt_index
+ set kdp_src_high32 = $kgm_pt_paddr >> 32
+ set kdp_trans_off = 1
+ set $entry = *(pt_entry_t *)($kgm_entryp & 0x0ffffffffULL)
+ if $kgm_pt_verbose == 2
+ x/512g ($kgm_pt_paddr & 0x0ffffffffULL)
+ end
+ set kdp_trans_off = 0
+ set kdp_src_high32 = 0
+ set $kgm_paddr_mask = ~((0xffffULL<<48) | 0xfffULL)
+ if $kgm_pt_verbose == 0
+ if $entry & (0x1 << 0)
+ set $kgm_pt_paddr = $entry & $kgm_paddr_mask
+ else
+ set $kgm_pt_paddr = 0
+ end
+ else
+ printf "0x%016llx:\n\t0x%016llx\n\t", $kgm_entryp, $entry
+ if $entry & (0x1 << 0)
+ printf "valid"
+ set $kgm_pt_paddr = $entry & $kgm_paddr_mask
+ else
+ printf "invalid"
+ set $kgm_pt_paddr = 0
+ end
+ if $entry & (0x1 << 1)
+ printf " writeable"
+ else
+ printf " read-only"
+ end
+ if $entry & (0x1 << 2)
+ printf " user"
+ else
+ printf " supervisor"
+ end
+ if $entry & (0x1 << 3)
+ printf " PWT"
+ end
+ if $entry & (0x1 << 4)
+ printf " PCD"
+ end
+ if $entry & (0x1 << 5)
+ printf " accessed"
+ end
+ if $entry & (0x1 << 6)
+ printf " dirty"
+ end
+ if $entry & (0x1 << 7)
+ printf " PAT"
+ end
+ if $entry & (0x1 << 8)
+ printf " global"
+ end
+ if $entry & (0x3 << 9)
+ printf " avail:0x%x", ($entry >> 9) & 0x3
+ end
+ if $entry & (0x1 << 63)
+ printf " noexec"
+ end
+ printf "\n"
+ end
+end
+
+define _pmap_walk
+ set $kgm_pmap = (pmap_t) $arg0
+ set $kgm_vaddr = $arg1
+ set $kgm_pt_paddr = $kgm_pmap->pm_cr3
+ if $kgm_pt_paddr && cpu_64bit
+ set $kgm_pt_index = ($kgm_vaddr >> 39) & 0x1ffULL
+ if $kgm_pt_verbose
+ printf "pml4 (index %d):\n", $kgm_pt_index
+ end
+ _pt_step
+ end
+ if $kgm_pt_paddr
+ set $kgm_pt_index = ($kgm_vaddr >> 30) & 0x1ffULL
+ if $kgm_pt_verbose
+ printf "pdpt (index %d):\n", $kgm_pt_index
+ end
+ _pt_step
+ end
+ if $kgm_pt_paddr
+ set $kgm_pt_index = ($kgm_vaddr >> 21) & 0x1ffULL
+ if $kgm_pt_verbose
+ printf "pdt (index %d):\n", $kgm_pt_index
+ end
+ _pt_step
+ end
+ if $kgm_pt_paddr
+ set $kgm_pt_index = ($kgm_vaddr >> 12) & 0x1ffULL
+ if $kgm_pt_verbose
+ printf "pt (index %d):\n", $kgm_pt_index
+ end
+ _pt_step
+ end
+ if $kgm_pt_paddr
+ set $kgm_paddr = $kgm_pt_paddr + ($kgm_vaddr & 0xfffULL)
+ set kdp_trans_off = 1
+ set kdp_src_high32 = $kgm_paddr >> 32
+ set $kgm_value = *($kgm_paddr & 0x0ffffffffULL)
+ set kdp_trans_off = 0
+ set kdp_src_high32 = 0
+ printf "phys 0x%016llx: 0x%08x\n", $kgm_paddr, $kgm_value
+ else
+ set $kgm_paddr = 0
+ printf "(no translation)\n"
+ end
+end
+
+define pmap_walk
+ if $kgm_mtype != 7
+ printf "Not available for current architecture.\n"
+ else
+ if $argc != 2
+ printf "pmap_walk <pmap> <vaddr>\n"
+ else
+ if !$kgm_pt_verbose
+ set $kgm_pt_verbose = 1
+ else
+ if $kgm_pt_verbose != 2
+ set $kgm_pt_verbose = 1
+ end
+ end
+ _pmap_walk $arg0 $arg1
+ end
+ end
+end
+
+document pmap_walk
+Syntax: (gdb) pmap_walk <pmap> <virtual_address>
+| Perform a page-table walk in <pmap> for <virtual_address>.
+| Set $kgm_pt_verbose=2 for full hex dump of page tables.
+end
+
+define pmap_vtop
+ if $kgm_mtype != 7
+ printf "Not available for current architecture.\n"
+ else
+ if $argc != 2
+ printf "pmap_vtop <pamp> <vaddr>\n"
+ else
+ set $kgm_pt_verbose = 0
+ _pmap_walk $arg0 $arg1
+ end
+ end
+end
+
+document pmap_vtop
+Syntax: (gdb) pmap_vtop <pmap> <virtual_address>
+| For page-tables in <pmap> translate <virtual_address> to physical address.
+end
+
+define zstack
+ set $index = $arg0
+
+ if (log_records == 0)
+ set $count = 0
+ printf "Zone logging not enabled. Add 'zlog=<zone name>' to boot-args.\n"
+ else
+ if ($argc == 2)
+ set $count = $arg1
+ else
+ set $count = 1
+ end
+ end
+
+ while ($count)
+ printf "\n--------------- "
+
+ if (zrecords[$index].z_opcode == 1)
+ printf "ALLOC "
+ else
+ printf "FREE "
+ end
+
+ printf " 0x%x : index %d : ztime %d -------------\n", zrecords[$index].z_element, $index, zrecords[$index].z_time
+
+ set $frame = 0
+
+ while ($frame < 15)
+ set $frame_pc = zrecords[$index].z_pc[$frame]
+
+ if ($frame_pc == 0)
+ loop_break
+ end
+
+ x/i $frame_pc
+ set $frame = $frame + 1
+ end
+
+ set $index = $index + 1
+ set $count = $count - 1
+ end
+end
+
+document zstack
+Syntax: (gdb) zstack <index> [<count>]
+| Zone leak debugging: print the stack trace of log element at <index>.
+| If a <count> is supplied, it prints <count> log elements starting at <index>.
+|
+| The suggested usage is to look at indexes below zcurrent and look for common stack traces.
+| The stack trace that occurs the most is probably the cause of the leak. Find the pc of the
+| function calling into zalloc and use the countpcs kgmacro to find out how often that pc occurs in the log.
+| The pc occuring in a high percentage of records is most likely the source of the leak.
+|
+| The findoldest kgmacro is also useful for leak debugging since it identifies the oldest record
+| in the log, which may indicate the leaker.
+end
+
+define findoldest
+ set $index = 0
+ set $count = log_records
+ set $cur_min = 2000000000
+ set $cur_index = 0
+
+ if (log_records == 0)
+ printf "Zone logging not enabled. Add 'zlog=<zone name>' to boot-args.\n"
+ else
+
+ while ($count)
+ if (zrecords[$index].z_element && zrecords[$index].z_time < $cur_min)
+ set $cur_index = $index
+ set $cur_min = zrecords[$index].z_time
+ end
+
+ set $count = $count - 1
+ set $index = $index + 1
+ end
+
+ printf "oldest record is at log index %d:\n", $cur_index
+ zstack $cur_index
+ end
+end
+
+document findoldest
+Syntax: (gdb) findoldest
+| Zone leak debugging: find and print the oldest record in the log. Note that this command
+| can take several minutes to run since it uses linear search.
+|
+| Once it prints a stack trace, find the pc of the caller above all the zalloc, kalloc and
+| IOKit layers. Then use the countpcs kgmacro to see how often this caller has allocated
+| memory. A caller with a high percentage of records in the log is probably the leaker.
+end
+
+define countpcs
+ set $target_pc = $arg0
+ set $index = 0
+ set $count = log_records
+ set $found = 0
+
+ if (log_records == 0)
+ printf "Zone logging not enabled. Add 'zlog=<zone name>' to boot-args.\n"
+ else
+
+ while ($count)
+ set $frame = 0
+
+ if (zrecords[$index].z_element != 0)
+ while ($frame < 15)
+ if (zrecords[$index].z_pc[$frame] == $target_pc)
+ set $found = $found + 1
+ set $frame = 15
+ end
+
+ set $frame = $frame + 1
+ end
+ end
+
+ set $index = $index + 1
+ set $count = $count - 1
+ end
+
+ printf "occurred %d times in log (%d%c of records)\n", $found, ($found * 100) / zrecorded, '%'
+ end
+end
+
+document countpcs
+Syntax: (gdb) countpcs <pc>
+| Zone leak debugging: search the log and print a count of all log entries that contain the given <pc>
+| in the stack trace. This is useful for verifying a suspected <pc> as being the source of
+| the leak. If a high percentage of the log entries contain the given <pc>, then it's most
+| likely the source of the leak. Note that this command can take several minutes to run.
+end
+
+define findelem
+ set $fe_index = zcurrent
+ set $fe_count = log_records
+ set $fe_elem = $arg0
+ set $fe_prev_op = -1
+
+ if (log_records == 0)
+ printf "Zone logging not enabled. Add 'zlog=<zone name>' to boot-args.\n"
+ end
+
+ while ($fe_count)
+ if (zrecords[$fe_index].z_element == $fe_elem)
+ zstack $fe_index
+
+ if (zrecords[$fe_index].z_opcode == $fe_prev_op)
+ printf "*************** DOUBLE OP! *********************\n
+ end
+
+ set $fe_prev_op = zrecords[$fe_index].z_opcode
+ end
+
+ set $fe_count = $fe_count - 1
+ set $fe_index = $fe_index + 1
+
+ if ($fe_index >= log_records)
+ set $fe_index = 0
+ end
+ end
+end
+
+document findelem
+Syntax: (gdb) findelem <elem addr>
+| Zone corruption debugging: search the log and print out the stack traces for all log entries that
+| refer to the given zone element. When the kernel panics due to a corrupted zone element, get the
+| element address and use this macro. This will show you the stack traces of all logged zalloc and
+| zfree operations which tells you who touched the element in the recent past. This also makes
+| double-frees readily apparent.
+end