FpDebug: Introduce "Intrinsics functions" for watches. Add "length()" for string (dwarf-3) and array

This commit is contained in:
Martin 2022-09-07 04:25:53 +02:00
parent f58a7bde79
commit 116b22ed72
6 changed files with 258 additions and 40 deletions

View File

@ -189,6 +189,7 @@ type
function IsValidTypeCast: Boolean; override;
function GetInternMemberByName(const AIndex: String): TFpValue;
procedure Reset; override;
function GetMemberCount: Integer; override;
private
FValue: String;
FValueDone: Boolean;
@ -240,10 +241,12 @@ type
TFpValueDwarfV3FreePascalString = class(TFpValueDwarf) // short & ansi...
private
FValue: String;
FValueDone: Boolean;
FLowBound, FHighBound: Int64;
FValueDone, FBoundsDone: Boolean;
FDynamicCodePage: TSystemCodePage;
function GetCodePage: TSystemCodePage;
function ObtainDynamicCodePage(Addr: TFpDbgMemLocation; out Codepage: TSystemCodePage): Boolean;
procedure CalcBounds;
protected
function IsValidTypeCast: Boolean; override;
procedure Reset; override;
@ -252,6 +255,7 @@ type
function GetAsWideString: WideString; override;
procedure SetAsCardinal(AValue: QWord); override;
function GetAsCardinal: QWord; override;
function GetMemberCount: Integer; override;
public
property DynamicCodePage: TSystemCodePage read GetCodePage;
end;
@ -1162,6 +1166,16 @@ begin
FValueDone := False;
end;
function TFpValueDwarfV2FreePascalShortString.GetMemberCount: Integer;
var
LenSym: TFpValueDwarf;
begin
LenSym := TFpValueDwarf(GetInternMemberByName('length'));
assert(LenSym is TFpValueDwarf, 'LenSym is TFpValueDwarf');
Result := LenSym.AsInteger;
LenSym.ReleaseReference;
end;
function TFpValueDwarfV2FreePascalShortString.GetFieldFlags: TFpValueFieldFlags;
begin
Result := inherited GetFieldFlags;
@ -1524,7 +1538,7 @@ end;
function TFpValueDwarfV3FreePascalString.GetAsString: AnsiString;
var
t, t2: TFpSymbol;
t: TFpSymbol;
LowBound, HighBound, i: Int64;
Addr, Addr2: TFpDbgMemLocation;
WResult: WideString;
@ -1545,11 +1559,6 @@ begin
if t.NestedSymbolCount < 1 then // subrange type
exit;
t2 := t.NestedSymbol[0]; // subrange type
if not( (t2 is TFpSymbolDwarfType) and TFpSymbolDwarfType(t2).GetValueBounds(self, LowBound, HighBound) )
then
exit;
GetDwarfDataAddress(Addr);
if (not IsValidLoc(Addr)) and
(HasTypeCastInfo) and
@ -1559,28 +1568,10 @@ begin
if not IsReadableLoc(Addr) then
exit;
assert((TypeInfo <> nil) and (TypeInfo.CompilationUnit <> nil) and (TypeInfo.CompilationUnit.DwarfSymbolClassMap is TFpDwarfFreePascalSymbolClassMapDwarf3), 'TFpValueDwarfV3FreePascalString.GetAsString: (Owner <> nil) and (Owner.CompilationUnit <> nil) and (TypeInfo.CompilationUnit.DwarfSymbolClassMap is TFpDwarfFreePascalSymbolClassMapDwarf3)');
if (TFpDwarfFreePascalSymbolClassMapDwarf3(TypeInfo.CompilationUnit.DwarfSymbolClassMap).FCompilerVersion > 0) and
(TFpDwarfFreePascalSymbolClassMapDwarf3(TypeInfo.CompilationUnit.DwarfSymbolClassMap).FCompilerVersion < $030100)
then begin
if t.Kind = skWideString then begin
if (t2 is TFpSymbolDwarfTypeSubRange) and (LowBound = 1) then begin
if (TFpSymbolDwarfTypeSubRange(t2).InformationEntry.GetAttribData(DW_AT_upper_bound, AttrData)) and
(TFpSymbolDwarfTypeSubRange(t2).InformationEntry.AttribForm[AttrData.Idx] = DW_FORM_block1) and
(IsReadableMem(Addr) and (LocToAddr(Addr) > AddressSize))
then begin
// fpc issue 0035359
// read data and check for DW_OP_shr ?
Addr2 := Addr;
Addr2.Address := Addr2.Address - AddressSize;
if Context.ReadSignedInt(Addr2, SizeVal(AddressSize), i) then begin
if (i shr 1) = HighBound then
HighBound := i;
end
end;
end;
end;
end;
CalcBounds;
LowBound := FLowBound;
HighBound := FHighBound;
if HighBound < LowBound then
exit; // empty string
@ -1664,6 +1655,12 @@ begin
Result := inherited GetAsCardinal;
end;
function TFpValueDwarfV3FreePascalString.GetMemberCount: Integer;
begin
CalcBounds;
Result := Max(0, FHighBound - FLowBound + 1);
end;
function TFpValueDwarfV3FreePascalString.ObtainDynamicCodePage(Addr: TFpDbgMemLocation; out
Codepage: TSystemCodePage): Boolean;
var
@ -1690,6 +1687,63 @@ begin
end;
end;
procedure TFpValueDwarfV3FreePascalString.CalcBounds;
var
t, t2: TFpSymbol;
i: Int64;
Addr, Addr2: TFpDbgMemLocation;
AttrData: TDwarfAttribData;
begin
if FBoundsDone then
exit;
FBoundsDone := True;
FLowBound := 0;
FHighBound := -1;
// get length
t := TypeInfo;
if t.NestedSymbolCount < 1 then // subrange type
exit;
t2 := t.NestedSymbol[0]; // subrange type
if not( (t2 is TFpSymbolDwarfType) and TFpSymbolDwarfType(t2).GetValueBounds(self, FLowBound, FHighBound) )
then
exit;
GetDwarfDataAddress(Addr);
if (not IsValidLoc(Addr)) and
(HasTypeCastInfo) and
(svfOrdinal in TypeCastSourceValue.FieldFlags)
then
Addr := TargetLoc(TypeCastSourceValue.AsCardinal);
if not IsReadableLoc(Addr) then
exit;
assert((TypeInfo <> nil) and (TypeInfo.CompilationUnit <> nil) and (TypeInfo.CompilationUnit.DwarfSymbolClassMap is TFpDwarfFreePascalSymbolClassMapDwarf3), 'TFpValueDwarfV3FreePascalString.GetAsString: (Owner <> nil) and (Owner.CompilationUnit <> nil) and (TypeInfo.CompilationUnit.DwarfSymbolClassMap is TFpDwarfFreePascalSymbolClassMapDwarf3)');
if (TFpDwarfFreePascalSymbolClassMapDwarf3(TypeInfo.CompilationUnit.DwarfSymbolClassMap).FCompilerVersion > 0) and
(TFpDwarfFreePascalSymbolClassMapDwarf3(TypeInfo.CompilationUnit.DwarfSymbolClassMap).FCompilerVersion < $030100)
then begin
if t.Kind = skWideString then begin
if (t2 is TFpSymbolDwarfTypeSubRange) and (FLowBound = 1) then begin
if (TFpSymbolDwarfTypeSubRange(t2).InformationEntry.GetAttribData(DW_AT_upper_bound, AttrData)) and
(TFpSymbolDwarfTypeSubRange(t2).InformationEntry.AttribForm[AttrData.Idx] = DW_FORM_block1) and
(IsReadableMem(Addr) and (LocToAddr(Addr) > AddressSize))
then begin
// fpc issue 0035359
// read data and check for DW_OP_shr ?
Addr2 := Addr;
Addr2.Address := Addr2.Address - AddressSize;
if Context.ReadSignedInt(Addr2, SizeVal(AddressSize), i) then begin
if (i shr 1) = FHighBound then
FHighBound := i;
end
end;
end;
end;
end;
end;
{ TFpSymbolDwarfFreePascalDataProc }
function TFpSymbolDwarfFreePascalDataProc.GetLine: Cardinal;

