Re: Questions about MI variable objects

Jim Blandy
2007-05-25T18:13:41Z

Nick Roberts <nickrob@snap.net.nz> writes:
> > Suppose I'm stopped where the stack is like this:
> >
> > main -> foo (2) -> foo (1) -> foo (0)
> >
> > Suppose the top frame is selected, and the user adds a display for
> > 'x'. Clearly, it should show '0'.
> >
> > These are questions about what the GUI should display --- not what GDB
> > or MI or varobjs should do. They're about what you want the user to
> > see.
> >
> > 1) If the user selects the 'foo (1)' frame, what should happen to the
> > display of 'x'? Should it grey out? Should it now show 1? (I'd
> > say it should show 1.)
>
> I think "-var-create - @ var1" does this i.e. follows the selected frame but I
> currently only use "-var-create - * var1" to generate watch expressions for
> Emacs. I only grey expressions out when the frame they are created in
> disappears.

Oh, I see --- USE_SELECTED_FRAME. That's not documented; we should
fix that.

So, if Emacs wants displays to grey out when the frame disappears,
does it ever expect those expressions to become ungrey again? After
all, that frame is gone forever. Under what conditions should the
display become ungrey?

> > 4) If the user lets control run to 'foo' again, so the stack now looks
> > like:
> >
> > main -> bar (10) -> foo (9)
> >
> > what should happen to the display of 'x'? (I'd say it should show
> > '9'.)
>
> That's what Emacs currently does and what the default GDB behaviour gives,
> although Daniel J doesn't seem to think it's useful.

Actually, I don't believe you. :) Would you humor me and try this
out?

- Start with the program I posted.
- Set a breakpoint on foo.
- Run to the third hit (that is, foo (0))
- Display 'x'.
- finish three times, so that we're back in main. We all agree the
display should be 'greyed' out at this point.
- continue to the next hit of foo.

When I do this with what I assume are the corresponding MI commands,
the varobj I made for x in foo (0) is still out of scope.

If I continue again, I happen to get a frame whose address matches the
original foo (0) address, and the varobj comes back in scope, but this
is, again, a spurious frame ID match. It happens only because the
frames sizes on the stack happen to line up. The compiler could
generate different, correct, code and the varobj behavior would stop
working.

So, at the moment, your displays based on '*'-frame varobjs ungrey at
times that appear reasonable in simple test programs, but are actually
dependent on flukes of your compiler's stack management.

Setting aside MI and GDB and varobjs, from your users' point of view,
when should that display come alive again?

> > If there's some general agreement on how these ought to behave (not
> > necessarily my guesses, just any agreement) then maybe we could make
> > MI varobjs match that behavior, so that each GUI variable display
> > could be backed by exactly one varobj, and GDB's reports on changes to
> > the varobj's state would correspond closely to changes in the GUI's
> > display. Varobjs exist to help GUIs, and GDB shouldn't be making GUIs
> > jump through hoops to get the behavior they want.
>
> I'm not sure how you intend to change GDB's behaviour but I think it should
> provide options rather than define the policy.

MI is a protocol; I don't want to break backwards compatibility. But
there's no point in retaining backwards compatibility with a bug ---
when that bug's behavior is unpredictable.

> > The principle behind my guesses is that a display should refer to a
> > particular variable in the source code --- a particular declaration
> > --- and should show its value whenever that declaration is in scope in
> > the selected frame. This is less specific than having the display
> > refer to a particular frame's instance of that variable, and more
> > specific than having it refer to any variable that happens to be in
> > scope under that name. But it's what I'd expect from a GUI.
>
> I don't know what to expect. I've always intended to base the display
> behavior on user feedback, but until Emacs is released I guess I won't get
> much.
>
> > At the moment, GDB tries to associate each varobj with a specific
> > function invocation. It's so easy to concoct a case where frame ID's
> > collide that in casual testing, the varobj code may appear to
> > implement something more like my suggested behavior. But it doesn't.
>
> I don't really follow. Can you give an real example where frame IDs collide?

Well, since it depends on the stack management code the compiler
emits, it's compiler-specific. But on a Fedora Core 6 x86 machine:

$ cat vo.c
void
foo (int x)
{
if (x > 0)
foo (x - 1);
}

void
bar (int x)
{
foo (x - 1);
}

