| #!/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() |