FpDebug

From Lazarus wiki
Jump to navigationJump to search

English (en) русский (ru)

About

FpDebug is a debugger written in Pascal for Pascal.

In detail:

  • FpDebug is written in Pascal
  • FpDebug uses the DWARF debug info format
    • The current implementation is a subset of DWARF with support for DWARF version 2 and 3. However the "subset" is merely the state of implementation. The aim is to extend this to implement the as much of the standard as possible.
    • The current implementation covers the subset that is used by FPC
    • FpDebug has special implementations to deal with FPC specific DWARF usage (bugs, missing info, implementation details)

Different meanings of "FpDebug"

FpDebug can refer to

The package "FpDebug"

This is the engine of the debugger. It is not IDE specific. The package is used by several IDE backends:

  • LLDB Debugger (with fpdebug): This debugger uses LLDB as a backend. In order to display Pascal-style results, it uses the "fpdebug" engine to show locals/watches.
  • GNU Debugger (with fpdebug): Using GDB for stepping/breakpoints/... and FpDebug for watches.
  • FpDebug internal Dwarf debugger: Pure FpDebug. Stepping,Running,Breakpoint,Watches,Locals,Stack....

FpDebug as reference to the IDE backend

The actual name of the IDE backend is LazDebuggerFp. This is the name of the Package that needs to be installed to use the debugger. However as seen above, in none technical context (such as captions in the IDE) this is referred to as "FpDebug ..."

Other FpDebug projects

The name is used for another project with the same goal.

Installation in the IDE

Install the package LazDebuggerFp

General Debugging Issues

These issues can not be fixed by the debugger alone. They need changes in FPC too. Some may also be affected by lack of support in DWARF.

These issues apply to all debugger backends.

Properties

Properties are one of the most wanted features for (any of) the debugger(s). Here is why they can not yet be inspected, and why it will still be a good while before this happens.

Before going into details: For "Properties without getter function" (i.e., "with direct field access") FPC (as means to a workaround of the issue) currently adds an entry for a variable with the property name. This means such properties can be displayed. They can also be written by the debugger (if the debugger supports changing data), and if they are written this is done bypassing any Setter procedure, since the debugger has no clue there even is a setter.

There are several missing pieces for allowing the debugger to show (and maybe modify) properties.

  • The debugger needs support to call functions
  • FPC needs a way to actually add info about the "properties" to the debug info.

As for calling function. This may just be missing implementation in the debugger. This has not yet been investigated in any more depth. It may or may not require additional info in the DWARF generated by FPC

As for adding "properties" to the debug info: DWARF (at least version 2 and 3 / not checked 4 or 5) does not actually have any way to encode this info. Consequently FPC currently does not add it either.

In case that DWARF does not add this in time, DWARF allows for vendor specific data (afaik). Up until now, no debugger would have known about any "private fpc data", so there was no point. In future such data could be agreed between FPC and FpDebug. But that is still a good way off. FpDebug does not even do function calling yet.


Strings (vs PChar and Array of Char)

Originally DWARF had no concept to encode strings. This has been added in DWARF 4 (need verification), but not yet been investigated for usability with FPC. Also FPC currently only implements DWARF 2 and 3 (with first steps towards version 4).

This leaves only 2 ways to encode a string in DWARF.

  • As pointer to char
  • As array of char

Which one is used depends on the version of FPC, and if DWARF 2 or 3 is used.

If it is "pointer to char", then this makes in indistinguishable from actual PChar. This is the reason all debuggers in Lazarus sometimes for a watch like "SomeString[4]" show the result "PChar: 'a'; String: 'b'". Because PChar is zero-based and string one-based, the index "4" can mean the 4th or the 5th char.

Recent FPC use the "array of char" when they generate DWARF 3. In that they have (currently) implementation detail differences between how they encode strings and real array of char. (Such as the omission of an optional/redundant bit of information. In this case: DW_AT_byte_stride). Those details are meaningless from the point of any other debugger, but FpDebug can detect them as it knows about those implementation details. Of course this is extremely fragile, the next version of FPC can always change this.

In a similar way FPC handles ShortStrings internally as record, and encodes them as this. Again distinguishing this is down to implementation details.

Modifying managed types

This applies to any debugger that can set new values to a watch (modify variable in the debugged exe).

