DEV: gdb: add a number of gdb scripts to navigate in core dumps

These is a collection of functions I'm occasionally using to navigate
in core dumps. Only working ones were extracted.

Those requiring knowledge of global variables (e.g. pools, proxy list)
use the one extracted from the post_mortem struct. That one is defined
in post-mortem.gdb and needs to be initialized using "pm_init post_mortem"
or "pm_init <pointer>". From this point a number of global variables are
accessible even if symbols are missing; those ones are then used by other
functions to dump streams, threads, pools, proxies etc.

The files can be sourced or copy-pasted into a gdb session. It's worth
trying to keep them up-to-date, as the old ones used to navigate through
tasks are no longer usable due to massive changes.
This commit is contained in:
Willy Tarreau 2024-10-28 17:51:29 +01:00
parent 52240680f1
commit e240be5495
7 changed files with 264 additions and 0 deletions

118
dev/gdb/ebtree.gdb Normal file
View File

@ -0,0 +1,118 @@
# sets $tag and $node from $arg0, for internal use only
define _ebtree_set_tag_node
set $tag = (unsigned long)$arg0 & 0x1
set $node = (unsigned long)$arg0 & 0xfffffffffffffffe
set $node = (struct eb_node *)$node
end
# get root from any node (leaf of node), returns in $node
define ebtree_root
set $node = (struct eb_root *)$arg0->node_p
if $node == 0
# sole node
set $node = (struct eb_root *)$arg0->leaf_p
end
# walk up
while 1
_ebtree_set_tag_node $node
if $node->branches.b[1] == 0
break
end
set $node = $node->node_p
end
# root returned in $node
end
# returns $node filled with the first node of ebroot $arg0
define ebtree_first
# browse ebtree left until encoutering leaf
set $node = (struct eb_node *)$arg0->b[0]
while 1
_ebtree_set_tag_node $node
if $tag == 0
loop_break
end
set $node = (struct eb_root *)$node->branches.b[0]
end
# extract last node
_ebtree_set_tag_node $node
end
# finds next ebtree node after $arg0, and returns it in $node
define ebtree_next
# get parent
set $node = (struct eb_root *)$arg0->leaf_p
# Walking up from right branch, so we cannot be below root
# while (eb_gettag(t) != EB_LEFT) // #define EB_LEFT 0
while 1
_ebtree_set_tag_node $node
if $tag == 0
loop_break
end
set $node = (struct eb_root *)$node->node_p
end
set $node = (struct eb_root *)$node->branches.b[1]
# walk down (left side => 0)
# while (eb_gettag(start) == EB_NODE) // #define EB_NODE 1
while 1
_ebtree_set_tag_node $node
if $node == 0
loop_break
end
if $tag != 1
loop_break
end
set $node = (struct eb_root *)$node->branches.b[0]
end
end
# sets $tag and $node from $arg0, for internal use only
define _ebsctree_set_tag_node
set $tag = (unsigned long)$arg0 & 0x1
set $node = (unsigned long)$arg0 & 0xfffffffffffffffe
set $node = (struct eb32sc_node *)$node
end
# returns $node filled with the first node of ebroot $arg0
define ebsctree_first
# browse ebsctree left until encoutering leaf
set $node = (struct eb32sc_node *)$arg0->b[0]
while 1
_ebsctree_set_tag_node $node
if $tag == 0
loop_break
end
set $node = (struct eb_root *)$node->branches.b[0]
end
# extract last node
_ebsctree_set_tag_node $node
end
# finds next ebtree node after $arg0, and returns it in $node
define ebsctree_next
# get parent
set $node = (struct eb_root *)$arg0->node.leaf_p
# Walking up from right branch, so we cannot be below root
# while (eb_gettag(t) != EB_LEFT) // #define EB_LEFT 0
while 1
_ebsctree_set_tag_node $node
if $tag == 0
loop_break
end
set $node = (struct eb_root *)$node->node.node_p
end
set $node = (struct eb_root *)$node->node.branches.b[1]
# walk down (left side => 0)
# while (eb_gettag(start) == EB_NODE) // #define EB_NODE 1
while 1
_ebsctree_set_tag_node $node
if $node == 0
loop_break
end
if $tag != 1
loop_break
end
set $node = (struct eb_root *)$node->node.branches.b[0]
end
end

26
dev/gdb/list.gdb Normal file
View File

@ -0,0 +1,26 @@
# lists entries starting at list head $arg0
define list_dump
set $h = $arg0
set $p = *(void **)$h
while ($p != $h)
printf "%#lx\n", $p
if ($p == 0)
loop_break
end
set $p = *(void **)$p
end
end
# list all entries starting at list head $arg0 until meeting $arg1
define list_find
set $h = $arg0
set $k = $arg1
set $p = *(void **)$h
while ($p != $h)
printf "%#lx\n", $p
if ($p == 0 || $p == $k)
loop_break
end
set $p = *(void **)$p
end
end

