mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-15 16:02:40 +02:00
559 lines
17 KiB
ObjectPascal
559 lines
17 KiB
ObjectPascal
unit FpDebugValueConvertors;
|
|
|
|
{$mode objfpc}{$H+}
|
|
{$ModeSwitch typehelpers}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, FpDbgInfo, FpdMemoryTools, FpDbgCallContextInfo,
|
|
FpPascalBuilder, FpErrorMessages, FpDbgClasses, FpDbgUtil, DbgIntfBaseTypes,
|
|
LazClasses, LCLProc, Forms, StdCtrls, Controls, StrUtils, FpDebugDebuggerBase,
|
|
FpDebugStringConstants, LazDebuggerValueConverter, LazDebuggerIntfBaseTypes;
|
|
|
|
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, TLazDbgValueConverterIntf)
|
|
private
|
|
FLastErrror: TFpError;
|
|
protected
|
|
function GetObject: TObject;
|
|
function GetSettingsFrame: TLazDbgValueConverterSettingsFrameIntf; 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: TLazDbgValueConverterIntf; virtual;
|
|
function ConvertValue(ASourceValue: TFpValue;
|
|
AnFpDebugger: TFpDebugDebuggerBase;
|
|
AnExpressionScope: TFpDbgSymbolScope
|
|
): TFpValue; virtual; abstract;
|
|
procedure SetError(AnError: TFpError);
|
|
property LastErrror: TFpError read FLastErrror;
|
|
end;
|
|
TFpDbgValueConverterClass = class of TFpDbgValueConverter;
|
|
|
|
{ TConverterWithFuncCallSettingsFrame }
|
|
|
|
TConverterWithFuncCallSettingsFrame = class(TFrame, TLazDbgValueConverterSettingsFrameIntf)
|
|
chkRunAll: TCheckBox;
|
|
protected
|
|
function GetFrame: TObject;
|
|
public
|
|
constructor Create(TheOwner: TComponent); override;
|
|
procedure ReadFrom(AConvertor: TLazDbgValueConverterIntf);
|
|
function WriteTo(AConvertor: TLazDbgValueConverterIntf): Boolean;
|
|
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 TLazDbgValueConvertSelectorIntf
|
|
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: TLazDbgValueConverterIntf; override;
|
|
class function GetName: String; override;
|
|
class function GetDebuggerClass: TClass; override;
|
|
end;
|
|
|
|
|
|
{ TFpDbgValueConverterVariantToLStr }
|
|
|
|
TFpDbgValueConverterVariantToLStr = class(TFpDbgValueConverterWithFuncCall)
|
|
private
|
|
function GetProcAddrFromMgr(AnFpDebugger: TFpDebugDebuggerBase; AnExpressionScope: TFpDbgSymbolScope): TDbgPtr;
|
|
protected
|
|
function GetSettingsFrame: TLazDbgValueConverterSettingsFrameIntf; override;
|
|
public
|
|
class function GetName: String; override;
|
|
function GetRegistryEntry: TLazDbgValueConvertRegistryEntryClass; override;
|
|
function ConvertValue(ASourceValue: TFpValue;
|
|
AnFpDebugger: TFpDebugDebuggerBase;
|
|
AnExpressionScope: TFpDbgSymbolScope
|
|
): 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: TLazDbgValueConverterIntf;
|
|
var
|
|
c: TFpDbgValueConverter;
|
|
begin
|
|
c := TFpDbgValueConverterClass(ClassType).Create;
|
|
c.Assign(Self);
|
|
Result := c;
|
|
end;
|
|
|
|
procedure TFpDbgValueConverter.SetError(AnError: TFpError);
|
|
begin
|
|
FLastErrror := AnError;
|
|
end;
|
|
|
|
function TFpDbgValueConverter.GetObject: TObject;
|
|
begin
|
|
Result := Self;
|
|
end;
|
|
|
|
function TFpDbgValueConverter.GetSettingsFrame: TLazDbgValueConverterSettingsFrameIntf;
|
|
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;
|
|
|
|
{ TConverterWithFuncCallSettingsFrame }
|
|
|
|
procedure TConverterWithFuncCallSettingsFrame.ReadFrom(
|
|
AConvertor: TLazDbgValueConverterIntf);
|
|
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: TLazDbgValueConverterIntf): 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;
|
|
|
|
function TConverterWithFuncCallSettingsFrame.GetFrame: TObject;
|
|
begin
|
|
Result := Self;
|
|
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: TLazDbgValueConverterIntf;
|
|
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);
|
|
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: TLazDbgValueConverterSettingsFrameIntf;
|
|
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
|
|
): 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');
|
|
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);
|
|
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.
|
|
|