[patch] Indirect access to GDB history variables

Steve Rodrigues
2006-12-15T02:41:11Z

[-- Attachment #1: Type: text/plain, Size: 2883 bytes --]

New Feature for GDB: Programmatic access to the value history.

Problem Description: Our company has a wide variety of GDB scripts used to
analyze problems within core files. Many of these scripts will generate values
that are useful to probe into later; however, the scripts will generate a LOT
of values, or values that aren't in sequential order (you care about, say,
every third value). GDB only lets you reference previous history value either
with absolute numbers ($10, $236) or with reference to the most recently printed value ($$, $$9, etc). It sure would be nice if there was a way to be able to
access the value history by indirecting through a variable.

Feature: This patch enables users to programmatcially access the value history
through a GDB variable, by overloading the "$$" construct to contain a variable
name. For example, if my script had printed out values $10-$27, but only every
3rd one was interesting (it was a pointer I wished to examine further), I could
do the following:

set $i=10
while ($i < 28)
p *$$i
set $i+= 3
end

... and I'd see values of $10, $13, $16 and so on. This makes it easier to
compose scripts together when debugging.

Implementation:
The meat of the change is in parse.c (write_dollar_variable) and eval.c
(evaluate_subexp_standard).

I introduced a new operand type, OP_LAST_INTERNALVAR, which is emitted
by write_dollar_variable (parse.c) when it sees a "$$" that is NOT
followed by digits (and not alone, which is already an OP_LAST
operand). OP_LAST_INTERNALVAR should always be followed by an
OP_INTERNALVAR operand, pointing to the GDB variable which we will
indirect through.

When evaluate_subexp_standard (eval.c) sees an OP_LAST_INTERNALVAR, we just
need to evaluate the following OP_INTERNALVAR expression and take the
value_contents of that expression, passing the results into
access_value_history.

