unit FpDbgCallContextInfo; {$mode objfpc}{$H+} {$IFDEF INLINE_OFF}{$INLINE OFF}{$ENDIF} interface uses DbgIntfBaseTypes, math, FpDbgInfo, FpdMemoryTools, FpDbgDwarfDataClasses, FpDbgDwarf, FpDbgClasses, FpErrorMessages, FpDbgUtil, {$ifdef FORCE_LAZLOGGER_DUMMY} LazLoggerDummy {$else} LazLoggerBase {$endif}, LazClasses; type { TFpSymbolCallParamOrdinalOrPointer } TFpSymbolCallParamOrdinalOrPointer = class(TFpSymbolDwarfTypeBasic) // act as pointer private type { TFpValueCallParamStringByRef } TFpValueCallParamStringByRef = class(TFpValueDwarfPointer) function GetDwarfDataAddress(out AnAddress: TFpDbgMemLocation; ATargetType: TFpSymbolDwarfType = nil): Boolean; override; end; protected procedure KindNeeded; override; procedure Init; override; public constructor Create(AName: String; AStringVarAddress: TDBGPtr); function GetTypedValueObject(ATypeCast: Boolean; AnOuterType: TFpSymbolDwarfType = nil): TFpValueDwarf; override; end; { TFpSymbolDwarfFunctionResult } TFpSymbolDwarfFunctionResult = class(TFpSymbolDwarfDataWithLocation) protected function GetValueAddress(AValueObj: TFpValueDwarf; out AnAddress: TFpDbgMemLocation): Boolean; override; procedure Init; override; public constructor Create(const AName: String; const AAddress: TFpDbgMemLocation; ATypeSymbol: TFpSymbol); overload; property NewAddress: TFpDbgMemLocation write SetAddress; end; { TFpDbgInfoCallContext } TOnCallRoutineHitBreapoint = procedure(AnAddress: TDBGPtr; out ACanContinue: Boolean) of object; TFpDbgInfoCallContext = class(TFpDbgAbstractCallContext) private FDbgProcess: TDbgProcess; FDbgThread: TDbgThread; FLastError: TFpError; FNextParamRegister: Integer; FOnCallRoutineHitBreapoint: TOnCallRoutineHitBreapoint; FOrigStackPtr: TDBGPtr; FPreparedStack: Array of Byte; FNeedStringResInFinalize: Boolean; FStringResultMem: TDBGPtr; function AllocStack(ASize: Integer): TDbgPtr; function CreatePreparedStackLocation(ASize: Integer): TFpDbgMemLocation; function InternalGetLocation(AParamIndex: Integer; ASize: Integer = 0): TFpDbgMemLocation; function InternalCreateParamSymbol(ParameterMemLocation: TFpDbgMemLocation; ASymbolType: TFpSymbol; AName: String): TFpValue; function InternalCreateParamSymbol(AParamIndex: Integer; ASymbolType: TFpSymbol; AName: String): TFpValue; inline; function AddRecordParam(AParamSymbolType: TFpSymbol; AValue: TFpValue): Boolean; function InternalAddStringResult: Boolean; public constructor Create(const ABaseContext: TFpDbgLocationContext; AMemReader: TFpDbgMemReaderBase; AMemModel: TFpDbgMemModel; AMemConverter: TFpDbgMemConvertor; ADbgProcess: TDbgProcess; ADbgThread: TDbgThread); destructor Destroy; override; function WriteStack: Boolean; // called by controller-command function CreateParamSymbol(AParamIndex: Integer; ASymbolType: TFpSymbol; AName: String = ''): TFpValue; virtual; function AddParam(AParamSymbolType: TFpSymbol; AValue: TFpValue): Boolean; function AddOrdinalParam(AParamSymbolType: TFpSymbol; AValue: QWord): Boolean; function AddOrdinalParam(AValue: QWord): Boolean; inline; (* AddOrdinalViaRefAsParam TODO: need size of the ordinal -- currently using SizeOfAddr in target *) function AddOrdinalViaRefAsParam(AValue: QWord): Boolean; inline; // For string dec-ref function AddOrdinalViaRefAsParam(AValue: QWord; out ATargetParamAddr: TDBGPtr): Boolean; (* AddStringResult: Must be called before any AddParam. Except for "Self": In case of a method, AddParm(self) must be set before the StringResult *) function AddStringResult: Boolean; function FinalizeParams: Boolean; // The caller must take care to call DecRef for the result function GetStringResultAsPointer(out AStringAsPtr: TDbgPtr): Boolean; function GetStringResult(out AVal: TFpValue; AStringSymbolType: TFpSymbol = nil): Boolean; function GetWideStringResult(out AVal: TFpValue; AStringSymbolType: TFpSymbol = nil): Boolean; property LastError: TFpError read FLastError; property OnCallRoutineHitBreapoint: TOnCallRoutineHitBreapoint read FOnCallRoutineHitBreapoint write FOnCallRoutineHitBreapoint; end; implementation var FPDBG_FUNCCALL: PLazLoggerLogGroup; { TFpSymbolCallParamOrdinalOrPointer.TFpValueCallParamStringByRef } function TFpSymbolCallParamOrdinalOrPointer.TFpValueCallParamStringByRef.GetDwarfDataAddress (out AnAddress: TFpDbgMemLocation; ATargetType: TFpSymbolDwarfType): Boolean; begin AnAddress := Address; Result := MemManager.MemModel.IsReadableLocation(AnAddress); end; { TFpSymbolCallParamOrdinalOrPointer } procedure TFpSymbolCallParamOrdinalOrPointer.KindNeeded; begin SetKind(skPointer); end; procedure TFpSymbolCallParamOrdinalOrPointer.Init; begin inherited Init; EvaluatedFields := EvaluatedFields + [sfiAddress]; end; function TFpSymbolCallParamOrdinalOrPointer.GetTypedValueObject(ATypeCast: Boolean; AnOuterType: TFpSymbolDwarfType): TFpValueDwarf; begin Result := TFpValueCallParamStringByRef.Create(AnOuterType); end; constructor TFpSymbolCallParamOrdinalOrPointer.Create(AName: String; AStringVarAddress: TDBGPtr); begin inherited Create(AName, skPointer, TargetLoc(AStringVarAddress)); SetTypeInfo(TFpSymbolDwarfTypePointer.Create(AName)); TypeInfo.ReleaseReference; Init; end; { TFpSymbolDwarfFunctionResult } function TFpSymbolDwarfFunctionResult.GetValueAddress(AValueObj: TFpValueDwarf; out AnAddress: TFpDbgMemLocation): Boolean; begin AnAddress := Address; Result := IsInitializedLoc(AnAddress); end; procedure TFpSymbolDwarfFunctionResult.Init; begin inherited Init; EvaluatedFields := EvaluatedFields + [sfiAddress]; end; constructor TFpSymbolDwarfFunctionResult.Create(const AName: String; const AAddress: TFpDbgMemLocation; ATypeSymbol: TFpSymbol); begin inherited Create(AName, ATypeSymbol.Kind, AAddress); // TODO: intercept at TDbgDwarfSymbolBase and call Init? SetTypeInfo(ATypeSymbol); Init; end; { TFpDbgInfoCallContext } function TFpDbgInfoCallContext.AllocStack(ASize: Integer): TDbgPtr; begin Result := FDbgThread.GetStackPointerRegisterValue; if FOrigStackPtr = 0 then FOrigStackPtr := Result; {$IF defined(WINDOWS)} if FDbgProcess.Mode = dm64 then begin ASize := (ASize + 32) and not(31); // keep aligned to 32 bytes end else {$ENDIF} ASize := (ASize + 8) and not(7); // keep aligned to 8 bytes dec(Result, ASize); FDbgThread.SetStackPointerRegisterValue(Result); end; function TFpDbgInfoCallContext.CreatePreparedStackLocation(ASize: Integer ): TFpDbgMemLocation; var l: SizeInt; begin l := Length(FPreparedStack); SetLength(FPreparedStack, l + ASize); if FDbgProcess.Mode = dm32 then begin if l > 0 then move(FPreparedStack[0], FPreparedStack[ASize], SizeOf(FPreparedStack[0]) * l); l := 0; end; Result := SelfLoc(@FPreparedStack[l]); end; function TFpDbgInfoCallContext.InternalGetLocation(AParamIndex: Integer; ASize: Integer): TFpDbgMemLocation; begin if (FDbgProcess.Mode = dm32) and (ASize = 8) then begin Result := CreatePreparedStackLocation(ASize); exit; end; Result := FDbgProcess.CallParamDefaultLocation(AParamIndex); if IsValidLoc(Result) then exit; if ASize = 0 then ASize := SizeOfAddress; Result := CreatePreparedStackLocation(ASize); end; function TFpDbgInfoCallContext.InternalCreateParamSymbol( ParameterMemLocation: TFpDbgMemLocation; ASymbolType: TFpSymbol; AName: String ): TFpValue; var ParamSymbol: TFpSymbol;// TFpSymbolDwarfFunctionResult; begin Result := nil; if not IsValidLoc(ParameterMemLocation) then begin FLastError := CreateError(fpErrAnyError, ['Too many params']); exit; end; ParamSymbol := TFpSymbolDwarfFunctionResult.Create(AName, ParameterMemLocation, ASymbolType); try Result := ParamSymbol.Value; finally ParamSymbol.ReleaseReference; end; TFpValueDwarf(Result).Context := Self; end; function TFpDbgInfoCallContext.InternalCreateParamSymbol(AParamIndex: Integer; ASymbolType: TFpSymbol; AName: String): TFpValue; begin Result := InternalCreateParamSymbol(InternalGetLocation(AParamIndex), ASymbolType, AName); end; function TFpDbgInfoCallContext.AddRecordParam(AParamSymbolType: TFpSymbol; AValue: TFpValue): Boolean; // Only intel/amd function ReadRecFromMem(Addr: TDbgPtr; sz: Integer; out Data: QWord): Boolean; var d: QWord; begin Result := FDbgProcess.ReadData(addr, sz, d); if Result then begin Data := 0; Move(d, Data, sz); end else begin FLastError := CreateError(fpErrAnyError, ['failed to read record data from memory']); end; end; function HasUnalignedFields: Boolean; var i: Integer; FldLoc: TFpDbgMemLocation; FldSize: TFpDbgValueSize; RecAddr, FldOffs: TDBGPtr; m: TFpValue; begin Result := False; RecAddr := AValue.Address.Address; m := nil; for i := 0 to AValue.MemberCount - 1 do begin m.ReleaseReference; m := AValue.Member[i]; FldLoc := m.Address; Result := FldLoc.BitOffset <> 0; if Result then break; FldOffs := FldLoc.Address - RecAddr; FldSize := m.DataSize; Result := FldSize.BitSize <> 0; if Result then break; Result := FldOffs mod FldSize.Size <> 0; if Result then break; end; m.ReleaseReference; end; var ParamSymbol: TFpValue; procedure InitParamSymbol; begin ParamSymbol := InternalCreateParamSymbol(FNextParamRegister, AParamSymbolType, ''); inc(FNextParamRegister); if Length(FPreparedStack) > 0 then MemManager.SetWritableSeflMem(TDBGPtr(@FPreparedStack[0]), Length(FPreparedStack)); end; var {$If defined(UNIX)} i: Integer; {$ENDIF} RecSize: Integer; RecLoc: TFpDbgMemLocation; RecAddr, RecData: TDBGPtr; l: SizeInt; begin ParamSymbol := nil; try RecSize := SizeToFullBytes(AValue.DataSize); Result := not IsError(AValue.LastError); if Result then begin RecLoc := AValue.Address; Result := IsValidLoc(RecLoc); RecAddr := RecLoc.Address; end; if not Result then begin FLastError := AValue.LastError; exit; end; {$IF defined(WINDOWS)} if FDbgProcess.Mode = dm32 then begin if (RecSize <= FDbgProcess.PointerSize) then begin Result := ReadRecFromMem(RecAddr, RecSize, RecData); if not Result then exit; l := Length(FPreparedStack); SetLength(FPreparedStack, l + 4); if l > 0 then move(FPreparedStack[0], FPreparedStack[4], SizeOf(FPreparedStack[0]) * l); PDWord(@FPreparedStack[0])^ := RecData; exit; end; InitParamSymbol; ParamSymbol.AsCardinal := RecAddr; Result := not IsError(ParamSymbol.LastError); FLastError := ParamSymbol.LastError; end else begin InitParamSymbol; if (RecSize <= FDbgProcess.PointerSize) and (RecSize in [2,4,8]) then begin Result := ReadRecFromMem(RecAddr, RecSize, RecData); RecAddr := RecData; end; if Result then begin ParamSymbol.AsCardinal := RecAddr; Result := not IsError(ParamSymbol.LastError); FLastError := ParamSymbol.LastError; end; end; {$ElseIf defined(UNIX)} if FDbgProcess.Mode = dm64 then begin if (RecSize <= 16) and not HasUnalignedFields then begin // Use 1 or 2 registers While RecSize > 0 do begin ParamSymbol.ReleaseReference; InitParamSymbol; Result := ReadRecFromMem(RecAddr, Min(8, RecSize), RecData); if not Result then exit; ParamSymbol.AsCardinal := RecData; Result := not IsError(ParamSymbol.LastError); if not result then begin FLastError := ParamSymbol.LastError; exit; end; dec(RecSize, 8); inc(RecAddr, 8); end; end else begin // on the stack i := (RecSize+7) and $FFFFFFF8; l := Length(FPreparedStack); SetLength(FPreparedStack, l + i); Result := FDbgProcess.ReadData(RecAddr, RecSize, FPreparedStack[l]); if not Result then begin FLastError := CreateError(fpErrAnyError, ['failed to read mem']); exit; end; end; end else begin // 32bit linux if (RecSize <= 4) then begin Result := ReadRecFromMem(RecAddr, 4, RecData); if not Result then exit; l := Length(FPreparedStack); SetLength(FPreparedStack, l + 4); if l > 0 then move(FPreparedStack[0], FPreparedStack[4], SizeOf(FPreparedStack[0]) * l); PDWord(@FPreparedStack[0])^ := RecData; end else begin InitParamSymbol; ParamSymbol.AsCardinal := RecAddr; Result := not IsError(ParamSymbol.LastError); FLastError := ParamSymbol.LastError; end; end; {$Else} Result := False; FLastError := CreateError(fpErrAnyError, ['record as param are not supported']); {$ENDIF} finally ParamSymbol.ReleaseReference; MemManager.ClearWritableSeflMem; end; end; function TFpDbgInfoCallContext.InternalAddStringResult: Boolean; var ParamSymbol: TFpValue; RefSym: TFpSymbolCallParamOrdinalOrPointer; begin RefSym := TFpSymbolCallParamOrdinalOrPointer.Create('', 0); ParamSymbol := InternalCreateParamSymbol(FNextParamRegister, RefSym, ''); try Result := ParamSymbol <> nil; if not Result then begin exit; end; ParamSymbol.AsCardinal := FStringResultMem; Result := not IsError(ParamSymbol.LastError); FLastError := ParamSymbol.LastError; finally ParamSymbol.ReleaseReference; RefSym.ReleaseReference; end; inc(FNextParamRegister); end; constructor TFpDbgInfoCallContext.Create( const ABaseContext: TFpDbgLocationContext; AMemReader: TFpDbgMemReaderBase; AMemModel: TFpDbgMemModel; AMemConverter: TFpDbgMemConvertor; ADbgProcess: TDbgProcess; ADbgThread: TDbgThread); begin inherited Create(ABaseContext, AMemReader, AMemModel, AMemConverter); FDbgProcess := ADbgProcess; FDbgThread := ADbgThread; FNextParamRegister := 0; end; destructor TFpDbgInfoCallContext.Destroy; begin inherited Destroy; if FOrigStackPtr <> 0 then FDbgThread.SetStackPointerRegisterValue(FOrigStackPtr); debugln(FPDBG_FUNCCALL, ['CallRoutine END (CTX DESTROY)']); end; function TFpDbgInfoCallContext.WriteStack: Boolean; var m: TDBGPtr; begin Result := True; if Length(FPreparedStack) = 0 then exit; {$IF defined(WINDOWS)} if FDbgProcess.Mode = dm64 then begin m := AllocStack(Length(FPreparedStack)+32) + 32; end else {$ENDIF} m := AllocStack(Length(FPreparedStack)); Result := FDbgProcess.WriteData(m, Length(FPreparedStack), FPreparedStack[0]); if not Result then FLastError := CreateError(fpErrAnyError, ['failed to write call info to stack memory']); end; function TFpDbgInfoCallContext.CreateParamSymbol(AParamIndex: Integer; ASymbolType: TFpSymbol; AName: String): TFpValue; begin if AName = '' then AName := ASymbolType.Name; Result := InternalCreateParamSymbol(AParamIndex, ASymbolType.TypeInfo, AName); end; function TFpDbgInfoCallContext.AddParam(AParamSymbolType: TFpSymbol; AValue: TFpValue): Boolean; var ParamSymbol: TFpSymbolDwarfFunctionResult; ParamValue: TFpValue; s: TFpDbgValueSize; begin if AValue.Kind = skRecord then begin Result := AddRecordParam(AParamSymbolType, AValue); exit; end; Result := False; // ParamValue := InternalCreateParamSymbol(FNextParamRegister, AParamSymbolType, ''); ParamValue := nil; ParamSymbol := TFpSymbolDwarfFunctionResult.Create('', InvalidLoc, AParamSymbolType); try ParamValue := ParamSymbol.Value; TFpValueDwarf(ParamValue).Context := Self; if (FDbgProcess.Mode = dm32) and (ParamValue.GetSize(s)) and (SizeToFullBytes(s) = 8) then ParamSymbol.NewAddress := InternalGetLocation(FNextParamRegister, SizeToFullBytes(s)) else ParamSymbol.NewAddress := InternalGetLocation(FNextParamRegister); Result := ParamValue <> nil; if not Result then exit; if Length(FPreparedStack) > 0 then MemManager.SetWritableSeflMem(TDBGPtr(@FPreparedStack[0]), Length(FPreparedStack)); ParamValue.AsCardinal := AValue.AsCardinal; Result := not IsError(ParamValue.LastError); FLastError := ParamValue.LastError; finally ParamSymbol.ReleaseReference; ParamValue.ReleaseReference; MemManager.ClearWritableSeflMem; end; inc(FNextParamRegister); end; function TFpDbgInfoCallContext.AddOrdinalParam(AParamSymbolType: TFpSymbol; AValue: QWord): Boolean; var ParamSymbol: TFpValue; begin debugln(FPDBG_FUNCCALL, ['TFpDbgInfoCallContext.AddOrdinalParam ',FNextParamRegister]); Result := False; if AParamSymbolType = nil then AParamSymbolType := TFpSymbolCallParamOrdinalOrPointer.Create('', 0) else AParamSymbolType.AddReference; ParamSymbol := InternalCreateParamSymbol(FNextParamRegister, AParamSymbolType, ''); AParamSymbolType.ReleaseReference; Result := ParamSymbol <> nil; if not Result then exit; try ParamSymbol.AsCardinal := AValue; Result := not IsError(ParamSymbol.LastError); FLastError := ParamSymbol.LastError; finally ParamSymbol.ReleaseReference; end; inc(FNextParamRegister); end; function TFpDbgInfoCallContext.AddOrdinalParam(AValue: QWord): Boolean; begin Result := AddOrdinalParam(nil, AValue); end; function TFpDbgInfoCallContext.AddStringResult: Boolean; var ANil: QWord; begin Result := True; ANil := 0; FStringResultMem := AllocStack(32); // TODO: only Win64 needs 32 alignemnt Result := FDbgProcess.WriteData(FStringResultMem, FDbgProcess.PointerSize, ANil); if not Result then begin FLastError := CreateError(fpErrAnyError, ['Error writing result param to stack memory']); exit; end; FNeedStringResInFinalize := FDbgProcess.Mode = dm32; if not FNeedStringResInFinalize then Result := InternalAddStringResult; end; function TFpDbgInfoCallContext.AddOrdinalViaRefAsParam(AValue: QWord): Boolean; var m: TDBGPtr; begin Result := AddOrdinalViaRefAsParam(AValue, m); end; function TFpDbgInfoCallContext.AddOrdinalViaRefAsParam(AValue: QWord; out ATargetParamAddr: TDBGPtr): Boolean; var ParamSymbol: TFpValue; RefSym: TFpSymbolCallParamOrdinalOrPointer; begin ATargetParamAddr := AllocStack(32); // TODO: need size of the ordinal Result := FDbgProcess.WriteData(ATargetParamAddr, FDbgProcess.PointerSize, AValue); if not Result then begin FLastError := CreateError(fpErrAnyError, ['Error writing param param to stack memory']); exit; end; RefSym := TFpSymbolCallParamOrdinalOrPointer.Create('', ATargetParamAddr); ParamSymbol := InternalCreateParamSymbol(FNextParamRegister, RefSym, ''); try Result := ParamSymbol <> nil; if not Result then exit; ParamSymbol.AsCardinal := ATargetParamAddr; Result := not IsError(ParamSymbol.LastError); FLastError := ParamSymbol.LastError; finally ParamSymbol.ReleaseReference; RefSym.ReleaseReference; end; inc(FNextParamRegister); end; function TFpDbgInfoCallContext.FinalizeParams: Boolean; begin Result := True; if FNeedStringResInFinalize then Result := InternalAddStringResult; end; function TFpDbgInfoCallContext.GetStringResultAsPointer(out AStringAsPtr: TDbgPtr): Boolean; begin Result := FDbgProcess.ReadAddress(FStringResultMem, AStringAsPtr); if not Result then FLastError := CreateError(fpErrAnyError, ['failed to read result from stack mem']); end; function TFpDbgInfoCallContext.GetStringResult(out AVal: TFpValue; AStringSymbolType: TFpSymbol): Boolean; var Addr, l: TDbgPtr; ResSymbol: TFpValue; s: String; r: Cardinal; begin AVal := nil; Result := GetStringResultAsPointer(Addr); if not Result then exit; if AStringSymbolType <> nil then begin ResSymbol := InternalCreateParamSymbol(ConstDerefLoc(Addr), AStringSymbolType, 'result'); AVal := TFpValueConstString.Create(ResSymbol.AsString); Result := IsError(ResSymbol.LastError); if not Result then FLastError := ResSymbol.LastError; ReleaseRefAndNil(ResSymbol); exit; end; s := ''; if Addr <> 0 then begin Result := FDbgProcess.ReadAddress(Addr - FDbgProcess.PointerSize, l); if Result then begin l := min(l, 1000*1024); SetLength(s, l); Result := FDbgProcess.ReadData(Addr, l, s[1], r); SetLength(s,r); end; if not Result then begin FLastError := CreateError(fpErrAnyError, ['failed to read result from mem']); exit; end; end; AVal := TFpValueConstString.Create(s); end; function TFpDbgInfoCallContext.GetWideStringResult(out AVal: TFpValue; AStringSymbolType: TFpSymbol): Boolean; var Addr, l: TDbgPtr; ResSymbol: TFpValue; s: WideString; r: Cardinal; begin AVal := nil; Result := GetStringResultAsPointer(Addr); if not Result then exit; if AStringSymbolType <> nil then begin ResSymbol := InternalCreateParamSymbol(ConstDerefLoc(Addr), AStringSymbolType, 'result'); AVal := TFpValueConstString.Create(ResSymbol.AsString); Result := IsError(ResSymbol.LastError); if not Result then FLastError := ResSymbol.LastError; ReleaseRefAndNil(ResSymbol); exit; end; s := ''; if Addr <> 0 then begin Result := FDbgProcess.ReadAddress(Addr - FDbgProcess.PointerSize, l); if Result then begin l := min(l, 1000*1024); SetLength(s, l); Result := FDbgProcess.ReadData(Addr, l*2, s[1], r); SetLength(s,r); end; if not Result then begin FLastError := CreateError(fpErrAnyError, ['failed to read result from mem']); exit; end; end; AVal := TFpValueConstString.Create(s); end; initialization FPDBG_FUNCCALL := DebugLogger.FindOrRegisterLogGroup('FPDBG_FUNCCALL' {$IFDEF FPDBG_FUNCCALL} , True {$ENDIF} ); end.