mirror of https://github.com/winsw/winsw
194 lines
6.5 KiB
C#
194 lines
6.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading;
|
|
using Microsoft.Diagnostics.Runtime;
|
|
using Microsoft.Diagnostics.Runtime.Interop;
|
|
using WinSW.Native;
|
|
using Xunit;
|
|
|
|
namespace WinSW.Tests.Util
|
|
{
|
|
internal sealed class InterProcessCodeCoverageSession : IDebugEventCallbacks
|
|
{
|
|
private readonly Type trackerType;
|
|
private readonly FieldInfo hitsField;
|
|
|
|
private readonly IDebugControl control;
|
|
private readonly DataTarget target;
|
|
private readonly Thread thread;
|
|
|
|
private List<Exception> exceptions;
|
|
private bool exited;
|
|
|
|
internal InterProcessCodeCoverageSession(string serviceName)
|
|
{
|
|
var trackerType = this.trackerType = typeof(Program).Assembly.GetTypes().Single(type => type.Namespace == "Coverlet.Core.Instrumentation.Tracker");
|
|
var hitsField = this.hitsField = trackerType.GetField("HitsArray", BindingFlags.Public | BindingFlags.Static);
|
|
Assert.NotNull(hitsField);
|
|
|
|
using var scm = ServiceManager.Open(ServiceApis.ServiceManagerAccess.Connect);
|
|
using var sc = scm.OpenService(serviceName, ServiceApis.ServiceAccess.QueryStatus);
|
|
|
|
int processId = sc.ProcessId;
|
|
Assert.True(processId >= 0);
|
|
|
|
var guid = new Guid("27fe5639-8407-4f47-8364-ee118fb08ac8");
|
|
int hr = Native.DebugCreate(guid, out object unknown);
|
|
AssertEx.Succeeded(hr);
|
|
|
|
var client = (IDebugClient)unknown;
|
|
this.control = (IDebugControl)unknown;
|
|
|
|
hr = client.AttachProcess(0, (uint)processId, DEBUG_ATTACH.DEFAULT);
|
|
AssertEx.Succeeded(hr);
|
|
|
|
hr = client.SetEventCallbacks(this);
|
|
AssertEx.Succeeded(hr);
|
|
|
|
#pragma warning disable CA1416 // Validate platform compatibility
|
|
IntPtr pointer = Marshal.GetIUnknownForObject(client);
|
|
Assert.Equal(1, Marshal.Release(pointer));
|
|
#pragma warning restore CA1416 // Validate platform compatibility
|
|
|
|
target = DataTarget.CreateFromDbgEng(pointer);
|
|
|
|
var thread = this.thread = new Thread(() =>
|
|
{
|
|
try
|
|
{
|
|
using (this.target)
|
|
{
|
|
do
|
|
{
|
|
int hr = this.control.WaitForEvent(DEBUG_WAIT.DEFAULT, 0xffffffff);
|
|
AssertEx.Succeeded(hr);
|
|
}
|
|
while (!this.exited);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
(this.exceptions ??= new List<Exception>()).Add(e);
|
|
}
|
|
});
|
|
thread.Start();
|
|
}
|
|
|
|
/// <exception cref="AggregateException" />
|
|
internal void Wait()
|
|
{
|
|
this.thread.Join();
|
|
|
|
if (this.exceptions != null)
|
|
{
|
|
throw new AggregateException(this.exceptions);
|
|
}
|
|
}
|
|
|
|
int IDebugEventCallbacks.GetInterestMask(out DEBUG_EVENT Mask)
|
|
{
|
|
Mask = DEBUG_EVENT.EXIT_PROCESS;
|
|
return 0;
|
|
}
|
|
|
|
int IDebugEventCallbacks.Breakpoint(IDebugBreakpoint Bp)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
int IDebugEventCallbacks.Exception(in EXCEPTION_RECORD64 Exception, uint FirstChance)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
int IDebugEventCallbacks.CreateThread(ulong Handle, ulong DataOffset, ulong StartOffset)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
int IDebugEventCallbacks.ExitThread(uint ExitCode)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
int IDebugEventCallbacks.CreateProcess(ulong ImageFileHandle, ulong Handle, ulong BaseOffset, uint ModuleSize, string ModuleName, string ImageName, uint CheckSum, uint TimeDateStamp, ulong InitialThreadHandle, ulong ThreadDataOffset, ulong StartOffset)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
int IDebugEventCallbacks.ExitProcess(uint ExitCode)
|
|
{
|
|
this.exited = true;
|
|
|
|
try
|
|
{
|
|
using var runtime = this.target.ClrVersions.Single().CreateRuntime();
|
|
|
|
var module = runtime.EnumerateModules().First(module => module.Name == typeof(Program).Assembly.Location);
|
|
|
|
var type = module.GetTypeByName(this.trackerType.FullName);
|
|
var field = type.GetStaticFieldByName(this.hitsField.Name);
|
|
var array = field.ReadObject(runtime.AppDomains.Single()).AsArray();
|
|
|
|
int[] hits = (int[])this.hitsField.GetValue(null);
|
|
|
|
int[] values = array.ReadValues<int>(0, hits.Length);
|
|
for (int i = 0; i < hits.Length; i++)
|
|
{
|
|
hits[i] += values[i];
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
(this.exceptions ??= new List<Exception>()).Add(e);
|
|
}
|
|
|
|
return (int)DEBUG_STATUS.BREAK;
|
|
}
|
|
|
|
int IDebugEventCallbacks.LoadModule(ulong ImageFileHandle, ulong BaseOffset, uint ModuleSize, string ModuleName, string ImageName, uint CheckSum, uint TimeDateStamp)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
int IDebugEventCallbacks.UnloadModule(string ImageBaseName, ulong BaseOffset)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
int IDebugEventCallbacks.SystemError(uint Error, uint Level)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
int IDebugEventCallbacks.SessionStatus(DEBUG_SESSION Status)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
int IDebugEventCallbacks.ChangeDebuggeeState(DEBUG_CDS Flags, ulong Argument)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
int IDebugEventCallbacks.ChangeEngineState(DEBUG_CES Flags, ulong Argument)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
int IDebugEventCallbacks.ChangeSymbolState(DEBUG_CSS Flags, ulong Argument)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private static class Native
|
|
{
|
|
[DllImport("dbgeng.dll")]
|
|
internal static extern int DebugCreate(in Guid InterfaceId, [MarshalAs(UnmanagedType.IUnknown)] out object Interface);
|
|
}
|
|
}
|
|
}
|