We have been using this modification internally for over a year on gdb 6.2
with no problems. The patch has been verified on gdb-6.6.50.20061212
(most recent weekly build) on Linux and on gdb-6.6.50.20061127 on Solaris 5.8
(couldn't get 20061212 to build).

Testing: This has been tested by hand. I've been trying to write a test
case but have been having no luck getting the test suite to run (due to
old versions of Tcl/expect on the systems this was developed on).

Future Directions: The obvious next step for this is to add a variable ($#?)
which indicates the current history value count, which makes this amenable to
full scripting rather than use by hand.

ChangeLog and Patch are attached.

--Steve

--
Steven Rodrigues | Lost, yesterday, somewhere between sunrise and
Member of Technical Staff | sunset, two golden hours, each set with sixty
Network Appliance Corp. | diamond minutes. No reward is offered, for they
steverod@netapp.com | are gone forever. -- Horace Mann

[-- Attachment #2: ChangeLog --]
[-- Type: text/plain, Size: 568 bytes --]

2006-10-17 Steve Rodrigues <steverod@netapp.com>
* expression.h (enum exp_opcode): Add OP_LAST_INTERNALVAR opcode.
* eval.c (evaluate_subexp_standard): Process a OP_LAST_INTERNALVAR
operand to indirectly access the value history.
* ada-lang.c (resolve_subexp): Support OP_LAST_INTERNALVAR.
* expprint.c (print_subexp_standard): Support OP_LAST_INTERNALVAR.
(op_name_standard): Support OP_LAST_INTERNALVAR.
(dump_subexp_body_standard): Support OP_LAST_INTERNALVAR.
* parse.c (write_dollar_variable): Support '$$' to be treated as
an OP_LAST_INTERNALVAR.

[-- Attachment #3: gdb66.diffs --]
[-- Type: text/plain, Size: 4291 bytes --]

--- ./gdb/ada-lang.c.orig Thu Nov 30 16:32:29 2006
+++ ./gdb/ada-lang.c Thu Dec 14 18:01:00 2006
@@ -2728,6 +2728,11 @@ resolve_subexp (struct expression **expp
*pos += 3;
break;

+ case OP_LAST_INTERNALVAR:
+ *pos ++;
+ nargs = 1;
+ break;
+
case UNOP_MEMVAL:
*pos += 3;
nargs = 1;
--- ./gdb/eval.c.orig Mon Oct 9 20:17:53 2006
+++ ./gdb/eval.c Thu Dec 14 18:01:00 2006
@@ -473,6 +473,12 @@ evaluate_subexp_standard (struct type *e
return
access_value_history (longest_to_int (exp->elts[pc + 1].longconst));

+ case OP_LAST_INTERNALVAR:
+ {
+ arg1 = evaluate_subexp(NULL_TYPE, exp, pos, noside);
+ return access_value_history(*(int*)value_contents(arg1));
+ }
+
case OP_REGISTER:
{
int regno = longest_to_int (exp->elts[pc + 1].longconst);
--- ./gdb/expprint.c.orig Mon Oct 9 20:17:53 2006
+++ ./gdb/expprint.c Thu Dec 14 18:01:00 2006
@@ -151,6 +151,12 @@ print_subexp_standard (struct expression
internalvar_name (exp->elts[pc + 1].internalvar));
return;

+ case OP_LAST_INTERNALVAR:
+ (*pos) += 2;
+ fprintf_filtered (stream, "$$%s",
+ internalvar_name (exp->elts[pc + 1].internalvar));
+ return;
+
case OP_FUNCALL:
(*pos) += 2;
nargs = longest_to_int (exp->elts[pc + 1].longconst);
@@ -695,6 +701,8 @@ op_name_standard (enum exp_opcode opcode
return "OP_REGISTER";
case OP_INTERNALVAR:
return "OP_INTERNALVAR";
+ case OP_LAST_INTERNALVAR:
+ return "OP_LAST_INTERNALVAR";
case OP_FUNCALL:
return "OP_FUNCALL";
case OP_STRING:
@@ -976,6 +984,9 @@ dump_subexp_body_standard (struct expres
fprintf_filtered (stream, " (%s)",
exp->elts[elt].internalvar->name);
elt += 2;
+ break;
+ case OP_LAST_INTERNALVAR:
+ fprintf_filtered (stream, "History element from Internal var");
break;
case OP_FUNCALL:
{
--- ./gdb/expression.h.orig Mon Oct 9 20:17:53 2006
+++ ./gdb/expression.h Thu Dec 14 18:01:00 2006
@@ -174,6 +174,12 @@ enum exp_opcode
/* OP_INTERNALVAR is followed by an internalvar ptr in the next exp_element.
With another OP_INTERNALVAR at the end, this makes three exp_elements. */
OP_INTERNALVAR,
+
+ /* OP_LAST_INTERNALVAR is used for accessing the history programmatically.
+ It is followed by an internalvar ptr in the next exp_element.
+ With another OP_LAST_INTERNALVAR at the end, this makes three
+ exp_elements. */
+ OP_LAST_INTERNALVAR,

/* OP_FUNCALL is followed by an integer in the next exp_element.
The integer is the number of args to the function call.
--- ./gdb/parse.c.orig Sat Nov 18 15:54:32 2006
+++ ./gdb/parse.c Thu Dec 14 18:01:00 2006
@@ -476,6 +476,7 @@ write_dollar_variable (struct stoken str
{
struct symbol *sym = NULL;
struct minimal_symbol *msym = NULL;
+ int stroff = 1;

/* Handle the tokens $digits; also $ (short for $0) and $$ (short for $$1)
and $$digits (equivalent to $<-digits> if you could type that). */
@@ -487,7 +488,7 @@ write_dollar_variable (struct stoken str
if (str.length >= 2 && str.ptr[1] == '$')
{
negate = 1;
- i = 2;
+ i = stroff = 2;
}
if (i == str.length)
{
@@ -536,11 +537,17 @@ write_dollar_variable (struct stoken str
return;
}

- /* Any other names starting in $ are debugger internal variables. */
+ /* Any other names starting in $ are debugger internal variables.
+ A 'negated' internal variable should be treated as an indirect
+ history reference, which adds another LAST_INTERNALVAR opcode */

write_exp_elt_opcode (OP_INTERNALVAR);
- write_exp_elt_intern (lookup_internalvar (copy_name (str) + 1));
+ write_exp_elt_intern (lookup_internalvar (copy_name (str) + stroff));
write_exp_elt_opcode (OP_INTERNALVAR);
+
+ if (negate)
+ write_exp_elt_opcode (OP_LAST_INTERNALVAR);
+
return;
handle_last:
write_exp_elt_opcode (OP_LAST);
@@ -904,6 +911,10 @@ operator_length_standard (struct express
oplen = 3;
break;

+ case OP_LAST_INTERNALVAR:
+ oplen = 1;
+ args = 1;
+ break;
case OP_COMPLEX:
oplen = 1;
args = 2;