FpDebug-Watches-Intrinsic-Functions

From Lazarus wiki
Jump to navigationJump to search

About

LazDebuggerFp has some build in helper functions and operators. They can be included in any watch using the pascal function calling syntax. The result is calculated internally be the debugger.

To avoid clashed with identifier names in the debugged target app, such intrinsic functions require a ":" prefix by default. E.g. ":length(s)".

Light bulb  Note: Mathematical functions operating on floating point values are calculated with the precision of float in the IDE. They do not take into account the precision available in the debugged app, or the data type of any float variable involved.

Intrinsic Operators

Array slice MyArray[n..m] with mapping

The array slice operator is available in freepascal, when calling a function that accepts an open array.

FpDebug extends this operator. The returned array slice can map any operation on its members. That is, if you do MyArray[1..5]+4 then the +4 will not act on the array slice, but instead on each entry in the slice.

This is true for any operation (including further index operations).

  • MyArray[5..9]+1 Adds 1 to each array element
  • TObjec(MyPointerArray[5..9]) type casts each array element
  • MyRecordArray[5..9].Field Show the field .field each array element
  • MyMultiDimArray[5..9][2..4] Selects a slice within each element of the outer slice
  • MySingleDimArray[5..9][0] Will not work. Since the array has only one dimension. It is not possible to select the element [0] from the slice. The operation is attempted on the elements of the slice.

! {End the mapping}

If an array slice should be used as an array (e.g. passed to an intrinsic that can take an array as argument) then the mapping can be stopped.

If you would give a slice with mapping active as argument to :Length()

   :Length(foo[1..5])

Then :Length is evaluated for each of the 5 items in the slice. If they happen to be arrays then that might be wanted. In this case the result would be an array with the 5 length of the 5 arrays.

But if the length of the slice itself is wanted, then the :length intrinsic must not be applied per item. For that the mapping can be stopped using an "!"

   :Length(foo[1..5]!)

String slice MyString[n..m]

Select a substring.

Unlike the array-slice, this returns a single string and not an array of chars. Therefore any operation on the result will affect the single string, and not each char.

Similar to the intrinsic SubStr. For strings this is always limited to the bounds. SubStr can use "ForcePtr" to ignore bounds.

... ? ... : ... {inline IF}

Watches can contain the ? : operator.

  Condition ? ValueIfTrue : ValueIfFalse

The condition must return a boolean result. Additionally, it may return a result that has an ordinal value, in which case 0/nil will be false and all others will be true. On it may return a string and treat the empty string as false and others as true.

Intrinsic Functions

Settings

The prefix can be chosen or switched off. In the property grid of the debugger backend setup the option "IntrinsicPrefix" can be found.

  • ipColon
    Require a ":" as prefix
  • ipExclamation
    Require a "!" as prefix
  • ipNoPrefix
    Don't require a prefix.
    If function calling is enabled for the watch, the intrinsic takes precedence, and may hide a call-able function in the target app. (Use the & to bypass the intrinsic and trigger function eval. E.g.: "&length()")

Try(expr1, expr2, ...) TryN(expr1, expr2, ...)

Will find the first expression in the list that does not return an error.

