scylla-gdb.py: add $coro_frame()
Adds a convenience function for inspecting the coroutine frame of a given
seastar task.
Short example of extracting a coroutine argument:
```
(gdb) p *$coro_frame(seastar::local_engine->_current_task)
$1 = {
__resume_fn = 0x2485f80 <sstables::parse(schema const&, sstables::sstable_version_types, sstables::random_access_reader&, sstables::statistics&)>,
...
PointerType_7 = 0x601008e67880,
...
__coro_index = 0 '\000'
...
(gdb) p $downcast_vptr($->PointerType_7)
$2 = (schema *) 0x601008e67880
```
Closes scylladb/scylladb#19479
This commit is contained in:
committed by
Avi Kivity
parent
45e27c0da2
commit
fdd8b03d4b
@@ -90,6 +90,8 @@ if(target_arch)
|
||||
add_compile_options("-march=${target_arch}")
|
||||
endif()
|
||||
|
||||
add_compile_options("SHELL:-Xclang -fexperimental-assignment-tracking=disabled")
|
||||
|
||||
function(maybe_limit_stack_usage_in_KB stack_usage_threshold_in_KB config)
|
||||
math(EXPR _stack_usage_threshold_in_bytes "${stack_usage_threshold_in_KB} * 1024")
|
||||
set(_stack_usage_threshold_flag "-Wstack-usage=${_stack_usage_threshold_in_bytes}")
|
||||
|
||||
@@ -1861,6 +1861,14 @@ def get_extra_cxxflags(mode, mode_config, cxx, debuginfo):
|
||||
if debuginfo and mode_config['can_have_debug_info']:
|
||||
cxxflags += ['-g', '-gz']
|
||||
|
||||
# Since AssignmentTracking was enabled by default in clang
|
||||
# (llvm/llvm-project@de6da6ad55d3ca945195d1cb109cb8efdf40a52a)
|
||||
# coroutine frame debugging info (`coro_frame_ty`) is broken.
|
||||
#
|
||||
# It seems that we aren't losing much by disabling AssigmentTracking,
|
||||
# so for now we choose to disable it to get `coro_frame_ty` back.
|
||||
cxxflags.append('-Xclang -fexperimental-assignment-tracking=disabled')
|
||||
|
||||
return cxxflags
|
||||
|
||||
|
||||
|
||||
@@ -6029,6 +6029,79 @@ class scylla_gdb_func_variant_member(gdb.Function):
|
||||
def invoke(self, obj):
|
||||
return std_variant(obj).get()
|
||||
|
||||
class scylla_gdb_func_coro_frame(gdb.Function):
|
||||
"""Given a seastar::task* pointing to a coroutine task, convert it to a pointer to the coroutine frame.
|
||||
|
||||
Usage:
|
||||
$coro_frame($ptr)
|
||||
|
||||
Where:
|
||||
$ptr - any expression that evaluates to an integer. It will be interpreted as seastar::task*.
|
||||
|
||||
Returns:
|
||||
The (typed) pointer to the coroutine frame.
|
||||
|
||||
Example:
|
||||
|
||||
1. Print the task chain:
|
||||
|
||||
(gdb) scylla fiber seastar::local_engine->_current_task
|
||||
[shard 1] #0 (task*) 0x0000601008e8e970 0x000000000047aae0 vtable for seastar::internal::coroutine_traits_base<void>::promise_type + 16 (sstables::parse<unsigned int, std::pair<sstables::metadata_type, unsigned int> >(schema const&, sstables::sstable_version_types, sstables::random_access_reader&, sstables::disk_array<unsigned int, std::pair<sstables::metadata_type, unsigned int> >&) at sstables/sstables.cc:352)
|
||||
[shard 1] #1 (task*) 0x00006010092acf10 0x000000000047aae0 vtable for seastar::internal::coroutine_traits_base<void>::promise_type + 16 (sstables::parse(schema const&, sstables::sstable_version_types, sstables::random_access_reader&, sstables::statistics&) at sstables/sstables.cc:570)
|
||||
...
|
||||
|
||||
2. Examine the coroutine frame of task #1:
|
||||
|
||||
(gdb) p *$coro_frame(0x00006010092acf10)
|
||||
$1 = {
|
||||
__resume_fn = 0x2485f80 <sstables::parse(schema const&, sstables::sstable_version_types, sstables::random_access_reader&, sstables::statistics&)>,
|
||||
...
|
||||
PointerType_7 = 0x601008e67880,
|
||||
PointerType_8 = 0x601008e64900,
|
||||
...
|
||||
__int_32_23 = 4,
|
||||
...
|
||||
__coro_index = 0 '\000'
|
||||
...
|
||||
|
||||
3. Examine coroutine arguments.
|
||||
(Needs some know-how. They should be at lowest-indexed PointerType* or __int* entries, depending on their type. Not so easy to automate):
|
||||
|
||||
(gdb) p $downcast_vptr($coro_frame(0x00006010092acf10)->PointerType_7)
|
||||
$2 = (schema *) 0x601008e67880
|
||||
(gdb) p (sstables::sstable_version_types)($coro_frame(0x00006010092acf10)->__int_32_23)
|
||||
$3 = sstables::sstable_version_types::me
|
||||
(gdb) p $downcast_vptr($coro_frame(0x00006010092acf10)->PointerType_8)
|
||||
$4 = (sstables::file_random_access_reader *) 0x601008e64900
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(scylla_gdb_func_coro_frame, self).__init__('coro_frame')
|
||||
|
||||
def invoke(self, ptr_raw):
|
||||
vptr_type = gdb.lookup_type('uintptr_t').pointer()
|
||||
ptr = gdb.Value(ptr_raw).reinterpret_cast(vptr_type)
|
||||
maybe_vptr = int(ptr.dereference())
|
||||
|
||||
# Validate that the task is a coroutine.
|
||||
with gdb.with_parameter("print demangle", "on"):
|
||||
symname = gdb.execute(f"info symbol {maybe_vptr}", False, True)
|
||||
if not symname.startswith('vtable for seastar::internal::coroutine_traits_base'):
|
||||
gdb.write(f"0x{maybe_vptr:x} does not seem to point to a coroutine task.")
|
||||
return None
|
||||
|
||||
# The promise object starts on the third `uintptr_t` in the frame.
|
||||
# The resume_fn pointer is the first `uintptr_t`.
|
||||
# So if the task is a coroutine, we should be able to find the resume function via offsetting by -2.
|
||||
# AFAIK both major compilers respect this convention.
|
||||
block = gdb.block_for_pc((ptr - 2).dereference())
|
||||
|
||||
# Look up the coroutine frame type.
|
||||
# I don't understand why, but gdb has problems looking up the coro_frame_ty type if demangling is enabled.
|
||||
with gdb.with_parameter("demangle-style", "none"):
|
||||
coro_ty = gdb.lookup_type(f"{block.function.linkage_name}.coro_frame_ty").pointer()
|
||||
|
||||
return (ptr - 2).cast(coro_ty)
|
||||
|
||||
# Commands
|
||||
scylla()
|
||||
@@ -6089,6 +6162,7 @@ scylla_range_tombstones()
|
||||
# (gdb) help function $function_name
|
||||
scylla_gdb_func_dereference_smart_ptr()
|
||||
scylla_gdb_func_downcast_vptr()
|
||||
scylla_gdb_func_coro_frame()
|
||||
scylla_gdb_func_collection_element()
|
||||
scylla_gdb_func_sharded_local()
|
||||
scylla_gdb_func_variant_member()
|
||||
|
||||
@@ -163,6 +163,21 @@ def task(gdb, scylla_gdb):
|
||||
def test_fiber(gdb, task):
|
||||
scylla(gdb, f'fiber {task}')
|
||||
|
||||
# Similar to task(), but looks for a coroutine frame.
|
||||
@pytest.fixture(scope="module")
|
||||
def coro_task(gdb, scylla_gdb):
|
||||
for obj_addr, vtable_addr in scylla_gdb.find_vptrs():
|
||||
name = scylla_gdb.resolve(vtable_addr)
|
||||
if name and name.strip() == 'service::topology_coordinator::run() [clone .resume]':
|
||||
return obj_addr.cast(gdb.lookup_type('uintptr_t'))
|
||||
raise gdb.error("No coroutine frames found with expected name")
|
||||
|
||||
def test_coro_frame(gdb, coro_task):
|
||||
# Note the offset by two words.
|
||||
# This moves the pointer from the outer coroutine frame to the inner seastar::task.
|
||||
# $coro_frame expects a seastar::task*.
|
||||
gdb.execute(f'p *$coro_frame({coro_task} + 16)')
|
||||
|
||||
def test_sstable_summary(gdb, sstable):
|
||||
scylla(gdb, f'sstable-summary {sstable}')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user