[Lazarus] Playing with debuggers
Martin Frb
lazarus at mfriebe.de
Tue Sep 14 14:27:13 CEST 2021
I started about "sub watches" before reading your "every watch has a
reference" ....
On 14/09/2021 11:00, Joost van der Sluis via lazarus wrote:
> Op 14-09-2021 om 02:18 schreef Martin Frb via lazarus:
>> But ideally this should just replace/extend the existing watches. Of
>> course that also would need the debugger intf to be updated. (That
>> does need an overhaul, and yes it could mean all existing debuggers
>> will need a bit of work to follow / ideas welcome)
>
> What I did now, is add new dialogs, and created an debug-interface
> separate from the existing one. But with the idea that it could
> replace/be merged with the existing one.
>
> What we could also do is make the debug-windows (watches, locals,
> evaluation pluggable, so that a debugger can choose which one to use.)
Actually all it would need is for the watch to have a new method, and to
have a flag if it can be expanded. (a debugger that can not do that,
will not set the flag).
However, it is a bit more....
1)
All watches should be part of the "watches" list. (or alternative means
must be found). This is so the debugger can update them, if the user
changes memory (set a new value to a variable).
This is also helpful for the history window, and for "idle eval" (if the
watches window is closed but the debugger is idle, then eval starts anyway).
The problem of not being in the "watches" exists for "debug inspector".
It does not always update correctly. Adding to the "watches" would
however mean that each watch needs some sort of owner, so the
watch-window does not display watches added by others. So that ends up
more rework.... (and maybe there is a better way)
Of course instead of adding new watch to the "watches", those watches
can be hold as a sublist by each watch.
Note that watches for structures already have a list, with a description
of each member field. (if the watch request had the correct flag set).
But those list should probably be reworked...
2)
Currently formatting (hex vs dec) is done in the debugger. That is also
nonsense. As much as can be should be moved to the frontend.
3)
Btw, if arrays can be expanded, precautions are needed. I don't think we
want to expand 100000 child values.
----
If we go for the flag and sub-watches, then "debug history" needs to be
aware of it.
>
>> Ideally the entire debugger frontend could move into a package of its
>> own too (all the current windows).
>
> Exactly, that is why I created a new debug-intf package instead of
> adapting the existing one.
Well we may need to create an interface for the debugger frontend, and
then add the frontend package. Though DebugManager already has parts of
that.
On the other hand its not strictly mandatory yet. As I see it your new
watches can simply replace the old one. (as soon as watches provide that
flag, so old watches can be displayed too)
>> Currently the debugger intf allows to fetch watches as plain text, or
>> as structure. But this has to be passed in as flag at the request time.
>>
>> Any watch object should instead have methods to fetch "sub watches"
>> so structures can unfold.
>
> What I have now is that each object has a 'reference', and there is a
> separate call to get the sub-watches that belong to the reference.
> That way the data (in the object) and the logic (function to retrieve
> sub-watches) are separated.
Ok, I have to look at that.
Another important factor on the design of every debugger-object (eg
watches) is "notify on free" or TRefCountedObject.
Any debugger backend holds on to the "watch object" while it evaluates
the data needed. However the frontend may remove (and free/dereference)
that watch during that time. The backend must know that. (and
potentially even call the callback, if the object no longer exists.... /
the editor hint code requires this )
>
> I don't have a solution for requesting the result as text(string)
> only. But that's not a bad idea.
Currently we have (as param to eval)
TDBGEvaluateFlag =
(defNoTypeInfo, // No Typeinfo object will be returned
defSimpleTypeInfo, // Returns: Kind (skSimple, skClass, ..);
TypeName (but does make no attempt to avoid an alias)
defFullTypeInfo, // Get all typeinfo, resolve all anchestors
defClassAutoCast, // Find real class of instance, and use,
instead of declared class of variable
defAllowFunctionCall
);
TDBGEvaluateFlags = set of TDBGEvaluateFlag;
Currently
TWatchValue = class(TFreeNotifyingObject)
property TypeInfo: TDBGType;
and
TDBGType = class(TObject)
property Fields: TDBGFields;
which is a list of TDBGField
So if (full)typeinfo is request, you have a list of fields, and you can
then get the sub-watches.
But that is not a good way....
That means you then have lots of dup data => each subwatch, has some
data also in a field.
** The other downside is, that the gdb debugger needs more time to fill
the fields. So not every debugger should provide them by default.....
** The upside is, that having type info (and fields) the frontend can do
all the formatting (almost... / mem dump still needs to get data as byte
stream)
If the backend delivers a point (x,y) as 2 fields, the frontend can
decide to print "(x: 1; y:2)" or "(1,2)". The frontend can also print
the individual values in dec,hex,bin,oct....
So always having type info would be nice.
But for gdb only remote debug targets, in needs to be seen if it can be
done, without slow down.
It should be possible with one limitations.
Currently each Field has type (tkInteger, tkClass, tkRecord, ....) and
ClassName (indicating which parent class introduced that field).
Getting those fields is time consuming.
===================================
So as a first draft I would aim for something like this:
TDBGField needs to be a TWatch. => avoid data duplication / and provide
for recursive eval.
By default the (if no flags are set) the debugger should provide fields.
But only field name, and a value (int or string).
All other info can be retrieved later, on request.
In fact, even the field info can be retrieved on request (editor hint
may not need it, but if formatting code goes to the frontend, then
editor hint also needs to do the formatting).
In the same way, that a watch has a "validity" field (dsEvaluating,
dsValid, dsError, ...) it can have validity for typeinfo and fields.
On top of that validity for typeinfo/fields can be dsNotAvail. In which
case there just is a default text value for the watch.
>
>> I see you pass the variable to the debugger, that is ok. But that may
>> well get abstracted into the Watch, so the watch has its own ref to
>> the debugger, and makes the needed calls.
>
> I don't follow exactly. But if there's a need for an extra abstraction
> layer before sending the variable to the debugger directly, this could
> be added.
See above, if the subwatches are already in place of the current fields,
then they have a ds...validity. And if accessed they will further evaluate.
>
>> Not sure about passing callbacks directly. Thinking of the current
>> notification system (data monitors).
>> What does your new watch window do, if the user modifies a variable?
>> This triggers re-eval of all watches (so they need to unfold again).
>> Currently the debugger backend can trigger the re-eval, and through
>> the data-monitors tell everyone about the updates.
>>
>> Have to look at it in more detail....
>
> I have something similar as the data-monitors.
>
> Main difference is that the events are based on what the gui needs,
> instead of what the debugger does.
>
> So: there is an event to tell the 'gui' that it is inpossible to show
> any data. (for example: the application has stopped or there is no
> debugger at all) There is an event for the case that debug-data has
> become available or unavailable. (Application paused or continued)
>
> And I should add an event for the case that the debug-info should be
> re-evaluated. (context switch, or variable-change). And I can think of
> another event that can be send when the debug-data could have been
> changed, due to unforeseen effect, for example when a propperty with a
> getter has been evaluated.
All of those are available already
Most of those are DebuggerState changes. They are currently propagated
hardcoded to each of the existing listeners. So they may need an API to
subscribe to.
But they also need to be done in a prioritized order.
Certain data needs to be done in specific order (partly that is enforced
in the backend anyway)
Context changes are subscribe-able.
CallStackNotification.OnChange := @CallStackChanged;
CallStackNotification.OnCurrent := @CallStackCurrent;
BreakpointsNotification.OnAdd := @BreakPointChanged;
BreakpointsNotification.OnUpdate := @BreakPointChanged;
BreakpointsNotification.OnRemove := @BreakPointChanged;
Note that the debugger is not the only thing that can change context. If
the user selects a history entry, then all watches change.
>
> The advantage of 'events' that are more geared towards the gui, is
> that it is easier to add catchy things. For example: to avoid
> flickering, when the gui receives an event that debug-data is not
> available anymore, it starts a timer of 200 mseconds, and only after
> that timer the data is removed from the screen. This way, when
> stepping through the code, there is no flickering in the small periods
> of time that the application runs.
That can be done with the above Notification too.
>
> It also made it easy to highlight variables that changed.
Ah, but does it?
Say I have an object with a delegate (object / class based => that is
pointer to heap).
TBar = class
i: integer;
end;
TFoo = class
bar: TBar;
end;
foo.bar := otherBar.
If otherBar has the same value in "i", and if the address of "bar" in
tfoo is not displayed. => then the displayed result of the "foo" watch
does not change. The value however has changed.
>
> But atm they are only suitable for displaying variables. Stuff like
> the call-stack are not touched.
In generally there may be reasons to extend on the current
monitor/notification.
However, I would avoid passing a callback to a function for that.
Rather adding a notification list [1] to each watch.
So if a single watch change (updates any of its validities), we no
longer need to call the global watch changed.
Downside, if all watches change (to invalid) we don't want to call
hundreds of callbacks.
So there are some design choices to be made.
There needs to be a good design to split into notifications for
individual watches, and for groups of watches (usually the whole list)
There could in future be more than one monitor.
One idea is to allow to open more than one watches-window. So you can
display an history entry next to a live value. (or 2 values from
different stack frames).
But then (even if not sensible) a user could have both windows showing
the same data too (the list of watches is in TWatches, it is not bound
to the window).
More information about the lazarus
mailing list