This is an automated email from the ASF dual-hosted git repository. wwbmmm pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/brpc.git
The following commit(s) were added to refs/heads/master by this push: new b67aa066 add lldb bthread stack debug script (#2514) b67aa066 is described below commit b67aa066640ec36fa402ba40caa3f82e35f30261 Author: Dongsheng He <ds_h...@163.com> AuthorDate: Tue Jan 23 12:11:55 2024 +0800 add lldb bthread stack debug script (#2514) * add lldb bthread stack helper script * fix value as unsigned --- tools/lldb_bthread_stack.py | 347 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 347 insertions(+) diff --git a/tools/lldb_bthread_stack.py b/tools/lldb_bthread_stack.py new file mode 100644 index 00000000..78cbf719 --- /dev/null +++ b/tools/lldb_bthread_stack.py @@ -0,0 +1,347 @@ +#!/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. lldb attach -p <pid> + 2. command script import lldb_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 + 9. bthread_all: print all bthread frames + +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 lldb + + +class GlobalState(): + def __init__(self): + self.started: bool = False + self.bthreads: list = [] + self.saved_regs: dict = {} + + def reset(self) -> None: + self.started = False + self.bthreads.clear() + + def get_bthread(self, idx_str: str) -> lldb.SBValue: + if not self.started: + print("Not in bthread debug mode") + return None + if len(idx_str) == 0: + print("bthread_frame <id>, see 'bthread_list'") + try: + bthread_idx = int(idx_str) + except ValueError: + print("please input a valid interger.") + return None + + if bthread_idx >= len(self.bthreads): + print("id {} exceeds max bthread nums {}".format( + bthread_idx, len(self.bthreads))) + return None + return self.bthreads[bthread_idx] + + +global_state = GlobalState() + + +def get_child(value: lldb.SBValue, childs_value_name: str) -> lldb.SBValue: + r"""get child value by value name str split by '.'""" + result = value + childs_value_list = childs_value_name.split('.') + for child_value_name in childs_value_list: + result = result.GetChildMemberWithName(child_value_name) + return result + + +def find_global_value(target: lldb.SBTarget, value_name: str) -> lldb.SBValue: + r""" find global value by value name""" + name_list = value_name.split('.') + root_name = name_list[0] + root_value = target.FindGlobalVariables( + root_name, 1, lldb.eMatchTypeNormal)[0] + if (len(name_list) == 1): + return root_value + return get_child(root_value, '.'.join(name_list[1:])) + + +def get_bthreads_num(target: lldb.SBTarget): + root_agent = find_global_value( + target, "bthread::g_task_control._nbthreads._combiner._agents.root_") + global_res = find_global_value( + target, "bthread::g_task_control._nbthreads._combiner._global_result").GetValueAsSigned() + long_type = target.GetBasicType(lldb.eBasicTypeLong) + + last_node = root_agent + # agent_type: bvar::detail::AgentCombiner<long, long, bvar::detail::AddTo<long> >::Agent> + agent_type: lldb.SBType = last_node.GetType().GetTemplateArgumentType(0) + while True: + agent = last_node.Cast(agent_type) + if (last_node.GetLocation() != root_agent.GetLocation()): + val = get_child(agent, "element._value").Cast( + long_type).GetValueAsSigned() + global_res += val + if (get_child(agent, "next_").Dereference().GetLocation() == root_agent.GetLocation()): + return global_res + last_node = get_child(agent, "next_").Dereference() + + +def get_all_bthreads(target: lldb.SBTarget, total: int): + bthreads = [] + groups = find_global_value( + target, "butil::ResourcePool<bthread::TaskMeta>::_ngroup.val").GetValueAsUnsigned() + long_type = target.GetBasicType(lldb.eBasicTypeLong) + uint32_t_type = target.FindFirstType("uint32_t") + block_groups = find_global_value( + target, "butil::ResourcePool<bthread::TaskMeta>::_block_groups") + for group in range(groups): + block_group = get_child( + block_groups.GetChildAtIndex(group), "val").Dereference() + nblock = get_child(block_group, "nblock").Cast( + long_type).GetValueAsUnsigned() + blocks = get_child(block_group, "blocks") + for block in range(nblock): + # block_type: butil::ResourcePool<bthread::TaskMeta>::Block + block_type = blocks.GetChildAtIndex( + block).GetType().GetTemplateArgumentType(0) + block = blocks.GetChildAtIndex( + block).Cast(block_type).Dereference() + nitem = get_child(block, "nitem").GetValueAsUnsigned() + task_meta_array_type = target.FindFirstType( + "bthread::TaskMeta").GetArrayType(nitem) + tasks = get_child(block, "items").Cast(task_meta_array_type) + for i in range(nitem): + task_meta = tasks.GetChildAtIndex(i) + version_tid = get_child( + task_meta, "tid").GetValueAsUnsigned() >> 32 + version_butex = get_child(task_meta, "version_butex").Cast( + uint32_t_type.GetPointerType()).Dereference().GetValueAsUnsigned() + # stack_type: bthread::ContextualStack + stack_type = get_child( + task_meta, "attr.stack_type").GetValueAsUnsigned() + if version_tid == version_butex and stack_type != 0: + if len(bthreads) >= total: + return bthreads + bthreads.append(task_meta) + return bthreads + +# lldb bthread commands +def bthread_begin(debugger, command, result, internal_dict): + if global_state.started: + print("Already in bthread debug mode, do not switch thread before exec 'bthread_end' !!!") + return + target = debugger.GetSelectedTarget() + active_bthreads = get_bthreads_num(target) + + if len(command) == 0: + request_bthreds = active_bthreads + else: + try: + request_bthreds = int(command) + except ValueError: + print("please input a valid interger.") + return + + scanned_bthreds = active_bthreads + if request_bthreds > active_bthreads: + print("requested bthreads {} more than actived, will display {} bthreads".format( + request_bthreds, active_bthreads)) + else: + scanned_bthreds = request_bthreds + print("Active bthreads: {}, will display {} bthreads".format( + active_bthreads, scanned_bthreds)) + global_state.bthreads = get_all_bthreads(target, scanned_bthreds) + + # backup registers + current_frame = target.GetProcess().GetSelectedThread().GetSelectedFrame() + saved_regs = dict() + saved_regs["rip"] = current_frame.FindRegister("rip").GetValueAsUnsigned() + saved_regs["rsp"] = current_frame.FindRegister("rsp").GetValueAsUnsigned() + saved_regs["rbp"] = current_frame.FindRegister("rbp").GetValueAsUnsigned() + global_state.saved_regs = saved_regs + + global_state.started = True + print("Enter bthread debug mode, do not switch thread before exec 'bthread_end' !!!") + + +def bthread_list(debugger, command, result, internal_dict): + r"""list all bthreads, print format is 'id\ttid\tfunction\thas stack'""" + if not global_state.started: + print("Not in bthread debug mode") + return + + print("id\t\ttid\t\tfunction\t\t\t\thas stack\t\t\ttotal:{}".format( + len(global_state.bthreads))) + for i, t in enumerate(global_state.bthreads): + tid = get_child(t, "tid").GetValueAsUnsigned() + fn = get_child(t, "fn") + has_stack = get_child(t, "stack").GetLocation() == "0x0" + print("#{}\t\t{}\t\t{}\t\t{}".format( + i, tid, fn, "no" if has_stack else "yes")) + + +def bthread_num(debugger, command, result, internal_dict): + r"""list active bthreads num""" + if not global_state.started: + print("Not in bthread debug mode") + return + + target = debugger.GetSelectedTarget() + active_bthreads = get_bthreads_num(target) + print(active_bthreads) + + +def bthread_frame(debugger, command, result, internal_dict): + r"""bthread_frame <id>, select bthread frame by id""" + bthread = global_state.get_bthread(command) + if bthread is None: + return + + stack = bthread.GetChildMemberWithName("stack") + context = stack.Dereference().GetChildMemberWithName("context") + + target = debugger.GetSelectedTarget() + uint64_t_type = target.FindFirstType("uint64_t") + target = debugger.GetSelectedTarget() + + rip = target.CreateValueFromAddress("rip", lldb.SBAddress( + context.GetValueAsUnsigned() + 7*8, target), uint64_t_type).GetValueAsUnsigned() + rbp = target.CreateValueFromAddress("rbp", lldb.SBAddress( + context.GetValueAsUnsigned() + 6*8, target), uint64_t_type).GetValueAsUnsigned() + rsp = context.GetValueAsUnsigned() + 8*8 + + debugger.HandleCommand(f"register write rip {rip}") + debugger.HandleCommand(f"register write rbp {rbp}") + debugger.HandleCommand(f"register write rsp {rsp}") + + +def bthread_all(debugger, command, result, internal_dict): + r"""print all bthread frames""" + if not global_state.started: + print("Not in bthread debug mode") + return + + bthreads = global_state.bthreads + bthread_num = len(bthreads) + for i in range(bthread_num): + bthread_frame(debugger, str(i), result, internal_dict) + debugger.HandleCommand("bt") + + +def bthread_meta(debugger, command, result, internal_dict): + r"""bthread_meta <id>, print task meta by id""" + bthread = global_state.get_bthread(command) + if bthread is None: + return + print(bthread) + + +def bthread_regs(debugger, command, result, internal_dict): + r"""bthread_regs <id>, print bthread registers""" + bthread = global_state.get_bthread(command) + if bthread is None: + return + target = debugger.GetSelectedTarget() + stack = get_child(bthread, "stack").Dereference() + context = get_child(stack, "context") + ctx_addr = context.GetValueAsUnsigned() + uint64_t_type = target.FindFirstType("uint64_t") + + rip = target.CreateValueFromAddress("rip", lldb.SBAddress( + ctx_addr + 7*8, target), uint64_t_type).GetValueAsUnsigned() + rbp = target.CreateValueFromAddress("rbp", lldb.SBAddress( + ctx_addr + 6*8, target), uint64_t_type).GetValueAsUnsigned() + rbx = target.CreateValueFromAddress("rbx", lldb.SBAddress( + ctx_addr + 5*8, target), uint64_t_type).GetValueAsUnsigned() + r15 = target.CreateValueFromAddress("r15", lldb.SBAddress( + ctx_addr + 4*8, target), uint64_t_type).GetValueAsUnsigned() + r14 = target.CreateValueFromAddress("r14", lldb.SBAddress( + ctx_addr + 3*8, target), uint64_t_type).GetValueAsUnsigned() + r13 = target.CreateValueFromAddress("r13", lldb.SBAddress( + ctx_addr + 2*8, target), uint64_t_type).GetValueAsUnsigned() + r12 = target.CreateValueFromAddress("r12", lldb.SBAddress( + ctx_addr + 1*8, target), uint64_t_type).GetValueAsUnsigned() + rsp = ctx_addr + 8*8 + + 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)) + + +def bthread_reg_restore(debugger, command, result, internal_dict): + r"""restore registers""" + if not global_state.started: + print("Not in bthread debug mode") + return + for reg_name, reg_value in global_state.saved_regs.items(): + debugger.HandleCommand(f"register write {reg_name} {reg_value}") + + +def bthread_end(debugger, command, result, internal_dict): + r"""exit bthread debug mode""" + if not global_state.started: + print("Not in bthread debug mode") + return + bthread_reg_restore(debugger, command, result, internal_dict) + global_state.reset() + print("Exit bthread debug mode") + + +# And the initialization code to add commands. +def __lldb_init_module(debugger, internal_dict): + debugger.HandleCommand( + 'command script add -f lldb_bthread_stack.bthread_begin bthread_begin') + debugger.HandleCommand( + 'command script add -f lldb_bthread_stack.bthread_list bthread_list') + debugger.HandleCommand( + 'command script add -f lldb_bthread_stack.bthread_frame bthread_frame') + debugger.HandleCommand( + 'command script add -f lldb_bthread_stack.bthread_num bthread_num') + debugger.HandleCommand( + 'command script add -f lldb_bthread_stack.bthread_all bthread_all') + debugger.HandleCommand( + 'command script add -f lldb_bthread_stack.bthread_meta bthread_meta') + debugger.HandleCommand( + 'command script add -f lldb_bthread_stack.bthread_regs bthread_regs') + debugger.HandleCommand( + 'command script add -f lldb_bthread_stack.bthread_reg_restore bthread_reg_restore') + debugger.HandleCommand( + 'command script add -f lldb_bthread_stack.bthread_end bthread_end') --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@brpc.apache.org For additional commands, e-mail: dev-h...@brpc.apache.org