Recent

Author Topic: No break where expected  (Read 3647 times)

440bx

  • Hero Member
  • *****
  • Posts: 4908
No break where expected
« on: November 28, 2024, 10:15:12 am »
Hello,

Please refer to the attached project and screenshot.

The project has a radio group and breakpoints have been set in TCustomRadioGroup.GetItemIndex and TCustomRadioGroup.SetItemIndex.

When one of the options in the radio group is selected the breakpoint in TCustomRadioGroup.GetItemIndex is triggered as expected but the breakpoint in TCustomRadioGroup.SetItemIndex isn't.  Why is that breakpoint not triggered and where is the ItemIndex value being set ?

Thank you for your help.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10703
  • Debugger - SynEdit - and more
    • wiki
Re: No break where expected
« Reply #1 on: November 28, 2024, 10:28:57 am »
Because it isn't called.

It is updated in
procedure TCustomRadioGroup.UpdateRadioButtonStates;


Set a watchpoint on FItemIndex and it will tell you.

rvk

  • Hero Member
  • *****
  • Posts: 6643
Re: No break where expected
« Reply #2 on: November 28, 2024, 10:30:01 am »
Why is that breakpoint not triggered and where is the ItemIndex value being set ?
It IS called when you set ItemIndex.
Why do you think it is not?

Do note... SetItemIndex is only used as Setter of the property ItemIndex and is only called when ItemIndex is set.
It is NOT called when the component directly set FItemIndex (which it does internally).

For example when clicking an item, SetItemIndex is not called.
When using the keyboard to select a next item, SetItemIndex IS called  ::)


440bx

  • Hero Member
  • *****
  • Posts: 4908
Re: No break where expected
« Reply #3 on: November 28, 2024, 11:38:35 am »
Thank you Martin and rvk.  That explains why the breakpoint isn't triggered.

Now my question is: How do I determine that the value is being set in procedure TCustomRadioGroup.UpdateRadioButtonStates ? Is setting a watchpoint the only way to find out ? or could this be determined simply (and ideally) by inspecting source code ?
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

rvk

  • Hero Member
  • *****
  • Posts: 6643
Re: No break where expected
« Reply #4 on: November 28, 2024, 12:31:33 pm »
Now my question is: How do I determine that the value is being set in procedure TCustomRadioGroup.UpdateRadioButtonStates ? Is setting a watchpoint the only way to find out ? or could this be determined simply (and ideally) by inspecting source code ?
Can't you use OnSelectionChanged for that?

It's called in all the appropriate places in the source (SetItemIndex, CheckItemIndexChanged, Clicked and Changed).

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10703
  • Debugger - SynEdit - and more
    • wiki