View File

@ -298,6 +298,7 @@ type
function GetFieldFlags: TFpValueFieldFlags; override;
function GetAsString: AnsiString; override;
function GetAsWideString: WideString; override;
function GetMemberCount: Integer; override;
public
constructor Create(const AValue: AnsiString);
end;
@ -796,6 +797,11 @@ begin
Result := GetAsString;
end;
function TFpValueConstString.GetMemberCount: Integer;
begin
Result := Length(FValue);
end;
constructor TFpValueConstString.Create(const AValue: AnsiString);
begin
inherited Create;

View File

@ -49,6 +49,9 @@ type
TSeparatorType = (ppstComma);
TFpIntrinsicPrefix = (ipExclamation, ipColon, ipNoPrefix);
TFpIntrinsicFunc = (ifErrorNotFound, ifLength);
TFpPascalParserCallFunctionProc = function (AnExpressionPart: TFpPascalExpressionPart;
AFunctionValue: TFpValue; ASelfValue: TFpValue; AParams: TFpPascalExpressionPartList;
out AResult: TFpValue; var AnError: TFpError): boolean of object;
@ -61,25 +64,27 @@ type
FContext: TFpDbgSymbolScope;
FFixPCharIndexAccess: Boolean;
FHasPCharIndexAccess: Boolean;
FIntrinsicPrefix: TFpIntrinsicPrefix;
FOnFunctionCall: TFpPascalParserCallFunctionProc;
FTextExpression: String;
FExpressionPart: TFpPascalExpressionPart;
FValid: Boolean;
function GetResultValue: TFpValue;
function GetValid: Boolean;
procedure Parse;
procedure SetError(AMsg: String); // deprecated;
procedure SetError(AnErrorCode: TFpErrorCode; AData: array of const);
procedure SetError(const AnErr: TFpError);
function PosFromPChar(APChar: PChar): Integer;
function LookupIntrinsic(AStart: PChar; ALen: Integer): TFpIntrinsicFunc;
protected
function GetDbgSymbolForIdentifier({%H-}AnIdent: String): TFpValue;
function GetRegisterValue({%H-}AnIdent: String): TFpValue;
property ExpressionPart: TFpPascalExpressionPart read FExpressionPart;
property Context: TFpDbgSymbolScope read FContext;
public
constructor Create(ATextExpression: String; AContext: TFpDbgSymbolScope);
constructor Create(ATextExpression: String; AContext: TFpDbgSymbolScope; ASkipParse: Boolean = False);
destructor Destroy; override;
procedure Parse;
function DebugDump(AWithResults: Boolean = False): String;
procedure ResetEvaluation;
property TextExpression: String read FTextExpression;
@ -89,6 +94,7 @@ type
property HasPCharIndexAccess: Boolean read FHasPCharIndexAccess;
// handle pchar as string (adjust index)
property FixPCharIndexAccess: Boolean read FFixPCharIndexAccess write FFixPCharIndexAccess;
property IntrinsicPrefix: TFpIntrinsicPrefix read FIntrinsicPrefix write FIntrinsicPrefix;
// ResultValue
// - May be a type, if expression is a type
// - Only valid, as long as the expression is not destroyed
@ -204,6 +210,22 @@ type
function DoGetResultValue: TFpValue; override;
end;
{ TFpPascalExpressionPartIntrinsic }
TFpPascalExpressionPartBracketArgumentList = class;
TFpPascalExpressionPartIntrinsic = class(TFpPascalExpressionPartContainer)
private
FIntrinsic: TFpIntrinsicFunc;
protected
function DoLength(AParams: TFpPascalExpressionPartBracketArgumentList): TFpValue;
function DoGetResultValue: TFpValue; override;
function DoGetResultValue(AParams: TFpPascalExpressionPartBracketArgumentList): TFpValue;
public
constructor Create(AExpression: TFpPascalExpression; AStartChar: PChar;
AnEndChar: PChar; AnIntrinsic: TFpIntrinsicFunc);
end;
TFpPascalExpressionPartConstant = class(TFpPascalExpressionPartContainer)
end;
@ -1471,6 +1493,12 @@ begin
end;
Itm0 := Items[0];
if Itm0 is TFpPascalExpressionPartIntrinsic then begin
Result := TFpPascalExpressionPartIntrinsic(Itm0).DoGetResultValue(Self);
exit;
end;
tmp := Itm0.ResultValue;
if (tmp = nil) or (not Expression.Valid) then
exit;
@ -1708,6 +1736,73 @@ begin
Result := FExpression.GetRegisterValue(GetText);
end;
{ TFpPascalExpressionPartIntrinsic }
function TFpPascalExpressionPartIntrinsic.DoLength(
AParams: TFpPascalExpressionPartBracketArgumentList): TFpValue;
var
Itm: TFpPascalExpressionPart;
Arg: TFpValue;
ResLen: Integer;
begin
Result := nil;
if (AParams.Count <> 2) then begin
SetError('wrong argument count');
exit;
end;
Itm := AParams.Items[1];
Arg := nil;
if Itm <> nil then
Arg := Itm.ResultValue;
if (Arg = nil) then begin
SetError('argument not supported');
exit;
end;
ResLen := 0;
case Arg.Kind of
skChar: ResLen := 1;
skString,
skAnsiString,
skWideString,
skArray: ResLen := Arg.MemberCount;
otherwise begin
SetError('argument not supported');
exit;
end;
end;
Result := TFpValueConstNumber.Create(ResLen, True)
end;
function TFpPascalExpressionPartIntrinsic.DoGetResultValue: TFpValue;
begin
Result := nil;
SetError('wrong argument count');
// this gets called, if an intrinsic has no () after it. I.e. no arguments and no empty brackets
end;
function TFpPascalExpressionPartIntrinsic.DoGetResultValue(
AParams: TFpPascalExpressionPartBracketArgumentList): TFpValue;
begin
Result := nil;
case FIntrinsic of
ifLength: Result := DoLength(AParams);
end;
// {$IFDEF WITH_REFCOUNT_DEBUG}Result.DbgRenameReference(nil, 'DoGetResultValue'){$ENDIF};
end;
constructor TFpPascalExpressionPartIntrinsic.Create(
AExpression: TFpPascalExpression; AStartChar: PChar; AnEndChar: PChar;
AnIntrinsic: TFpIntrinsicFunc);
begin
inherited Create(AExpression, AStartChar, AnEndChar);
FIntrinsic := AnIntrinsic;
end;
{ TFpPascalExpressionPartConstantNumber }
function TFpPascalExpressionPartConstantNumber.DoGetResultValue: TFpValue;
@ -1809,7 +1904,27 @@ var
else AddPart(TFpPascalExpressionPartOperatorPlusMinus);
end;
procedure AddIntrinsic(AnIntrinsic: TFpIntrinsicFunc);
begin
if AnIntrinsic = ifErrorNotFound then
SetError('Unknown build-in')
else
NewPart := TFpPascalExpressionPartIntrinsic.Create(Self, CurPtr, TokenEndPtr-1, AnIntrinsic);
end;
procedure AddIntrinsic;
var
intr: TFpIntrinsicFunc;
begin
while TokenEndPtr^ in ['a'..'z', 'A'..'Z', '_', '0'..'9', '$'] do
inc(TokenEndPtr);
intr := LookupIntrinsic(CurPtr, TokenEndPtr - CurPtr);
AddIntrinsic(intr);
end;
procedure AddIdentifier;
var
intr: TFpIntrinsicFunc;
begin
while TokenEndPtr^ in ['a'..'z', 'A'..'Z', '_', '0'..'9', '$'] do
inc(TokenEndPtr);
@ -1840,6 +1955,15 @@ var
NewPart := TFpPascalExpressionPartOperatorUnaryNot.Create(Self, CurPtr, TokenEndPtr-1);
end;
end;
if (FIntrinsicPrefix = ipNoPrefix) then begin
intr := LookupIntrinsic(CurPtr, TokenEndPtr - CurPtr);
if intr <> ifErrorNotFound then begin
AddIntrinsic(intr);
exit;
end;
end;
if NewPart = nil then
NewPart := TFpPascalExpressionPartIdentifier.Create(Self, CurPtr, TokenEndPtr-1);
end;
@ -2062,6 +2186,16 @@ begin
NewPart := nil;
TokenEndPtr := CurPtr + 1;
if (FIntrinsicPrefix = ipExclamation) and (CurPtr^ = '!') then begin
inc(CurPtr);
AddIntrinsic;
end
else
if (FIntrinsicPrefix = ipColon) and (CurPtr^ = ':') then begin
inc(CurPtr);
AddIntrinsic;
end
else
case CurPtr^ of
'@' : AddPart(TFpPascalExpressionPartOperatorAddressOf);
'^': AddRefOperator; // ^A may be #$01
@ -2161,6 +2295,15 @@ begin
Result := APChar - @FTextExpression[1] + 1;
end;
function TFpPascalExpression.LookupIntrinsic(AStart: PChar; ALen: Integer
): TFpIntrinsicFunc;
begin
Result := ifErrorNotFound;
case ALen of
6: if strlicomp(AStart, 'LENGTH', 6) = 0 then Result := ifLength;
end;
end;
function TFpPascalExpression.GetDbgSymbolForIdentifier(AnIdent: String): TFpValue;
begin
if FContext <> nil then
@ -2192,14 +2335,15 @@ begin
end;
constructor TFpPascalExpression.Create(ATextExpression: String;
AContext: TFpDbgSymbolScope);
AContext: TFpDbgSymbolScope; ASkipParse: Boolean);
begin
FContext := AContext;
FContext.AddReference;
FTextExpression := ATextExpression;
FError := NoError;
FValid := True;
Parse;
if not ASkipParse then
Parse;
end;
destructor TFpPascalExpression.Destroy;

