Recent

Author Topic: Debugger not work in advanced records  (Read 782 times)

LemonParty

  • Sr. Member
  • ****
  • Posts: 360
Debugger not work in advanced records
« on: October 15, 2025, 05:07:02 pm »
Hello.

I have an advanced record. Inside of it I have a method that return dynamic array. When I set up a breakpoint inside of method I can't see the value of dynamic array. It says Cannot access memory at address 0xb.
I use gdb.
Lazarus v. 4.99. FPC v. 3.3.1. Windows 11

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11799
  • Debugger - SynEdit - and more
    • wiki
Re: Debugger not work in advanced records
« Reply #1 on: October 15, 2025, 05:33:46 pm »
A small sample piece of code might be helpful.

Also:
- FPC version
- DWARF2 or 3 (or Stabs?)
- OS / Target
- gdb version?

EDIT "use gdb" => means: the gdb backend in the IDE - or gdb standalone outside the IDE ?



I do recall an issue in the compiler with "object" (old style object / similar to records but with inheritance), which was with one (but not the other) dwarf version...
« Last Edit: October 15, 2025, 05:45:06 pm by Martin_fr »

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11799
  • Debugger - SynEdit - and more
    • wiki
Re: Debugger not work in advanced records
« Reply #2 on: October 15, 2025, 05:41:43 pm »
On Windows, 64 bit, tested combinations (others not tested):
  fpc 3.2.2 (dwarf 2) 
  fpc 3.2.3 (dwarf 2 and 3)

all with gdb 12

It works

Code: Pascal  [Select][+][-]
  1. program Project1;{$Mode objfpc}{$ModeSwitch advancedrecords}
  2. type
  3.   TIntegerArray = array of integer;
  4.   t = record
  5.     function x: TIntegerArray;
  6.   end;
  7.  
  8. function t.x: TIntegerArray;
  9. begin
  10.   SetLength(Result, 99);
  11.   Result[2]:=8;  // break
  12.   Result[2]:=8;
  13. end;
  14.  
  15. procedure foo;
  16. var a: t;  c: TIntegerArray;
  17. begin
  18.  c :=a.x;
  19.  write(c[2]);
  20. end;
  21.  
  22. begin
  23. foo;
  24. end.

LemonParty

  • Sr. Member
  • ****
  • Posts: 360
Re: Debugger not work in advanced records
« Reply #3 on: October 15, 2025, 06:02:13 pm »
On small project with small amount of variables I can't reproduce this behaviour. I think this is happening because compiler reuse the result variable as loop counter, because in one place of my method I can see the value of array, in loop I can't.
« Last Edit: October 15, 2025, 06:06:42 pm by LemonParty »
Lazarus v. 4.99. FPC v. 3.3.1. Windows 11

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11799
  • Debugger - SynEdit - and more
    • wiki
Re: Debugger not work in advanced records
« Reply #4 on: October 15, 2025, 06:28:28 pm »
On small project with small amount of variables I can't reproduce this behaviour. I think this is happening because compiler reuse the result variable as loop counter, because in one place of my method I can see the value of array in loop I can't.

Well, that leaves a lot of possible causes.

There are cases were the compiler issues incorrect debug info.
There are cases were gdb does not understand correct debug info from fpc. (because gcc does use different encodings, but both are correct / or gcc does not have a type of that kind...)
It can be trashed by the linker too.

0xb is an odd address (likely gdb reading a pointer = nil, then adds an offset of 11 / 0xb).
But a dynamic array should be aligned (unless you have a packed record).
- the dyn-array var itself just holds a pointer to the heap / and in a record that would be aligned
- inside the array there is the length (that the debugger may have to read before getting data), and that should also be on an even address.
- Since its "result", it does not need "self" (as it would for a field), but self would also be aligned.....

Mind you, "result" is a local var. They are not available
- on the initial "begin" (the "begin" line of the procedure itself => the stackframe has not been set up yet)
- on the final "end" of the procedure (stack frame gone already)

Also you must compile with -O- or -O1 / and you MUST NOT use OptRegVar -Or (use registers for locals) => fpc does not issue debug info for variables in registers (unless, maybe if you use the llvm backend).

Do you see "self"? And fields on self?



