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