View File

@ -3482,7 +3482,9 @@ begin
if Context <> nil then begin
PasExpr := nil;
try
PasExpr := TFpPascalExpression.Create(ABreakPoint.Expression, Context);
PasExpr := TFpPascalExpression.Create(ABreakPoint.Expression, Context, True);
PasExpr.IntrinsicPrefix := TFpDebugDebuggerProperties(GetProperties).IntrinsicPrefix;
PasExpr.Parse;
PasExpr.ResultValue; // trigger full validation
if PasExpr.Valid and (svfBoolean in PasExpr.ResultValue.FieldFlags) and
(not PasExpr.ResultValue.AsBool) // false => do not pause

View File

@ -30,8 +30,8 @@ unit FpDebugDebuggerUtils;
interface
uses
FpDbgUtil, FpdMemoryTools, {$ifdef FORCE_LAZLOGGER_DUMMY} LazLoggerDummy {$else} LazLoggerBase {$endif}, DbgIntfDebuggerBase, sysutils,
Classes, syncobjs, Forms;
FpDbgUtil, FpdMemoryTools, FpPascalParser, LazLoggerBase, DbgIntfDebuggerBase,
sysutils, Classes, syncobjs, Forms;
type
@ -109,6 +109,7 @@ type
FForceNewConsole: boolean;
{$endif windows}
FHandleDebugBreakInstruction: TFpInt3DebugBreakOptions;
FIntrinsicPrefix: TFpIntrinsicPrefix;
FMemLimits: TFpDebugDebuggerPropertiesMemLimits;
FNextOnlyStopOnStartLine: boolean;
procedure SetMemLimits(AValue: TFpDebugDebuggerPropertiesMemLimits);
@ -128,6 +129,7 @@ type
property MemLimits: TFpDebugDebuggerPropertiesMemLimits read FMemLimits write SetMemLimits;
property HandleDebugBreakInstruction: TFpInt3DebugBreakOptions read FHandleDebugBreakInstruction write FHandleDebugBreakInstruction default [dboIgnoreAll];
property IntrinsicPrefix: TFpIntrinsicPrefix read FIntrinsicPrefix write FIntrinsicPrefix default ipExclamation;
end;
@ -368,6 +370,7 @@ begin
{$endif windows}
FMemLimits := TFpDebugDebuggerPropertiesMemLimits.Create;
FHandleDebugBreakInstruction := [dboIgnoreAll];
FIntrinsicPrefix := ipExclamation;
end;
destructor TFpDebugDebuggerProperties.Destroy;
@ -387,6 +390,7 @@ begin
{$endif windows}
FMemLimits.Assign(TFpDebugDebuggerProperties(Source).MemLimits);
FHandleDebugBreakInstruction:=TFpDebugDebuggerProperties(Source).FHandleDebugBreakInstruction;
FIntrinsicPrefix:=TFpDebugDebuggerProperties(Source).FIntrinsicPrefix;
end;
end;

