From babf3f59d24074c288ace91701051c236a3ec5bc Mon Sep 17 00:00:00 2001 From: Lex Li Date: Sun, 25 Jan 2026 23:29:25 -0500 Subject: [PATCH 1/2] Add test case --- tests/SharpDbg.Cli.Tests/StopAtEntryTests.cs | 38 ++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 tests/SharpDbg.Cli.Tests/StopAtEntryTests.cs diff --git a/tests/SharpDbg.Cli.Tests/StopAtEntryTests.cs b/tests/SharpDbg.Cli.Tests/StopAtEntryTests.cs new file mode 100644 index 0000000..7066e41 --- /dev/null +++ b/tests/SharpDbg.Cli.Tests/StopAtEntryTests.cs @@ -0,0 +1,38 @@ +using AwesomeAssertions; +using SharpDbg.Cli.Tests.Helpers; + +namespace SharpDbg.Cli.Tests; + +public class StopAtEntryTests(ITestOutputHelper testOutputHelper) +{ + [Fact] + public async Task StopAtEntry_ResumeContinues_AndHitsBreakpoint() + { + var startSuspended = true; + + var (debugProtocolHost, initializedEventTcs, stoppedEventTcs, adapter, p2) = TestHelper.GetRunningDebugProtocolHostInProc(testOutputHelper, startSuspended); + using var _ = adapter; + using var __ = new ProcessKiller(p2); + + await debugProtocolHost + .WithInitializeRequest() + .WithAttachRequest(p2.Id) + .WaitForInitializedEvent(initializedEventTcs); + + // Compute a sensible breakpoint line inside Program.cs (the "Log2" WriteLine) + var programPath = Path.JoinFromGitRoot("tests", "DebuggableConsoleApp", "Program.cs"); + var lines = File.ReadAllLines(programPath); + var bpLine = Array.FindIndex(lines, l => l.Contains("Log2")) + 1; + if (bpLine == 0) bpLine = 4; // fallback + + debugProtocolHost.WithBreakpointsRequest(bpLine, programPath) + .WithConfigurationDoneRequest() + .WithOptionalResumeRuntime(p2.Id, startSuspended); + + // Now we should hit the breakpoint we just set + var stoppedEvent2 = await debugProtocolHost.WaitForStoppedEvent(stoppedEventTcs).WaitAsync(TimeSpan.FromSeconds(15)); + var stopInfo2 = stoppedEvent2.ReadStopInfo(); + stopInfo2.filePath.Should().EndWith("Program.cs"); + stopInfo2.line.Should().Be(bpLine); + } +} From 17eeaaf93d53e3591c04a6fd612a8fe223f542c8 Mon Sep 17 00:00:00 2001 From: Lex Li Date: Sun, 25 Jan 2026 23:33:05 -0500 Subject: [PATCH 2/2] Implement stop-at-entry functionality --- .../Debugger/ManagedDebugger.cs | 7 +++++ .../Debugger/ManagedDebugger_EventHandlers.cs | 26 +++++++++++++++++++ .../ManagedDebugger_RequestHandlers.cs | 22 ++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/src/SharpDbg.Infrastructure/Debugger/ManagedDebugger.cs b/src/SharpDbg.Infrastructure/Debugger/ManagedDebugger.cs index c2a2681..ecf6106 100644 --- a/src/SharpDbg.Infrastructure/Debugger/ManagedDebugger.cs +++ b/src/SharpDbg.Infrastructure/Debugger/ManagedDebugger.cs @@ -24,6 +24,8 @@ public partial class ManagedDebugger : IDisposable private int? _pendingAttachProcessId; private AsyncStepper? _asyncStepper; private CompiledExpressionInterpreter? _expressionInterpreter; + private bool _stopAtEntryActive; + private bool _stopAtEntrySignaled; public event Action? OnStopped; // ThreadId, FilePath, Line, Reason @@ -98,6 +100,11 @@ private void PerformAttach(int processId) private void ContinueProcess() { + if (_stopAtEntryActive && _stopAtEntrySignaled) + { + _logger?.Invoke("Auto-continue suppressed (stop-at-entry signaled)"); + return; + } if (_rawProcess != null) { IsRunning = true; diff --git a/src/SharpDbg.Infrastructure/Debugger/ManagedDebugger_EventHandlers.cs b/src/SharpDbg.Infrastructure/Debugger/ManagedDebugger_EventHandlers.cs index 0d1766f..26ca035 100644 --- a/src/SharpDbg.Infrastructure/Debugger/ManagedDebugger_EventHandlers.cs +++ b/src/SharpDbg.Infrastructure/Debugger/ManagedDebugger_EventHandlers.cs @@ -10,6 +10,25 @@ private void HandleProcessCreated(object? sender, CreateProcessCorDebugManagedCa { _logger?.Invoke("Process created event"); _rawProcess = createProcessCorDebugManagedCallbackEventArgs.Process; + CorDebugThread? corThread = null; + foreach (var thread in _rawProcess.EnumerateThreads()) + { + corThread = thread; + break; + } + if (corThread != null) + { + _threads[corThread.Id] = corThread; + OnThreadStarted?.Invoke(corThread.Id, $"Thread {corThread.Id}"); + } + + if (_stopAtEntryActive && !_stopAtEntrySignaled && corThread != null) + { + _stopAtEntrySignaled = true; + IsRunning = false; + OnStopped?.Invoke(corThread.Id, "entry"); + return; + } ContinueProcess(); } @@ -27,6 +46,13 @@ private void HandleThreadCreated(object? sender, CreateThreadCorDebugManagedCall var corThread = createThreadCorDebugManagedCallbackEventArgs.Thread; _threads[corThread.Id] = corThread; OnThreadStarted?.Invoke(corThread.Id, $"Thread {corThread.Id}"); + if (_stopAtEntryActive && !_stopAtEntrySignaled) + { + _stopAtEntrySignaled = true; + IsRunning = false; + OnStopped?.Invoke(corThread.Id, "entry"); + return; + } ContinueProcess(); } diff --git a/src/SharpDbg.Infrastructure/Debugger/ManagedDebugger_RequestHandlers.cs b/src/SharpDbg.Infrastructure/Debugger/ManagedDebugger_RequestHandlers.cs index aab6954..9f4c1ce 100644 --- a/src/SharpDbg.Infrastructure/Debugger/ManagedDebugger_RequestHandlers.cs +++ b/src/SharpDbg.Infrastructure/Debugger/ManagedDebugger_RequestHandlers.cs @@ -53,6 +53,8 @@ private void PerformLaunch() _pendingLaunchProgram = null; _pendingLaunchArgs = null; _pendingLaunchWorkingDirectory = null; + _stopAtEntryActive = stopAtEntry; + _stopAtEntrySignaled = false; // Build command line: "program" "arg1" "arg2" ... var commandLine = new StringBuilder(); @@ -191,6 +193,11 @@ public void Continue() _logger?.Invoke("Continue"); if (_rawProcess != null) { + if (_stopAtEntryActive) + { + _stopAtEntryActive = false; + _stopAtEntrySignaled = false; + } IsRunning = true; _variableManager.ClearAndDisposeHandleValues(); _rawProcess.Continue(false); @@ -217,6 +224,11 @@ public void Pause() public async void StepNext(int threadId) { _logger?.Invoke($"StepNext on thread {threadId}"); + if (_stopAtEntryActive) + { + _stopAtEntryActive = false; + _stopAtEntrySignaled = false; + } if (_threads.TryGetValue(threadId, out var thread)) { var frame = thread.ActiveFrame; @@ -250,6 +262,11 @@ public async void StepNext(int threadId) public async void StepIn(int threadId) { _logger?.Invoke($"StepIn on thread {threadId}"); + if (_stopAtEntryActive) + { + _stopAtEntryActive = false; + _stopAtEntrySignaled = false; + } if (_threads.TryGetValue(threadId, out var thread)) { var frame = thread.ActiveFrame; @@ -283,6 +300,11 @@ public async void StepIn(int threadId) public async void StepOut(int threadId) { _logger?.Invoke($"StepOut on thread {threadId}"); + if (_stopAtEntryActive) + { + _stopAtEntryActive = false; + _stopAtEntrySignaled = false; + } if (_threads.TryGetValue(threadId, out var thread)) { var frame = thread.ActiveFrame;