21
dev/gdb/pools.gdb Normal file
View File

@ -0,0 +1,21 @@
# dump pool contents (2.9 and above, with buckets)
define pools_dump
set $h = $po
set $p = *(void **)$h
while ($p != $h)
set $e = (struct pool_head *)(((char *)$p) - (unsigned long)&((struct pool_head *)0)->list)
set $total = 0
set $used = 0
set $idx = 0
while $idx < sizeof($e->buckets) / sizeof($e->buckets[0])
set $total=$total + $e->buckets[$idx].allocated
set $used=$used + $e->buckets[$idx].used
set $idx=$idx + 1
end
set $mem = $total * $e->size
printf "list=%#lx pool_head=%p name=%s size=%u alloc=%u used=%u mem=%u\n", $p, $e, $e->name, $e->size, $total, $used, $mem
set $p = *(void **)$p
end
end

47
dev/gdb/post-mortem.gdb Normal file
View File

@ -0,0 +1,47 @@
# This script will set the post_mortem struct pointer ($pm) from the one found
# in the "post_mortem" symbol. If not found or if not correct, it's the same
# address as the "_post_mortem" section, which can be found using "info files"
# or "objdump -h" on the executable. The guessed value is the by a first call
# to pm_init, but if not correct, you just need to call pm_init again with the
# correct pointer, e.g:
# pm_init 0xcfd400
define pm_init
set $pm = (struct post_mortem*)$arg0
set $g = $pm.global
set $ti = $pm.thread_info
set $tc = $pm.thread_ctx
set $tgi = $pm.tgroup_info
set $tgc = $pm.tgroup_ctx
set $fd = $pm.fdtab
set $pxh = *$pm.proxies
set $po = $pm.pools
set $ac = $pm.activity
end
# show basic info on the running process (OS, uid, etc)
define pm_show_info
print $pm->platform
print $pm->process
end
# show thread IDs to easily map between gdb threads and tid
define pm_show_threads
set $t = 0
while $t < $g.nbthread
printf "Tid %4d: pthread_id=%#lx stack_top=%#lx\n", $t, $ti[$t].pth_id, $ti[$t].stack_top
set $t = $t + 1
end
end
# dump all threads' dump buffers
define pm_show_thread_dump
set $t = 0
while $t < $g.nbthread
printf "%s\n", $tc[$t].thread_dump_buffer->area
set $t = $t + 1
end
end
# initialize the various pointers
pm_init &post_mortem

25
dev/gdb/proxies.gdb Normal file
View File

@ -0,0 +1,25 @@
# list proxies starting with the one in argument (typically $pxh)
define px_list
set $p = (struct proxy *)$arg0
while ($p != 0)
printf "%p (", $p
if $p->cap & 0x10
printf "LB,"
end
if $p->cap & 0x1
printf "FE,"
end
if $p->cap & 0x2
printf "BE,"
end
printf "%s)", $p->id
if $p->cap & 0x1
printf " feconn=%u cmax=%u cum_conn=%llu cpsmax=%u", $p->feconn, $p->fe_counters.conn_max, $p->fe_counters.cum_conn, $p->fe_counters.cps_max
end
if $p->cap & 0x2
printf " beconn=%u served=%u queued=%u qmax=%u cum_sess=%llu wact=%u", $p->beconn, $p->served, $p->queue.length, $p->be_counters.nbpend_max, $p->be_counters.cum_sess, $p->lbprm.tot_wact
end
printf "\n"
set $p = ($p)->next
end
end

9
dev/gdb/servers.gdb Normal file
View File

@ -0,0 +1,9 @@
# list servers in a proxy whose pointer is passed in argument
define px_list_srv
set $h = (struct proxy *)$arg0
set $p = ($h)->srv
while ($p != 0)
printf "%#lx %s maxconn=%u cur_sess=%u max_sess=%u served=%u queued=%u st=%u->%u ew=%u sps_max=%u\n", $p, $p->id, $p->maxconn, $p->cur_sess, $p->counters.cur_sess_max, $p->served, $p->queue.length, $p->cur_state, $p->next_state, $p->cur_eweight, $p->counters.sps_max
set $p = ($p)->next
end
end

18
dev/gdb/stream.gdb Normal file
View File

@ -0,0 +1,18 @@
# list all streams for all threads
define stream_dump
set $t = 0
while $t < $g.nbthread
set $h = &$tc[$t].streams
printf "Tid %4d: &streams=%p\n", $t, $h
set $p = *(void **)$h
while ($p != $h)
set $s = (struct stream *)(((char *)$p) - (unsigned long)&((struct stream *)0)->list)
printf " &list=%#lx strm=%p uid=%u strm.fe=%s strm.flg=%#x strm.list={n=%p,p=%p}\n", $p, $s, $s->uniq_id, $s->sess->fe->id, $s->flags, $s->list.n, $s->list.p
if ($p == 0)
loop_break
end
set $p = *(void **)$p
end
set $t = $t + 1
end
end