#!/usr/bin/env python # coding=utf-8 # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. """ Bthread Stack Print Tool this only for running process, core dump is not supported. Get Started: 1. gdb attach 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 : enter bthread debug mode, `num` is max scanned bthreads, default will scan all 3. bthread_list: list all bthreads 4. bthread_frame : switch stack to bthread, id will displayed in bthread_list 5. bthread_meta : 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 : 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 >::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::_ngroup'")["val"]) for group in range(groups): blocks = int(gdb.parse_and_eval("(*((*((('butil::static_atomic::BlockGroup*>' *)('butil::ResourcePool::_block_groups')) + {})).val)).nblock._M_i".format(group))) for block in range(blocks): items = int(gdb.parse_and_eval("(*(*(('butil::atomic::Block*>' *)((*((*((('butil::static_atomic::BlockGroup*>' *)('butil::ResourcePool::_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::Block*>' *)((*((*((('butil::static_atomic::BlockGroup*>' *)('butil::ResourcePool::_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 , 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 , 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 , 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 , 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 , 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 , 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()