First attempt at MI command to list all frames for all threads.

This commit is contained in:
Ernie Pasveer
2025-12-23 14:44:51 -06:00
parent 9b99897e58
commit 01df7095c9
+280
View File
@@ -0,0 +1,280 @@
# SPDX-FileCopyrightText: 2025 Ernie Pasveer <epasveer@att.net>
#
# SPDX-License-Identifier: MIT
import gdb
#
# Python MI command to add a -frames commands.
#
class MIFrames(gdb.MICommand):
"""
Add a command to list all frames for all threads.
Usage: -frames-list [OPTIONS]
Options:
--max-frames N Maximum frames per thread
--thread-id ID Only show specific thread
--include-args Include function arguments
--include-locals Include local variables
--simple-values Only simple types
--no-values Don't include variable values
--frame-filter PATTERN Only frames matching pattern
"""
def __init__(self, name, mode):
self._mode = mode
super(MIFrames, self).__init__(name);
def invoke(self, argv):
if self._mode == "list":
# Parse options
options = {
'max_frames': None,
'thread_id': None,
'include_args': False,
'include_locals': False,
'simple_values': False,
'no_values': False,
'frame_filter': None
}
i = 0
while i < len(argv):
if argv[i] == "--max-frames" and i + 1 < len(argv):
options['max_frames'] = int(argv[i + 1])
i += 2
elif argv[i] == "--thread-id" and i + 1 < len(argv):
options['thread_id'] = int(argv[i + 1])
i += 2
elif argv[i] == "--include-args":
options['include_args'] = True
i += 1
elif argv[i] == "--include-locals":
options['include_locals'] = True
i += 1
elif argv[i] == "--simple-values":
options['simple_values'] = True
i += 1
elif argv[i] == "--no-values":
options['no_values'] = True
i += 1
elif argv[i] == "--frame-filter" and i + 1 < len(argv):
options['frame_filter'] = argv[i + 1]
i += 2
else:
raise gdb.GdbError(f"Unknown option: {argv[i]}")
# Save current thread and frame
current_thread = gdb.selected_thread()
current_frame_level = gdb.selected_frame().level() if current_thread else None
try:
result = {
'threads': [],
'current_thread_id': current_thread.num if current_thread else None,
'current_frame_level': current_frame_level,
'total_threads': 0,
'total_frames': 0
}
# Get all threads
all_threads = gdb.selected_inferior().threads()
# Filter by thread ID if specified
if options['thread_id']:
all_threads = [t for t in all_threads if t.num == options['thread_id']]
if not all_threads:
raise gdb.GdbError(f"Thread {options['thread_id']} not found")
for thread in all_threads:
thread_data = self._process_thread(thread, current_thread, options)
if thread_data['frames']: # Only include if we got frames
result['threads'].append(thread_data)
result['total_frames'] += len(thread_data['frames'])
result['total_threads'] = len(result['threads'])
return result
finally:
# Restore original thread and frame
if current_thread:
current_thread.switch()
if current_frame_level is not None:
try:
frame = gdb.newest_frame()
for _ in range(current_frame_level):
frame = frame.older()
frame.select()
except:
pass
else:
raise gdb.GdbError("frames: Invalid parameter: %s" % self._mode)
def _process_thread(self, thread, current_thread, options):
"""Process a single thread."""
thread_data = {
'id': thread.num,
'target-id': str(thread.ptid),
'name': thread.name or '',
'state': 'running' if thread.is_running() else 'stopped',
'current': thread == current_thread,
'frames': []
}
if thread.is_running():
thread_data['state_detail'] = 'Thread is running, no frames available'
return thread_data
try:
# Switch to thread
thread.switch()
# Walk the stack
frame = gdb.newest_frame()
frame_count = 0
while frame is not None:
# Check max frames limit
if options['max_frames'] and frame_count >= options['max_frames']:
thread_data['frames_truncated'] = True
break
# Get frame info
frame_info = self._extract_frame_info(frame, options)
# Apply frame filter
if options['frame_filter']:
func_name = frame_info.get('func', '')
if options['frame_filter'] not in func_name:
frame = frame.older()
continue
thread_data['frames'].append(frame_info)
frame = frame.older()
frame_count += 1
except Exception as e:
thread_data['error'] = str(e)
return thread_data
def _extract_frame_info(self, frame, options):
"""Extract detailed frame information."""
info = {
'level': frame.level(),
'addr': hex(frame.pc()),
'func': frame.name() or '??',
}
# Architecture info
try:
info['arch'] = frame.architecture().name()
except:
pass
# Source location
try:
sal = frame.find_sal()
if sal.symtab:
info['file'] = sal.symtab.filename
info['fullname'] = sal.symtab.fullname()
info['line'] = sal.line
else:
info['from'] = gdb.solib_name(frame.pc()) or 'unknown'
except:
pass
# Frame type
try:
frame_type = frame.type()
type_names = {
gdb.NORMAL_FRAME: 'normal',
gdb.DUMMY_FRAME: 'dummy',
gdb.INLINE_FRAME: 'inline',
gdb.TAILCALL_FRAME: 'tailcall',
gdb.SIGTRAMP_FRAME: 'sigtramp',
gdb.ARCH_FRAME: 'arch',
gdb.SENTINEL_FRAME: 'sentinel'
}
info['type'] = type_names.get(frame_type, 'unknown')
except:
pass
# Variables
if options['include_args'] or options['include_locals']:
try:
block = frame.block()
if block:
if options['include_args']:
info['args'] = self._get_block_variables(
block, frame, True, options
)
if options['include_locals']:
info['locals'] = self._get_block_variables(
block, frame, False, options
)
except:
pass
return info
def _get_block_variables(self, block, frame, get_args, options):
"""Get variables from a block."""
variables = []
while block:
for symbol in block:
if get_args and not symbol.is_argument:
continue
if not get_args and (symbol.is_argument or not symbol.is_variable):
continue
var_info = {'name': symbol.name}
try:
value = symbol.value(frame)
var_type = value.type
# Type filtering
if options['simple_values'] and not self._is_simple_type(var_type):
continue
var_info['type'] = str(var_type)
# Value
if not options['no_values']:
try:
var_info['value'] = str(value)
# Add address for pointers
if var_type.code == gdb.TYPE_CODE_PTR:
var_info['address'] = hex(int(value))
except:
var_info['value'] = '<unavailable>'
except Exception as e:
var_info['error'] = str(e)
variables.append(var_info)
if block.function:
break
block = block.superblock
return variables
def _is_simple_type(self, gdb_type):
"""Check if type is scalar."""
return gdb_type.code in [
gdb.TYPE_CODE_INT, gdb.TYPE_CODE_FLT,
gdb.TYPE_CODE_BOOL, gdb.TYPE_CODE_CHAR,
gdb.TYPE_CODE_ENUM, gdb.TYPE_CODE_PTR
]
# Register
MIFrames("-frames-list", "list")