lazarus/components/lazdebuggers/lazdebuggerfp/fpdebugvalueconvertors.pas

623 lines
19 KiB
ObjectPascal

unit FpDebugValueConvertors;
{$mode objfpc}{$H+}
{$ModeSwitch typehelpers}
interface
uses
Classes, SysUtils, FpDbgInfo, FpdMemoryTools, FpDbgCallContextInfo,
FpPascalBuilder, FpErrorMessages, FpDbgClasses, FpDbgUtil, DbgIntfBaseTypes,
LazClasses, Forms, StdCtrls, Controls, StrUtils, FpDebugDebuggerBase,
FpDebugStringConstants, LazDebuggerValueConverter, LazDebuggerIntfBaseTypes,
LazDebuggerIntf;
type
(* TFpDbgValueConverter and descendants
- A TFpDbgValueConverter should be immutable, once in the list.
To change settings a new instance can be set to TDbgBackendConverterConfig
This allows for TFpDbgValueConverter to be used outside the lock (reduces lock time)
- Any setting that the IDE may need to store, should be published
*)
TFpDbgValueConverter = class(TRefCountedObject, ILazDbgValueConverterIntf)
private
FLastErrror: TFpError;
protected
function GetObject: TObject;
function GetSettingsFrame: ILazDbgValueConverterSettingsFrameIntf; virtual;
procedure Init; virtual;
public
class function GetName: String; virtual; abstract;
function GetRegistryEntry: TLazDbgValueConvertRegistryEntryClass; virtual;
constructor Create; virtual;
procedure Assign(ASource: TFpDbgValueConverter); virtual;
function CreateCopy: ILazDbgValueConverterIntf; virtual;
function NeedConversionLimit: Boolean; virtual;
(* CanHandleValue must return the SAME RESULT if called repeatedly (e.g. in an array)
CanHandleValue must NOT depend on DATA, or anything that can change
CanHandleValue must ONLY check type info (or other data that will not change)
- For any other/uncertain case the converter should create an error (empty text) in
ConvertValue, changing AnResData
*)
function CanHandleValue(ASourceValue: TFpValue; AnFpDebugger: TFpDebugDebuggerBase): Boolean; virtual;
(* ConvertValue
* AnResData = nil
=> Do nothing => ConvertValue must have done AnResData.Create....
* Result <> nil / AnResData <> nil
=> Use Result (instead of ASourceValue) to build watch-data
=> use the new AnResData (in case it changed)
If the new AnResData = nil, then do nothing
* Result = nil
- AnResData NOT-changed
=> Create an error (IDE will show ASourceValue)
- AnResData CHANGED
=> Do nothing => ConvertValue must have done AnResData.Create....
*)
function ConvertValue(ASourceValue: TFpValue;
AnFpDebugger: TFpDebugDebuggerBase;
AnExpressionScope: TFpDbgSymbolScope;
var AnResData: IDbgWatchDataIntf // if changed, then the converter has done its job, and should return nil
): TFpValue; virtual; abstract;
procedure SetError(AnError: TFpError);
property LastErrror: TFpError read FLastErrror;
end;
TFpDbgValueConverterClass = class of TFpDbgValueConverter;
{ TConverterSettingsFrameBase }
TConverterSettingsFrameBase = class(TFrame, ILazDbgValueConverterSettingsFrameIntf)
protected
function GetFrame: TObject; virtual;
public
procedure ReadFrom(AConvertor: ILazDbgValueConverterIntf); virtual;
function WriteTo(AConvertor: ILazDbgValueConverterIntf): Boolean; virtual;
end;
{ TConverterWithFuncCallSettingsFrame }
TConverterWithFuncCallSettingsFrame = class(TConverterSettingsFrameBase)
chkRunAll: TCheckBox;
public
constructor Create(TheOwner: TComponent); override;
procedure ReadFrom(AConvertor: ILazDbgValueConverterIntf); override;
function WriteTo(AConvertor: ILazDbgValueConverterIntf): Boolean; override;
end;
{ TFpDbgValueConverterWithFuncCall }
TFpDbgValueConverterWithFuncCall = class(TFpDbgValueConverter)
private
FFuncCallRunAllThreads: Boolean;
public
procedure Assign(ASource: TFpDbgValueConverter); override;
published
property FuncCallRunAllThreads: Boolean read FFuncCallRunAllThreads write FFuncCallRunAllThreads;
end;
{ TFpDbgValueConvertSelectorIntfHelper }
TFpDbgValueConvertSelectorIntfHelper = type helper for ILazDbgValueConvertSelectorIntf
function CheckMatch(AValue: TFpValue; IgnoreInstanceClass: boolean = False): Boolean;
function CheckTypeMatch(AValue: TFpValue; IgnoreInstanceClass: boolean = False): Boolean;
end;
{ TFpDbgValueConverterRegistryEntry }
TFpDbgValueConverterRegistryEntry = class(TLazDbgValueConvertRegistryEntry)
public
class function CreateValueConvertorIntf: ILazDbgValueConverterIntf; override;
class function GetName: String; override;
class function GetDebuggerClass: TClass; override;
end;
(**** Call SysVarToLStr on variant ****)
{ TFpDbgValueConverterVariantToLStr }
TFpDbgValueConverterVariantToLStr = class(TFpDbgValueConverterWithFuncCall)
private
function GetProcAddrFromMgr(AnFpDebugger: TFpDebugDebuggerBase; AnExpressionScope: TFpDbgSymbolScope): TDbgPtr;
protected
function GetSettingsFrame: ILazDbgValueConverterSettingsFrameIntf; override;
public
class function GetName: String; override;
function GetRegistryEntry: TLazDbgValueConvertRegistryEntryClass; override;
function ConvertValue(ASourceValue: TFpValue;
AnFpDebugger: TFpDebugDebuggerBase;
AnExpressionScope: TFpDbgSymbolScope;
var AnResData: IDbgWatchDataIntf
): TFpValue; override;
end;
{ TFpDbgValueConverterVariantToLStrRegistryEntry }
TFpDbgValueConverterVariantToLStrRegistryEntry = class(TFpDbgValueConverterRegistryEntry)
public
class function GetConvertorClass: TClass; override;
end;
implementation
{$R *.lfm}
{ TFpDbgValueConverterWithFuncCall }
procedure TFpDbgValueConverterWithFuncCall.Assign(ASource: TFpDbgValueConverter);
begin
inherited Assign(ASource);
if ASource is TFpDbgValueConverterWithFuncCall then begin
FFuncCallRunAllThreads := TFpDbgValueConverterWithFuncCall(ASource).FFuncCallRunAllThreads;
end;
end;
{ TFpDbgValueConverter }
function TFpDbgValueConverter.CreateCopy: ILazDbgValueConverterIntf;
var
c: TFpDbgValueConverter;
begin
c := TFpDbgValueConverterClass(ClassType).Create;
c.Assign(Self);
Result := c;
end;
function TFpDbgValueConverter.NeedConversionLimit: Boolean;
begin
Result := True;
end;
function TFpDbgValueConverter.CanHandleValue(ASourceValue: TFpValue;
AnFpDebugger: TFpDebugDebuggerBase): Boolean;
begin
Result := True;
end;
procedure TFpDbgValueConverter.SetError(AnError: TFpError);
begin
FLastErrror := AnError;
end;
function TFpDbgValueConverter.GetObject: TObject;
begin
Result := Self;
end;
function TFpDbgValueConverter.GetSettingsFrame: ILazDbgValueConverterSettingsFrameIntf;
begin
Result := nil;
end;
procedure TFpDbgValueConverter.Init;
begin
//
end;
function TFpDbgValueConverter.GetRegistryEntry: TLazDbgValueConvertRegistryEntryClass;
begin
Result := nil;
end;
constructor TFpDbgValueConverter.Create;
begin
inherited Create;
Init;
end;
procedure TFpDbgValueConverter.Assign(ASource: TFpDbgValueConverter);
begin
//
end;
{ TConverterSettingsFrameBase }
function TConverterSettingsFrameBase.GetFrame: TObject;
begin
Result := Self;
end;
procedure TConverterSettingsFrameBase.ReadFrom(
AConvertor: ILazDbgValueConverterIntf);
begin
//
end;
function TConverterSettingsFrameBase.WriteTo(
AConvertor: ILazDbgValueConverterIntf): Boolean;
begin
Result := False; // nothing changed
end;
{ TConverterWithFuncCallSettingsFrame }
procedure TConverterWithFuncCallSettingsFrame.ReadFrom(
AConvertor: ILazDbgValueConverterIntf);
var
c: TFpDbgValueConverterWithFuncCall;
begin
if not (AConvertor.GetObject is TFpDbgValueConverterWithFuncCall) then
exit;
c := TFpDbgValueConverterWithFuncCall(AConvertor.GetObject);
chkRunAll.Checked := c.FuncCallRunAllThreads;
end;
function TConverterWithFuncCallSettingsFrame.WriteTo(
AConvertor: ILazDbgValueConverterIntf): Boolean;
var
c: TFpDbgValueConverterWithFuncCall;
begin
Result := False;
if not (AConvertor.GetObject is TFpDbgValueConverterWithFuncCall) then
exit;
c := TFpDbgValueConverterWithFuncCall(AConvertor.GetObject);
Result := chkRunAll.Checked <> c.FuncCallRunAllThreads;
c.FuncCallRunAllThreads := chkRunAll.Checked;
end;
constructor TConverterWithFuncCallSettingsFrame.Create(TheOwner: TComponent);
begin
inherited Create(TheOwner);
chkRunAll.Caption := drsRunAllThreadsWhileEval;
end;
{ TFpDbgValueConvertSelectorIntfHelper }
function TFpDbgValueConvertSelectorIntfHelper.CheckMatch(AValue: TFpValue;
IgnoreInstanceClass: boolean): Boolean;
begin
Result := //(AValue.Kind in (MatchKinds * GetConverter.GetSupportedKinds)) and
CheckTypeMatch(AValue, IgnoreInstanceClass);
end;
function TFpDbgValueConvertSelectorIntfHelper.CheckTypeMatch(AValue: TFpValue;
IgnoreInstanceClass: boolean): Boolean;
function MatchPattern(const AName, APattern: String): Boolean;
var
NamePos, PatternPos, p: Integer;
begin
Result := False;
if APattern = '' then
exit;
NamePos := 1;
PatternPos := 1;
while PatternPos <= Length(APattern) do begin
if APattern[PatternPos] = '*' then begin
inc(PatternPos);
end
else begin
p := PatternPos;
PatternPos := PosEx('*', APattern, p);
if PatternPos < 1 then
PatternPos := Length(APattern)+1;
if PatternPos-p > Length(AName)+1 - NamePos then
break;
NamePos := PosEx(Copy(APattern, p, PatternPos-p), AName, NamePos);
if (NamePos < 1) or
( (p = 1) and (NamePos <> 1) ) // APattern does not start with *
then
break;
inc(NamePos, PatternPos-p);
end;
end;
Result := (PatternPos = Length(APattern)+1) and
( (NamePos = Length(AName)+1) or
( (APattern[Length(APattern)] = '*') and
(NamePos <= Length(AName)+1)
)
);
end;
var
i, CnIdx: Integer;
TpName, Pattern, ValClassName, ValUnitName: String;
t: TFpSymbol;
HasMaybeUnitDot: Boolean;
MatchTypeNames: TStrings;
begin
t := AValue.TypeInfo;
Result := (t <> nil) and GetTypeName(TpName, t, [tnfNoSubstitute]);
if not Result then
exit;
TpName := LowerCase(TpName);
MatchTypeNames := AllowedTypeNames;
i := MatchTypeNames.Count;
while i > 0 do begin
dec(i);
Pattern := LowerCase(trim(MatchTypeNames[i]));
HasMaybeUnitDot := (pos('.', Pattern) > 1) and
(AValue.Kind in [skClass]); // only class supports unitnames (via rtti)
if AnsiStrLIComp('is:', @Pattern[1], 3) = 0 then begin
Delete(Pattern, 1, 3);
Pattern := trim(Pattern);
if (AValue.Kind in [skRecord, skClass, skObject, skInterface]) then begin
ValClassName := TpName;
while t <> nil do begin
Result := MatchPattern(ValClassName, Pattern);
if Result then
exit;
t := t.TypeInfo;
if (t = nil) or not GetTypeName(ValClassName, t, [tnfNoSubstitute]) then
break;
ValClassName := LowerCase(ValClassName);
end;
if not IgnoreInstanceClass then begin
CnIdx := 0;
while AValue.GetInstanceClassName(@ValClassName, @ValUnitName, CnIdx) and
(ValClassName <> '')
do begin
ValClassName := LowerCase(ValClassName);
if (ValClassName = TpName) and (not HasMaybeUnitDot) then
Break;
Result := MatchPattern(ValClassName, Pattern);
if Result then
exit;
if HasMaybeUnitDot and (ValUnitName <> '') then begin
ValUnitName := LowerCase(ValUnitName);
Result := MatchPattern(ValUnitName+'.'+ValClassName, Pattern);
if Result then
exit;
end;
inc(CnIdx);
end;
end;
AValue.ResetError;
Continue;
end;
end;
Result := MatchPattern(TpName, Pattern);
if Result then
exit;
if HasMaybeUnitDot then begin
if AValue.GetInstanceClassName(@ValClassName, @ValUnitName) and
(ValUnitName <> '') and (ValClassName <> '')
then begin
Result := MatchPattern(ValUnitName+'.'+ValClassName, Pattern);
if Result then
exit;
end;
AValue.ResetError;
end;
end;
end;
{ TFpDbgValueConverterRegistryEntry }
class function TFpDbgValueConverterRegistryEntry.CreateValueConvertorIntf: ILazDbgValueConverterIntf;
begin
Result := TFpDbgValueConverterClass(GetConvertorClass).Create;
end;
class function TFpDbgValueConverterRegistryEntry.GetName: String;
begin
Result := TFpDbgValueConverterClass(GetConvertorClass).GetName;
end;
class function TFpDbgValueConverterRegistryEntry.GetDebuggerClass: TClass;
begin
Result := TFpDebugDebuggerBase;
end;
{ TFpDbgValueConverterVariantToLStr }
function TFpDbgValueConverterVariantToLStr.GetProcAddrFromMgr(
AnFpDebugger: TFpDebugDebuggerBase; AnExpressionScope: TFpDbgSymbolScope
): TDbgPtr;
var
ProcSym: TFpSymbol;
MgrAddr, Fnd: TDBGPtr;
CallContext: TFpDbgInfoCallContext;
CurProc: TDbgProcess;
begin
Result := 0;
CurProc := AnFpDebugger.DbgController.CurrentProcess;
if CurProc = nil then
exit;
ProcSym := nil;
ProcSym := CurProc.FindProcSymbol('SYSTEM_$$_GETVARIANTMANAGER$TVARIANTMANAGER');
try
if (ProcSym = nil) or (not (ProcSym.Kind = skProcedure)) or
(not IsTargetAddr(ProcSym.Address))
then
exit;
CallContext := nil;
MgrAddr := AnFpDebugger.DbgController.CurrentThread.AllocStackMem(1024); // enough space for the record
CallContext := AnFpDebugger.DbgController.Call(ProcSym.Address, AnExpressionScope.LocationContext,
AnFpDebugger.MemReader, AnFpDebugger.MemConverter);
if CallContext = nil then
exit;
try
CallContext.AddOrdinalParam(nil, MgrAddr);
CallContext.FinalizeParams;
AnFpDebugger.BeforeWatchEval(CallContext);
AnFpDebugger.RunProcessLoop(True);
if not CallContext.IsValid then
exit;
if CurProc.ReadAddress(MgrAddr + 8 * CurProc.PointerSize, Fnd) then
Result := Fnd;
finally
AnFpDebugger.DbgController.AbortCurrentCommand(True);
CallContext.ReleaseReference;
AnFpDebugger.DbgController.CurrentThread.RestoreStackMem;
end;
finally
ProcSym.ReleaseReference;
end;
end;
function TFpDbgValueConverterVariantToLStr.GetSettingsFrame: ILazDbgValueConverterSettingsFrameIntf;
begin
Result := TConverterWithFuncCallSettingsFrame.Create(nil);
end;
class function TFpDbgValueConverterVariantToLStr.GetName: String;
begin
Result := drsCallSysVarToLStr;
end;
function TFpDbgValueConverterVariantToLStr.GetRegistryEntry: TLazDbgValueConvertRegistryEntryClass;
begin
Result := TFpDbgValueConverterVariantToLStrRegistryEntry;
end;
function TFpDbgValueConverterVariantToLStr.ConvertValue(ASourceValue: TFpValue;
AnFpDebugger: TFpDebugDebuggerBase; AnExpressionScope: TFpDbgSymbolScope;
var AnResData: IDbgWatchDataIntf): TFpValue;
var
NewResult, ProcVal, m: TFpValue;
ProcSym: TFpSymbol;
CallContext: TFpDbgInfoCallContext;
StringAddr, ProcAddr, StringDecRefAddress: TDbgPtr;
ProcLoc: TFpDbgMemLocation;
r: Boolean;
begin
Result := nil;
if (ASourceValue.Kind <> skRecord) or
(AnFpDebugger.DbgController.CurrentProcess = nil) or
( (AnFpDebugger.DbgController.CurrentProcess.Mode = dm32) and
(ASourceValue.DataSize.Size <> 16)
) or
( (AnFpDebugger.DbgController.CurrentProcess.Mode = dm64) and
(ASourceValue.DataSize.Size <> 24)
)
then begin
SetError(CreateError(fpErrAnyError, ['Value not a variant']));
exit;
end;
m := ASourceValue.MemberByName['vtype'];
r := (m = nil) or (SizeToFullBytes(m.DataSize) <> 2);
m.ReleaseReference;
if r then begin
SetError(CreateError(fpErrAnyError, ['Value not a variant']));
exit;
end;
ProcVal := nil;
ProcSym := nil;
try
(*
//VARIANTS_$$_SYSVARTOLSTR$ANSISTRING$VARIANT
//U_$SYSTEM_$$_VARIANTMANAGER
//SYSTEM_$$_GETVARIANTMANAGER$TVARIANTMANAGER
ProcVal := AnExpressionScope.FindSymbol('sysvartolstr', 'variants', [fsfIgnoreEnumVals]);
if ProcVal <> nil then begin
ProcSym := ProcVal.DbgSymbol;
if ProcSym <> nil then
ProcSym.AddReference;
end;
if (ProcSym = nil) or (not (ProcSym.Kind = skProcedure)) or
(not IsTargetAddr(ProcSym.Address))
then
ProcSym := AnFpDebugger.DbgController.CurrentProcess.FindProcSymbol('sysvartolstr');
if (ProcSym = nil) or (not IsTargetAddr(ProcSym.Address)) then
exit;
ProcLoc := ProcSym.Address
*)
if not IsTargetAddr(ASourceValue.Address) then begin
SetError(CreateError(fpErrAnyError, ['Value not in memory']));
exit;
end;
ProcAddr := AnFpDebugger.GetCachedData(pointer(TFpDbgValueConverterVariantToLStr));
if ProcAddr = 0 then begin
ProcAddr := GetProcAddrFromMgr(AnFpDebugger, AnExpressionScope);
if ProcAddr = 0 then begin
SetError(CreateError(fpErrAnyError, ['SysVarToLStr not found']));
exit;
end;
AnFpDebugger.SetCachedData(pointer(TFpDbgValueConverterVariantToLStr), ProcAddr);
end;
ProcLoc := TargetLoc(ProcAddr);
StringDecRefAddress := AnFpDebugger.GetCached_FPC_ANSISTR_DECR_REF;
if (StringDecRefAddress = 0) then begin
SetError(CreateError(fpErrAnyError, ['STRING_DEC_REF not found']));
exit;
end;
StringAddr := 0;
CallContext := AnFpDebugger.DbgController.Call(ProcLoc, AnExpressionScope.LocationContext,
AnFpDebugger.MemReader, AnFpDebugger.MemConverter);
if CallContext = nil then begin
SetError(CreateError(fpErrAnyError, ['function call not possible']));
exit;
end;
try
CallContext.AddStringResult;
CallContext.FinalizeParams; // force the string as first param (32bit) // TODO
CallContext.AddOrdinalParam(nil, ASourceValue.DataAddress.Address);
AnFpDebugger.BeforeWatchEval(CallContext);
AnFpDebugger.RunProcessLoop(not FuncCallRunAllThreads);
if not CallContext.IsValid then begin
if (IsError(CallContext.LastError)) then
SetError(CallContext.LastError)
else
if (CallContext.Message <> '') then
SetError(CreateError(fpErrAnyError, [CallContext.Message]));
exit;
end;
if not CallContext.GetStringResultAsPointer(StringAddr) then begin
SetError(CallContext.LastError);
exit;
end;
if not CallContext.GetStringResult(NewResult) then begin
SetError(CallContext.LastError);
exit;
end;
Result := NewResult;
finally
AnFpDebugger.DbgController.AbortCurrentCommand(True);
CallContext.ReleaseReference;
AnFpDebugger.CallTargetFuncStringDecRef(StringDecRefAddress, StringAddr, AnExpressionScope.LocationContext);
end;
finally
ProcVal.ReleaseReference;
ProcSym.ReleaseReference;
end;
end;
{ TFpDbgValueConverterVariantToLStrRegistryEntry }
class function TFpDbgValueConverterVariantToLStrRegistryEntry.GetConvertorClass: TClass;
begin
Result := TFpDbgValueConverterVariantToLStr;
end;
initialization
ValueConverterRegistry.Add(TFpDbgValueConverterVariantToLStrRegistryEntry);
end.