lazarus/components/fpdebug/fpwatchresultdata.pas

1002 lines
32 KiB
ObjectPascal

unit FpWatchResultData;
{$mode objfpc}{$H+}
{$IFDEF INLINE_OFF}{$INLINE OFF}{$ENDIF}
interface
uses
FpDbgInfo, FpPascalBuilder, FpdMemoryTools, FpErrorMessages, FpDbgDwarf,
FpDbgDwarfDataClasses, DbgIntfBaseTypes, LazClasses, LazLoggerBase, fgl, Math,
SysUtils, LazDebuggerIntf;
type
TDbgPtrList = specialize TFPGList<TDBGPtr>;
{ TFpWatchResultConvertor }
TFpWatchResultConvertor = class
private const
MAX_RECURSE_LVL = 10;
MAX_RECURSE_LVL_ARRAY = 5;
MAX_RECURSE_LVL_PTR = 8; // max depth for a chain of pointers starting at the initial value
private
FContext: TFpDbgLocationContext;
FExtraDepth: Boolean;
FFirstIndexOffs: Integer;
FRecurseCnt, FRecurseCntLow,
FRecursePointerCnt,
FRecurseInstanceCnt, FRecurseDynArray, FRecurseArray: integer;
FRecurseAddrList: TDbgPtrList;
FLastValueKind: TDbgSymbolKind;
FHasEmbeddedPointer: Boolean;
FOuterArrayIdx, FTotalArrayCnt: integer;
FRepeatCount: Integer;
FArrayTypeDone: Boolean;
FEncounteredError: Boolean;
protected
function CheckError(AnFpValue: TFpValue; AnResData: IDbgWatchDataIntf): boolean;
procedure AddTypeNameToResData(AnFpValue: TFpValue; AnResData: IDbgWatchDataIntf; ADeref: Boolean = False);
function TypeToResData(AnFpValue: TFpValue; AnResData: IDbgWatchDataIntf): Boolean;
function PointerToResData(AnFpValue: TFpValue; AnResData: IDbgWatchDataIntf): Boolean;
function NumToResData(AnFpValue: TFpValue; AnResData: IDbgWatchDataIntf): Boolean;
function CharToResData(AnFpValue: TFpValue; AnResData: IDbgWatchDataIntf): Boolean;
function StringToResData(AnFpValue: TFpValue; AnResData: IDbgWatchDataIntf): Boolean;
function WideStringToResData(AnFpValue: TFpValue; AnResData: IDbgWatchDataIntf): Boolean;
function BoolToResData(AnFpValue: TFpValue; AnResData: IDbgWatchDataIntf): Boolean;
function EnumToResData(AnFpValue: TFpValue; AnResData: IDbgWatchDataIntf): Boolean;
function SetToResData(AnFpValue: TFpValue; AnResData: IDbgWatchDataIntf): Boolean;
function FloatToResData(AnFpValue: TFpValue; AnResData: IDbgWatchDataIntf): Boolean;
function ArrayToResData(AnFpValue: TFpValue; AnResData: IDbgWatchDataIntf): Boolean;
function StructToResData(AnFpValue: TFpValue; AnResData: IDbgWatchDataIntf): Boolean;
function ProcToResData(AnFpValue: TFpValue; AnResData: IDbgWatchDataIntf): Boolean;
function DoValueToResData(AnFpValue: TFpValue;
AnResData: IDbgWatchDataIntf
): Boolean; virtual;
function DoWriteWatchResultData(AnFpValue: TFpValue;
AnResData: IDbgWatchDataIntf
): Boolean;
function DoWritePointerWatchResultData(AnFpValue: TFpValue;
AnResData: IDbgWatchDataIntf;
AnAddr: TDbgPtr
): Boolean;
property RecurseCnt: Integer read FRecurseCnt;
property RecurseCntLow: Integer read FRecurseCntLow;
public
constructor Create(AContext: TFpDbgLocationContext);
destructor Destroy; override;
function WriteWatchResultData(AnFpValue: TFpValue;
AnResData: IDbgWatchDataIntf;
ARepeatCount: Integer = 0
): Boolean;
property Context: TFpDbgLocationContext read FContext write FContext;
property ExtraDepth: Boolean read FExtraDepth write FExtraDepth;
property FirstIndexOffs: Integer read FFirstIndexOffs write FFirstIndexOffs;
//property RepeatCount: Integer read FRepeatCount write SetRepeatCount;
end;
implementation
{ TFpWatchResultConvertor }
function TFpWatchResultConvertor.CheckError(AnFpValue: TFpValue;
AnResData: IDbgWatchDataIntf): boolean;
begin
Result := AnFpValue = nil;
if Result then
exit;
Result := IsError(AnFpValue.LastError);
if Result then begin
FEncounteredError := True;
if AnResData <> nil then
AnResData.CreateError(ErrorHandler.ErrorAsString(AnFpValue.LastError));
AnFpValue.ResetError;
end;
end;
procedure TFpWatchResultConvertor.AddTypeNameToResData(AnFpValue: TFpValue;
AnResData: IDbgWatchDataIntf; ADeref: Boolean);
var
t: TFpSymbol;
TpName: String;
begin
if FArrayTypeDone then
exit;
t := AnFpValue.TypeInfo;
if ADeref and (t <> nil) then
t := t.TypeInfo;
if (t <> nil) and
GetTypeName(TpName, t, [tnfNoSubstitute]) and
(TpName <> '')
then
AnResData.SetTypeName(TpName);
end;
function TFpWatchResultConvertor.TypeToResData(AnFpValue: TFpValue;
AnResData: IDbgWatchDataIntf): Boolean;
var
APrintedValue: String;
begin
if GetTypeAsDeclaration(APrintedValue, AnFpValue.DbgSymbol) then
AnResData.CreatePrePrinted('type '+APrintedValue)
else
AnResData.CreateError('Unknown type');
Result := True;
end;
function TFpWatchResultConvertor.PointerToResData(AnFpValue: TFpValue;
AnResData: IDbgWatchDataIntf): Boolean;
var
DerefRes: IDbgWatchDataIntf;
DerefVal: TFpValue;
addr: QWord;
begin
Result := True;
addr := AnFpValue.AsCardinal;
AnResData.CreatePointerValue(addr);
AddTypeNameToResData(AnFpValue, AnResData);
if CheckError(AnFpValue, AnResData) then
exit;
if addr = 0 then
exit;
if svfString in AnFpValue.FieldFlags then begin
// PChar: Get zero-terminated string, rather than just one single char
DerefRes := AnResData.SetDerefData;
if DerefRes <> nil then begin
DerefRes.CreateString(AnFpValue.AsString);
AddTypeNameToResData(AnFpValue, DerefRes, True);
CheckError(AnFpValue, DerefRes);
end;
end
else
if svfWideString in AnFpValue.FieldFlags then begin
// PWideChar: Get zero-terminated string, rather than just one single char
DerefRes := AnResData.SetDerefData;
if DerefRes <> nil then begin
DerefRes.CreateWideString(AnFpValue.AsWideString);
AddTypeNameToResData(AnFpValue, DerefRes, True);
CheckError(AnFpValue, DerefRes);
end;
end
else begin
DerefVal := AnFpValue.Member[0];
if IsError(AnFpValue.LastError) then begin
CheckError(AnFpValue, AnResData.SetDerefData);
end
else
if (DerefVal <> nil) then begin
DerefRes := nil;
if (DerefVal.Kind in [skString, skAnsiString, skChar, skWideString,
skInteger, skCardinal, skBoolean, skFloat, skCurrency, skEnum, skSet])
then begin
(* (Nested) Pointer to
- Pascal-String type
- Any basic type (any type that has no reference or internal pointer)
(skChar should not happen: Should be PChar above)
- Any other
*)
DerefRes := AnResData.SetDerefData;
if DerefRes <> nil then begin
// In case of nested pointer MAX_RECURSE_LVL may already be reached. Make an exception here, to allow one more.
dec(FRecurseCnt);
DoWritePointerWatchResultData(DerefVal, DerefRes, addr);
inc(FRecurseCnt);
end;
end
else
if (DerefVal.Kind =skPointer) and (svfString in DerefVal.FieldFlags) then begin
DerefRes := AnResData.SetDerefData;
if DerefRes <> nil then begin
DerefRes.CreateString(DerefVal.AsString);
AddTypeNameToResData(DerefVal, DerefRes, True);
end;
end
else
if (DerefVal.Kind =skPointer) and (svfWideString in DerefVal.FieldFlags) then begin
DerefRes := AnResData.SetDerefData;
if DerefRes <> nil then begin
DerefRes.CreateWideString(DerefVal.AsString);
AddTypeNameToResData(DerefVal, DerefRes, True);
end;
end
else begin
DerefRes := AnResData.SetDerefData;
if DerefRes <> nil then
DoWritePointerWatchResultData(DerefVal, DerefRes, addr);
end;
CheckError(DerefVal, DerefRes);
DerefVal.ReleaseReference;
end;
end;
end;
function TFpWatchResultConvertor.NumToResData(AnFpValue: TFpValue;
AnResData: IDbgWatchDataIntf): Boolean;
begin
Result := True;
if AnFpValue.Kind = skCardinal then
AnResData.CreateNumValue(AnFpValue.AsCardinal, False, SizeToFullBytes(AnFpValue.DataSize))
else
AnResData.CreateNumValue(QWord(AnFpValue.AsInteger), True, SizeToFullBytes(AnFpValue.DataSize));
AddTypeNameToResData(AnFpValue, AnResData);
end;
function TFpWatchResultConvertor.CharToResData(AnFpValue: TFpValue;
AnResData: IDbgWatchDataIntf): Boolean;
begin
Result := True;
AnResData.CreateCharValue(AnFpValue.AsCardinal, SizeToFullBytes(AnFpValue.DataSize));
AddTypeNameToResData(AnFpValue, AnResData);
end;
function TFpWatchResultConvertor.StringToResData(AnFpValue: TFpValue;
AnResData: IDbgWatchDataIntf): Boolean;
begin
Result := True;
AnResData.CreateString(AnFpValue.AsString);
if svfDataAddress in AnFpValue.FieldFlags then
AnResData.SetDataAddress(AnFpValue.DataAddress.Address);
AddTypeNameToResData(AnFpValue, AnResData);
end;
function TFpWatchResultConvertor.WideStringToResData(AnFpValue: TFpValue;
AnResData: IDbgWatchDataIntf): Boolean;
begin
Result := True;
AnResData.CreateWideString(AnFpValue.AsWideString);
if svfDataAddress in AnFpValue.FieldFlags then
AnResData.SetDataAddress(AnFpValue.DataAddress.Address);
AddTypeNameToResData(AnFpValue, AnResData);
end;
function TFpWatchResultConvertor.BoolToResData(AnFpValue: TFpValue;
AnResData: IDbgWatchDataIntf): Boolean;
begin
Result := True;
AnResData.CreateBoolValue(AnFpValue.AsCardinal, SizeToFullBytes(AnFpValue.DataSize));
AddTypeNameToResData(AnFpValue, AnResData);
end;
function TFpWatchResultConvertor.EnumToResData(AnFpValue: TFpValue;
AnResData: IDbgWatchDataIntf): Boolean;
var
ValSize: TFpDbgValueSize;
begin
Result := True;
if not( (svfSize in AnFpValue.FieldFlags) and AnFpValue.GetSize(ValSize) ) then
ValSize := ZeroSize;
if IsError(AnFpValue.LastError) then
ValSize := ZeroSize;
AnFpValue.ResetError;
AnResData.CreateEnumValue(AnFpValue.AsCardinal, AnFpValue.AsString, SizeToFullBytes(ValSize), AnFpValue.Kind=skEnumValue);
AddTypeNameToResData(AnFpValue, AnResData);
end;
function TFpWatchResultConvertor.SetToResData(AnFpValue: TFpValue;
AnResData: IDbgWatchDataIntf): Boolean;
var
m: TFpValue;
Names: array of String;
i: Integer;
begin
Result := True;
SetLength(Names, AnFpValue.MemberCount);
for i := 0 to AnFpValue.MemberCount-1 do begin
m := AnFpValue.Member[i];
if svfIdentifier in m.FieldFlags then
Names[i] := m.AsString
else
if svfOrdinal in m.FieldFlags then // set of byte
Names[i] := IntToStr(m.AsCardinal)
else
Names[i] := '';
m.ReleaseReference;
end;
AnResData.CreateSetValue(Names);
AddTypeNameToResData(AnFpValue, AnResData);
end;
function TFpWatchResultConvertor.FloatToResData(AnFpValue: TFpValue;
AnResData: IDbgWatchDataIntf): Boolean;
var
p: TLzDbgFloatPrecission;
s: TFpDbgValueSize;
begin
Result := True;
p := dfpSingle;
if AnFpValue.GetSize(s) then begin
if SizeToFullBytes(s) > SizeOf(Double) then
p := dfpExtended
else
if SizeToFullBytes(s) > SizeOf(Single) then
p := dfpDouble
end;
AnResData.CreateFloatValue(AnFpValue.AsFloat, p);
AddTypeNameToResData(AnFpValue, AnResData);
end;
function TFpWatchResultConvertor.ArrayToResData(AnFpValue: TFpValue;
AnResData: IDbgWatchDataIntf): Boolean;
const
MAX_TOTAL_ARRAY_CNT = 5000;
MAX_TOTAL_ARRAY_CNT_EXTRA_DEPTH = 3500; // reset
var
Cnt, i, CurRecurseDynArray, OuterIdx, CacheCnt: Integer;
LowBnd, StartIdx, CacheMax, CacheSize, j: Int64;
Addr: TDBGPtr;
ti: TFpSymbol;
EntryRes: IDbgWatchDataIntf;
MemberValue, TmpVal: TFpValue;
Cache: TFpDbgMemCacheBase;
Dummy: QWord;
MLoc: TFpDbgMemLocation;
begin
Result := True;
Cnt := AnFpValue.MemberCount;
if CheckError(AnFpValue, AnResData) then begin
AddTypeNameToResData(AnFpValue, AnResData);
exit;
end;
CurRecurseDynArray := FRecurseDynArray;
OuterIdx := FOuterArrayIdx;
if (AnFpValue.IndexTypeCount = 0) or (not AnFpValue.IndexType[0].GetValueLowBound(AnFpValue, LowBnd)) then
LowBnd := 0;
Addr := 0;
ti := AnFpValue.TypeInfo;
if (ti = nil) or (ti.Flags * [sfDynArray, sfStatArray] = []) then begin
EntryRes := AnResData.CreateArrayValue(datUnknown, Cnt, LowBnd);
end
else
if (sfDynArray in ti.Flags) and (LowBnd = 0) then begin // LowBnd = 0 => there is some bug, reporting some dyn arrays as stat.
EntryRes := AnResData.CreateArrayValue(datDynArray, Cnt, 0);
if AnFpValue.FieldFlags * [svfInteger, svfCardinal] <> [] then
Addr := AnFpValue.AsCardinal
else
if svfDataAddress in AnFpValue.FieldFlags then
Addr := AnFpValue.DataAddress.Address;
AnResData.SetDataAddress(Addr);
if FRecurseCnt >= 0 then
inc(FRecurseDynArray);
end
else begin
EntryRes := AnResData.CreateArrayValue(datStatArray, Cnt, LowBnd);
end;
AddTypeNameToResData(AnFpValue, AnResData);
inc(FRecurseArray);
Cache := nil;
try
if (Cnt <= 0) or
(FHasEmbeddedPointer) or
(FRecurseCnt > MAX_RECURSE_LVL_ARRAY) or
( (FRecurseCnt > 0) and (FTotalArrayCnt > MAX_TOTAL_ARRAY_CNT) )
then
exit;
StartIdx := 0;
If (FOuterArrayIdx < 0) and (FRecurseCnt = FRecurseCntLow) then
StartIdx := FFirstIndexOffs;
Cnt := max(1, Cnt - StartIdx);
if (Context.MemManager.MemLimits.MaxArrayLen > 0) and (Cnt > Context.MemManager.MemLimits.MaxArrayLen) then
Cnt := Context.MemManager.MemLimits.MaxArrayLen;
If (FOuterArrayIdx < 0) and (FRecurseCnt = FRecurseCntLow) and (FRepeatCount > 0) then Cnt := FRepeatCount
else if (FRecurseCnt > 1) and (FOuterArrayIdx > 10) and (Cnt > 10) then Cnt := 10
else if (FRecurseCnt > 1) and (FOuterArrayIdx > 1) and (Cnt > 20) then Cnt := 20
else if (FRecurseCnt > 0) and (FOuterArrayIdx > 100) and (Cnt > 10) then Cnt := 10
else if (FRecurseCnt > 0) and (FOuterArrayIdx > 1) and (Cnt > 50) then Cnt := 50;
/////////////////////
// Bound types ??
CacheMax := StartIdx;
CacheSize := 0;
CacheCnt := 200;
//if (ti = nil) or (ti.Flags * [sfDynArray, sfStatArray] = []) then
if (ti = nil) then
MemberValue := nil // could be mapped array slice, with non consecutive entries
else
MemberValue := AnFpValue.Member[StartIdx+LowBnd]; // // TODO : CheckError // ClearError for AnFpValue
if (MemberValue = nil) or (not IsTargetNotNil(MemberValue.Address)) or
(Context.MemManager.CacheManager = nil)
then begin
CacheMax := StartIdx + Cnt; // no caching possible
end
else begin
repeat
TmpVal := AnFpValue.Member[StartIdx + Min(CacheCnt, Cnt) + LowBnd]; // // TODO : CheckError // ClearError for AnFpValue
if (TmpVal <> nil) and IsTargetNotNil(TmpVal.Address) then begin
{$PUSH}{$R-}{$Q-}
CacheSize := TmpVal.Address.Address - MemberValue.Address.Address;
TmpVal.ReleaseReference;
{$POP}
if CacheSize > Context.MemManager.MemLimits.MaxMemReadSize then begin
CacheSize := 0;
CacheCnt := CacheCnt div 2;
if CacheCnt <= 1 then
break;
continue;
end;
end;
break;
until false;
if CacheSize = 0 then
CacheMax := StartIdx + Cnt; // no caching possible
end;
MemberValue.ReleaseReference;
inc(FTotalArrayCnt, Cnt);
for i := StartIdx to StartIdx + Cnt - 1 do begin
if (FRecurseCnt < 0) and (FTotalArrayCnt > MAX_TOTAL_ARRAY_CNT_EXTRA_DEPTH) then
FTotalArrayCnt := MAX_TOTAL_ARRAY_CNT_EXTRA_DEPTH;
if i > FOuterArrayIdx then
FOuterArrayIdx := i;
MemberValue := AnFpValue.Member[i+LowBnd]; // // TODO : CheckError // ClearError for AnFpValue
if (i >= CacheMax) and (CacheSize > 0) and (MemberValue <> nil) then begin
if Cache <> nil then
Context.MemManager.CacheManager.RemoveCache(Cache);
Cache := nil;
if IsTargetNotNil(MemberValue.Address) then begin
CacheMax := Min(i + CacheCnt, StartIdx + Cnt);
if (CacheMax > i + 1) then begin
j := CacheMax - i;
if j < CacheCnt then
CacheSize := (CacheSize div CacheCnt) * j + j div 2;
if CacheSize > 0 then
Cache := Context.MemManager.CacheManager.AddCache(MemberValue.Address.Address, CacheSize)
end;
end
else
CacheMax := StartIdx + Cnt; // no caching possible
end;
EntryRes := AnResData.SetNextArrayData;
if MemberValue = nil then
EntryRes.CreateError('Error: Could not get member')
else
DoWritePointerWatchResultData(MemberValue, EntryRes, Addr);
if (i = StartIdx) and (MemberValue <> nil) and FEncounteredError then begin
MLoc := MemberValue.Address;
if IsValidLoc(MLoc) then
Context.ReadMemory(MLoc, SizeVal(1), @Dummy);
if ( IsError(Context.LastMemError) or (not IsValidLoc(MLoc)) ) and
(MLoc <> AnFpValue.DataAddress) and (IsValidLoc(AnFpValue.DataAddress))
then
Context.ReadMemory(AnFpValue.DataAddress, SizeVal(1), @Dummy);
if IsError(Context.LastMemError) then begin
// array is in unreadable memory
AnResData.CreateError(ErrorHandler.ErrorAsString(Context.LastMemError));
MemberValue.ReleaseReference;
exit;
end;
end;
MemberValue.ReleaseReference;
if FRecurseArray = 1 then
FArrayTypeDone := True;
end;
DebugLn(IsError(AnFpValue.LastError), ['!!! ArrayToResData() unexpected error in array value', ErrorHandler.ErrorAsString(AnFpValue.LastError)]);
AnFpValue.ResetError;
finally
if FRecurseArray = 1 then
FArrayTypeDone := False;
FRecurseDynArray := CurRecurseDynArray;
FOuterArrayIdx := OuterIdx;
dec(FRecurseArray);
if Cache <> nil then
Context.MemManager.CacheManager.RemoveCache(Cache);
end
end;
function TFpWatchResultConvertor.StructToResData(AnFpValue: TFpValue;
AnResData: IDbgWatchDataIntf): Boolean;
procedure AddVariantMembers(VariantPart: TFpValue; ResAnch: IDbgWatchDataIntf);
var
VariantContainer, VMember: TFpValue;
i, j, CurRecurseArray: Integer;
ResField, ResList: IDbgWatchDataIntf;
discr: QWord;
hasDiscr, FoundDiscr, UseDefault, CurArrayTypeDone: Boolean;
MBVis: TLzDbgFieldVisibility;
n: String;
begin
VariantContainer := VariantPart.Member[-1];
if VariantContainer = nil then
exit;
CurRecurseArray := FRecurseArray;
CurArrayTypeDone := FArrayTypeDone;
FArrayTypeDone := False;
FRecurseArray := 0; // Allow an inside array to optimize
try
ResList := ResAnch.AddField('', dfvUnknown, [dffVariant]);
ResList.CreateArrayValue(datUnknown);
hasDiscr := (VariantContainer <> nil) and
(VariantContainer.FieldFlags * [svfInteger, svfCardinal, svfOrdinal] <> []);
if hasDiscr then begin
discr := VariantContainer.AsCardinal;
n := '';
MBVis := dfvUnknown;
if (VariantContainer.DbgSymbol <> nil) then begin
n := VariantContainer.DbgSymbol.Name;
case VariantContainer.DbgSymbol.MemberVisibility of
svPrivate: MBVis := dfvPrivate;
svProtected: MBVis := dfvProtected;
svPublic: MBVis := dfvPublic;
else MBVis := dfvUnknown;
end;
end;
if n <> '' then begin
ResField := ResList.SetNextArrayData;
ResField := ResField.CreateVariantValue(n, MBVis);
if not DoWritePointerWatchResultData(VariantContainer, ResField, 0) then // addr
ResField.CreateError('Unknown');
end;
end;
VariantContainer.ReleaseReference;
FoundDiscr := False;
For UseDefault := (not hasDiscr) to True do begin
for i := 0 to VariantPart.MemberCount - 1 do begin
VariantContainer := VariantPart.Member[i];
if (VariantContainer.DbgSymbol <> nil) and
(VariantContainer.DbgSymbol is TFpSymbolDwarfTypeVariant) and
( ( (not UseDefault) and
(TFpSymbolDwarfTypeVariant(VariantContainer.DbgSymbol).MatchesDiscr(discr))
) or
( (UseDefault) and
(TFpSymbolDwarfTypeVariant(VariantContainer.DbgSymbol).IsDefaultDiscr)
)
)
then begin
FoundDiscr := True;
for j := 0 to VariantContainer.MemberCount - 1 do begin
VMember := VariantContainer.Member[j];
n := '';
MBVis := dfvUnknown;
if (VMember.DbgSymbol <> nil) then begin
n := VMember.DbgSymbol.Name;
case VariantContainer.DbgSymbol.MemberVisibility of
svPrivate: MBVis := dfvPrivate;
svProtected: MBVis := dfvProtected;
svPublic: MBVis := dfvPublic;
else MBVis := dfvUnknown;
end;
end;
// TODO visibility
ResField := ResList.SetNextArrayData;
ResField := ResField.CreateVariantValue(n, MBVis);
if not DoWritePointerWatchResultData(VMember, ResField, 0) then // addr
ResField.CreateError('Unknown');
VMember.ReleaseReference;
end;
end;
VariantContainer.ReleaseReference;
end;
if FoundDiscr then
break;
end;
finally
FRecurseArray := CurRecurseArray;
FArrayTypeDone := CurArrayTypeDone;
end;
end;
type
TAnchestorMap = specialize TFPGMap<PtrUInt, IDbgWatchDataIntf>;
var
vt: TLzDbgStructType;
Cache: TFpDbgMemCacheBase;
AnchestorMap: TAnchestorMap;
i, j, WasRecurseInstanceCnt: Integer;
MemberValue: TFpValue;
ti, sym: TFpSymbol;
ResAnch, ResField, TopAnch, UnkAnch: IDbgWatchDataIntf;
MbName: String;
MBVis: TLzDbgFieldVisibility;
Addr: TDBGPtr;
Dummy: QWord;
MLoc: TFpDbgMemLocation;
begin
Result := True;
case AnFpValue.Kind of
skRecord: vt := dstRecord;
skObject: vt := dstObject;
skClass: vt := dstClass;
skInterface: vt := dstInterface;
else vt := dstUnknown;
end;
if not Context.MemManager.CheckDataSize(SizeToFullBytes(AnFpValue.DataSize)) then begin
AnResData.CreateError(ErrorHandler.ErrorAsString(Context.LastMemError));
exit;
end;
Addr := 0;
if (AnFpValue.Kind in [skClass, skInterface]) then begin
if AnFpValue.FieldFlags * [svfInteger, svfCardinal, svfOrdinal] <> [] then
Addr := AnFpValue.AsCardinal
else
if svfDataAddress in AnFpValue.FieldFlags then
Addr := AnFpValue.DataAddress.Address;
end;
AnResData.CreateStructure(vt, Addr);
AddTypeNameToResData(AnFpValue, AnResData);
if (AnFpValue.Kind in [skClass, skInterface]) and
( (Addr = 0) or
(FRecurseInstanceCnt >= 1) or (FRecurseDynArray >= 2)
)
then
exit;
if FHasEmbeddedPointer then
exit;
if Context.MemManager.CacheManager <> nil then
Cache := Context.MemManager.CacheManager.AddCache(AnFpValue.DataAddress.Address, SizeToFullBytes(AnFpValue.DataSize))
else
Cache := nil;
AnchestorMap := TAnchestorMap.Create;
WasRecurseInstanceCnt := FRecurseInstanceCnt;
if (AnFpValue.Kind in [skClass, skInterface]) and (FRecurseCnt >= 0) then
inc(FRecurseInstanceCnt);
try
TopAnch := AnResData;
UnkAnch := nil;
ti := AnFpValue.TypeInfo;
if ti <> nil then
ti := ti.InternalTypeInfo;
if ti <> nil then begin
AnchestorMap.Add(PtrUInt(ti), AnResData);
if (AnFpValue.Kind in [skObject, skClass, skInterface]) then begin
ti := ti.TypeInfo;
ResAnch := AnResData;
while ti <> nil do begin
ResAnch := ResAnch.SetAnchestor(ti.Name);
AnchestorMap.Add(PtrUInt(ti), ResAnch);
ti := ti.TypeInfo;
end;
TopAnch := ResAnch;
end;
end;
for i := 0 to AnFpValue.MemberCount-1 do begin
MemberValue := AnFpValue.Member[i];
if (MemberValue = nil) or (MemberValue.Kind in [skProcedure, skFunction]) then begin
MemberValue.ReleaseReference;
(* Has Field
- $vmt => Constructor or Destructor
- $vmt_aftercontstruction_local => Constructor
*)
continue;
end;
ResAnch := nil;
ti := MemberValue.ParentTypeInfo; // TODO: variant returens nil, membervalue.sturcturevalue.parenttypesymbol
if ti <> nil then
ti := ti.InternalTypeInfo;
j := AnchestorMap.IndexOf(PtrUInt(ti));
if j >= 0 then begin
ResAnch := AnchestorMap.Data[j];
end
else
if UnkAnch <> nil then begin
ResAnch := UnkAnch;
end
else begin
UnkAnch := TopAnch.SetAnchestor('');
ResAnch := UnkAnch;
end;
if MemberValue.Kind = skVariantPart then begin
AddVariantMembers(MemberValue, ResAnch);
MemberValue.ReleaseReference;
continue;
end;
sym := MemberValue.DbgSymbol;
if sym <> nil then begin
MbName := sym.Name;
case sym.MemberVisibility of
svPrivate: MBVis := dfvPrivate;
svProtected: MBVis := dfvProtected;
svPublic: MBVis := dfvPublic;
else MBVis := dfvUnknown;
end;
end
else begin
MbName := '';
MBVis := dfvUnknown;
end;
ResField := ResAnch.AddField(MbName, MBVis, []);
if not DoWritePointerWatchResultData(MemberValue, ResField, Addr) then
ResField.CreateError('Unknown');
if (i = 0) and (MemberValue <> nil) and FEncounteredError then begin
MLoc := MemberValue.Address;
if IsValidLoc(MLoc) then
Context.ReadMemory(MemberValue.Address, SizeVal(1), @Dummy);
if ( IsError(Context.LastMemError) or (not IsValidLoc(MLoc)) ) and
(MLoc <> AnFpValue.DataAddress) and (IsValidLoc(AnFpValue.DataAddress))
then
Context.ReadMemory(AnFpValue.DataAddress, SizeVal(1), @Dummy);
if IsError(Context.LastMemError) then begin
// struct is in unreadable memory
AnResData.CreateError(ErrorHandler.ErrorAsString(Context.LastMemError));
MemberValue.ReleaseReference;
exit;
end;
end;
MemberValue.ReleaseReference;
end;
finally
FRecurseInstanceCnt := WasRecurseInstanceCnt;
AnchestorMap.Free;
if Cache <> nil then
Context.MemManager.CacheManager.RemoveCache(Cache)
end;
end;
function TFpWatchResultConvertor.ProcToResData(AnFpValue: TFpValue;
AnResData: IDbgWatchDataIntf): Boolean;
var
addr: TDBGPtr;
s, LocName: String;
t, sym: TFpSymbol;
proc: TFpSymbolDwarf;
par: TFpValueDwarf;
begin
Result := True;
addr := AnFpValue.DataAddress.Address;
LocName := '';
if AnFpValue.Kind in [skFunctionRef, skProcedureRef] then begin
t := AnFpValue.TypeInfo;
sym := AnFpValue.DbgSymbol;
proc := nil;
if (sym <> nil) and (sym is TFpSymbolDwarfDataProc) then
proc := TFpSymbolDwarf(sym)
else
if t <> nil then
proc := TFpSymbolDwarf(TDbgDwarfSymbolBase(t).CompilationUnit.Owner.FindProcSymbol(addr));
if proc <> nil then begin
LocName := proc.Name;
if (proc is TFpSymbolDwarfDataProc) then begin
par := TFpSymbolDwarfDataProc(proc).GetSelfParameter; // Has no Context set, but we only need TypeInfo.Name
if (par <> nil) and (par.TypeInfo <> nil) then
LocName := par.TypeInfo.Name + '.' + LocName;
par.ReleaseReference;
end;
ReleaseRefAndNil(proc);
end;
end
else
t := AnFpValue.DbgSymbol;
GetTypeAsDeclaration(s, t);
case AnFpValue.Kind of
skProcedure: AnResData.CreateProcedure(addr, False, LocName, s);
skFunction: AnResData.CreateProcedure(addr, True, LocName, s);
skProcedureRef: AnResData.CreateProcedureRef(addr, False, LocName, s);
skFunctionRef: AnResData.CreateProcedureRef(addr, True, LocName, s);
end;
AddTypeNameToResData(AnFpValue, AnResData);
end;
function TFpWatchResultConvertor.DoValueToResData(AnFpValue: TFpValue;
AnResData: IDbgWatchDataIntf): Boolean;
var
PrettyPrinter: TFpPascalPrettyPrinter;
s: String;
begin
Result := False;
FEncounteredError := False;
case AnFpValue.Kind of
skPointer: Result := PointerToResData(AnFpValue, AnResData);
skInteger,
skCardinal: Result := NumToResData(AnFpValue, AnResData);
skFloat: Result := FloatToResData(AnFpValue, AnResData);
skChar: Result := CharToResData(AnFpValue, AnResData);
skString,
skAnsiString: Result := StringToResData(AnFpValue, AnResData);
skWideString: Result := WideStringToResData(AnFpValue, AnResData);
skRecord,
skObject,
skClass,
skInterface: Result := StructToResData(AnFpValue, AnResData);
//skNone: ;
//skInstance: ;
//skUnit: ;
skType: Result := TypeToResData(AnFpValue, AnResData);
skProcedure,
skFunction,
skProcedureRef,
skFunctionRef: Result := ProcToResData(AnFpValue, AnResData);
skSimple: ;
skBoolean: Result := BoolToResData(AnFpValue, AnResData);
skCurrency: ;
skVariant: ;
skEnum,
skEnumValue: Result := EnumToResData(AnFpValue, AnResData);
skSet: Result := SetToResData(AnFpValue, AnResData);
skArray: Result := ArrayToResData(AnFpValue, AnResData);
//skRegister: ;
//skAddress: ;
else begin
AnResData.CreateError('Unknown data');
Result := True;
end;
end;
if Result then
CheckError(AnFpValue, AnResData)
else
if FRecurseCnt > 0 then begin
PrettyPrinter := TFpPascalPrettyPrinter.Create(Context.SizeOfAddress);
PrettyPrinter.Context := Context;
PrettyPrinter.PrintValue(s, AnFpValue, wdfDefault, 1, [], [ppvSkipClassBody]);
AnResData.CreatePrePrinted(s);
PrettyPrinter.Free;
Result := True;
end;
end;
function TFpWatchResultConvertor.DoWriteWatchResultData(AnFpValue: TFpValue;
AnResData: IDbgWatchDataIntf): Boolean;
var
DidHaveEmbeddedPointer: Boolean;
begin
// FRecurseCnt should be handled by the caller
Result := (FRecurseCnt > MAX_RECURSE_LVL) or (AnFpValue = nil);
if Result then
exit;
Result := False;
DidHaveEmbeddedPointer := FHasEmbeddedPointer;
if (FRecurseCnt <= 0) and
( (FLastValueKind = skPointer) or (FRecurseCnt=-1) ) and
(AnFpValue.Kind = skPointer) and
(FRecursePointerCnt < MAX_RECURSE_LVL_PTR)
then begin
inc(FRecursePointerCnt);
end
else begin
inc(FRecurseCnt);
if (AnFpValue.Kind = skPointer) then
FHasEmbeddedPointer := True
else
if FHasEmbeddedPointer and (FLastValueKind <> skPointer) then // TODO: create a value as marker // also arrays cannot store the absence of a value
exit(True); // not an error
// Allow only one level, after an embedded pointer (pointer nested in other data-type)
end;
FLastValueKind := AnFpValue.Kind;
try
if vfVariant in AnFpValue.Flags then
AnResData := AnResData.CreateVariantValue;
Result := DoValueToResData(AnFpValue, AnResData);
finally
if FRecursePointerCnt > 0 then
dec(FRecursePointerCnt)
else
dec(FRecurseCnt);
FHasEmbeddedPointer := DidHaveEmbeddedPointer;
end;
end;
function TFpWatchResultConvertor.DoWritePointerWatchResultData(
AnFpValue: TFpValue; AnResData: IDbgWatchDataIntf; AnAddr: TDbgPtr
): Boolean;
begin
if FRecurseAddrList.IndexOf(AnAddr) >= 0 then begin
AnResData.CreateError('Recursive Value at '+HexStr(AnAddr, 16)); // TOOD: correct size // TODO: dedicated entry
exit(True);
end;
if AnAddr <> 0 then
FRecurseAddrList.Add(AnAddr);
Result := DoWriteWatchResultData(AnFpValue, AnResData);
if AnAddr <> 0 then
FRecurseAddrList.Remove(AnAddr);
end;
constructor TFpWatchResultConvertor.Create(AContext: TFpDbgLocationContext);
begin
inherited Create;
FRecurseAddrList := TDbgPtrList.Create;
FContext := AContext;
end;
destructor TFpWatchResultConvertor.Destroy;
begin
inherited Destroy;
FRecurseAddrList.Free;
end;
function TFpWatchResultConvertor.WriteWatchResultData(AnFpValue: TFpValue;
AnResData: IDbgWatchDataIntf; ARepeatCount: Integer): Boolean;
begin
Result := False;
if AnResData = nil then
exit;
if AnFpValue = nil then begin
AnResData.CreateError('No Data');
exit;
end;
if CheckError(AnFpValue, AnResData) then
exit;
FRecurseAddrList.Clear;
FRepeatCount := ARepeatCount;
FRecurseCnt := -1;
if FExtraDepth then
FRecurseCnt := -2;
FRecurseInstanceCnt := 0;
FRecurseDynArray := 0;
FRecurseArray := 0;
FRecursePointerCnt := 0;
FRecurseCntLow := FRecurseCnt+1;
FOuterArrayIdx := -1;
FTotalArrayCnt := 0;
FArrayTypeDone := False;
FLastValueKind := AnFpValue.Kind;
FHasEmbeddedPointer := False;
Result := DoWriteWatchResultData(AnFpValue, AnResData);
end;
end.