Skip to content

Commit

Permalink
Add JIT event signalling and --break arg for attach
Browse files Browse the repository at this point in the history
  • Loading branch information
mitchcapper committed Mar 16, 2024
1 parent 6b7d3c5 commit 132344d
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,101 @@ You should have received a copy of the GNU General Public License
using System;
using System.ComponentModel.Composition;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using dnSpy.Contracts.App;
using dnSpy.Contracts.Debugger;
using dnSpy.Contracts.Debugger.Attach;

namespace dnSpy.Debugger.Attach {
[Export(typeof(IAppCommandLineArgsHandler))]
sealed class AppCommandLineArgsHandler : IAppCommandLineArgsHandler {
readonly Lazy<AttachableProcessesService> attachableProcessesService;
readonly Lazy<DbgManager> dbgManager;

[ImportingConstructor]
AppCommandLineArgsHandler(Lazy<AttachableProcessesService> attachableProcessesService) =>
AppCommandLineArgsHandler(Lazy<AttachableProcessesService> attachableProcessesService, Lazy<DbgManager> dbgManager) {
this.attachableProcessesService = attachableProcessesService;
this.dbgManager = dbgManager;
}

public double Order => 0;

[DllImport("kernel32.dll")]
private static extern bool SetEvent(IntPtr hEvent);

[DllImport("kernel32.dll")]
private static extern bool CloseHandle(IntPtr hObject);

private async Task HandleEventOnAttach(AttachableProcess process, IntPtr jitDebugStartEventHandle, bool BreakOnStart) {
var mgr = dbgManager.Value;
var tsc = new TaskCompletionSource<object?>();
Func<Task>? SendSignalEvent = jitDebugStartEventHandle != IntPtr.Zero ? async () => {
var hdl = jitDebugStartEventHandle;
jitDebugStartEventHandle = IntPtr.Zero;//we might get called multiple times
if (hdl != IntPtr.Zero) {
await Task.Delay(500); //without the delay we will be 15-40 ticks late on attaching, there is no maximum here other than responsiveness for user. Most of the time without the resume event sent we will not fully attach
SetEvent(hdl);
CloseHandle(hdl);
}
}
: null;

EventHandler<DbgMessageThreadCreatedEventArgs>? threadCreatedHandler = null;

threadCreatedHandler = async (_, e) => {

Check warning on line 66 in Extensions/dnSpy.Debugger/dnSpy.Debugger/Attach/AppCommandLineArgsHandler.cs

View workflow job for this annotation

GitHub Actions / Build (netframework)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 66 in Extensions/dnSpy.Debugger/dnSpy.Debugger/Attach/AppCommandLineArgsHandler.cs

View workflow job for this annotation

GitHub Actions / Build (netframework)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 66 in Extensions/dnSpy.Debugger/dnSpy.Debugger/Attach/AppCommandLineArgsHandler.cs

View workflow job for this annotation

GitHub Actions / Build (netframework)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 66 in Extensions/dnSpy.Debugger/dnSpy.Debugger/Attach/AppCommandLineArgsHandler.cs

View workflow job for this annotation

GitHub Actions / Build (netframework)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 66 in Extensions/dnSpy.Debugger/dnSpy.Debugger/Attach/AppCommandLineArgsHandler.cs

View workflow job for this annotation

GitHub Actions / Build (net-x86)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 66 in Extensions/dnSpy.Debugger/dnSpy.Debugger/Attach/AppCommandLineArgsHandler.cs

View workflow job for this annotation

GitHub Actions / Build (net-x86)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 66 in Extensions/dnSpy.Debugger/dnSpy.Debugger/Attach/AppCommandLineArgsHandler.cs

View workflow job for this annotation

GitHub Actions / Build (net-x64)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 66 in Extensions/dnSpy.Debugger/dnSpy.Debugger/Attach/AppCommandLineArgsHandler.cs

View workflow job for this annotation

GitHub Actions / Build (net-x64)

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
mgr.MessageThreadCreated -= threadCreatedHandler;
if (BreakOnStart)
e.Pause = true;
tsc.TrySetResult(default);

};
EventHandler? runningDebuggingHandler = null;
runningDebuggingHandler = (_, _) => {
if (!mgr.IsDebugging || mgr.IsRunning != true)
return;
SendSignalEvent?.Invoke();
mgr.IsRunningChanged -= runningDebuggingHandler;
mgr.IsDebuggingChanged -= runningDebuggingHandler;
};

if (jitDebugStartEventHandle != IntPtr.Zero) {
mgr.IsRunningChanged += runningDebuggingHandler;
mgr.IsDebuggingChanged += runningDebuggingHandler;
}

mgr.MessageThreadCreated += threadCreatedHandler; // even if we are not BreakOnStart we will use this to complete the wait event

process.Attach();

/*
We want to do cleanup here for a few just in cases:
- If we don't attach we want to remove the event listeners so we don't randomly pause a future session.
- if the debug manager status events don't go off as expected and we haven't already sent the event handle signal the process is suspended until the signal comes (or we exit) so it acts as a backup to firing that event.
*/
if (await Task.WhenAny(Task.Delay(TimeSpan.FromSeconds(10)), tsc.Task) != tsc.Task) {
SendSignalEvent?.Invoke();
mgr.MessageThreadCreated -= threadCreatedHandler;
mgr.IsRunningChanged -= runningDebuggingHandler;
mgr.IsDebuggingChanged -= runningDebuggingHandler;
}
}
public async void OnNewArgs(IAppCommandLineArgs args) {
AttachableProcess? process = null;
if (args.DebugAttachPid is int pid && pid != 0) {
var processes = await attachableProcessesService.Value.GetAttachableProcessesAsync(null, new[] { pid }, null, CancellationToken.None).ConfigureAwait(false);
var process = processes.FirstOrDefault(p => p.ProcessId == pid);
process?.Attach();
process = processes.FirstOrDefault(p => p.ProcessId == pid);
}
else if (args.DebugAttachProcess is string processName && !string.IsNullOrEmpty(processName)) {
var processes = await attachableProcessesService.Value.GetAttachableProcessesAsync(processName, CancellationToken.None).ConfigureAwait(false);
var process = processes.FirstOrDefault();
process?.Attach();
process = processes.FirstOrDefault();
}
if ((args.DebugBreakOnAttach || args.DebugEvent != 0) && process is not null)
await HandleEventOnAttach(process, new IntPtr(args.DebugEvent), args.DebugBreakOnAttach);
else
process?.Attach();
}

}
}
3 changes: 3 additions & 0 deletions dnSpy/dnSpy.Contracts.DnSpy/App/IAppCommandLineArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ public interface IAppCommandLineArgs {
/// <summary>Attach to this process, unless it's 0</summary>
int DebugAttachPid { get; }

/// <summary>If attaching on startup break right away</summary>
bool DebugBreakOnAttach { get; }

/// <summary>Event handle duplicated into the postmortem debugger process</summary>
uint DebugEvent { get; }

Expand Down
6 changes: 6 additions & 0 deletions dnSpy/dnSpy/MainApp/AppCommandLineArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ sealed class AppCommandLineArgs : IAppCommandLineArgs {
public string HideToolWindow { get; }
public bool ShowStartupTime { get; }
public int DebugAttachPid { get; }
public bool DebugBreakOnAttach { get; }
public uint DebugEvent { get; }
public ulong JitDebugInfo { get; }
public string DebugAttachProcess { get; }
Expand Down Expand Up @@ -76,6 +77,7 @@ public AppCommandLineArgs(string[] args) {
HideToolWindow = string.Empty;
ShowStartupTime = false;
DebugAttachPid = 0;
DebugBreakOnAttach = false;
DebugAttachProcess = string.Empty;
ExtraExtensionDirectory = string.Empty;

Expand Down Expand Up @@ -178,6 +180,10 @@ public AppCommandLineArgs(string[] args) {
i++;
break;

case "--break":
DebugBreakOnAttach = true;
break;

case "-e":
if (TryParseUInt32(next, out uint debugEvent))
DebugEvent = debugEvent;
Expand Down

0 comments on commit 132344d

Please sign in to comment.