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:
Michał Chojnowski
2024-06-25 09:45:25 +02:00
committed by Avi Kivity
parent 45e27c0da2
commit fdd8b03d4b
4 changed files with 99 additions and 0 deletions

View File

@@ -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}")

View File

@@ -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

View File

@@ -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()

View File

@@ -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}')