unit uPSDebugger; {$I PascalScript.inc} interface uses SysUtils, uPSRuntime, uPSUtils; type TDebugMode = (dmRun , dmStepOver , dmStepInto , dmPaused ); TPSCustomDebugExec = class(TPSExec) protected FDebugDataForProcs: TIfList; FLastProc: TPSProcRec; FCurrentDebugProc: Pointer; FProcNames: TIFStringList; FGlobalVarNames: TIfStringList; FCurrentSourcePos, FCurrentRow, FCurrentCol: Cardinal; FCurrentFile: tbtstring; function GetCurrentProcParams: TIfStringList; function GetCurrentProcVars: TIfStringList; protected procedure ClearDebug; virtual; public function GetCurrentProcNo: Cardinal; function GetCurrentPosition: Cardinal; function TranslatePosition(Proc, Position: Cardinal): Cardinal; function TranslatePositionEx(Proc, Position: Cardinal; var Pos, Row, Col: Cardinal; var Fn: tbtstring): Boolean; procedure LoadDebugData(const Data: tbtstring); procedure Clear; override; property GlobalVarNames: TIfStringList read FGlobalVarNames; property ProcNames: TIfStringList read FProcNames; property CurrentProcVars: TIfStringList read GetCurrentProcVars; property CurrentProcParams: TIfStringList read GetCurrentProcParams; function GetGlobalVar(I: Cardinal): PIfVariant; function GetProcVar(I: Cardinal): PIfVariant; function GetProcParam(I: Cardinal): PIfVariant; constructor Create; destructor Destroy; override; end; TPSDebugExec = class; TOnSourceLine = procedure (Sender: TPSDebugExec; const Name: tbtstring; Position, Row, Col: Cardinal); TOnIdleCall = procedure (Sender: TPSDebugExec); TPSDebugExec = class(TPSCustomDebugExec) private FDebugMode: TDebugMode; FStepOverProc: TPSInternalProcRec; FStepOverStackBase: Cardinal; FOnIdleCall: TOnIdleCall; FOnSourceLine: TOnSourceLine; FDebugEnabled: Boolean; protected procedure SourceChanged; procedure ClearDebug; override; procedure RunLine; override; public constructor Create; function LoadData(const s: tbtstring): Boolean; override; procedure Pause; override; procedure Run; procedure StepInto; procedure StepOver; procedure Stop; override; property DebugMode: TDebugMode read FDebugMode; property OnSourceLine: TOnSourceLine read FOnSourceLine write FOnSourceLine; property OnIdleCall: TOnIdleCall read FOnIdleCall write FOnIdleCall; property DebugEnabled: Boolean read FDebugEnabled write FDebugEnabled; end; TIFPSDebugExec = TPSDebugExec; implementation {$IFDEF DELPHI3UP } resourceString {$ELSE } const {$ENDIF } RPS_ExpectedReturnAddressStackBase = 'Expected return address at stack base'; type PPositionData = ^TPositionData; TPositionData = packed record FileName: tbtstring; Position, Row, Col, SourcePosition: Cardinal; end; PFunctionInfo = ^TFunctionInfo; TFunctionInfo = packed record Func: TPSProcRec; FParamNames: TIfStringList; FVariableNames: TIfStringList; FPositionTable: TIfList; end; { TPSCustomDebugExec } procedure TPSCustomDebugExec.Clear; begin inherited Clear; if FGlobalVarNames <> nil then ClearDebug; end; procedure TPSCustomDebugExec.ClearDebug; var i, j: Longint; p: PFunctionInfo; begin FCurrentDebugProc := nil; FLastProc := nil; FProcNames.Clear; FGlobalVarNames.Clear; FCurrentSourcePos := 0; FCurrentRow := 0; FCurrentCol := 0; FCurrentFile := ''; for i := 0 to FDebugDataForProcs.Count -1 do begin p := FDebugDataForProcs[I]; for j := 0 to p^.FPositionTable.Count -1 do begin Dispose(PPositionData(P^.FPositionTable[J])); end; p^.FPositionTable.Free; p^.FParamNames.Free; p^.FVariableNames.Free; Dispose(p); end; FDebugDataForProcs.Clear; end; constructor TPSCustomDebugExec.Create; begin inherited Create; FCurrentSourcePos := 0; FCurrentRow := 0; FCurrentCol := 0; FCurrentFile := ''; FDebugDataForProcs := TIfList.Create; FLastProc := nil; FCurrentDebugProc := nil; FProcNames := TIFStringList.Create; FGlobalVarNames := TIfStringList.Create; end; destructor TPSCustomDebugExec.Destroy; begin Clear; FDebugDataForProcs.Free; FProcNames.Free; FGlobalVarNames.Free; FGlobalVarNames := nil; inherited Destroy; end; function TPSCustomDebugExec.GetCurrentPosition: Cardinal; begin Result := TranslatePosition(GetCurrentProcNo, 0); end; function TPSCustomDebugExec.GetCurrentProcNo: Cardinal; var i: Longint; begin for i := 0 to FProcs.Count -1 do begin if FProcs[i]= FCurrProc then begin Result := I; Exit; end; end; Result := Cardinal(-1); end; function TPSCustomDebugExec.GetCurrentProcParams: TIfStringList; begin if FCurrentDebugProc <> nil then begin Result := PFunctionInfo(FCurrentDebugProc)^.FParamNames; end else Result := nil; end; function TPSCustomDebugExec.GetCurrentProcVars: TIfStringList; begin if FCurrentDebugProc <> nil then begin Result := PFunctionInfo(FCurrentDebugProc)^.FVariableNames; end else Result := nil; end; function TPSCustomDebugExec.GetGlobalVar(I: Cardinal): PIfVariant; begin Result := FGlobalVars[I]; end; function TPSCustomDebugExec.GetProcParam(I: Cardinal): PIfVariant; begin Result := FStack[Cardinal(Longint(FCurrStackBase) - Longint(I) - 1)]; end; function TPSCustomDebugExec.GetProcVar(I: Cardinal): PIfVariant; begin Result := FStack[Cardinal(Longint(FCurrStackBase) + Longint(I) + 1)]; end; function GetProcDebugInfo(FProcs: TIFList; Proc: TPSProcRec): PFunctionInfo; var i: Longint; c: PFunctionInfo; begin if Proc = nil then begin Result := nil; exit; end; for i := FProcs.Count -1 downto 0 do begin c := FProcs.Data^[I]; if c^.Func = Proc then begin Result := c; exit; end; end; new(c); c^.Func := Proc; c^.FPositionTable := TIfList.Create; c^.FVariableNames := TIfStringList.Create; c^.FParamNames := TIfStringList.Create; FProcs.Add(c); REsult := c; end; procedure TPSCustomDebugExec.LoadDebugData(const Data: tbtstring); var CP, I: Longint; c: tbtchar; CurrProcNo, LastProcNo: Cardinal; LastProc: PFunctionInfo; NewLoc: PPositionData; s: tbtstring; begin ClearDebug; if FStatus = isNotLoaded then exit; CP := 1; LastProcNo := Cardinal(-1); LastProc := nil; while CP <= length(Data) do begin c := Data[CP]; inc(cp); case c of #0: begin i := cp; if i > length(data) then exit; while Data[i] <> #0 do begin if Data[i] = #1 then begin FProcNames.Add(Copy(Data, cp, i-cp)); cp := I + 1; end; inc(I); if I > length(data) then exit; end; cp := i + 1; end; #1: begin i := cp; if i > length(data) then exit; while Data[i] <> #0 do begin if Data[i] = #1 then begin FGlobalVarNames.Add(Copy(Data, cp, i-cp)); cp := I + 1; end; inc(I); if I > length(data) then exit; end; cp := i + 1; end; #2: begin if cp + 4 > Length(data) then exit; CurrProcNo := Cardinal((@Data[cp])^); if CurrProcNo = Cardinal(-1) then Exit; if CurrProcNo <> LastProcNo then begin LastProcNo := CurrProcNo; LastProc := GetProcDebugInfo(FDebugDataForProcs, FProcs[CurrProcNo]); if LastProc = nil then exit; end; inc(cp, 4); i := cp; if i > length(data) then exit; while Data[i] <> #0 do begin if Data[i] = #1 then begin LastProc^.FParamNames.Add(Copy(Data, cp, i-cp)); cp := I + 1; end; inc(I); if I > length(data) then exit; end; cp := i + 1; end; #3: begin if cp + 4 > Length(data) then exit; CurrProcNo := Cardinal((@Data[cp])^); if CurrProcNo = Cardinal(-1) then Exit; if CurrProcNo <> LastProcNo then begin LastProcNo := CurrProcNo; LastProc := GetProcDebugInfo(FDebugDataForProcs, FProcs[CurrProcNo]); if LastProc = nil then exit; end; inc(cp, 4); i := cp; if i > length(data) then exit; while Data[i] <> #0 do begin if Data[i] = #1 then begin LastProc^.FVariableNames.Add(Copy(Data, cp, i-cp)); cp := I + 1; end; inc(I); if I > length(data) then exit; end; cp := i + 1; end; #4: begin i := cp; if i > length(data) then exit; while Data[i] <> #0 do begin if Data[i] = #1 then begin s := Copy(Data, cp, i-cp); cp := I + 1; Break; end; inc(I); if I > length(data) then exit; end; if cp + 4 > Length(data) then exit; CurrProcNo := Cardinal((@Data[cp])^); if CurrProcNo = Cardinal(-1) then Exit; if CurrProcNo <> LastProcNo then begin LastProcNo := CurrProcNo; LastProc := GetProcDebugInfo(FDebugDataForProcs, FProcs[CurrProcNo]); if LastProc = nil then exit; end; inc(cp, 4); if cp + 16 > Length(data) then exit; new(NewLoc); NewLoc^.Position := Cardinal((@Data[Cp])^); NewLoc^.FileName := s; NewLoc^.SourcePosition := Cardinal((@Data[Cp+4])^); NewLoc^.Row := Cardinal((@Data[Cp+8])^); NewLoc^.Col := Cardinal((@Data[Cp+12])^); inc(cp, 16); LastProc^.FPositionTable.Add(NewLoc); end; else begin ClearDebug; Exit; end; end; end; end; function TPSCustomDebugExec.TranslatePosition(Proc, Position: Cardinal): Cardinal; var D1, D2: Cardinal; s: tbtstring; begin if not TranslatePositionEx(Proc, Position, Result, D1, D2, s) then Result := 0; end; function TPSCustomDebugExec.TranslatePositionEx(Proc, Position: Cardinal; var Pos, Row, Col: Cardinal; var Fn: tbtstring): Boolean; // Made by Martijn Laan (mlaan@wintax.nl) var i: LongInt; fi: PFunctionInfo; pt: TIfList; r: PPositionData; lastfn: tbtstring; LastPos, LastRow, LastCol: Cardinal; pp: TPSProcRec; begin fi := nil; pp := FProcs[Proc]; for i := 0 to FDebugDataForProcs.Count -1 do begin fi := FDebugDataForProcs[i]; if fi^.Func = pp then Break; fi := nil; end; LastPos := 0; LastRow := 0; LastCol := 0; if fi <> nil then begin pt := fi^.FPositionTable; for i := 0 to pt.Count -1 do begin r := pt[I]; if r^.Position >= Position then begin if r^.Position = Position then begin Pos := r^.SourcePosition; Row := r^.Row; Col := r^.Col; Fn := r^.Filename; end else begin Pos := LastPos; Row := LastRow; Col := LastCol; Fn := LastFn; end; Result := True; exit; end else begin LastPos := r^.SourcePosition; LastRow := r^.Row; LastCol := r^.Col; LastFn := r^.FileName; end; end; Pos := LastPos; Row := LastRow; Col := LastCol; Result := True; end else begin Result := False; end; end; { TPSDebugExec } procedure TPSDebugExec.ClearDebug; begin inherited; FDebugMode := dmRun; end; function TPSDebugExec.LoadData(const s: tbtstring): Boolean; begin Result := inherited LoadData(s); FDebugMode := dmRun; end; procedure TPSDebugExec.RunLine; var i: Longint; pt: TIfList; r: PPositionData; begin inherited RunLine; if not DebugEnabled then exit; if FCurrProc <> FLastProc then begin FLastProc := FCurrProc; FCurrentDebugProc := nil; for i := 0 to FDebugDataForProcs.Count -1 do begin if PFunctionInfo(FDebugDataForProcs[I])^.Func = FLastProc then begin FCurrentDebugProc := FDebugDataForProcs[I]; break; end; end; end; if FCurrentDebugProc <> nil then begin pt := PFunctionInfo(FCurrentDebugProc)^.FPositionTable; for i := 0 to pt.Count -1 do begin r := pt[I]; if r^.Position = FCurrentPosition then begin FCurrentSourcePos := r^.SourcePosition; FCurrentRow := r^.Row; FCurrentCol := r^.Col; FCurrentFile := r^.FileName; SourceChanged; break; end; end; end else begin FCurrentSourcePos := 0; FCurrentRow := 0; FCurrentCol := 0; FCurrentFile := ''; end; while FDebugMode = dmPaused do begin if @FOnIdleCall <> nil then begin FOnIdleCall(Self); end else break; // endless loop end; end; procedure TPSDebugExec.SourceChanged; function StepOverShouldPause: Boolean; var I: Cardinal; V: PPSVariant; begin if (FCurrProc <> FStepOverProc) or (FCurrStackBase <> FStepOverStackBase) then begin { We're not inside the function being stepped, so scan the call stack to see if we're inside a function called by the function being stepped } I := FCurrStackBase; while Longint(I) > Longint(FStepOverStackBase) do begin V := FStack.Items[I]; if (V = nil) or (V.FType <> FReturnAddressType) then raise Exception.Create(RPS_ExpectedReturnAddressStackBase); if (PPSVariantReturnAddress(V).Addr.ProcNo = FStepOverProc) and (PPSVariantReturnAddress(V).Addr.StackBase = FStepOverStackBase) then begin { We are, so don't pause } Result := False; Exit; end; I := PPSVariantReturnAddress(V).Addr.StackBase; end; end; Result := True; end; begin case FDebugMode of dmStepInto: begin FDebugMode := dmPaused; end; dmStepOver: begin if StepOverShouldPause then begin FDebugMode := dmPaused; end; end; end; if @FOnSourceLine <> nil then FOnSourceLine(Self, FCurrentFile, FCurrentSourcePos, FCurrentRow, FCurrentCol); end; procedure TPSDebugExec.Pause; begin FDebugMode := dmPaused; end; procedure TPSDebugExec.Stop; begin FDebugMode := dmRun; inherited Stop; end; procedure TPSDebugExec.Run; begin FDebugMode := dmRun; end; procedure TPSDebugExec.StepInto; begin FDebugMode := dmStepInto; end; procedure TPSDebugExec.StepOver; begin FStepOverProc := FCurrProc; FStepOverStackBase := FCurrStackBase; FDebugMode := dmStepOver; end; constructor TPSDebugExec.Create; begin inherited Create; FDebugEnabled := True; end; end.