- I assume you can't test with FpDebug?
- Have you tried Dwarf 2 vs Dwarf 3?
  You may also try  -godwarfcpp  (that may not work well with some types when using the IDE driven gdb, but its worth a try)

If you change Dwarf version, on some targets like Linux the dwarf info between units has cross references. Which may mean that if you have mixed dwarf version (diff packages, or RTL) then you may get unexpected results.
This may be relevant if the type for "result" (e.g. TIntegerDynArray) is declared in a different unit, because then you may need to change dwarf version for that too.



I don't know if that may affect you.

There is a bug in some fpc version (don't recall, will have to search the bug report) where the address range for some procedures is wrong. I.e. some addresses are attribute partly to the wrong procedure. But, I have (with gdb) only ever seen that messing up breakpoints => I.e. breakpoints not acting at all, or the app crashing instead of the debugger pausing.
I have never seen this messing with watches. So I would not expect that...
This happens most often if some function before/after is never called (and not linked into the app / may still have blue dots, but setting a breakpoint in those "not linked" code will give a non-active breakpoint).



what does your dyn array contain?

Simple data (integer, boolean, enum...)?
Strings?
other records?

gdb may or may not dislike arrays with some types (e.g. pointer to record, iirc). It's a long time I looked into that. But that would happen in a simple example as well, if you use the same data types.

But then you would see the array, just the members would each be an error instead of data.



« Last Edit: October 15, 2025, 06:30:06 pm by Martin_fr »

LemonParty

  • Sr. Member
  • ****
  • Posts: 360
Re: Debugger not work in advanced records
« Reply #5 on: October 15, 2025, 07:03:51 pm »
Dyn array contain a simple data (integers).

I also noticed that fields from record are also not visible when standing on breakpoint. I compile with O4 optimization.

Result of function is correct so everything is OK.
Lazarus v. 4.99. FPC v. 3.3.1. Windows 11

cdbc

  • Hero Member
  • *****
  • Posts: 2464
    • http://www.cdbc.dk
Re: Debugger not work in advanced records
« Reply #6 on: October 15, 2025, 07:27:54 pm »
Hi
Quote
I compile with O4 optimization.
Sorry mate, you're barking up the wrong tree... How the H*ll would you think the debugger should work, when you're compiling with highest optimization level(which even carries a warning)?!? For all we know, what you're looking for is optimized away or somewhere else... %) :P
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6 -> FPC 3.2.2 -> Lazarus 4.0 up until Jan 2025 from then on it's both above &: KDE6/QT6 -> FPC 3.3.1 -> Lazarus 4.99

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11799
  • Debugger - SynEdit - and more
    • wiki
Re: Debugger not work in advanced records
« Reply #7 on: October 15, 2025, 07:37:41 pm »
I compile with O4 optimization.

O4 means reg-var. That is local vars (includes self and result) are stored (at least for most of the time) in CPU registers.

FPC does not generate debug info for that (except maybe (not tested) with the llvm backend). That means with O4 (also O3 and Or) you can not inspect locals. (even if sometimes you get to see a value, it may be the wrong value)

Well in very simple cases reg-var will be debug able (depends maybe on fpc version). IIRC I have seen FPC writing some debug info like "local var FOO is in register rbx" => Problem, as soon as you have more than a handful of locals (and / or other conditions / nested function calls) then rbx holds different locals at different times. and FOO may at some time be in rbx, at other times on the stackframe or in another register.
But FPC still just says => it is in RBX (as in: all the time / which is wrong).

You may try to use
   {$optimization noRegVar}
Of course it will undo some of your gained speed.

And, other optimizations may still affect the inspected data. I haven't tested with O4, but its possible that the peephole will change ordering, which means updates to a variable could be hidden, and become visible only a few lines further down. (not sure / I know that happened with O1 in a much older FPC version / but O4 has a more aggressive peephole optimizer).

And of course, if O4 completely removes a variable (e.g. constant propagation / dfa / dead store) then that won't be visible either. But a dyn array is likely no be kept.


Unfortunately, any debugger is limited by what the compiler provides. So gdb, lldb, fpdebug will all be unable to show this.

FPC 3.3.1 can use llvm as backend. Afaik llvm generates correct debug info for register variables. But I haven't done much testing on it.

Khrys

  • Sr. Member
  • ****
  • Posts: 342
Re: Debugger not work in advanced records
« Reply #8 on: October 16, 2025, 08:04:18 am »
This is the perfect time to learn a bit of assembly-level debugging  :D

Code: Pascal  [Select][+][-]
  1. type
  2.   TDoubleArray = array of Double;
  3.  
  4.   TNumbers = record
  5.     function List(): TDoubleArray;
  6.   end;
  7.  
  8. function TNumbers.List(): TDoubleArray;
  9. begin
  10.   Result := [0.69315, 1.41421, 2.71828, 3.14159];
  11.   asm int3 end; // <-- Hardcoded breakpoint
  12. end;

Compile the program and start it with a debugger (sorry Martin, I'm more familiar with GDB  :-[)

fpc -gw2 -O4 test.pas -otest.exe
gdb -q --args test.exe


In GDB:

r       # run - starts the program from the beginning and continues until a breakpoint is hit
la sp   # layout split - switches to the visual (TUI) source/assembly/command view

┌---------------------------------------------------------------------------------------------------┐
│   0x100001783 <LIST+131>  mov    rax,QWORD PTR [rbp-0x18]                                         │
│   0x100001787 <LIST+135>  mov    rdx,QWORD PTR [rip+0x138a2]        # 0x100015030 <_$TEST$_Ld4>   │
│   0x10000178e <LIST+142>  mov    QWORD PTR [rax+0x18],rdx                                         │
│   0x100001792 <LIST+146>  mov    rdx,QWORD PTR [rbp-0x18]                                         │
│   0x100001796 <LIST+150>  mov    rcx,QWORD PTR [rbp-0x10]                                         │
│   0x10000179a <LIST+154>  mov    r8,rbx                                                           │
│   0x10000179d <LIST+157>  call   0x100006700 <fpc_dynarray_assign>                                │
│   0x1000017a2 <LIST+162>  int3                                                                    │
│  >0x1000017a3 <LIST+163>  nop                                                                     │
│   0x1000017a4 <LIST+164>  mov    rcx,rbp                                                          │
│   0x1000017a7 <LIST+167>  call   0x1000016d0 <fin$00000004>                                       │
│   0x1000017ac <LIST+172>  mov    rbx,QWORD PTR [rbp-0x28]                                         │
│   0x1000017b0 <LIST+176>  lea    rsp,[rbp+0x0]                                                    │
│   0x1000017b4 <LIST+180>  pop    rbp                                                              │
│   0x1000017b5 <LIST+181>  ret                                                                     |
└---------------------------------------------------------------------------------------------------┘


Knowing the signature of  fpc_dynarray_assign  and the calling convention (Microsoft x64), we can manually deduce the location of the array data:

Code: Pascal  [Select][+][-]
  1. procedure fpc_dynarray_assign(var dest: Pointer {rcx}; src: Pointer {rdx}; ti: pointer {r8});



rdx  (aka  src)  is loaded from a local variable  (rbp-0x18),  which we can examine:

x/gx $rbp-0x18  # examine / giant word (8 bytes), hexadecimal
0x13ffdd8:      0x000000000010c1b0

This is the address of the first element. Let's store it in a convenience variable:

set $arr := $__ # store examination result in  arr

Now we can use the  examine  command again to look at the values:

x/gf $arr+0    # examine giant float (64-bit double) at  arr + 0
0x10c1b0:      0.69315000000000004

x/gf $arr+8
0x10c1b8:      1.41421

x/gf $arr+16
0x10c1c0:      2.71828

x/gf $arr+24
0x10c1c8:      3.1415899999999999



Dynamic arrays contain the following structure right before the first element in memory:

Code: Pascal  [Select][+][-]
  1. tdynarray = packed record
  2.   refcount : ptrint;
  3.   high : tdynarrayindex; // sizeint (ptrint)
  4. end;

We can just specify a negative offset to access these values:

x/gd $arr-8       # access  high  (highest occupied index)
0x10c1a8:      3  # 4 elements

x/gd $arr-16      # access  refcount  (reference count)
0x10c1a0:      2  # local variable (not yet finalized) + result

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11799
  • Debugger - SynEdit - and more
    • wiki
Re: Debugger not work in advanced records
« Reply #9 on: October 16, 2025, 09:19:13 am »
This is the perfect time to learn a bit of assembly-level debugging  :D
Quote
This is the address of the first element. Let's store it in a convenience variable:

set $arr := $__ # store examination result in  arr

While knowing your way in asm is certainly useful, for cases like this it is imho beyound of being last resort....

So you got the address, and in your case in memory (in the frame). But if you have some more complex code, then the value of the array will change location. Even if there is a location in mem, while you step through the code, that mem location is likely not being updated most of the time. Instead the value is hold in a register. Well that would be ok, we can examine a register.
But the value will be held in different registers over time, and then temporarily in memory, and back in registers.

Each time you single step, you need to find where the value has moved too... It is possible, but a heck of a lot of work...



And then it depends what is in the array. With "simple numbers" you can tell gdb to display the range of memory as a list of integers (or words, or qwords, ...). I don't know the gdb syntax, but I am sure it can be done.

But if the array contains records, or pointer to records, and you want to display the first n elements in one go? I would guess its possible, though I don't know how to tell gdb that.

And (at least in older gdb version) certain mix of pointers and structures did get gdb to give errors. At least when debug info was coming from fpc (which uses different dwarf encoding than gcc for some data types / so gdb is not as well tested for those).



That said, if you use fpdebug (if you are on Win/Linux -- i386/x86-64) and you need to cast numbers/addresses into other data (be that due to looking at asm, or for any other reason), then you can express that in Pascal-ish ways.

%rbp for a register
$12345678 for a number
^pointer($12345678)^  or  ^qword($12345678)^  for reading from memory (depending on what types are declared)


^longint(%rbp-$10)[0..9] for 9 longints at the address
^TMyRecord(%rbp-$10)[0..9] for 9 TMyRecords at the address
...






But in the end, if you compiled the code yourself... Just don't add the extra work, just compile without the optimization...

@LemonParty

I don't know if there is a reason why you try to compile with O4?

** If other parts of the code are to slow otherwise, and you don't need to debug the other parts, then try to use
  {$optimization off}  not sure, similar / please google fpc docs
to disable it for the code you need to debug.

Or better, compile with -O- (no opt) and use the directive to enable it for just the parts of your codes that really, really, really need it.


** If you are trying to find an error that only happens when you compile with optimization....
There are several possibilities.

1) It could be an uninitialized value (including dangling pointer). "valgrind memcheck" is really good at finding them. But you need to be able to run your code on Linux. You can give the code compiled with O1 to valgrind. Even if the error does not show. valgrind may still be able to detect it. E.g. if with O1 the dangling pointer points to  a place that does not cause an error, it is still dangling, valgrind will know that the memory was freed.

2) Threads, if you use them
Optimization changes timing. So if there are race conditions, the may only show with opt on. But that also means they may not show in the debugger at all.

3) Depending on your compiler version, you may be looking at a bug in the compiler's optimizer. I.e. the compiler does incorrectly translate your code.
If that is the case, then it wont be easy to find. Most likely this means to analyse/compare generate asm. You can toggle individual optimizations, to find with which optimization the error happens or goes away.
But you need then to be sure its not your code. And that means to actually find the wrong asm, and prove its wrong by the compiler....

4) ...

Khrys

  • Sr. Member
  • ****
  • Posts: 342
Re: Debugger not work in advanced records
« Reply #10 on: October 16, 2025, 10:00:44 am »
Each time you single step, you need to find where the value has moved too... It is possible, but a heck of a lot of work...

Yes, assembly-level debugging is unergonomic and a lot more work, but it's the only option when source-level debugging fails due to heavy optimizations, so what gives?

LemonParty

  • Sr. Member
  • ****
  • Posts: 360
Re: Debugger not work in advanced records
« Reply #11 on: October 16, 2025, 03:20:04 pm »
Thank you for help. We figure out why debugger shows incorrect values. I will know for future to disable optimization.
Lazarus v. 4.99. FPC v. 3.3.1. Windows 11

 

TinyPortal © 2005-2018