Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/SharpDbg.Application/DebugAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -224,14 +224,15 @@ protected override LaunchResponse HandleLaunchRequest(LaunchArguments arguments)
protected override AttachResponse HandleAttachRequest(AttachArguments arguments)
{
var processId = GetConfigValue<int?>(arguments.ConfigurationProperties, "processId");
var stopAtEntry = GetConfigValue<bool?>(arguments.ConfigurationProperties, "stopAtEntry") ?? false;
if (processId == null)
{
throw new ProtocolException("Missing process ID");
}

try
{
_debugger.Attach(processId.Value);
_debugger.Attach(processId.Value, stopAtEntry);
return new AttachResponse();
}
catch (Exception ex)
Expand Down
7 changes: 7 additions & 0 deletions src/SharpDbg.Infrastructure/Debugger/ManagedDebugger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<int, string>? OnStopped;
// ThreadId, FilePath, Line, Reason
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ private void HandleProcessCreated(object? sender, CreateProcessCorDebugManagedCa
{
_logger?.Invoke("Process created event");
_rawProcess = createProcessCorDebugManagedCallbackEventArgs.Process;
var corThread = _rawProcess.EnumerateThreads().FirstOrDefault();
if (corThread != null) _threads.TryAdd(corThread.Id, corThread);

if (_stopAtEntryActive && !_stopAtEntrySignaled && corThread != null)
{
_stopAtEntrySignaled = true;
IsRunning = false;
OnStopped?.Invoke(corThread.Id, "entry");
return;
}

ContinueProcess();
}
Expand All @@ -25,8 +35,16 @@ private void HandleProcessExited(object? sender, ExitProcessCorDebugManagedCallb
private void HandleThreadCreated(object? sender, CreateThreadCorDebugManagedCallbackEventArgs createThreadCorDebugManagedCallbackEventArgs)
{
var corThread = createThreadCorDebugManagedCallbackEventArgs.Thread;
var threadAlreadyKnown = _threads.ContainsKey(corThread.Id);
_threads[corThread.Id] = corThread;
OnThreadStarted?.Invoke(corThread.Id, $"Thread {corThread.Id}");
if (!threadAlreadyKnown) OnThreadStarted?.Invoke(corThread.Id, $"Thread {corThread.Id}");
if (_stopAtEntryActive && !_stopAtEntrySignaled)
{
_stopAtEntrySignaled = true;
IsRunning = false;
OnStopped?.Invoke(corThread.Id, "entry");
return;
}
ContinueProcess();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -156,10 +158,12 @@ public bool RemoveBreakpoint(int id)
/// <summary>
/// Store process ID for later attach (actual attach happens in ConfigurationDone)
/// </summary>
public void Attach(int processId)
public void Attach(int processId, bool stopAtEntry = false)
{
_logger?.Invoke($"Storing attach target: {processId}");
_pendingAttachProcessId = processId;
_stopAtEntryActive = stopAtEntry;
_stopAtEntrySignaled = false;
}

/// <summary>
Expand Down Expand Up @@ -191,6 +195,11 @@ public void Continue()
_logger?.Invoke("Continue");
if (_rawProcess != null)
{
if (_stopAtEntryActive)
{
_stopAtEntryActive = false;
_stopAtEntrySignaled = false;
}
IsRunning = true;
_variableManager.ClearAndDisposeHandleValues();
_rawProcess.Continue(false);
Expand All @@ -217,6 +226,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;
Expand Down Expand Up @@ -250,6 +264,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;
Expand Down Expand Up @@ -283,6 +302,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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public static InitializeRequest GetInitializeRequest()
};
}

public static AttachRequest GetAttachRequest(int processId)
public static AttachRequest GetAttachRequest(int processId, bool stopAtEntry = false)
{
return new AttachRequest
{
Expand All @@ -81,6 +81,7 @@ public static AttachRequest GetAttachRequest(int processId)
["name"] = "AttachRequestName",
["type"] = "coreclr",
["processId"] = processId,
["stopAtEntry"] = stopAtEntry,
["console"] = "internalConsole", // integratedTerminal, externalTerminal, internalConsole
}
};
Expand Down
44 changes: 44 additions & 0 deletions tests/SharpDbg.Cli.Tests/StopAtEntryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using AwesomeAssertions;
using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.Messages;
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, stopAtEntry: true)
.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.WithConfigurationDoneRequest()
.WithOptionalResumeRuntime(p2.Id, startSuspended);

var entryStoppedEvent = await stoppedEventTcs.Tcs.Task.WaitAsync(TimeSpan.FromSeconds(15));
stoppedEventTcs.Tcs = new TaskCompletionSource<StoppedEvent>(TaskCreationOptions.RunContinuationsAsynchronously);
entryStoppedEvent.Reason.Should().Be(StoppedEvent.ReasonValue.Entry);

debugProtocolHost.WithBreakpointsRequest(bpLine, programPath)
.WithContinueRequest();

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);
}
}
4 changes: 2 additions & 2 deletions tests/SharpDbg.Cli.Tests/TestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ public static DebugProtocolHost WithInitializeRequest(this DebugProtocolHost deb
return debugProtocolHost;
}

public static DebugProtocolHost WithAttachRequest(this DebugProtocolHost debugProtocolHost, int debuggableProcessId)
public static DebugProtocolHost WithAttachRequest(this DebugProtocolHost debugProtocolHost, int debuggableProcessId, bool stopAtEntry = false)
{
var attachRequest = DebugAdapterProcessHelper.GetAttachRequest(debuggableProcessId);
var attachRequest = DebugAdapterProcessHelper.GetAttachRequest(debuggableProcessId, stopAtEntry);
debugProtocolHost.SendRequestSync(attachRequest);
return debugProtocolHost;
}
Expand Down