unit TestBreakPoint; {$mode objfpc}{$H+} interface uses Classes, SysUtils, math, TestDbgControl, TestDbgTestSuites, TTestWatchUtilities, TestCommonSources, TestDbgConfig, TTestDebuggerClasses, LazDebuggerIntf, LazDebuggerIntfBaseTypes, DbgIntfDebuggerBase, DbgIntfBaseTypes, LazLoggerBase, Forms; type // Info used by tests based on TestBreakPointThreadPrg TBreakThreadPrgInfoEntry = record ID: Integer; Address: TDBGPtr; Line, PreviousLine: Integer; Val, LastVal: int64; // AX register Loop, PreviousLoop: int64; // BX register IsCurrent: Boolean; LastBrkLine, LastBrkLoop: Integer; PreviousLastBrkLine, PreviousLastBrkLoop: Integer; end; TBreakThreadPrgInfo = record ThrLoopFirst, ThrLoopLast, ThrLoopLine0, ThrLoopInc: Integer; // Linenumbers of the breakpoints // -1 => Main thread Threads: array[-1..10] of TBreakThreadPrgInfoEntry; end; { TTestBreakPoint } TTestBreakPoint = class(TDBGTestCase) protected // Info/Methods used by tests based on TestBreakPointThreadPrg FThrPrgInfo: TBreakThreadPrgInfo; procedure ThrPrgInitializeThreads(ATestName: String); procedure ThrPrgUpdateThreads(ATestName: String); (* ThrPrgCheckNoSkip Check that AX is correct for the line. Ensure the instruction restored from "int3" was executed *) procedure ThrPrgCheckNoSkip(ATestName: String=''); (* ThrPrgInfoHasGoneThroughLine Has gone over the line WITHOUT stopping at the breakpoint - If the thread was NOT on the line, it has gone over it at least once. - if the thread was AT the line, ~ and had NOT reorted yet for the line => it as gone away from the line anyway ~ and had reported for the line => it has gone OVER the line in the NEXT loop already *) function ThrPrgInfoHasGoneThroughLine(AIndex, ALine: Integer): boolean; protected Src: TCommonSource; Dbg: TDebuggerIntf; procedure TestLocation(ATestName, ABrkName: String; ABreakHitCount: Integer = 1); procedure TestHitCnt(ATestName, ABrkName: String; ABreakHitCount: Integer); published (* Ensure the debugger can correctly run/step after hidding a breakpoit - the original instruction is executed - the breakpoint can be hit again - stepping continues correctly to next line - breakpoints are "hit" (recognized) when stepping onto them (not actually triggering them) *) procedure TestBreakPoints; (* Ensure, that while one thread steps over a breakpoint (int3 removed), no other thread ignores the breakpoint *) procedure TestBreakThreadsNoSkip; (* Remove Breakpoints, while other threads have a pending event for the Brk. Make sure the other thread, still executeds the original instruction, hidden by the int3 *** The test can actually NOT force multiple breakpoints to be hit. It only creates a high likelihood for this to happen. *) procedure TestBreakThreadsMoveBreak1; procedure TestBreakThreadsMoveBreak2; // check that each brk point hit was reported (one per thread) procedure TestBreakThreadsHitBreak; (* Only ONE thread running the breakpoints. Plenty of events from other threads (including thread-exit). Hit each breakpoint in order, WITHOUT ever re-hitting the last brk. *HOPE* is that some events will happen while the main thread single steps over a temp-removed break, and the single step has NOT yet moved. So the single step will still need the brk to be tmp-removed when it continues. *) procedure TestBreakThreadsIgnoreOther; end; implementation var ControlTest, ControlTestBreak, ControlTestThreadNoSkip, ControlTestThreadMove1, ControlTestThreadMove2, ControlTestThreadHit, ControlTestThreadIgnoreOther: Pointer; procedure TTestBreakPoint.TestLocation(ATestName, ABrkName: String; ABreakHitCount: Integer); var lc: TDBGLocationRec; begin AssertDebuggerState(dsPause); lc := Debugger.LazDebugger.GetLocation; TestEquals(ATestName+' '+ABrkName+' Loc', Src.BreakPoints[ABrkName], lc.SrcLine); if ABreakHitCount >= 0 then TestEquals(ATestName+' '+ABrkName+' HitCnt', Debugger.BreakPointByName(ABrkName).HitCount, ABreakHitCount); end; procedure TTestBreakPoint.TestHitCnt(ATestName, ABrkName: String; ABreakHitCount: Integer); begin TestEquals(ATestName+' '+ABrkName+' HitCnt', Debugger.BreakPointByName(ABrkName).HitCount, ABreakHitCount); end; procedure TTestBreakPoint.TestBreakPoints; var ExeName: String; loc: TDBGLocationRec; b1, b2: TDBGBreakPoint; MainThreadId: Integer; begin if SkipTest then exit; if not TestControlCanTest(ControlTestBreak) then exit; Src := GetCommonSourceFor(AppDir + 'BreakPointPrg.pas'); TestCompile(Src, ExeName); AssertTrue('Start debugger', Debugger.StartDebugger(AppDir, ExeName)); dbg := Debugger.LazDebugger; try Debugger.SetBreakPoint(Src, 'PrgStep1'); Debugger.SetBreakPoint(Src, 'PrgStep2'); Debugger.SetBreakPoint(Src, 'PrgRun1'); Debugger.SetBreakPoint(Src, 'PrgRun2'); Debugger.SetBreakPoint(Src, 'AsmStep1'); Debugger.SetBreakPoint(Src, 'AsmStep2'); Debugger.SetBreakPoint(Src, 'AsmRun1'); Debugger.SetBreakPoint(Src, 'AsmRun2'); Debugger.SetBreakPoint(Src, 'AsmBranch1'); Debugger.SetBreakPoint(Src, 'AsmNever1'); Debugger.SetBreakPoint(Src, 'AsmBranchEnd1'); Debugger.SetBreakPoint(Src, 'AsmBranch2'); Debugger.SetBreakPoint(Src, 'AsmNever2'); Debugger.SetBreakPoint(Src, 'AsmBranchEnd2'); Debugger.SetBreakPoint(Src, 'Foo1'); Debugger.SetBreakPoint(Src, 'Foo2'); Debugger.SetBreakPoint(Src, 'PrgAfterFoo2'); Debugger.SetBreakPoint(Src, 'Thread1'); AssertDebuggerNotInErrorState; // Step to break next line Debugger.RunToNextPause(dcRun); TestLocation('Init', 'PrgStep1'); Debugger.RunToNextPause(dcStepOver); TestLocation('Stepped to break at next line', 'PrgStep2'); TestHitCnt('Stepped to break at next line', 'PrgStep1', 1); // Run to break next line Debugger.RunToNextPause(dcRun); TestLocation('Run to break', 'PrgRun1'); Debugger.RunToNextPause(dcRun); TestLocation('Run to break at next line', 'PrgRun2'); TestHitCnt('Run to break at next line', 'PrgRun1', 1); // Run/Step with one byte asm steps Debugger.RunToNextPause(dcRun); TestLocation('Init NOP', 'AsmStep1'); Debugger.RunToNextPause(dcStepOver); TestLocation('Stepped to NOP at next line', 'AsmStep2'); TestHitCnt('Stepped to NOP at next line', 'AsmStep1', 1); Debugger.RunToNextPause(dcRun); TestLocation('Run to NOP', 'AsmRun1'); Debugger.RunToNextPause(dcRun); TestLocation('Run to NOP at next line', 'AsmRun2'); TestHitCnt('Run to NOP at next line', 'AsmRun1', 1); // check that the command hidden by the int3 breakpoint is executed Debugger.RunToNextPause(dcRun); TestLocation('Run to NOP at next line', 'AsmBranch1'); Debugger.RunToNextPause(dcStepOver); TestLocation('Run to NOP at next line', 'AsmBranchEnd1'); TestHitCnt('Run to NOP at next line', 'AsmBranch1', 1); TestHitCnt('Run to NOP at next line', 'AsmNever1', 0); Debugger.RunToNextPause(dcRun); TestLocation('Run to NOP at next line', 'AsmBranch2'); Debugger.RunToNextPause(dcRun); TestLocation('Run to NOP at next line', 'AsmBranchEnd2'); TestHitCnt('Run to NOP at next line', 'AsmBranch2', 1); TestHitCnt('Run to NOP at next line', 'AsmNever2', 0); // Run to same line (repeat proc call) Debugger.RunToNextPause(dcRun); TestLocation('Init Foo1', 'Foo1'); Debugger.RunToNextPause(dcRun); TestLocation('Run Foo1', 'Foo1', 2); Debugger.RunToNextPause(dcStepOver); Debugger.RunToNextPause(dcRun); TestLocation('Step/Run Foo1', 'Foo1', 3); // Run to same line (loop) Debugger.RunToNextPause(dcRun); TestLocation('Run Foo2', 'Foo2'); Debugger.RunToNextPause(dcRun); TestLocation('Run Foo2', 'Foo2', 2); Debugger.RunToNextPause(dcRun); TestLocation('Run Foo2 ended in prg', 'PrgAfterFoo2'); TestHitCnt('Run Foo2', 'Foo2', 2); // new breakpoint Debugger.RunToNextPause(dcStepOver); TestLocation('Before insernt', 'New1', -1); Debugger.SetBreakPoint(Src, 'New1'); Debugger.RunToNextPause(dcStepOver); TestLocation('After insernt', 'New2', -1); TestHitCnt('After insernt', 'New1', 0); Debugger.SetBreakPoint(Src, 'New2'); Debugger.RunToNextPause(dcRun); TestHitCnt('After insernt', 'New1', 0); TestHitCnt('After insernt', 'New2', 0); // in threads (* In each thread set a breakpoint at the next instruction. So when all threads are started, both should run/step OVER the breakpoint without hitting it *) TestLocation('After insernt', 'Thread1'); MainThreadId := dbg.Threads.CurrentThreads.CurrentThreadId; Debugger.RunToNextPause(dcStepOver); TestLocation('After insernt', 'Thread2', -1); // not set loc := dbg.GetLocation; TestEquals('loc in thread', loc.SrcLine, Src.BreakPoints['Thread2']); b1 := dbg.BreakPoints.Add(loc.Address); b1.InitialEnabled := True; b1.Enabled := True; dbg.Threads.ChangeCurrentThread(MainThreadId); Debugger.RunToNextPause(dcStepOver); // continue stepping in main thread //TODO: not yet implemented loc := dbg.GetLocation; // TestTrue('loc thread main', loc.SrcLine > Src.BreakPoints['New2']); b2 := dbg.BreakPoints.Add(loc.Address); b2.InitialEnabled := True; b2.Enabled := True; If loc.SrcLine >= Src.BreakPoints['Main2']-2 then Debugger.SetBreakPoint(Src, 'Main1') else Debugger.SetBreakPoint(Src, 'Main2'); Debugger.RunToNextPause(dcRun); (* // TODO: breakpoints in diff threads may need to be hit, after the other break continued If loc.SrcLine >= Src.BreakPoints['Main2']-2 then begin TestLocation('main1', 'Main1'); Debugger.BreakPointByName('Main1').Enabled := False; end else begin TestLocation('main2', 'Main2'); Debugger.BreakPointByName('Main2').Enabled := False; end; TestEquals('b1 hits', 0, b1.HitCount); TestEquals('b2 hits', 0, b2.HitCount); Debugger.RunToNextPause(dcRun); TestEquals('b1 hits after', 0, b1.HitCount); TestEquals('b2 hits after', 0, b2.HitCount); *) dbg.Stop; finally Debugger.ClearDebuggerMonitors; Debugger.FreeDebugger; AssertTestErrors; end; end; procedure TTestBreakPoint.ThrPrgInitializeThreads(ATestName: String); var i, j: Integer; t: TThreadEntry; begin FThrPrgInfo.ThrLoopFirst := Src.BreakPoints['BrkThreadBegin']; FThrPrgInfo.ThrLoopLast := Src.BreakPoints['BrkThreadEnd']; FThrPrgInfo.ThrLoopLine0 := Src.BreakPoints['BrkThread1']; FThrPrgInfo.ThrLoopInc := Src.BreakPoints['BrkThreadIncLoop']; FThrPrgInfo.Threads[-1].ID := 0; (* Initialize Find all threads *) dbg.Threads.CurrentThreads.Count; j := 0; while TTestThreads(dbg.Threads.CurrentThreads).DataValidity <> ddsValid do begin sleep(50); Application.ProcessMessages; inc(j); if j > 100 then break; end; j := 0; for i := 0 to dbg.Threads.CurrentThreads.Count-1 do begin t := dbg.Threads.CurrentThreads.Entries[i]; if t.TopFrame.Line < FThrPrgInfo.ThrLoopFirst then begin debugln(['Ignoring Thread ', t.ThreadId, ' at ',t.TopFrame.Address]); Continue; end; if t.TopFrame.Line > FThrPrgInfo.ThrLoopLast then begin debugln(['MAIN Thread ', t.ThreadId, ' at ',t.TopFrame.Address, ' line ', t.TopFrame.Line]); TestTrue('Only one main thread', FThrPrgInfo.Threads[-1].ID = 0); if FThrPrgInfo.Threads[-1].ID = 0 then FThrPrgInfo.Threads[-1].ID := t.ThreadId; Continue; end; FThrPrgInfo.Threads[j].ID := t.ThreadId; FThrPrgInfo.Threads[j].Line := -1; FThrPrgInfo.Threads[j].Loop := -5; FThrPrgInfo.Threads[j].LastBrkLine := -1; FThrPrgInfo.Threads[j].PreviousLastBrkLine := -1; debugln(['++ ADDED tid ',t.ThreadId]); inc(j); if j >= 11 then break; end; AssertEquals('Found 10 threads', 10, j); TestTrue('Found main thread', FThrPrgInfo.Threads[-1].ID <> 0); end; procedure TTestBreakPoint.ThrPrgUpdateThreads(ATestName: String); var i, j: Integer; t: TThreadEntry; r: TRegisters; ax, bx: TRegisterValue; begin dbg.Threads.CurrentThreads.Count; j := 0; while TTestThreads(dbg.Threads.CurrentThreads).DataValidity <> ddsValid do begin sleep(50); Application.ProcessMessages; inc(j); if j > 100 then break; end; for i := -1 to Min(9, dbg.Threads.CurrentThreads.Count-1) do begin t := dbg.Threads.CurrentThreads.EntryById[FThrPrgInfo.Threads[i].ID]; TestTrue(ATestName+' thread for '+inttostr(FThrPrgInfo.Threads[i].ID), t<> nil); if t=nil then continue; r := dbg.Registers.CurrentRegistersList.Entries[FThrPrgInfo.Threads[i].ID, 0]; ax := nil; for j := 0 to r.Count-1 do if (lowercase(r.Entries[j].Name) = 'eax') or (lowercase(r.Entries[j].Name) = 'rax') then ax := r.Entries[j]; bx := nil; for j := 0 to r.Count-1 do if (lowercase(r.Entries[j].Name) = 'ebx') or (lowercase(r.Entries[j].Name) = 'rbx') then bx := r.Entries[j]; FThrPrgInfo.Threads[i].IsCurrent := FThrPrgInfo.Threads[i].ID = dbg.Threads.CurrentThreads.CurrentThreadId; FThrPrgInfo.Threads[i].PreviousLine := FThrPrgInfo.Threads[i].Line; FThrPrgInfo.Threads[i].LastVal := FThrPrgInfo.Threads[i].Val; FThrPrgInfo.Threads[i].PreviousLoop := FThrPrgInfo.Threads[i].Loop; FThrPrgInfo.Threads[i].Address := t.TopFrame.Address; FThrPrgInfo.Threads[i].Line := t.TopFrame.Line; if ax <> nil then FThrPrgInfo.Threads[i].Val := StrToInt64Def(ax.Value,-1) and $7FFFFFFF; if bx <> nil then FThrPrgInfo.Threads[i].Loop := StrToInt64Def(bx.Value,-1) and $7FFFFFFF; if FThrPrgInfo.Threads[i].ID = dbg.Threads.CurrentThreads.CurrentThreadId then begin FThrPrgInfo.Threads[i].PreviousLastBrkLine := FThrPrgInfo.Threads[i].LastBrkLine; FThrPrgInfo.Threads[i].PreviousLastBrkLoop := FThrPrgInfo.Threads[i].LastBrkLoop; FThrPrgInfo.Threads[i].LastBrkLine := FThrPrgInfo.Threads[i].Line; FThrPrgInfo.Threads[i].LastBrkLoop := FThrPrgInfo.Threads[i].Loop; end; debugln('Thread %d: ID=%d Cur=%s (%x): Line: %d (%d) (was %d) Val: %d (was %d) LOOP: %d (was %d) Brk: %d %d (%d %d)', [ i, FThrPrgInfo.Threads[i].ID, dbgs(FThrPrgInfo.Threads[i].IsCurrent), FThrPrgInfo.Threads[i].Address, FThrPrgInfo.Threads[i].Line-FThrPrgInfo.ThrLoopLine0, FThrPrgInfo.Threads[i].Line, FThrPrgInfo.Threads[i].PreviousLine-FThrPrgInfo.ThrLoopLine0, FThrPrgInfo.Threads[i].Val, FThrPrgInfo.Threads[i].LastVal, FThrPrgInfo.Threads[i].Loop, FThrPrgInfo.Threads[i].PreviousLoop, FThrPrgInfo.Threads[i].LastBrkLine, FThrPrgInfo.Threads[i].LastBrkLoop, FThrPrgInfo.Threads[i].PreviousLastBrkLine, FThrPrgInfo.Threads[i].PreviousLastBrkLoop ]); end; end; procedure TTestBreakPoint.ThrPrgCheckNoSkip(ATestName: String); // Make sure no thread skipped any add. All EAX values must be correct var i, l: Integer; const ExpVal: array[0..10] of integer = ( 0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023 ); begin for i := 0 to 9 do begin if FThrPrgInfo.Threads[i].Line = -1 then continue; l := FThrPrgInfo.Threads[i].Line - FThrPrgInfo.ThrLoopLine0; TestTrue(ATestName+' line in range tid: '+inttostr(FThrPrgInfo.Threads[i].ID), (l>=-1) and (l 9 then l := 10; if l < 0 then l := 10; TestEquals(ATestName+' Reg val for '+inttostr(FThrPrgInfo.Threads[i].ID)+ ' / '+inttostr(FThrPrgInfo.Threads[i].Line - FThrPrgInfo.ThrLoopLine0), ExpVal[l], FThrPrgInfo.Threads[i].Val ); end; end; function TTestBreakPoint.ThrPrgInfoHasGoneThroughLine(AIndex, ALine: Integer): boolean; var LoopAdjust, LastLoopAdjust: Integer; LoopDiff: Integer; Entry: TBreakThreadPrgInfoEntry; begin Result := True; LoopAdjust := 0; Entry := FThrPrgInfo.Threads[AIndex]; if Entry.Line > FThrPrgInfo.ThrLoopInc then LoopAdjust := 1; LastLoopAdjust := 0; if Entry.PreviousLine > FThrPrgInfo.ThrLoopInc then LastLoopAdjust := 1; LoopDiff := (Entry.Loop-LoopAdjust) - (Entry.PreviousLoop-LastLoopAdjust); // Was in front of line, and now after (or even in next loop)? if (Entry.PreviousLine < ALine) and ( (Entry.Line > ALine) or (LoopDiff > 0) ) then exit; // Was after line, and now after AND in next loop-LoopAdjust? if (Entry.PreviousLine > ALine) and (Entry.Line > ALine) and (LoopDiff > 0) then exit; // Was exactly at line, and now after AND in next loop-LoopAdjust? if (Entry.PreviousLine = ALine) and (Entry.Line > ALine) and (LoopDiff > 1) then exit; // More than one loop-LoopAdjust ... if (LoopDiff > 1) then exit; Result := False; end; procedure TTestBreakPoint.TestBreakThreadsNoSkip; procedure HasManyAtLine(ALine: Integer; var AManyAt, AManyAfter: Integer); var AtLine, AfterLine, i: Integer; begin AtLine := 0; AfterLine := 0; for i := 0 to 9 do if FThrPrgInfo.Threads[i].Line = ALine then inc(AtLine) else if FThrPrgInfo.Threads[i].PreviousLine = ALine then // Current line moved on, stepped over break inc(AfterLine); if AtLine > 3 then Inc(AManyAt); if AfterLine > 2 then Inc(AManyAfter); end; var ExeName: String; i, j: Integer; MainBrk, Brk1, Brk2, Brk3, Brk4, Brk5: TDBGBreakPoint; ManyAtBrk1, ManyAfterBrk1: Integer; Entry: TBreakThreadPrgInfoEntry; begin if SkipTest then exit; if not TestControlCanTest(ControlTestThreadNoSkip) then exit; Src := GetCommonSourceFor(AppDir + 'BreakPointThreadPrg.pas'); TestCompile(Src, ExeName); TestTrue('Start debugger', Debugger.StartDebugger(AppDir, ExeName)); dbg := Debugger.LazDebugger; try MainBrk := Debugger.SetBreakPoint(Src, 'BrkMain1'); AssertDebuggerNotInErrorState; Debugger.RunToNextPause(dcRun); AssertDebuggerState(dsPause); ThrPrgInitializeThreads(''); ThrPrgUpdateThreads('Init'); ThrPrgCheckNoSkip('Init'); (* Stopped in the main thread. Set fixed breakpoints in the thread loop. On each run, no thread must step over any of the breakpoints. Since threads do reach the breakpoints, they will have to temp remove them *) MainBrk.Enabled := False; Brk1 := Debugger.SetBreakPoint(Src, 'BrkThread1'); (* ManyAtBrk1 / ManyAfterBrk1 Cumulative count accross all "j" loop iterations. ManyAtBrk1: Count "j"-iterations with at least 3 threads have been at "Brk1" ManyAfterBrk1: Count "j"-iterations with at least 3 threads just stepped/run away from "Brk1" *) ManyAtBrk1 := 0; ManyAfterBrk1 := 0; (* Each iteration the test checks that all "add eax, n" have been executed. *) for j := 0 to 300 do begin AssertDebuggerNotInErrorState; Debugger.RunToNextPause(dcRun); AssertDebuggerState(dsPause); ThrPrgUpdateThreads('loop fixed brk '+IntToStr(j)); ThrPrgCheckNoSkip('loop, fixed brk '+IntToStr(j)); // Compare AX with line for i := 0 to 9 do begin Entry := FThrPrgInfo.Threads[i]; if Entry.PreviousLine < 0 then continue; TestTrue('THread not gone over break 1 at line '+IntToStr(Brk1.Line)+' '+IntToStr(i), not ThrPrgInfoHasGoneThroughLine(i, Brk1.Line) ); end; HasManyAtLine(Brk1.Line, ManyAtBrk1, ManyAfterBrk1); if (j > 50) and (ManyAtBrk1 > 20) and (ManyAfterBrk1 > 15) then begin DebugLn('~~~~~~~~~~~~~ End loop early j=%d at=%d after=%d', [j, ManyAtBrk1, ManyAfterBrk1]); break; end; end; TestTrue('Had Many at brk1 (loop1)', ManyAtBrk1 > 0, 0, 'Ignore / not enforcable'); TestTrue('Had Many after brk1 (loop1)', ManyAfterBrk1 > 0, 0, 'Ignore / not enforcable'); // Add more breaks Brk3 := Debugger.SetBreakPoint(Src, 'BrkThread5'); Brk5 := Debugger.SetBreakPoint(Src, 'BrkThread9'); // clear values from last loop for i := 0 to 9 do begin FThrPrgInfo.Threads[i].LastBrkLine := -1; FThrPrgInfo.Threads[i].PreviousLastBrkLine := -1; end; for j := 0 to 100 do begin AssertDebuggerNotInErrorState; Debugger.RunToNextPause(dcRun); AssertDebuggerState(dsPause); ThrPrgUpdateThreads('loop fixed brk '+IntToStr(j)); ThrPrgCheckNoSkip('loop, fixed brk '+IntToStr(j)); for i := 0 to 9 do begin Entry := FThrPrgInfo.Threads[i]; if Entry.PreviousLine < 0 then continue; TestTrue('THread not gone over break(n/3) 1 at line '+IntToStr(Brk1.Line)+' '+IntToStr(i), not ThrPrgInfoHasGoneThroughLine(i, Brk1.Line) ); TestTrue('THread not gone over break(n/3) 3 at line '+IntToStr(Brk3.Line)+' '+IntToStr(i), not ThrPrgInfoHasGoneThroughLine(i, Brk3.Line) ); TestTrue('THread not gone over break(n/3) 5 at line '+IntToStr(Brk5.Line)+' '+IntToStr(i), not ThrPrgInfoHasGoneThroughLine(i, Brk5.Line) ); if Entry.PreviousLastBrkLine > 0 then begin if Entry.LastBrkLine = Brk1.Line then begin TestEquals('Previous break(n/3) before Brk1', Brk5.Line, Entry.PreviousLastBrkLine); TestTrue ('Previous break-loop(n/3) before Brk1', Entry.LastBrkLoop = Entry.PreviousLastBrkLoop + 1); end else if Entry.LastBrkLine = Brk3.Line then begin TestEquals('Previous break(n/3) before Brk3', Brk1.Line, Entry.PreviousLastBrkLine); TestTrue ('Previous break-loop(n/3) before Brk3', Entry.LastBrkLoop = Entry.PreviousLastBrkLoop); end else if Entry.LastBrkLine = Brk5.Line then begin TestEquals('Previous break(n/3) before Brk5', Brk3.Line, Entry.PreviousLastBrkLine); TestTrue ('Previous break-loop(n/3) before Brk5', Entry.LastBrkLoop = Entry.PreviousLastBrkLoop); end; end; end; end; // Add more breaks Brk2 := Debugger.SetBreakPoint(Src, 'BrkThread3'); Brk4 := Debugger.SetBreakPoint(Src, 'BrkThread7'); // clear values from last loop for i := 0 to 9 do begin FThrPrgInfo.Threads[i].LastBrkLine := -1; FThrPrgInfo.Threads[i].PreviousLastBrkLine := -1; end; for j := 0 to 100 do begin AssertDebuggerNotInErrorState; Debugger.RunToNextPause(dcRun); AssertDebuggerState(dsPause); ThrPrgUpdateThreads('loop fixed brk '+IntToStr(j)); ThrPrgCheckNoSkip('loop, fixed brk '+IntToStr(j)); for i := 0 to 9 do begin Entry := FThrPrgInfo.Threads[i]; if Entry.PreviousLine < 0 then continue; TestTrue('THread not gone over break(n/5) 1 at line '+IntToStr(Brk1.Line)+' '+IntToStr(i), not ThrPrgInfoHasGoneThroughLine(i, Brk1.Line) ); TestTrue('THread not gone over break(n/5) 2 at line '+IntToStr(Brk2.Line)+' '+IntToStr(i), not ThrPrgInfoHasGoneThroughLine(i, Brk2.Line) ); TestTrue('THread not gone over break(n/5) 3 at line '+IntToStr(Brk3.Line)+' '+IntToStr(i), not ThrPrgInfoHasGoneThroughLine(i, Brk3.Line) ); TestTrue('THread not gone over break(n/5) 4 at line '+IntToStr(Brk4.Line)+' '+IntToStr(i), not ThrPrgInfoHasGoneThroughLine(i, Brk4.Line) ); TestTrue('THread not gone over break(n/5) 5 at line '+IntToStr(Brk5.Line)+' '+IntToStr(i), not ThrPrgInfoHasGoneThroughLine(i, Brk5.Line) ); end; end; dbg.Stop; finally Debugger.ClearDebuggerMonitors; Debugger.FreeDebugger; AssertTestErrors; end; end; procedure TTestBreakPoint.TestBreakThreadsMoveBreak1; var ExeName: String; i, j: Integer; MainBrk, Brk1: TDBGBreakPoint; Entry: TBreakThreadPrgInfoEntry; begin if SkipTest then exit; if not TestControlCanTest(ControlTestThreadMove1) then exit; Src := GetCommonSourceFor(AppDir + 'BreakPointThreadPrg.pas'); TestCompile(Src, ExeName); TestTrue('Start debugger', Debugger.StartDebugger(AppDir, ExeName)); dbg := Debugger.LazDebugger; try MainBrk := Debugger.SetBreakPoint(Src, 'BrkMain1'); AssertDebuggerNotInErrorState; Debugger.RunToNextPause(dcRun); AssertDebuggerState(dsPause); ThrPrgInitializeThreads(''); ThrPrgUpdateThreads('Init'); ThrPrgCheckNoSkip('Init'); (* Stopped in the main thread. Set a new breakpoint at the current address of one of the subthreads. It should be skipped until next loop. Other sub-thread must not accidentally go over the breakpoint for sub-threads *) for j := 0 to 100 do begin Brk1 := Debugger.SetBreakPoint(Src.FileName, FThrPrgInfo.Threads[0].Line); AssertDebuggerNotInErrorState; Debugger.RunToNextPause(dcRun); AssertDebuggerState(dsPause); ThrPrgUpdateThreads('loop one brk '+IntToStr(j)); ThrPrgCheckNoSkip('loop, one brk '+IntToStr(j)); for i := 0 to 9 do begin Entry := FThrPrgInfo.Threads[i]; if Entry.PreviousLine < 0 then continue; TestTrue('THread not gone over break at line '+IntToStr(Brk1.Line)+' '+IntToStr(i), not ThrPrgInfoHasGoneThroughLine(i, Brk1.Line) ); end; Brk1.ReleaseReference; if j = 0 then MainBrk.Enabled := False; end; dbg.Stop; finally Debugger.ClearDebuggerMonitors; Debugger.FreeDebugger; AssertTestErrors; end; end; procedure TTestBreakPoint.TestBreakThreadsMoveBreak2; var ExeName: String; i, j: Integer; MainBrk, Brk1, Brk2, Brk3, Brk4, Brk5: TDBGBreakPoint; Entry: TBreakThreadPrgInfoEntry; begin if SkipTest then exit; if not TestControlCanTest(ControlTestThreadMove2) then exit; Src := GetCommonSourceFor(AppDir + 'BreakPointThreadPrg.pas'); TestCompile(Src, ExeName); TestTrue('Start debugger', Debugger.StartDebugger(AppDir, ExeName)); dbg := Debugger.LazDebugger; try MainBrk := Debugger.SetBreakPoint(Src, 'BrkMain1'); AssertDebuggerNotInErrorState; Debugger.RunToNextPause(dcRun); AssertDebuggerState(dsPause); ThrPrgInitializeThreads(''); ThrPrgUpdateThreads('Init'); ThrPrgCheckNoSkip('Init'); (* Try more breakpoints => so there is a likelihood that a several threads hit breakpoints at the same time. Then remove the breakpoints, while the FThrPrgInfo.ThrLoopFirst thread reports the hit *) for j := 0 to 150 do begin if (j and 1) = 0 then begin Brk1 := Debugger.SetBreakPoint(Src, 'BrkThread1'); Brk2 := Debugger.SetBreakPoint(Src, 'BrkThread3'); Brk3 := Debugger.SetBreakPoint(Src, 'BrkThread5'); Brk4 := Debugger.SetBreakPoint(Src, 'BrkThread7'); Brk5 := Debugger.SetBreakPoint(Src, 'BrkThread9'); end else begin Brk1 := Debugger.SetBreakPoint(Src, 'BrkThread2'); Brk2 := Debugger.SetBreakPoint(Src, 'BrkThread4'); Brk3 := Debugger.SetBreakPoint(Src, 'BrkThread6'); Brk4 := Debugger.SetBreakPoint(Src, 'BrkThread8'); Brk5 := Debugger.SetBreakPoint(Src, 'BrkThread10'); end; AssertDebuggerNotInErrorState; Debugger.RunToNextPause(dcRun); AssertDebuggerState(dsPause); ThrPrgUpdateThreads('loop, changing brk '+IntToStr(j)); ThrPrgCheckNoSkip('loop, changing brk '+IntToStr(j)); for i := 0 to 9 do begin Entry := FThrPrgInfo.Threads[i]; if Entry.PreviousLine < 0 then continue; TestTrue('THread not gone over break '+IntToStr(i), not ThrPrgInfoHasGoneThroughLine(i, Brk1.Line) ); TestTrue('THread not gone over break '+IntToStr(i), not ThrPrgInfoHasGoneThroughLine(i, Brk2.Line) ); TestTrue('THread not gone over break '+IntToStr(i), not ThrPrgInfoHasGoneThroughLine(i, Brk3.Line) ); TestTrue('THread not gone over break '+IntToStr(i), not ThrPrgInfoHasGoneThroughLine(i, Brk4.Line) ); TestTrue('THread not gone over break '+IntToStr(i), not ThrPrgInfoHasGoneThroughLine(i, Brk5.Line) ); end; Brk1.ReleaseReference; Brk2.ReleaseReference; Brk3.ReleaseReference; Brk4.ReleaseReference; Brk5.ReleaseReference; end; dbg.Stop; finally Debugger.ClearDebuggerMonitors; Debugger.FreeDebugger; AssertTestErrors; end; end; procedure TTestBreakPoint.TestBreakThreadsHitBreak; function CheckLastBrk(AnIdx, BrkLine, NextBrkLine: Integer): Boolean; begin Result := True; // defaults to ok, if Line is not in this range. if FThrPrgInfo.Threads[AnIdx].LastBrkLine = -1 then exit; if FThrPrgInfo.Threads[AnIdx].PreviousLastBrkLine = -1 then exit; if FThrPrgInfo.Threads[AnIdx].Line = BrkLine then begin if FThrPrgInfo.Threads[AnIdx].IsCurrent then Result := FThrPrgInfo.Threads[AnIdx].LastBrkLine = BrkLine; // otherwise... end; if FThrPrgInfo.Threads[AnIdx].Line = NextBrkLine then begin if (FThrPrgInfo.Threads[AnIdx].LastBrkLine = NextBrkLine) then Result := (FThrPrgInfo.Threads[AnIdx].LastBrkLoop = FThrPrgInfo.Threads[AnIdx].Loop) and (FThrPrgInfo.Threads[AnIdx].PreviousLastBrkLine = BrkLine) // PreviousLastBrkLoop can be equal or 1 less else Result := (FThrPrgInfo.Threads[AnIdx].LastBrkLine = BrkLine) ; end; if BrkLine < NextBrkLine then begin if (FThrPrgInfo.Threads[AnIdx].Line > BrkLine) and (FThrPrgInfo.Threads[AnIdx].Line < NextBrkLine) then begin Result := (FThrPrgInfo.Threads[AnIdx].LastBrkLine = BrkLine) and (FThrPrgInfo.Threads[AnIdx].LastBrkLoop = FThrPrgInfo.Threads[AnIdx].Loop); end; end else begin // going over loop-end if (FThrPrgInfo.Threads[AnIdx].Line > BrkLine) or (FThrPrgInfo.Threads[AnIdx].Line < NextBrkLine) then begin Result := (FThrPrgInfo.Threads[AnIdx].LastBrkLine = BrkLine) and ( (FThrPrgInfo.Threads[AnIdx].LastBrkLoop = FThrPrgInfo.Threads[AnIdx].Loop) or (FThrPrgInfo.Threads[AnIdx].LastBrkLoop = FThrPrgInfo.Threads[AnIdx].Loop - 1) ); end; end; end; var ExeName: String; i, j: Integer; MainBrk, Brk1, Brk2, Brk3, Brk4, Brk5: TDBGBreakPoint; Entry: TBreakThreadPrgInfoEntry; begin if SkipTest then exit; if not TestControlCanTest(ControlTestThreadHit) then exit; Src := GetCommonSourceFor(AppDir + 'BreakPointThreadPrg.pas'); TestCompile(Src, ExeName); TestTrue('Start debugger', Debugger.StartDebugger(AppDir, ExeName)); dbg := Debugger.LazDebugger; try MainBrk := Debugger.SetBreakPoint(Src, 'BrkMain1'); AssertDebuggerNotInErrorState; Debugger.RunToNextPause(dcRun); AssertDebuggerState(dsPause); ThrPrgInitializeThreads(''); ThrPrgUpdateThreads('Init'); ThrPrgCheckNoSkip('Init'); Brk1 := Debugger.SetBreakPoint(Src, 'BrkThread1'); Brk2 := Debugger.SetBreakPoint(Src, 'BrkThread3'); Brk3 := Debugger.SetBreakPoint(Src, 'BrkThread5'); Brk4 := Debugger.SetBreakPoint(Src, 'BrkThread7'); Brk5 := Debugger.SetBreakPoint(Src, 'BrkThread9'); for j := 0 to 150 do begin AssertDebuggerNotInErrorState; Debugger.RunToNextPause(dcRun); AssertDebuggerState(dsPause); ThrPrgUpdateThreads('loop, changing brk '+IntToStr(j)); ThrPrgCheckNoSkip('loop, changing brk '+IntToStr(j)); for i := 0 to 9 do begin Entry := FThrPrgInfo.Threads[i]; if Entry.PreviousLine < 0 then continue; TestTrue('THread not gone over break '+IntToStr(i), not ThrPrgInfoHasGoneThroughLine(i, Brk1.Line) ); TestTrue('THread not gone over break '+IntToStr(i), not ThrPrgInfoHasGoneThroughLine(i, Brk2.Line) ); TestTrue('THread not gone over break '+IntToStr(i), not ThrPrgInfoHasGoneThroughLine(i, Brk3.Line) ); TestTrue('THread not gone over break '+IntToStr(i), not ThrPrgInfoHasGoneThroughLine(i, Brk4.Line) ); TestTrue('THread not gone over break '+IntToStr(i), not ThrPrgInfoHasGoneThroughLine(i, Brk5.Line) ); // The thread must be between any 2 of thebreakpoint, and it must have hit // the breakpoint at the start of the range. TestTrue('Has hit last brk between 1 - 2 / thr= '+IntToStr(i), CheckLastBrk(i, Brk1.Line, Brk2.Line)); TestTrue('Has hit last brk between 2 - 3 / thr= '+IntToStr(i), CheckLastBrk(i, Brk2.Line, Brk3.Line)); TestTrue('Has hit last brk between 3 - 4 / thr= '+IntToStr(i), CheckLastBrk(i, Brk3.Line, Brk4.Line)); TestTrue('Has hit last brk between 4 - 5 / thr= '+IntToStr(i), CheckLastBrk(i, Brk4.Line, Brk5.Line)); TestTrue('Has hit last brk between 5 - 1 / thr= '+IntToStr(i), CheckLastBrk(i, Brk5.Line, Brk1.Line)); end; end; dbg.Stop; finally Debugger.ClearDebuggerMonitors; Debugger.FreeDebugger; AssertTestErrors; end; end; procedure TTestBreakPoint.TestBreakThreadsIgnoreOther; var ExeName: String; i, j, ThreadIdMain: Integer; Brk: array [0..9] of TDBGBreakPoint; begin if SkipTest then exit; if not TestControlCanTest(ControlTestThreadIgnoreOther) then exit; Src := GetCommonSourceFor(AppDir + 'BreakPointThread2Prg.pas'); TestCompile(Src, ExeName); TestTrue('Start debugger', Debugger.StartDebugger(AppDir, ExeName)); dbg := Debugger.LazDebugger; try Debugger.SetBreakPoint(Src, 'BrkMainBegin'); for i := 0 to 9 do Brk[i] := Debugger.SetBreakPoint(Src, 'BrkMain'+IntToStr(i)); AssertDebuggerNotInErrorState; Debugger.RunToNextPause(dcRun); AssertDebuggerState(dsPause, 'main init'); ThreadIdMain := dbg.Threads.CurrentThreads.CurrentThreadId; for j := 1 to 70 do begin for i := 0 to 9 do begin Debugger.RunToNextPause(dcRun); AssertDebuggerState(dsPause, 'in loop'); TestEquals('ThreadId', ThreadIdMain, dbg.Threads.CurrentThreads.CurrentThreadId); TestLocation('loop '+IntToStr(j)+', '+IntToStr(i), 'BrkMain'+IntToStr(i), j); if i > 0 then TestEquals('prev brk hitcnt '+IntToStr(j)+', '+IntToStr(i), j, Debugger.BreakPointByName('BrkMain'+IntToStr(i)).HitCount) else TestEquals('prev brk hitcnt '+IntToStr(j)+', '+IntToStr(i), j-1, Debugger.BreakPointByName('BrkMain9').HitCount); end; end; dbg.Stop; finally Debugger.ClearDebuggerMonitors; Debugger.FreeDebugger; AssertTestErrors; end; end; initialization RegisterDbgTest(TTestBreakPoint); ControlTest := TestControlRegisterTest('TTestBreak'); ControlTestBreak := TestControlRegisterTest('TTestBreakPoint', ControlTest); ControlTestThreadNoSkip := TestControlRegisterTest('TTestBreakThreadNoSkip', ControlTest); ControlTestThreadMove1 := TestControlRegisterTest('TTestBreakThreadMove1', ControlTest); ControlTestThreadMove2 := TestControlRegisterTest('TTestBreakThreadMove2', ControlTest); ControlTestThreadHit := TestControlRegisterTest('TTestBreakThreadHit', ControlTest); ControlTestThreadIgnoreOther := TestControlRegisterTest('TTestBreakThreadIgnoreOther', ControlTest); end.