View File

@ -734,7 +734,9 @@ begin
if ExpressionScope = nil then
exit;
APasExpr := TFpPascalExpression.Create(FExpression, ExpressionScope);
APasExpr := TFpPascalExpression.Create(FExpression, ExpressionScope, True);
APasExpr.IntrinsicPrefix := TFpDebugDebuggerProperties(FDebugger.GetProperties).IntrinsicPrefix;
APasExpr.Parse;
try
APasExpr.ResultValue; // trigger full validation
if not APasExpr.Valid then
@ -1121,7 +1123,9 @@ begin
end;
PrettyPrinter := nil;
APasExpr := TFpPascalExpression.Create(AnExpression, FExpressionScope);
APasExpr := TFpPascalExpression.Create(AnExpression, FExpressionScope, True);
APasExpr.IntrinsicPrefix := TFpDebugDebuggerProperties(FDebugger.GetProperties).IntrinsicPrefix;
APasExpr.Parse;
try
if FAllowFunctions and (dfEvalFunctionCalls in FDebugger.EnabledFeatures) then
APasExpr.OnFunctionCall := @DoWatchFunctionCall;
@ -1154,7 +1158,9 @@ begin
(not IsError(ResValue.LastError)) and (defClassAutoCast in AnEvalFlags)
then begin
if ResValue.GetInstanceClassName(CastName) then begin
PasExpr2 := TFpPascalExpression.Create(CastName+'('+AnExpression+')', FExpressionScope);
PasExpr2 := TFpPascalExpression.Create(CastName+'('+AnExpression+')', FExpressionScope, True);
PasExpr2.IntrinsicPrefix := TFpDebugDebuggerProperties(FDebugger.GetProperties).IntrinsicPrefix;
PasExpr2.Parse;
PasExpr2.ResultValue;
if PasExpr2.Valid then begin
APasExpr.Free;
@ -1389,7 +1395,9 @@ begin
bpkData: begin
CurContext := FDebugger.DbgController.CurrentProcess.FindSymbolScope(FThreadId, FStackFrame);
if CurContext <> nil then begin
WatchPasExpr := TFpPascalExpression.Create(FWatchData, CurContext);
WatchPasExpr := TFpPascalExpression.Create(FWatchData, CurContext, True);
WatchPasExpr.IntrinsicPrefix := TFpDebugDebuggerProperties(FDebugger.GetProperties).IntrinsicPrefix;
WatchPasExpr.Parse;
R := WatchPasExpr.ResultValue; // Address and Size
// TODO: Cache current value
if WatchPasExpr.Valid and IsTargetNotNil(R.Address) and R.GetSize(s) then begin