forked from apache/brpc
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add lldb bthread stack debug script (apache#2514)
* add lldb bthread stack helper script * fix value as unsigned
- Loading branch information
1 parent
093cbae
commit 8e6af88
Showing
1 changed file
with
347 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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') |