Note, the expressions must be syntactically correct, i.e., they must be parse-able. "Try" will take each expression staring with the left-most and evaluate it. If it evaluates with no error, then its result will be returned as the result of "Try()". If it encounters an error (Can't read memory, field not found, ...) then the next expression will be taken. If all expressions fail, the error of the last expression is returned.

Note that "data at address 0x0000" in not an error until you access it. Following a nil pointer to a record does not give an error, until you try to read any field of that record.

"TryN" (also available as "TryNN") aka TryNotNull will also test that if a result is returned, and the result is in memory (i.e. has an address) then it is not at address 0x0000. It will only check for 0x0000, other unreadable memory is not detected.

CC(instance) {ChildClass}

Type-casts an object (class instance) to the instantiated class type.


E.g. "Sender: TObject" is by default evaluated as TObject. ":CC(Sender)" will find the actual class of the instance and the result will be e.g. a TButton or TForm. ":CC(Sender)" is the same as either writing "TForm(Sender)" or "TButton(Sender)", but automatically adapts to the correct type.


Simply watching ":CC(Sender)" returns the same result as checking the "Use instance class" checkbox in the watches properties.

The difference between ":CC()" and "Use instance class" is

  • "Use instance class" will not allow you to write "Sender.FOwner"
  • The intrinsic can be used: ":CC(Sender).FOwner"
The "Use instance class" checkbox can then be used on the result of ":CC(Sender).FOwner"
  • ":CC()" can be nested: ":CC(:CC(Sender).FOwner).FHint"


":CC()" takes exactly one argument. If the argument is not an object, or can not be typecasted, then it is passed through. E.g. ":CC(1)" just return "1".

I2O(interface) {show object behind an interface}

I2O (aka Interface2Object) takes an interface, and shows the object behind the current instance of the interface.

This only works for Windows/Linux, and only on Intel(compatible) systems. The intrinsic looks at the generate asm to find the object, it therefore only works with FPC versions that generate expected code (currently 3.0 to 3.3.1 should work).

Ord(val)

Returns the ordinal value of "val".

Length(String-or-array)

Returns the length of an array or string.

See FpDebug#Strings_(vs_PChar_and_Array_of_Char) for info about string types in the debugger.


RefCnt(string-or-array)

Get FPC's internal ref-count for LongString(AnsiString) or dynamic arrays.

Strings may work only with DWARF-3.

Pos(SubString, MainString [, CaseInsensitive=False])

Works like the "Pos" function.

Accepts an optional 3rd argument of boolean type. If True, both strings will be run through "LowerCase" to make it case insensitive. (May not work for utf-8)

SubStr(Str, Start, Len [, ForcePtr=False])

Similar to "copy()"

Gets a substring.

  • Str: A String (Ansi or short) or pointer (pchar or other)
Note: the handling of String vs Pchar may depend on the Dwarf version and the debuggers ability to distinguish between PChar and String
Using Dwarf-2 a string may be seen as pchar instead
  • Start: A 1-based index, that must be 1 or greater (if the argument is a string and ForcePtr absent or false)
  • Len: Amount of chars to copy.
  • ForcePtr: Forces String to be handled the same as pointers


If acting on a string:

  • If "Start" is greater than the length of Str, the the result is empty
  • If "Start + Len" exceeds the length or Str, then the result will be cut at the end of Str.


If acting on Pointers or ForcePtr

  • "Start" is effectively 0-based. Start will be added to the address that the pointer points to.
"Start" can be negative.
  • "Len" will not be limited.
  • This means any memory around the pointers destination can be selected and returned as string.

This function will be limited my the MaxStringLen and MaxMemReadSize LazDebuggerFp#Debugger_specific_options

Lower(Str) / Upper(Str)

Take a string as argument and lower/upper-case it.

This function will be limited my the MaxStringLen and MaxMemReadSize LazDebuggerFp#Debugger_specific_options

Pi()

Returns the value of PI

Trunc(float)

Truncates a float to integer.

Round(float, [digits])

rounds a float.

If digits are not specified or zero, then the result is integer. Otherwise the result is floating point.

Note that digits should be negative to keep decimals after the dot.

Sqrt(float)

Returns the square root.

Ln(float) / Log(float, float)

return the ln or log.

"Log" also exists as "LogN".

Sin,Cos,Tan(float)

Return the sin, cos or tan.


Flatten/F_(LinkedList_or_Tree, FieldName, FieldName, [opts]) {Display as array}

":Flatten" (short ":F_") can transform a linked-list, tree, or other connected objects into a flat list (array).

In its simplest form, it takes an object as a starting point and the name of a field on that object. It will then look at the field, and if there is another object there, it will add it to the result list and repeat the process with the other object, looking for the named field again.

For a linked list that may look like this:

   :flatten( FirstEntry, NextLink )

In case of a tree, it can take more than one field to traverse:

   :flatten( RootNode, Left, Right )

This will work depth-first, that is follow the nodes found in "Left", until it reaches a node with nothing in "Left". Then it will check that nodes "Right" (in which it will continue both again, the "Left" and the "Right"). After that it will go to the previous node for which it had not looked at "Right" and follow that.

In order to work with records that may have pointers to other records, it will automatically dereference, if any of the fields return a pointer to a structure (record, old object, but also class-instance). This can be disabled. See the details on options for more.

Alternatively the Field may be an expression.

   :flatten( FirstRecord, PtrToNextRec^ )

"PtrToNextRec^" will expect a pointer in "PtrToNextRec", and dereference it.

A more complex example:

   type
  PRec = ^TRec;
  TRec
     Next: array [1..9] of PRec;
     NextIdx: integer; // Entry in next that is used as link.
   end;

Can be flattened with

   :flatten( MyRec, Next[:_.NextIdx]^ )
  • The expression starts with "Next" which is the field in the record.
  • It then has ":_.NextIdx". ":_" is a special place holder that represents the current object. This allows to use data from the object anywhere inside the expression.
Light bulb  Note: If an expression in ":flatten" starts with an identifier, then this will always be treated as a field of the object

That means if you had

   var
     Next: array [1..9] of PRec; // Not on the object
   type
  PRec = ^TRec;
  TRec
     NextIdx: integer; // Entry in next that is used as link.
   end;

Then it requires:

   :flatten( MyRec, (Next[:_.NextIdx]^) )
  • If "Next" would be at the very start of the expression, it would be handle as a field of the record. And it would not be found.
  • Starting the expression with any non-identifier (in this case the opening "(") will stop the parser from making it a field. "Next" is then a normal variable (global, local, or field on "self" of the current function)

The same goes, for typecasting.

   :flatten( Foo, (TSpecialFoo(:_.Next) )
  • Without the starting "(", the word "TSpecialFoo" would be handled as field on the object.
Displaying a mapped value

Normally the result contains the data received by each key. As the keys usually need to fetch the object for the next iteration, the result contains the full objects. Sometimes only certain parts of that data is needed for display.

In this case the result of flatten can be treated as slice

   :flatten( RootNode, Left, Right ) [0..999].DataToShow

However, if different data is needed from Left and Right, or if :flatten uses the "[obj]" option, or if more than one field is wanted, then that does not work.

In this case a mapping can be specified per key, by using the ":" separator.

   :flatten( RootNode, Left:ValueToShow, Right:OtherValue )

Or to show more than one key the :obj intrinsic can be used, combined with the above)

   :flatten( RootNode, Left: :obj(v1::_.ValueToShow,v2::_.Foo), Right:1 )

Note, that the :_ must be used.

In the last example "Right" should show the same as "Left", and therefore the mapping is specified as an integer constant giving the index of the argument that has the desired mapping. The number must be a plain constant number. (no "+1", no "(1)").


Options

If the last parameter to ":flatten" is a set, then it is treated as options.

By default all options are ON. But if a set is given, then it starts with all options OFF. The following therefore disables all options:

  :f_(obj, field, [])

If you want to only switch some options off, but leave all others on, then the set can be prefixed with a "-". The following will switch off the "dup" option, but leave all others on

  :f_(obj, field, -[dup])

Options can also be set depending on other factors, the following will the "dup" option, but only if "a=1". (All other options will be off)

  :f_(obj, field, [dup=(a=1)])

To have all options ON, and conditionally remove one option use the "-" prefix. The option is removed, if the condition is TRUE.

  :f_(obj, field, -[dup=(a=1)])

Available Options

  • "nil": If a field is nil (or the expression returned nil), then the result will have an entry containing this "nil". Without the option the value is skipped.
  • "field" or "fld": Inserts an error entry into the result list, if the field was not found on the object. E.g., if a different type of object was returned, and it does not have the fld. If there are multiple fields/expression then the iteration will continue independent of the error being returned in the list or not.
  • "err" or "error": If the field is an expression, and it returned an error, then that error is inserted into the result. If there are multiple fields/expression then the iteration will continue independent of the error being returned in the list or not.
  • "loop" or "recurse": If a value is found that has been seen before, and will lead to a recursion, then an error is inserted to mark the end of the list, or the current branch. If there are multiple fields/expression then the iteration will continue independent of the error being returned in the list or not.
  • "seen" or "dup": This will replace previously seen (non-recursive) values (branches) with an error. Otherwise the values will be inserted into the result again.
  • "ptr" or "deref": If any field/expression returns a pointer to an object/record, then this will be dereferenced automatically.
  • "max=1000": (default is 1000). Stop when reaching as many items in the result list. If the "max" option is given in a "-[]" it will act the same as in a "[]". If the max option is explicitly given, then accessing ":f_(...)[1001]" is not possible (1001 being a value greater than the given max).
  • "array=1" or "array": (the latter defaults to 1). If any key returns an array or nested array, then this will be flattened up to the give nesting depth. Array slices (by the ".." operator) must be terminated with the "!" (end-mapping), then they can be flattened using the "array" option.
  • obj, obj1, obj2, obj3, obj4, o1, o2, o3, o4: Add a record for each row, showing info about where in the structure the entry was found.
    "obj" is the same as "obj1", except when disabling it, all o* will be disabled.
    Obj1/Obj2 show "d=depth, k=key, v=entry" with key either being the text or index.
    Obj3/Obj4 show "k=0.0.1, v=entry", the key being a list of all keys followed (can get very long).

"seen" vs "recurse":

  • With a single field/expression to follow any "seen" value is always a "recurse" value too. And the result list will always end. Either of the options will add the error to the result list, only disabling both options will keep it out.
  • With multiple fields/expressions, a seen value can be a recursion. However, if the "right" field returns the same as the "left" (or any previous sub-item of the "left", then the items are not recursive. But they have been included in the result before. In this case "recurse" will not check for them. But "seen" will detect them.

Dereferencing: If the global debugger option to dereference pointers is turned on, then this will affect :flatten. Any field lookup will then work on a pointer to object/record too. Just like it would in "MyPtrToRecord.Foo". (which translates to "MyPtrToRecord^.Foo")


Light bulb  Note: Todo: Calling Methods on the object is not yet implemented. It might work using ":_.MyMethod()"

obj(key, value, key2, value2, ...)

Create a record from multiple values.

Keys must be identifiers.