int
main (int argc, char **argv)
{
foo (2);
bar (2);

return 0;
}
$ gcc -g vo.c -o vo
$ ~/uberbaum/build-cvs-out/gdb/gdb vo
GNU gdb 6.6.50.20070516-cvs
Copyright (C) 2007 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) break foo if x == 0
Breakpoint 1 at 0x804832a: file vo.c, line 4.
(gdb) run
Starting program: /home/jimb/play/vo

Breakpoint 1, foo (x=0) at vo.c:4
4 if (x > 0)
(gdb) info frame
Stack level 0, frame at 0xbf9c98d8:
eip = 0x804832a in foo (vo.c:4); saved eip 0x804833e
called by frame at 0xbf9c98e4
source language c.
Arglist at 0xbf9c98d0, args: x=0
Locals at 0xbf9c98d0, Previous frame's sp is 0xbf9c98d8
Saved registers:
ebp at 0xbf9c98d0, eip at 0xbf9c98d4

Since the breakpoint was conditional on x being zero, the stack now
looks like this:

main -> foo (2) -> foo (1) -> foo (0)

Note the "frame at" address. The frame ID is that address, along with
the entry point of foo. We'll create a varobj looking at x, just for
fun:

(gdb) interpreter mi3 "-var-create a * x"
^done,name="a",numchild="0",value="0",type="int"
(gdb)
(gdb) finish
Run till exit from #0 foo (x=0) at vo.c:4
foo (x=1) at vo.c:6
6 }
(gdb) interpreter mi3 "-var-update *"
^done,changelist=[{name="a",in_scope="false"}]
(gdb)

So, we're in a different call to foo, but it's got a different frame
ID, and MI considers our varobj to be out of scope. Which is fine: we
created it with '*', so it's attached to the now-popped 'foo (0)'
frame.

(gdb) finish
Run till exit from #0 foo (x=1) at vo.c:6
foo (x=2) at vo.c:6
6 }
(gdb) finish
#0 fooRun till exit from (x=2) at vo.c:6
main () at vo.c:18
18 bar (2);
(gdb) info frame
Stack level 0, frame at 0xbf9c9900:
eip = 0x8048373 in main (vo.c:18); saved eip 0x98cf2c
source language c.
Arglist at 0xbf9c98f8, args:
Locals at 0xbf9c98f8, Previous frame's sp at 0xbf9c98f4
Saved registers:
ebp at 0xbf9c98f8, eip at 0xbf9c98fc
(gdb) interpreter mi3 "-var-update *"
^done,changelist=[{name="a",in_scope="false"}]
(gdb)

This is all as we'd expect: the frame address for 'main' is greater
(the stack grows towards lower addresses), and the varobj is out of
scope.

We'll run until the next hit of the breakpoint --- which is
conditional on x == 0, remember, so the stack will look like this:

main -> bar (2) -> foo (1) -> foo (0)

(gdb) continue
Continuing.

Breakpoint 1, foo (x=0) at vo.c:4
4 if (x > 0)
(gdb) info frame
Stack level 0, frame at 0xbf9c98d8:
eip = 0x804832a in foo (vo.c:4); saved eip 0x804833e
called by frame at 0xbf9c98e4
source language c.
Arglist at 0xbf9c98d0, args: x=0
Locals at 0xbf9c98d0, Previous frame's sp is 0xbf9c98d8
Saved registers:
ebp at 0xbf9c98d0, eip at 0xbf9c98d4

Here is the spurious frame ID collision. This call to foo is entirely
distict from the one in which we created the varobj, but because 1)
main didn't change its stack size, 2) bar (2) happens to have the same
frame size as foo (2), and 3) foo happens to use consistent frame
sizes each time it's called, the frame address is the same for the
second foo (0) as it was for the first. And indeed, MI considers the
varobj to be back in scope:

(gdb) interpreter mi3 "-var-update *"
^done,changelist=[{name="a",in_scope="true",type_changed="false"}]
(gdb)
(gdb)

Now, if I add a local variable 'int a[1];' to bar, and compile again
without optimization, that changes bar's frame size, and the exact
same series of commands plays out differently: the varobj won't come
back into scope.

Any GUI which relies on '*'-frame varobjs coming back into scope has
behavior dependent on internal details of the compiler's behavior, and
is buggy.