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 <[email protected]>
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: [email protected]
For additional commands, e-mail: [email protected]