From 7686b1473e54805987d85c143a2f64b9061ab11a Mon Sep 17 00:00:00 2001 From: James Jones Date: Tue, 1 Oct 2024 14:05:59 -0500 Subject: [PATCH] Move to a single Python script that implements dd This will pro9bably be the schema for any future commands added to gdb and lldb. --- debugger/dd.py | 158 ++++++++++++++++++++++++++++++++++++++++++++ debugger/gdb/dd.py | 51 -------------- debugger/lldb/dd.py | 87 ------------------------ 3 files changed, 158 insertions(+), 138 deletions(-) create mode 100644 debugger/dd.py delete mode 100644 debugger/gdb/dd.py delete mode 100644 debugger/lldb/dd.py diff --git a/debugger/dd.py b/debugger/dd.py new file mode 100644 index 000000000000..9c184271e2af --- /dev/null +++ b/debugger/dd.py @@ -0,0 +1,158 @@ +# +# The "dd" command (no relation to Unix/Linux dd or JCL) +# +# Syntax: dd +# +# where the variable named either has one of the types +# shown in the _howTo list or whose address has one of the +# types shown in the _howTo list. +# +# When the command is run, it calls the appropriate FreeRADIUS +# function to display the value the variable points at (or +# contains, if it's not a pointer). +# +# Apologia: +# +# Debuggers can print values, but they print them in +# accordance with the C declaration. That's nice, but... +# FreeRADIUS has lots of container types, implemented as +# C's flavor of sum types, i.e. unions with some other +# field used as a tag indicating which variant is in use. +# Debuggers have no way to know which field that is or how +# to interpret its value, so you get to see all those +# variants, only one of which matters. +# +# gdb does support custom printing for data of the type +# of your choice, but +# +# 1. lldb doesn't. +# 2. We already have code to display these structures in a +# human-friendly form, used for logging. Why not use them? +# +# Both gdb and lldb support Python scripting, and provide +# ways to +# +# 1. Given a string, get a Python value representing it as +# a variable (or possibly expression) that one can retrieve +# the type of (and get a textual representation of the type) +# 2. Invoke debugger commands from the Python script. +# +# so we can write Python code to call the FreeRADIUS function +# that displays the value as we want. Since we can tell whether +# we're running under gdb or lldb, we can have a single Python +# source that can do the right thing for the debugger it's running +# under. + +import sys + +_howTo = { + 'fr_value_box_t *' : ('fr_value_box_debug', True), + 'fr_value_box_list_t *' : ('fr_value_box_list_debug', True), + 'tmpl_t *' : ('tmpl_debug', True), + 'CONF_ITEM *' : ('_cf_debug', True), + 'dl_loader_t *' : ('dl_loader_debug', False), + 'fr_dict_gctx_t * ' : ('fr_dict_global_ctx_debug', True), + 'fr_pair_t *' : ('fr_pair_debug', True), + 'fr_pair_list_t *' : ('fr_pair_list_debug', True), + 'fr_sbuff_term_t *' : ('fr_sbuff_terminal_debug', True), + 'fr_sbuff_parse_rules_t *' : ('fr_sbuff_parse_rules_debug', True), + 'fr_sbuff_unescape_rules_t *': ('fr_sbuff_unescape_debug', True), + 'tmpl_attr_list_head_t *' : ('tmpl_attr_ref_list_debug', True), + 'tmpl_attr_rules_t *' : ('tmpl_attr_rules_debug', True), + 'fr_dlist_head_t *' : ('tmpl_extents_debug', False), + 'tmpl_request_list_head_t *' : ('tmpl_request_ref_list_debug', True), + 'tmpl_rules_t *' : ('tmpl_rules_debug', True), + 'lua_State *' : ('_util_log_debug', False), + 'xlat_exp_t *' : ('xlat_debug', True) +} + +try: + import gdb # Attempt to import GDB module + dbg = "gdb" +except ImportError: + import lldb # If not available, use LLDB module + dbg = "lldb" + +if dbg == "lldb": + # create LLDB command + + def dd(debugger, command, exe_ctx, result, internal_dict): + target = debugger.GetSelectedTarget() + process = target.GetProcess() + thread = process.GetSelectedThread() + frame = thread.GetSelectedFrame() + sb_var = frame.FindVariable(command) + if not sb_var.IsValid(): + sb_var = target.FindFirstGlobalVariable(command) + if not sb_var.IsValid(): + result.SetError('{} is not a variable'.format(command)) + return + arg = sb_var if sb_var.type.is_pointer else sb_var.address_of + type = arg.GetDisplayTypeName() + if not (type in _howTo): + result.SetError('unsupported type "{}"'.format(type)) + return + function, const = _howTo[type] + cast = '({} const *)'.format(type[0:-2]) if const else '' + argName = arg.GetName() + cmd = 'expr {0}({1}({2}))'.format(function, cast, argName) + interpreter = debugger.GetCommandInterpreter() + if not interpreter.IsValid(): + result.SetError("can't set up SBCommandInterpreter") + return + # The use of fr_value_box_t to represent two different structures + # makes the command fail for it, but only once(!). Until we find + # the right way to disambiguate, we'll give it up to two tries. + for i in range(2): + if (cmdStatus := interpreter.HandleCommand(cmd, result)) == lldb.eReturnStatusSuccessFinishResult: + return + result.SetError("command {} failed, status {}".format(cmd, cmdStatus)) + + # And then some boilerplate to set up the command and announce its availability. + # I'm guessing that the -f option is ., + # and it's followed by the name one uses on the lldb command line. + def __lldb_init_module(debugger, internal_dict): + debugger.HandleCommand('command script add -f dd.dd dd') + print('The "dd" python command has been installed and is ready for use.') + + # To make this available to you in lldb, you need to do this: + # + # (lldb) command script import + # + # or have it done in your .lldbinit file, which needn't be in your home directory; + # giving lldb the --local-lldbinit option makes it look in the current directory. +else: + # create GDB command + class DD (gdb.Command): + """Display selected data structures using FreeRADIUS C calls.""" + def __init__ (self): + super (DD, self).__init__ ("dd", gdb.COMMAND_USER) + print('The "dd" command has been installed and is ready for use.') + def invoke (self, arg, from_tty): + # Python code goes here + var = gdb.parse_and_eval(arg) # really just sets up for eventual evaluation + isAddress = var.type.code == gdb.TYPE_CODE_PTR + if isAddress: + argMod = '' + else: + argMod = '&' + var = var.address + varType = str(var.type) + if not (varType in _howTo): + print('unsupported type "{}"'.format(varType)) + return + function, const = _howTo[varType] + cast = '({} const *)'.format(varType[0:-2]) if const else '' + command = 'call {0}({1}{2}({3}))'.format(function, cast, argMod, arg) + try: + gdb.execute(command) + except: + print("command failed") + DD () # Create an instance so you can run it + + # to make this available to you in gdb, execute the command + # + # (gdb) source + # + # or have that command run when gdb starts, which would involve putting it in + # the .gdbinit file diff --git a/debugger/gdb/dd.py b/debugger/gdb/dd.py deleted file mode 100644 index dad8109a9eb3..000000000000 --- a/debugger/gdb/dd.py +++ /dev/null @@ -1,51 +0,0 @@ -import gdb - -_howTo = { - 'fr_value_box_t *' : ('fr_value_box_debug', True), - 'fr_value_box_list_t *' : ('fr_value_box_list_debug', True), - 'tmpl_t *' : ('tmpl_debug', True), - 'CONF_ITEM *' : ('_cf_debug', True), - 'dl_loader_t *' : ('dl_loader_debug', False), - 'fr_dict_gctx_t * ' : ('fr_dict_global_ctx_debug', True), - 'fr_pair_t *' : ('fr_pair_debug', True), - 'fr_pair_list_t *' : ('fr_pair_list_debug', True), - 'fr_sbuff_term_t *' : ('fr_sbuff_terminal_debug', True), - 'fr_sbuff_parse_rules_t *' : ('fr_sbuff_parse_rules_debug', True), - 'fr_sbuff_unescape_rules_t *': ('fr_sbuff_unescape_debug', True), - 'tmpl_attr_list_head_t *' : ('tmpl_attr_ref_list_debug', True), - 'tmpl_attr_rules_t *' : ('tmpl_attr_rules_debug', True), - 'fr_dlist_head_t *' : ('tmpl_extents_debug', False), - 'tmpl_request_list_head_t *' : ('tmpl_request_ref_list_debug', True), - 'tmpl_rules_t *' : ('tmpl_rules_debug', True), - 'lua_State *' : ('_util_log_debug', False), - 'xlat_exp_t *' : ('xlat_debug', True) -} - -class DD (gdb.Command): - """Display selected data structures using FreeRADIUS C calls.""" - - def __init__ (self): - super (DD, self).__init__ ("dd", gdb.COMMAND_USER) - - def invoke (self, arg, from_tty): - # Python code goes here - var = gdb.parse_and_eval(arg) # really just sets up for eventual evaluation - isAddress = var.type.code == gdb.TYPE_CODE_PTR - if isAddress: - argMod = '' - else: - argMod = '&' - var = var.address - varType = str(var.type) - if not (varType in _howTo): - print('unsupported type "{}"'.format(varType)) - return - function, const = _howTo[varType] - cast = '({} const *)'.format(varType[0:-2]) if const else '' - command = 'call {0}({1}{2}({3}))'.format(function, cast, argMod, arg) - try: - gdb.execute(command) - except: - print("command failed") - -DD () # Create an instance so you can run it diff --git a/debugger/lldb/dd.py b/debugger/lldb/dd.py deleted file mode 100644 index 26aa9823a84b..000000000000 --- a/debugger/lldb/dd.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env python - -import lldb -import optparse -import shlex - -_howTo = { - 'fr_value_box_t *' : ('fr_value_box_debug', True), - 'fr_value_box_list_t *' : ('fr_value_box_list_debug', True), - 'tmpl_t *' : ('tmpl_debug', True), - 'CONF_ITEM *' : ('_cf_debug', True), - 'dl_loader_t *' : ('dl_loader_debug', False), - 'fr_dict_gctx_t * ' : ('fr_dict_global_ctx_debug', True), - 'fr_pair_t *' : ('fr_pair_debug', True), - 'fr_pair_list_t *' : ('fr_pair_list_debug', True), - 'fr_sbuff_term_t *' : ('fr_sbuff_terminal_debug', True), - 'fr_sbuff_parse_rules_t *' : ('fr_sbuff_parse_rules_debug', True), - 'fr_sbuff_unescape_rules_t *': ('fr_sbuff_unescape_debug', True), - 'tmpl_attr_list_head_t *' : ('tmpl_attr_ref_list_debug', True), - 'tmpl_attr_rules_t *' : ('tmpl_attr_rules_debug', True), - 'fr_dlist_head_t *' : ('tmpl_extents_debug', False), - 'tmpl_request_list_head_t *' : ('tmpl_request_ref_list_debug', True), - 'tmpl_rules_t *' : ('tmpl_rules_debug', True), - 'lua_State *' : ('_util_log_debug', False), - 'xlat_exp_t *' : ('xlat_debug', True) -} - -# A Python function to be called from lldb must have the following parameters: -# -# debugger the debugger currently running -# command the curiously named string containing the parameters -# exe_ctx the execution context -# result an SBCommandReturnObject. We use its SetError() method -# for error messages, but in this particular case the -# real goal is to call the right foo_debug() function -# which writes a readable version of the data to stderr. -# In other cases, you may use result.PutOutput() or -# result.PutCString(). -# gather that output and result.PutCString() it. -# internal_dict a Python dictionary containing all variables and -# functions for the current embedded script session - -def dd(debugger, command, exe_ctx, result, internal_dict): - target = debugger.GetSelectedTarget() - process = target.GetProcess() - thread = process.GetSelectedThread() - frame = thread.GetSelectedFrame() - sb_var = frame.FindVariable(command) - if not sb_var.IsValid(): - sb_var = target.FindFirstGlobalVariable(command) - if not sb_var.IsValid(): - result.SetError('{} is not a variable'.format(command)) - return - arg = sb_var if sb_var.type.is_pointer else sb_var.address_of - type = arg.GetDisplayTypeName() - if not (type in _howTo): - result.SetError('unsupported type "{}"'.format(type)) - return - function, const = _howTo[type] - cast = '({} const *)'.format(type[0:-2]) if const else '' - argName = arg.GetName() - cmd = 'expr {0}({1}({2}))'.format(function, cast, argName) - interpreter = debugger.GetCommandInterpreter() - if not interpreter.IsValid(): - result.SetError("can't set up SBCommandInterpreter") - return - # The use of fr_value_box_t to represent two different structures - # makes the command fail for it, but only once(!). Until we find - # the right way to disambiguate, we'll give it up to two tries. - for i in range(2): - if (cmdStatus := interpreter.HandleCommand(cmd, result)) == lldb.eReturnStatusSuccessFinishResult: - return - result.SetError("command {} failed, status {}".format(cmd, cmdStatus)) - -# And then some boilerplate to set up the command and announce its availability. -# I'm guessing that the -f option is ., -# and it's followed by the name one uses on the lldb command line. -def __lldb_init_module(debugger, internal_dict): - debugger.HandleCommand('command script add -f dd.dd dd') - print('The "dd" python command has been installed and is ready for use.') - -# To make this available to you in lldb, you need to do this: -# -# (lldb) command script import -# -# or have it done in your .lldbinit file, which needn't be in your home directory; -# giving lldb the --local-lldbinit option makes it look in the current directory.