Recent

Author Topic: Debug members of my class kept as interface variable.  (Read 1704 times)

tol

  • New member
  • *
  • Posts: 8
Debug members of my class kept as interface variable.
« on: May 16, 2024, 09:38:10 pm »
How can I debug members of my class, which is kept as an interface?
With "normal" classes everything seems to work:
For example, I can see the members of a previously constructed object of type TStringList in the local variables window.

But not if I create my own class.
The type of the own class type was derived from

Code: Pascal  [Select][+][-]
  1. type TTest = class(TInterfacedObject, ISomeInterface)

( And instantiated  via a factory class, just in case if that is relevant.  )

The instance is then stored in a variable of type ISomeInterface.

When debugging, I only see the address of the interface in the "local variables" window. None of the members.
The same applies to the list of monitored expressions etc.

I can't see the members via any debugger window. I can't enter an expression that uses a member of the interface either. ("Cannot get member "X" from non-structured type .. ").

How can I debug instances of such classes?
« Last Edit: May 16, 2024, 10:02:12 pm by tol »

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10001
  • Debugger - SynEdit - and more
    • wiki
Re: Debug members of my class kept as interface variable.
« Reply #1 on: May 17, 2024, 09:23:08 am »
Unfortunately there is no way.

Currently FPC does not write any debug info for the interface.

But even if, the interface does only have methods. it does not have a read-able translation into the object that implements it. (Though that would be up to fpc to implement creating such a translation for the debugger/ at least when possible).




Usually an interface has a different implementation for each object that it implements.

If you use asm to step into the call to the interface, then you find code like
Code: Pascal  [Select][+][-]
  1. 0000000100001660 4883E920                 sub rcx,$20
  2. 0000000100001664 E997FFFFFF               jmp -$00000069    # $0000000100001600 Foo project1.lpr:21
  3. 0000000100001669 0000                     add [rax],al
  4. 000000010000166B 0000                     add [rax],al
  5. 000000010000166D 0000                     add [rax],al
  6. 000000010000166F 005548                   add [rbp+$48],dl
  7.  

The exact code can vary. Or may for other fpc versions be completely different.

The "$20" in the first line is important. But keep in mind that even for the same interface it will vary, depending on the underlaying object that implements that interface.

If you know this value you can watch
  TObject(pointer(MYInterfacei)-$20)

Again, if you run the same code again, but another class is hidden behind the interface then the $20 changes. So you must asm step into the interface each time.



cdbc

  • Hero Member
  • *****
  • Posts: 1229
    • http://www.cdbc.dk
Re: Debug members of my class kept as interface variable.
« Reply #2 on: May 17, 2024, 11:27:12 am »
Hi
Remember, there's *always* an object behind an interface, implementing it!
I use interfaces extensively and what I do is this:
1) for properties, I step into the getter/setter, this lands me right smack in
    the implementing object, then I can just hold the mouse over the internal
    fields.
2) for functions/procedures it's pretty much the same, step into and then I
    usually watch local variables...
3) when debugging I 'Log' a lot to screen and file and I keep 'Logging' an
    option via paramstr() in released programs, then I can just start the app
    from the command-line and let it tell me what's wrong...
edit: Try debugging interfaces kept as plugins in libraries ...then you can talk about *Fiddly* :D
HTH
Regards Benny
« Last Edit: May 17, 2024, 11:30:31 am by cdbc »
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE5 -> FPC 3.2.2 -> Lazarus 2.2.6 up until Jan 2024 from then on it's: KDE5/QT5 -> FPC 3.3.1 -> Lazarus 3.0

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10001
  • Debugger - SynEdit - and more
    • wiki
Re: Debug members of my class kept as interface variable.
« Reply #3 on: May 17, 2024, 07:53:16 pm »
Ok, so now I did take the time...

With my version of FPC 3.2.3, fpc/rtl/project all compiled with -O1
Setting the watch to "use instance type"
Code: Text  [Select][+][-]
  1. TObject(Pointer(MyInterface)-^byte(((^^^byte(MyInterface)^+3)^)+3)^)

The expression gets the pointer to a function in the interface (the first + 3 is an offset into the internal data - using pointermath, on 64bit that is 24 bytes), and then adds 3. This is the offset to the "20" that is subtracted (in the asm sample of my last post) in the machine code. So if the interface is using a different value (but the same machine code/ compiled with same optimization settings, etc)  then this will find the underlaying object.

tol

  • New member
  • *
  • Posts: 8
Re: Debug members of my class kept as interface variable.
« Reply #4 on: May 17, 2024, 08:07:43 pm »
@Martin_fr

Thank you for the time spent on this.
That's what I would have expected, that it would run automatically in the background. (In other languages, debugging the object via one of its interfaces is no problem)

As a "hi-level" developer, I don't really want to (have to) know the internal implementation of the interfaces.
Ok, so now I did take the time...

With my version of FPC 3.2.3, fpc/rtl/project all compiled with -O1
Setting the watch to "use instance type"
Code: Text  [Select][+][-]
  1. TObject(Pointer(MyInterface)-^byte(((^^^byte(MyInterface)^+3)^)+3)^)

The expression gets the pointer to a function in the interface (the first + 3 is an offset into the internal data - using pointermath, on 64bit that is 24 bytes), and then adds 3. This is the offset to the "20" that is subtracted (in the asm sample of my last post) in the machine code. So if the interface is using a different value (but the same machine code/ compiled with same optimization settings, etc)  then this will find the underlaying object.

PascalDragon

  • Hero Member
  • *****
  • Posts: 5539
  • Compiler Developer