Re: No break where expected
« Reply #5 on: November 28, 2024, 12:32:55 pm »
You can use "Find identifier references" (or search in files) to find all source locations, and then filter search results for ":=" (hoping that it isn't on a new line).

Or just look through the unit that implements the class. (find next word occurrence)

440bx

  • Hero Member
  • *****
  • Posts: 4908
Re: No break where expected
« Reply #6 on: November 28, 2024, 02:31:28 pm »
Thank you Martin and rvk.

Is there a way I can find out the _first_ function/procedure that is called when an option is selected ?  e.g, in pure API procedural programming I can simply go to the message handler and see the code that will be executed when an option is clicked on, how is that done with this OOP code ?
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10703
  • Debugger - SynEdit - and more
    • wiki
Re: No break where expected
« Reply #7 on: November 28, 2024, 04:36:27 pm »
Define "first function"....

As rvk already pointed out there are different ways to select an option, and they change the FItemIndex in different places. So there are many first functions.

You can breakpoint each location, and then go through all triggers, and look at the stack of each.


Below is a stack for the click on a radio (taking out windows kernel)

What is the first function?

RunLoop pulls a message from the Win-API. So the first thing it calls is TApplication.HandleMessage

Closer to what you may expect the first thing called would be
  TCustomCheckBox.DoChange
which handles the message LM_CHANGED. So that message is the first thing that goes into code specific to the control.

But maybe you are more interrested in the  Click or Changed method?


Code: Text  [Select][+][-]
  1. #0 TCustomRadioGroup.UpdateRadioButtonStates at radiogroup.inc:627
  2. #1 TCustomRadioGroup.CheckItemIndexChanged at radiogroup.inc:455
  3. #2 TCustomRadioGroup.Changed at radiogroup.inc:550
  4. #3 TButtonControl.DoOnChange at buttoncontrol.inc:49
  5. #4 TButtonControl.Click at buttoncontrol.inc:54
  6. #5 TCustomCheckBox.DoClickOnChange at customcheckbox.inc:281
  7. #6 TRadioButton.DoClickOnChange at radiobutton.inc:74
  8. #7 TCustomCheckBox.DoChange at customcheckbox.inc:107
  9. #8 TObject.Dispatch at objpas.inc:684
  10. #9 TControl.WndProc at control.inc:2304
  11. #10 TWinControl.WndProc at wincontrol.inc:5474
  12. #11 DeliverMessage at lclmessageglue.pas:114
  13. #12 TWINDOWPROCHELPER.DoWindowProc at win32callback.inc:2621
  14. #13 WindowProc at win32callback.inc:2786
  15. #14 ButtonWndProc at win32wsstdctrls.pp:1973
  16. ...
  17. #18 TWINDOWPROCHELPER.DoCmdCheckBoxParam at win32callback.inc:1333
  18. #19 TWINDOWPROCHELPER.DoWindowProc at win32callback.inc:2176
  19. #20 WindowProc at win32callback.inc:2786
  20. #21 GroupBoxWindowProc at win32wsstdctrls.pp:680
  21. ...
  22. #29 CallDefaultWindowProc at win32callback.inc:125
  23. #30 TWINDOWPROCHELPER.DoWindowProc at win32callback.inc:2529
  24. #31 WindowProc at win32callback.inc:2786
  25. #32 ButtonWndProc at win32wsstdctrls.pp:1973
  26. ...
  27. #35 TWin32WidgetSet.AppProcessMessages at win32object.inc:420
  28. #36 TApplication.HandleMessage at application.inc:1306
  29. #37 TApplication.RunLoop at application.inc:1449
  30.  

Of course key presses may do different stuff. You can use the cursor keys and select with afaik space.
Or if you have captions with & then you can press the letter that is underlined in the caption.

In the end the OOP really should not make it much difference, except that some calls go through base classes. But for each action something comes from the OS, and it calls a function. Only then the OOP means that tracking it becomes a bit harder, because once codetool took you into the base class, it wont use the original class for further lookup of called methods.

440bx

  • Hero Member
  • *****
  • Posts: 4908
Re: No break where expected
« Reply #8 on: November 28, 2024, 05:55:17 pm »
Thank you Martin.

I realize now that the situation is totally different than when programming non-OOP and pure API.

Looking at the call stack definitely helps.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10703
  • Debugger - SynEdit - and more
    • wiki
Re: No break where expected
« Reply #9 on: November 28, 2024, 06:25:49 pm »
Thank you Martin.

I realize now that the situation is totally different than when programming non-OOP and pure API.

Looking at the call stack definitely helps.

None OOP you can still have lots of different entry points for API reporting keys (for each different key) or mouse.
And none OOP you can still go through a dispatcher, that decides what to do for each event, and searches some list for the handle that the API would have given for the control.
This is probably more the difference between using a framework (that must do a lot of checks because of all the things it supports), or pure API only implementing the exact stuff that the current app needs.

As far as OOP goes in this context, IMHO the only different is tracking virtual methods (other methods that may be on several classes, are just like same-name-functions (plain / non-oop) across different units. Because you can't just take the declared class of the caller, but you need the instance class. So you need an earlier caller that decided the instance.

440bx

  • Hero Member
  • *****
  • Posts: 4908
Re: No break where expected
« Reply #10 on: November 29, 2024, 05:25:53 am »
Set a watchpoint on FItemIndex and it will tell you.
I followed that suggestion but the breakpoint shows as invalid.

What did I do wrong ?

Screenshot attached.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10703
  • Debugger - SynEdit - and more
    • wiki
Re: No break where expected
« Reply #11 on: November 29, 2024, 11:25:34 am »
How did you set it?

FItemIndex must be in scope at the time of setting (and enabling) the watchpoint.
- There could be many variables of that name (e.g. locals) and many classes or instances.
- In this case the debugger can neither know which instance you want to look at, nor when it gets created.

A watchpoint simply means a byte (or word, dword, qword (on 64bit cpu)) in memory. And the CPU allows up to 4 such watchpoints, not more.
=> That is the debugger sets a special flag in the CPU and the CPU will tell if that memory get read or written. That is how watch points can be fast..


So, since it is a field, you need to stop once in any method of that object. Then you can set the watchpoint.
(IIRC, but I need to check it, if you disable it, you may not currently be able to enable it again... Or maybe you can. Don't recall the internals. But that would be a todo...)

If the app stopped and you start it again, then you need to delete the watchpoint, and create it again => as the old address is no longer where the data is.
That can't be changed. (Well if it is a global var, it should work).

Ideally, but not sure how well that currently works, you should be able to use a normal breakpoint to automatically initialize the watchpoint. (via Enable groups).
But never tested.

440bx

  • Hero Member
  • *****
  • Posts: 4908
Re: No break where expected
« Reply #12 on: November 29, 2024, 01:12:28 pm »
If the app stopped and you start it again, then you need to delete the watchpoint, and create it again => as the old address is no longer where the data is.
That can't be changed. (Well if it is a global var, it should work).
Now I know what happened.

Please refer to the attachment.

The two watchpoints were set on line 71, after single stepping the inherited Create, the first one (Invalid) in a previous run. the second one in the current run.  The address of FItemIndex remained the same across runs.  IOW, the "old address" did not change and, under Windows, as long as the source code remains the same _and_ objects/classes are created in the same sequence then the addresses of variables including heap and locals will remain the same.

Based on fpdebug's behavior, I guess it associates watchpoints with a process id.  That would explain why the the first is invalid and the second one valid in spite of being identical and the address of FItemIndex being the same in both.

It would be very nice if fpdebug associated the watchpoints with the process module and its creation date instead of a pid. That would allow the watchpoints to remain valid across program executions (just as regular breakpoints) provided the code and its execution sequence did not change.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10703
  • Debugger - SynEdit - and more
    • wiki
Re: No break where expected
« Reply #13 on: November 29, 2024, 01:41:40 pm »
Even if in your case the address of FInternalIndex was kept the same, that is not a given. Not even, if the executable is still identical (i.e. not modified).

Chances are that it may be the same, if there was no user input, no calls to random, no decencies on OS resource allocs that may have changed. (e.g. if the watchpoint was inside a block of memory that was returned by a call to WIN-API, and may have changed as Handles and GDI objects may be at different mem location (not sure if that can happen, but wouldn't be sure).
Also in case of threads, timings between threads may lead to different order of mem allocs.

As soon as at any one point the memory layout changes, then the instance may be at a different location.


In your app, the behaviour may be simple and predictable.

And maybe an option for such a case can be added. There are various todo for watchpoints...

However, if a watchpoint for a field is active at start of the app (and has the correct address), then it will
- trigger when the constructor runs, because it zeros the memory
- if any other code uses (and then frees) the memory before the instance is created. (because if it is the same memory, it does not matter if it belongs to that instance)



The more generic way would be to automatically recreate it.

But I don't think it currently would work, because it likely is not correctly reset when the app is started again.

If the watchpoint would be correctly reset, and disabled at launch of the app, then
- you could have a breakpoint at the location at which the var/field is first in scope (the point were you want to start watching)
- It would have the "break" action disabled, so your app would keep running (or stepping)
- it would enable the group in which the watchpoint is / and the watchpoint would initialize
- the breakpoint would disable itself, so it would not act again.

As I said, I don't think it currently will work that way.
(It might with gdb, (maybe needing "reset debugger", because then there is no old data....)


440bx

  • Hero Member
  • *****
  • Posts: 4908
Re: No break where expected
« Reply #14 on: November 29, 2024, 04:33:00 pm »
Everything you mentioned is correct.

In the case where everything stays the same between runs then the addresses between runs will be the same _and_ that is a case the programmer can cause by duplicating the sequence of events between runs (which is commonly done when debugging.)

In your app, the behaviour may be simple and predictable.

And maybe an option for such a case can be added.
That would be very useful in some cases.   Granted that for such an option to work as expected the user must know what he is doing.

(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

 

TinyPortal © 2005-2018