1 
2 unit uPSDebugger;
3 {$I PascalScript.inc}
4 interface
5 uses
6   SysUtils, uPSRuntime, uPSUtils;
7 
8 type
9 
10   TDebugMode = (dmRun
11   , dmStepOver
12   , dmStepInto
13   , dmPaused
14   );
15 
16   TPSCustomDebugExec = class(TPSExec)
17   protected
18     FDebugDataForProcs: TIfList;
19     FLastProc: TPSProcRec;
20     FCurrentDebugProc: Pointer;
21     FProcNames: TIFStringList;
22     FGlobalVarNames: TIfStringList;
23     FCurrentSourcePos, FCurrentRow, FCurrentCol: Cardinal;
24     FCurrentFile: tbtstring;
25 
GetCurrentProcParamsnull26     function GetCurrentProcParams: TIfStringList;
27 
GetCurrentProcVarsnull28     function GetCurrentProcVars: TIfStringList;
29   protected
30 
31     procedure ClearDebug; virtual;
32   public
33 
GetCurrentProcNonull34     function GetCurrentProcNo: Cardinal;
35 
GetCurrentPositionnull36     function GetCurrentPosition: Cardinal;
37 
TranslatePositionnull38     function TranslatePosition(Proc, Position: Cardinal): Cardinal;
39 
TranslatePositionExnull40     function TranslatePositionEx(Proc, Position: Cardinal; var Pos, Row, Col: Cardinal; var Fn: tbtstring): Boolean;
41 
42     procedure LoadDebugData(const Data: tbtstring);
43 
44     procedure Clear; override;
45 
46     property GlobalVarNames: TIfStringList read FGlobalVarNames;
47 
48     property ProcNames: TIfStringList read FProcNames;
49 
50     property CurrentProcVars: TIfStringList read GetCurrentProcVars;
51 
52     property CurrentProcParams: TIfStringList read GetCurrentProcParams;
53 
GetGlobalVarnull54     function GetGlobalVar(I: Cardinal): PIfVariant;
55 
GetProcVarnull56     function GetProcVar(I: Cardinal): PIfVariant;
57 
GetProcParamnull58     function GetProcParam(I: Cardinal): PIfVariant;
59 
60     constructor Create;
61 
62     destructor Destroy; override;
63   end;
64   TPSDebugExec = class;
65 
66   TOnSourceLine = procedure (Sender: TPSDebugExec; const Name: tbtstring; Position, Row, Col: Cardinal);
67 
68   TOnIdleCall = procedure (Sender: TPSDebugExec);
69 
70   TPSDebugExec = class(TPSCustomDebugExec)
71   private
72     FDebugMode: TDebugMode;
73     FStepOverProc: TPSInternalProcRec;
74     FStepOverStackBase: Cardinal;
75     FOnIdleCall: TOnIdleCall;
76     FOnSourceLine: TOnSourceLine;
77     FDebugEnabled: Boolean;
78   protected
79 
80     procedure SourceChanged;
81     procedure ClearDebug; override;
82     procedure RunLine; override;
83   public
84     constructor Create;
85 
LoadDatanull86     function LoadData(const s: tbtstring): Boolean; override;
87 
88     procedure Pause; override;
89 
90     procedure Run;
91 
92     procedure StepInto;
93 
94     procedure StepOver;
95 
96     procedure Stop; override;
97 
98     property DebugMode: TDebugMode read FDebugMode;
99 
100     property OnSourceLine: TOnSourceLine read FOnSourceLine write FOnSourceLine;
101 
102     property OnIdleCall: TOnIdleCall read FOnIdleCall write FOnIdleCall;
103 
104     property DebugEnabled: Boolean read FDebugEnabled write FDebugEnabled;
105   end;
106   TIFPSDebugExec = TPSDebugExec;
107 
108 implementation
109 
110 {$IFDEF DELPHI3UP }
111 resourceString
112 {$ELSE }
113 const
114 {$ENDIF }
115 
116   RPS_ExpectedReturnAddressStackBase = 'Expected return address at stack base';
117 
118 type
119   PPositionData = ^TPositionData;
120   TPositionData = packed record
121     FileName: tbtstring;
122     Position,
123     Row,
124     Col,
125     SourcePosition: Cardinal;
126   end;
127   PFunctionInfo = ^TFunctionInfo;
128   TFunctionInfo = packed record
129     Func: TPSProcRec;
130     FParamNames: TIfStringList;
131     FVariableNames: TIfStringList;
132     FPositionTable: TIfList;
133   end;
134 
135 { TPSCustomDebugExec }
136 
137 procedure TPSCustomDebugExec.Clear;
138 begin
139   inherited Clear;
140   if FGlobalVarNames <> nil then ClearDebug;
141 end;
142 
143 procedure TPSCustomDebugExec.ClearDebug;
144 var
145   i, j: Longint;
146   p: PFunctionInfo;
147 begin
148   FCurrentDebugProc := nil;
149   FLastProc := nil;
150   FProcNames.Clear;
151   FGlobalVarNames.Clear;
152   FCurrentSourcePos := 0;
153   FCurrentRow := 0;
154   FCurrentCol := 0;
155   FCurrentFile := '';
156   for i := 0 to FDebugDataForProcs.Count -1 do
157   begin
158     p := FDebugDataForProcs[I];
159     for j := 0 to p^.FPositionTable.Count -1 do
160     begin
161       Dispose(PPositionData(P^.FPositionTable[J]));
162     end;
163     p^.FPositionTable.Free;
164     p^.FParamNames.Free;
165     p^.FVariableNames.Free;
166     Dispose(p);
167   end;
168   FDebugDataForProcs.Clear;
169 end;
170 
171 constructor TPSCustomDebugExec.Create;
172 begin
173   inherited Create;
174   FCurrentSourcePos := 0;
175   FCurrentRow := 0;
176   FCurrentCol := 0;
177   FCurrentFile := '';
178   FDebugDataForProcs := TIfList.Create;
179   FLastProc := nil;
180   FCurrentDebugProc := nil;
181   FProcNames := TIFStringList.Create;
182   FGlobalVarNames := TIfStringList.Create;
183 end;
184 
185 destructor TPSCustomDebugExec.Destroy;
186 begin
187   Clear;
188   FDebugDataForProcs.Free;
189   FProcNames.Free;
190   FGlobalVarNames.Free;
191   FGlobalVarNames := nil;
192   inherited Destroy;
193 end;
194 
GetCurrentPositionnull195 function TPSCustomDebugExec.GetCurrentPosition: Cardinal;
196 begin
197   Result := TranslatePosition(GetCurrentProcNo, 0);
198 end;
199 
GetCurrentProcNonull200 function TPSCustomDebugExec.GetCurrentProcNo: Cardinal;
201 var
202   i: Longint;
203 begin
204   for i := 0 to FProcs.Count -1 do
205   begin
206     if FProcs[i]=  FCurrProc then
207     begin
208       Result := I;
209       Exit;
210     end;
211   end;
212   Result := Cardinal(-1);
213 end;
214 
GetCurrentProcParamsnull215 function TPSCustomDebugExec.GetCurrentProcParams: TIfStringList;
216 begin
217   if FCurrentDebugProc <> nil then
218   begin
219     Result := PFunctionInfo(FCurrentDebugProc)^.FParamNames;
220   end else Result := nil;
221 end;
222 
GetCurrentProcVarsnull223 function TPSCustomDebugExec.GetCurrentProcVars: TIfStringList;
224 begin
225   if FCurrentDebugProc <> nil then
226   begin
227     Result := PFunctionInfo(FCurrentDebugProc)^.FVariableNames;
228   end else Result := nil;
229 end;
230 
TPSCustomDebugExec.GetGlobalVarnull231 function TPSCustomDebugExec.GetGlobalVar(I: Cardinal): PIfVariant;
232 begin
233   Result := FGlobalVars[I];
234 end;
235 
TPSCustomDebugExec.GetProcParamnull236 function TPSCustomDebugExec.GetProcParam(I: Cardinal): PIfVariant;
237 begin
238   Result := FStack[Cardinal(Longint(FCurrStackBase) - Longint(I) - 1)];
239 end;
240 
GetProcVarnull241 function TPSCustomDebugExec.GetProcVar(I: Cardinal): PIfVariant;
242 begin
243   Result := FStack[Cardinal(Longint(FCurrStackBase) + Longint(I) + 1)];
244 end;
245 
GetProcDebugInfonull246 function GetProcDebugInfo(FProcs: TIFList; Proc: TPSProcRec): PFunctionInfo;
247 var
248   i: Longint;
249   c: PFunctionInfo;
250 begin
251   if Proc = nil then
252   begin
253     Result := nil;
254     exit;
255   end;
256   for i := FProcs.Count -1 downto 0 do
257   begin
258     c := FProcs.Data^[I];
259     if c^.Func = Proc then
260     begin
261       Result := c;
262       exit;
263     end;
264   end;
265   new(c);
266   c^.Func := Proc;
267   c^.FPositionTable := TIfList.Create;
268   c^.FVariableNames := TIfStringList.Create;
269   c^.FParamNames := TIfStringList.Create;
270   FProcs.Add(c);
271   REsult := c;
272 end;
273 
274 procedure TPSCustomDebugExec.LoadDebugData(const Data: tbtstring);
275 var
276   CP, I: Longint;
277   c: tbtchar;
278   CurrProcNo, LastProcNo: Cardinal;
279   LastProc: PFunctionInfo;
280   NewLoc: PPositionData;
281   s: tbtstring;
282 begin
283   ClearDebug;
284   if FStatus = isNotLoaded then exit;
285   CP := 1;
286   LastProcNo := Cardinal(-1);
287   LastProc := nil;
288   while CP <= length(Data) do
289   begin
290     c := Data[CP];
291     inc(cp);
292     case c of
293       #0:
294         begin
295           i := cp;
296           if i > length(data) then exit;
297           while Data[i] <> #0 do
298           begin
299             if Data[i] = #1 then
300             begin
301               FProcNames.Add(Copy(Data, cp, i-cp));
302               cp := I + 1;
303             end;
304             inc(I);
305             if I > length(data) then exit;
306           end;
307           cp := i + 1;
308         end;
309       #1:
310         begin
311           i := cp;
312           if i > length(data) then exit;
313           while Data[i] <> #0 do
314           begin
315             if Data[i] = #1 then
316             begin
317               FGlobalVarNames.Add(Copy(Data, cp, i-cp));
318               cp := I + 1;
319             end;
320             inc(I);
321             if I > length(data) then exit;
322           end;
323           cp := i + 1;
324         end;
325       #2:
326         begin
327           if cp + 4 > Length(data) then exit;
328           CurrProcNo := Cardinal((@Data[cp])^);
329           if CurrProcNo = Cardinal(-1) then Exit;
330           if CurrProcNo <> LastProcNo then
331           begin
332             LastProcNo := CurrProcNo;
333             LastProc := GetProcDebugInfo(FDebugDataForProcs, FProcs[CurrProcNo]);
334             if LastProc = nil then exit;
335           end;
336           inc(cp, 4);
337 
338           i := cp;
339           if i > length(data) then exit;
340           while Data[i] <> #0 do
341           begin
342             if Data[i] = #1 then
343             begin
344               LastProc^.FParamNames.Add(Copy(Data, cp, i-cp));
345               cp := I + 1;
346             end;
347             inc(I);
348             if I > length(data) then exit;
349           end;
350           cp := i + 1;
351         end;
352       #3:
353         begin
354           if cp + 4 > Length(data) then exit;
355           CurrProcNo := Cardinal((@Data[cp])^);
356           if CurrProcNo = Cardinal(-1) then Exit;
357           if CurrProcNo <> LastProcNo then
358           begin
359             LastProcNo := CurrProcNo;
360             LastProc := GetProcDebugInfo(FDebugDataForProcs, FProcs[CurrProcNo]);
361             if LastProc = nil then exit;
362           end;
363           inc(cp, 4);
364 
365           i := cp;
366           if i > length(data) then exit;
367           while Data[i] <> #0 do
368           begin
369             if Data[i] = #1 then
370             begin
371               LastProc^.FVariableNames.Add(Copy(Data, cp, i-cp));
372               cp := I + 1;
373             end;
374             inc(I);
375             if I > length(data) then exit;
376           end;
377           cp := i + 1;
378         end;
379       #4:
380         begin
381           i := cp;
382           if i > length(data) then exit;
383           while Data[i] <> #0 do
384           begin
385             if Data[i] = #1 then
386             begin
387               s := Copy(Data, cp, i-cp);
388               cp := I + 1;
389               Break;
390             end;
391             inc(I);
392             if I > length(data) then exit;
393           end;
394           if cp + 4 > Length(data) then exit;
395           CurrProcNo := Cardinal((@Data[cp])^);
396           if CurrProcNo = Cardinal(-1) then Exit;
397           if CurrProcNo <> LastProcNo then
398           begin
399             LastProcNo := CurrProcNo;
400             LastProc := GetProcDebugInfo(FDebugDataForProcs, FProcs[CurrProcNo]);
401             if LastProc = nil then exit;
402           end;
403           inc(cp, 4);
404           if cp + 16 > Length(data) then exit;
405           new(NewLoc);
406           NewLoc^.Position := Cardinal((@Data[Cp])^);
407           NewLoc^.FileName := s;
408           NewLoc^.SourcePosition := Cardinal((@Data[Cp+4])^);
409           NewLoc^.Row := Cardinal((@Data[Cp+8])^);
410           NewLoc^.Col := Cardinal((@Data[Cp+12])^);
411           inc(cp, 16);
412           LastProc^.FPositionTable.Add(NewLoc);
413         end;
414       else
415         begin
416           ClearDebug;
417           Exit;
418         end;
419     end;
420 
421   end;
422 end;
423 
424 
425 
426 
427 
428 
TranslatePositionnull429 function TPSCustomDebugExec.TranslatePosition(Proc, Position: Cardinal): Cardinal;
430 var
431   D1, D2: Cardinal;
432   s: tbtstring;
433 begin
434   if not TranslatePositionEx(Proc, Position, Result, D1, D2, s) then
435     Result := 0;
436 end;
437 
TranslatePositionExnull438 function TPSCustomDebugExec.TranslatePositionEx(Proc, Position: Cardinal;
439   var Pos, Row, Col: Cardinal; var Fn: tbtstring): Boolean;
440 // Made by Martijn Laan (mlaan@wintax.nl)
441 var
442   i: LongInt;
443   fi: PFunctionInfo;
444   pt: TIfList;
445   r: PPositionData;
446   lastfn: tbtstring;
447   LastPos, LastRow, LastCol: Cardinal;
448   pp: TPSProcRec;
449 begin
450   fi := nil;
451   pp := FProcs[Proc];
452   for i := 0 to FDebugDataForProcs.Count -1 do
453   begin
454     fi := FDebugDataForProcs[i];
455     if fi^.Func = pp then
456       Break;
457     fi := nil;
458   end;
459   LastPos := 0;
460   LastRow := 0;
461   LastCol := 0;
462   if fi <> nil then begin
463     pt := fi^.FPositionTable;
464     for i := 0 to pt.Count -1 do
465     begin
466       r := pt[I];
467       if r^.Position >= Position then
468       begin
469         if r^.Position = Position then
470         begin
471           Pos := r^.SourcePosition;
472           Row := r^.Row;
473           Col := r^.Col;
474           Fn := r^.Filename;
475         end
476         else
477         begin
478           Pos := LastPos;
479           Row := LastRow;
480           Col := LastCol;
481           Fn := LastFn;
482         end;
483         Result := True;
484         exit;
485       end else
486       begin
487         LastPos := r^.SourcePosition;
488         LastRow := r^.Row;
489         LastCol := r^.Col;
490         LastFn := r^.FileName;
491       end;
492     end;
493     Pos := LastPos;
494     Row := LastRow;
495     Col := LastCol;
496     Result := True;
497   end else
498   begin
499     Result := False;
500   end;
501 end;
502 
503 { TPSDebugExec }
504 procedure TPSDebugExec.ClearDebug;
505 begin
506   inherited;
507   FDebugMode := dmRun;
508 end;
509 
LoadDatanull510 function TPSDebugExec.LoadData(const s: tbtstring): Boolean;
511 begin
512   Result := inherited LoadData(s);
513   FDebugMode := dmRun;
514 end;
515 
516 procedure TPSDebugExec.RunLine;
517 var
518   i: Longint;
519   pt: TIfList;
520   r: PPositionData;
521 begin
522   inherited RunLine;
523   if not DebugEnabled then exit;
524   if FCurrProc <> FLastProc then
525   begin
526     FLastProc := FCurrProc;
527     FCurrentDebugProc := nil;
528     for i := 0 to FDebugDataForProcs.Count -1 do
529     begin
530       if PFunctionInfo(FDebugDataForProcs[I])^.Func = FLastProc then
531       begin
532         FCurrentDebugProc := FDebugDataForProcs[I];
533         break;
534       end;
535     end;
536   end;
537   if FCurrentDebugProc <> nil then
538   begin
539     pt := PFunctionInfo(FCurrentDebugProc)^.FPositionTable;
540     for i := 0 to pt.Count -1 do
541     begin
542       r := pt[I];
543       if r^.Position = FCurrentPosition then
544       begin
545         FCurrentSourcePos := r^.SourcePosition;
546         FCurrentRow := r^.Row;
547         FCurrentCol := r^.Col;
548         FCurrentFile := r^.FileName;
549         SourceChanged;
550         break;
551       end;
552     end;
553   end else
554   begin
555     FCurrentSourcePos := 0;
556     FCurrentRow := 0;
557     FCurrentCol := 0;
558     FCurrentFile := '';
559   end;
560   while FDebugMode = dmPaused do
561   begin
562     if @FOnIdleCall <> nil then
563     begin
564       FOnIdleCall(Self);
565     end else break; // endless loop
566   end;
567 end;
568 
569 
570 procedure TPSDebugExec.SourceChanged;
571 
StepOverShouldPausenull572   function StepOverShouldPause: Boolean;
573   var
574     I: Cardinal;
575     V: PPSVariant;
576   begin
577     if (FCurrProc <> FStepOverProc) or (FCurrStackBase <> FStepOverStackBase) then
578     begin
579       { We're not inside the function being stepped, so scan the call stack to
580         see if we're inside a function called by the function being stepped }
581       I := FCurrStackBase;
582       while Longint(I) > Longint(FStepOverStackBase) do
583       begin
584         V := FStack.Items[I];
585         if (V = nil) or (V.FType <> FReturnAddressType) then
586           raise Exception.Create(RPS_ExpectedReturnAddressStackBase);
587         if (PPSVariantReturnAddress(V).Addr.ProcNo = FStepOverProc) and
588            (PPSVariantReturnAddress(V).Addr.StackBase = FStepOverStackBase) then
589         begin
590           { We are, so don't pause }
591           Result := False;
592           Exit;
593         end;
594         I := PPSVariantReturnAddress(V).Addr.StackBase;
595       end;
596     end;
597     Result := True;
598   end;
599 
600 begin
601   case FDebugMode of
602     dmStepInto:
603       begin
604         FDebugMode := dmPaused;
605       end;
606     dmStepOver:
607       begin
608         if StepOverShouldPause then
609         begin
610           FDebugMode := dmPaused;
611         end;
612       end;
613   end;
614   if @FOnSourceLine <> nil then
615     FOnSourceLine(Self, FCurrentFile, FCurrentSourcePos, FCurrentRow, FCurrentCol);
616 end;
617 
618 
619 procedure TPSDebugExec.Pause;
620 begin
621   FDebugMode := dmPaused;
622 end;
623 
624 procedure TPSDebugExec.Stop;
625 begin
626   FDebugMode := dmRun;
627   inherited Stop;
628 end;
629 
630 procedure TPSDebugExec.Run;
631 begin
632   FDebugMode := dmRun;
633 end;
634 
635 procedure TPSDebugExec.StepInto;
636 begin
637   FDebugMode := dmStepInto;
638 end;
639 
640 procedure TPSDebugExec.StepOver;
641 begin
642   FStepOverProc := FCurrProc;
643   FStepOverStackBase := FCurrStackBase;
644   FDebugMode := dmStepOver;
645 end;
646 
647 
648 constructor TPSDebugExec.Create;
649 begin
650   inherited Create;
651   FDebugEnabled := True;
652 end;
653 
654 end.
655