Managed types can not be correctly modified.

To modify managed types the debugger would need to know about:

  • the ref count
  • copy on write behaviour
  • how to allocate/free memory / or better how to call the inc/dec_ref methods for each types, so they will do the work correctly

None of that info is available.

If you assign a new value to a string or dynamic array, then the ref count of the old and new value must be adjusted. If needed memory must be allocated or freed. If that is not done correctly it will at least cause a memory leak. But it can also cause crashes or random other buggy behaviour when the debugged exe continues.

In an array it is possible to change a single element. "SomeArray[5] := 7;". If the array is referenced from several variables, this will affect all of them. The same happens if you execute this statement in your app.

String (AnsiString) have copy-on-write. If you do "SomeString[4] := 'a'" then the variable from which you accessed the string gets a new copy, and other variables (that share the memory) should not be affected. The debugger does not have that info. Nor does it have access to the refcount and memory alloc/de-alloc (or internal string procs).

Calling function with managed types as param or result

Assuming function calling was implemented in general....

For the same reason as in "Modifying managed types" the debugger could not create strings or arrays in order to call functions that expect them as param.

Result values could be accepted, but would cause a memory leak.

Scope / Variable search order

There are various issues where FPC does not put enough information into the DWARF info so that a debugger could compute the correct scope for all variables. In same cases this may be due to DWARF not offering this, or not offering it at the time where it was implemented or last updated in FPC. Some of it may be fixable, if FPC would change the way it encodes the info.

Nested Procedures

var SomeVar: Integer; // global var

procedure Outer(NumText: string);
var 
  SomeVar: Integer; // local var, declared above "Nested"

  procedure Nested;
  var 
    I: Integer;
  begin
    WriteLn(OuterVarInScope);  
  end;

//var 
//  SomeVar: Integer; // local var, declared below "Nested"
begin
  Nested;
end;

The local var "SomeVar" hides the global var of the same name, if you are in the procedure "Outer". The local var is also visible (and hides the global) if you are in "Nested".

But if you comment the local var "declared above Nested" and uncomment the local var "declared below Nested", then the local var is no longer in the scope of "Nested". "Nested" would now see the global var. ("Outer" still sees the local).

The DWARF info currently only tells the debugger that "SomeVar" is declared as local var in "Outer". And that "Nested" is inside "Outer". But the DWARF info does not include if "SomeVar" is declared above or below "Nested". So the debugger can not tell the difference. The debugger will in both cases show the local "SomeVar" when paused in "Nested"


Global vars from other units

The DWARF info contains a section for each unit. All Symbols of a unit are listed in the correct section. However there is no info, which unit is in the "uses" clause of which other unit. And evidently even less, in which order they are used.

If there are several global variables of the same name, one in each of several units, then FPC uses the order of the uses clause (right to left) to find the variable that will be seen from code in the current unit.

If there is code in a unit FooBar that access a variable named NotInFooBar (that is not declared in unit FooBar), then this variable must be from one of the other units. If there are several variables named NotInFooBar in different units, then the debugger has no information which one FPC did use. The debugger is currently showing the first it finds. (Which is kind of random)

FPC Issues

This affects all debuggers

  • resourcestring

Currently not implemented https://bugs.freepascal.org/view.php?id=35571

  • const Foo = unicodestring('abc')

No debug info implemented https://bugs.freepascal.org/view.php?id=35569

  • type Foo = object (Dwarf 3)

Incorrectly encoded as class-instance https://bugs.freepascal.org/view.php?id=36017

  • Dynamic array when cross compiling

https://bugs.freepascal.org/view.php?id=35567

  • Wrong stride for bitpacked array

https://bugs.freepascal.org/view.php?id=36144
FpDebug currently has a workaround


Status

FpDebug supports Linux, Windows and MacOs.

Any functionality regarding execution control (stepping/pausing...) is only tested for Linux and Windows. Support for this on Mac is incomplete/fragile.

Run / Stop

Attach / Detach

Stepping

Breakpoints

Watchpoints (aka Data Breakpoints)

Exceptions

Thread and Stack

Watches / Locals / Inspect

Modify variables (not implemented)

Console Output

Not implemented yet

  • Modify watches
  • Changing Location of execution (while paused) / Resume execution from different line
  • Properties
  • Function calls
  • ...