Re: Debug members of my class kept as interface variable.
« Reply #5 on: May 22, 2024, 10:08:14 pm »
Remember, there's *always* an object behind an interface, implementing it!

That is in fact not true. It's possible to construct an interface “instance” without there being an object instance backing it. The Generics.Collections unit uses this for example.

cdbc

  • Hero Member
  • *****
  • Posts: 1229
    • http://www.cdbc.dk
Re: Debug members of my class kept as interface variable.
« Reply #6 on: May 22, 2024, 10:47:50 pm »
Hi
...Ok Sven I'll bite, have you by any chance, got a short example of this constructing, without an object, business, up your sleeve? You've just peek'ed my curiousity...  :D
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE5 -> FPC 3.2.2 -> Lazarus 2.2.6 up until Jan 2024 from then on it's: KDE5/QT5 -> FPC 3.3.1 -> Lazarus 3.0

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10001
  • Debugger - SynEdit - and more
    • wiki
Re: Debug members of my class kept as interface variable.
« Reply #7 on: May 22, 2024, 11:52:41 pm »
Look at the unit he mentioned, and find stuff like "    // IComparer VMT" => constructing the interface without the compiler (if you know the memory layout that the compiler expects....)

An interface is (simplified) a jump table (a VMT), and maybe some data. So putting together your own VMT, then it can have any code pointer, as long as the code they point to, matches the method signatures in the interface.  But this "any code" then does not need to go to an object/instance... 

I haven't checked, but there may also be code to inject those "interfaces" into classes, so that "SomeClass as IThisInterface" will return such an interface. And then such an interface, despite being returned by that class, has no link to that class.

At least I guess that is what he meant.
« Last Edit: May 22, 2024, 11:55:47 pm by Martin_fr »

PascalDragon

  • Hero Member
  • *****
  • Posts: 5539
  • Compiler Developer
Re: Debug members of my class kept as interface variable.
« Reply #8 on: May 26, 2024, 10:41:19 pm »
It's as Martin_fr said.

I've extracted the important parts here:

Code: Pascal  [Select][+][-]
  1. program tmanintf;
  2.  
  3. {$mode objfpc}
  4.  
  5. type
  6.   TInterface = class
  7.   public
  8.     function QueryInterface(constref {%H-}IID: TGUID;{%H-} out Obj): HResult; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; virtual;
  9.     function _AddRef: LongInt; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; virtual; abstract;
  10.     function _Release: LongInt; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};  virtual; abstract;
  11.   end;
  12.  
  13.   TRawInterface = class(TInterface)
  14.   public
  15.     function _AddRef: LongInt; override;
  16.     function _Release: LongInt; override;
  17.   end;
  18.  
  19.   ICompare = interface
  20.     function Compare(const ALeft, ARight: Int8): Integer;
  21.   end;
  22.  
  23.   PComparerVMT = ^TComparerVMT;
  24.   TComparerVMT = packed record
  25.     QueryInterface: CodePointer;
  26.     _AddRef: CodePointer;
  27.     _Release: CodePointer;
  28.     Compare: CodePointer;
  29.   end;
  30.  
  31.   TCompare = class
  32.     class function Int8(const ALeft, ARight: Int8): Integer;
  33.   end;
  34.  
  35. const
  36.   { please not that while this uses instance methods of TRawInterface this does not necessarily
  37.     mean that a TRawInterface instance is passed in; e.g. the code in Generics.Defaults uses
  38.     a different type as Self that contains the reference count for those types where the
  39.     reference count is necessary }
  40.   Comparer_Int8_VMT  : TComparerVMT = (QueryInterface: @TRawInterface.QueryInterface; _AddRef: @TRawInterface._AddRef; _Release: @TRawInterface._Release; Compare: @TCompare.Int8);
  41.  
  42.   Comparer_Int8_Instance  : Pointer = @Comparer_Int8_VMT;
  43.  
  44. function TInterface.QueryInterface(constref IID: TGUID; out Obj): HResult; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
  45. begin
  46.   Result := E_NOINTERFACE;
  47. end;
  48.  
  49. function TRawInterface._AddRef: LongInt; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
  50. begin
  51.   Result := -1;
  52. end;
  53.  
  54. function TRawInterface._Release: LongInt; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF};
  55. begin
  56.   Result := -1;
  57. end;
  58.  
  59. class function TCompare.Int8(const ALeft, ARight: Int8): Integer;
  60. begin
  61.   Result := ALeft - ARight;
  62. end;
  63.  
  64. var
  65.   intf: ICompare;
  66. begin
  67.   intf := ICompare(@Comparer_Int8_Instance);
  68.   Writeln(intf.Compare(2, 4));
  69.   Writeln(intf.Compare(4, 2));
  70.   Writeln(intf.Compare(4, 4));
  71. end.

cdbc

  • Hero Member
  • *****
  • Posts: 1229
    • http://www.cdbc.dk
Re: Debug members of my class kept as interface variable.
« Reply #9 on: May 27, 2024, 08:27:18 am »
Hi
@PascalDragon & @Martin_fr: Thank you, this is all possible kinds of Cool  8-)
It's like getting a new toy  :D ...now onto playing with it....
Hmmm, the /alternate/ 'Self' would then be the 'Spoof'-thingy in 'Generics.Collections'?!? -> Gotta get my head 'round that  %)
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE5 -> FPC 3.2.2 -> Lazarus 2.2.6 up until Jan 2024 from then on it's: KDE5/QT5 -> FPC 3.3.1 -> Lazarus 3.0

 

TinyPortal © 2005-2018