blob: 582ba9eb803edac094ca0417a978d5b6b7f68a1b [file] [log] [blame]
#!/usr/bin/env python
# coding=utf-8
"""
Bthread Stack Print Tool
this only for running process, core dump is not supported.
Get Started:
1. gdb attach <pid>
2. source gdb_bthread_stack.py
3. bthread_begin
4. bthread_list
5. bthread_frame 0
6. bt / up / down
7. bthread_end
Commands:
1. bthread_num: print all bthread nums
2. bthread_begin <num>: enter bthread debug mode, `num` is max scanned bthreads, default will scan all
3. bthread_list: list all bthreads
4. bthread_frame <id>: switch stack to bthread, id will displayed in bthread_list
5. bthread_meta <id>: print bthread meta
6. bthread_reg_restore: bthread_frame will modify registers, reg_restore will restore them
7. bthread_end: exit bthread debug mode
8. bthread_regs <id>: print bthread registers
when call bthread_frame, registers will be modified,
remember to call bthread_end after debug, or process will be corrupted
after call bthread_frame, you can call `bt`/`up`/`down`, or other gdb command
"""
import gdb
bthreads = []
status = False
def get_bthread_num():
root_agent = gdb.parse_and_eval("&(((((*bthread::g_task_control)._nbthreads)._combiner)._agents).root_)")
global_res = int(gdb.parse_and_eval("((*bthread::g_task_control)._nbthreads)._combiner._global_result"))
get_agent = "(*(('bvar::detail::AgentCombiner<long, long, bvar::detail::AddTo<long> >::Agent' *){}))"
last_node = root_agent
while True:
agent = gdb.parse_and_eval(get_agent.format(last_node))
if last_node != root_agent:
val = int(agent["element"]["_value"]["_M_i"])
global_res += val
if agent["next_"] == root_agent:
return global_res
last_node = agent["next_"]
def get_all_bthreads(total):
global bthreads
bthreads = []
count = 0
groups = int(gdb.parse_and_eval("'butil::ResourcePool<bthread::TaskMeta>::_ngroup'")["val"])
for group in range(groups):
blocks = int(gdb.parse_and_eval("(*((*((('butil::static_atomic<butil::ResourcePool<bthread::TaskMeta>::BlockGroup*>' *)('butil::ResourcePool<bthread::TaskMeta>::_block_groups')) + {})).val)).nblock._M_i".format(group)))
for block in range(blocks):
items = int(gdb.parse_and_eval("(*(*(('butil::atomic<butil::ResourcePool<bthread::TaskMeta>::Block*>' *)((*((*((('butil::static_atomic<butil::ResourcePool<bthread::TaskMeta>::BlockGroup*>' *)('butil::ResourcePool<bthread::TaskMeta>::_block_groups')) + {})).val)).blocks) + {}))._M_b._M_p).nitem".format(group, block)))
for item in range(items):
task_meta = gdb.parse_and_eval("*(('bthread::TaskMeta' *)((*(*(('butil::atomic<butil::ResourcePool<bthread::TaskMeta>::Block*>' *)((*((*((('butil::static_atomic<butil::ResourcePool<bthread::TaskMeta>::BlockGroup*>' *)('butil::ResourcePool<bthread::TaskMeta>::_block_groups')) + {})).val)).blocks) + {}))._M_b._M_p).items) + {})".format(group, block, item))
version_tid = (int(task_meta["tid"]) >> 32)
version_butex = gdb.parse_and_eval("*(uint32_t *){}".format(task_meta["version_butex"]))
if version_tid == int(version_butex) and int(task_meta["attr"]["stack_type"]) != 0:
bthreads.append(task_meta)
count += 1
if count >= total:
return
class BthreadListCmd(gdb.Command):
"""list all bthreads, print format is 'id\ttid\tfunction\thas stack'"""
def __init__(self):
gdb.Command.__init__(self, "bthread_list", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
def invoke(self, arg, tty):
global status
global bthreads
if not status:
print("Not in bthread debug mode")
return
print("id\t\ttid\t\tfunction\t\thas stack\t\t\ttotal:{}".format(len(bthreads)))
for i, t in enumerate(bthreads):
print("#{}\t\t{}\t\t{}\t\t{}".format(i, t["tid"], t["fn"], "no" if str(t["stack"]) == "0x0" else "yes"))
class BthreadNumCmd(gdb.Command):
"""list active bthreads num"""
def __init__(self):
gdb.Command.__init__(self, "bthread_num", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
def invoke(self, arg, tty):
res = get_bthread_num()
print(res)
class BthreadFrameCmd(gdb.Command):
"""bthread_frame <id>, select bthread frame by id"""
def __init__(self):
gdb.Command.__init__(self, "bthread_frame", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
def invoke(self, arg, tty):
global status
global bthreads
if not status:
print("Not in bthread debug mode")
return
if not arg:
print("bthread_frame <id>, see 'bthread_list'")
return
bthread_id = int(arg)
if bthread_id >= len(bthreads):
print("id {} exceeds max bthread nums {}".format(bthread_id, len(bthreads)))
return
stack = bthreads[bthread_id]["stack"]
if str(stack) == "0x0":
print("this bthread has no stack")
return
context = gdb.parse_and_eval("(*(('bthread::ContextualStack' *){})).context".format(stack))
rip = gdb.parse_and_eval("*(uint64_t*)({}+7*8)".format(context))
rbp = gdb.parse_and_eval("*(uint64_t*)({}+6*8)".format(context))
rsp = gdb.parse_and_eval("{}+8*8".format(context))
gdb.parse_and_eval("$rip = {}".format(rip))
gdb.parse_and_eval("$rsp = {}".format(rsp))
gdb.parse_and_eval("$rbp = {}".format(rbp))
class BthreadRegsCmd(gdb.Command):
"""bthread_regs <id>, print bthread registers"""
def __init__(self):
gdb.Command.__init__(self, "bthread_regs", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
def invoke(self, arg, tty):
global status
global bthreads
if not status:
print("Not in bthread debug mode")
return
if not arg:
print("bthread_regs <id>, see 'bthread_list'")
return
bthread_id = int(arg)
if bthread_id >= len(bthreads):
print("id {} exceeds max bthread nums {}".format(bthread_id, len(bthreads)))
return
stack = bthreads[bthread_id]["stack"]
if str(stack) == "0x0":
print("this bthread has no stack")
return
context = gdb.parse_and_eval("(*(('bthread::ContextualStack' *){})).context".format(stack))
rip = int(gdb.parse_and_eval("*(uint64_t*)({}+7*8)".format(context)))
rbp = int(gdb.parse_and_eval("*(uint64_t*)({}+6*8)".format(context)))
rbx = int(gdb.parse_and_eval("*(uint64_t*)({}+5*8)".format(context)))
r15 = int(gdb.parse_and_eval("*(uint64_t*)({}+4*8)".format(context)))
r14 = int(gdb.parse_and_eval("*(uint64_t*)({}+3*8)".format(context)))
r13 = int(gdb.parse_and_eval("*(uint64_t*)({}+2*8)".format(context)))
r12 = int(gdb.parse_and_eval("*(uint64_t*)({}+1*8)".format(context)))
rsp = int(gdb.parse_and_eval("{}+8*8".format(context)))
print("rip: 0x{:x}\nrsp: 0x{:x}\nrbp: 0x{:x}\nrbx: 0x{:x}\nr15: 0x{:x}\nr14: 0x{:x}\nr13: 0x{:x}\nr12: 0x{:x}".format(rip, rsp, rbp, rbx, r15, r14, r13, r12))
class BthreadMetaCmd(gdb.Command):
"""bthread_meta <id>, print task meta by id"""
def __init__(self):
gdb.Command.__init__(self, "bthread_meta", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
def invoke(self, arg, tty):
global status
global bthreads
if not status:
print("Not in bthread debug mode")
return
if not arg:
print("bthread_meta <id>, see 'bthread_list'")
return
bthread_id = int(arg)
if bthread_id >= len(bthreads):
print("id {} exceeds max bthread nums {}".format(bthread_id, len(bthreads)))
return
print(bthreads[bthread_id])
class BthreadBeginCmd(gdb.Command):
"""enter bthread debug mode"""
def __init__(self):
gdb.Command.__init__(self, "bthread_begin", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
def invoke(self, arg, tty):
global status
if status:
print("Already in bthread debug mode, do not switch thread before exec 'bthread_end' !!!")
return
active_bthreads = get_bthread_num()
scanned_bthreads = active_bthreads
if arg:
num_arg = int(arg)
if num_arg < active_bthreads:
scanned_bthreads = num_arg
else:
print("requested bthreads {} more than actived, will display {} bthreads".format(num_arg, scanned_bthreads))
print("Active bthreads: {}, will display {} bthreads".format(active_bthreads, scanned_bthreads))
get_all_bthreads(scanned_bthreads)
gdb.parse_and_eval("$saved_rip = $rip")
gdb.parse_and_eval("$saved_rsp = $rsp")
gdb.parse_and_eval("$saved_rbp = $rbp")
status = True
print("Enter bthread debug mode, do not switch thread before exec 'bthread_end' !!!")
class BthreadRegRestoreCmd(gdb.Command):
"""restore registers"""
def __init__(self):
gdb.Command.__init__(self, "bthread_reg_restore", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
def invoke(self, arg, tty):
global status
if not status:
print("Not in bthread debug mode")
return
gdb.parse_and_eval("$rip = $saved_rip")
gdb.parse_and_eval("$rsp = $saved_rsp")
gdb.parse_and_eval("$rbp = $saved_rbp")
print("OK")
class BthreadEndCmd(gdb.Command):
"""exit bthread debug mode"""
def __init__(self):
gdb.Command.__init__(self, "bthread_end", gdb.COMMAND_STACK, gdb.COMPLETE_NONE)
def invoke(self, arg, tty):
global status
if not status:
print("Not in bthread debug mode")
return
gdb.parse_and_eval("$rip = $saved_rip")
gdb.parse_and_eval("$rsp = $saved_rsp")
gdb.parse_and_eval("$rbp = $saved_rbp")
status = False
print("Exit bthread debug mode")
BthreadListCmd()
BthreadNumCmd()
BthreadBeginCmd()
BthreadEndCmd()
BthreadFrameCmd()
BthreadMetaCmd()
BthreadRegRestoreCmd()
BthreadRegsCmd()