|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#if FEATURE_FILE_TRACKER
using System;
using System.IO;
using System.Runtime.InteropServices;
#if FEATURE_CONSTRAINED_EXECUTION
using System.Runtime.ConstrainedExecution;
#endif
using System.Security;
using Microsoft.Build.Shared.FileSystem;
#if FEATURE_RESOURCE_EXPOSURE
using System.Runtime.Versioning;
#endif
#nullable disable
namespace Microsoft.Build.Shared
{
/// <summary>
/// Methods that are invoked on FileTracker.dll in order to handle inproc tracking
/// </summary>
/// <comments>
/// We want to P/Invoke to the FileTracker methods, but FileTracker.dll is not guaranteed to be on PATH (since it's
/// in the MSBuild directory), and there is no DefaultDllImportSearchPath that explicitly points to us. Thus, we
/// are sneaking around P/Invoke by manually acquiring the method pointers and calling them ourselves. The vast
/// majority of this code was lifted from ndp\fx\src\CLRCompression\ZLibNative.cs, which does the same thing for
/// that assembly.
/// </comments>
internal static class InprocTrackingNativeMethods
{
#pragma warning disable format // region formatting is different in net7.0 and net472, and cannot be fixed for both
#region Delegates for the tracking functions
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
#if FEATURE_SECURITY_PERMISSIONS
[SuppressUnmanagedCodeSecurity]
#endif
private delegate int StartTrackingContextDelegate([In, MarshalAs(UnmanagedType.LPWStr)] string intermediateDirectory, [In, MarshalAs(UnmanagedType.LPWStr)] string taskName);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
#if FEATURE_SECURITY_PERMISSIONS
[SuppressUnmanagedCodeSecurity]
#endif
private delegate int StartTrackingContextWithRootDelegate([In, MarshalAs(UnmanagedType.LPWStr)] string intermediateDirectory, [In, MarshalAs(UnmanagedType.LPWStr)] string taskName, [In, MarshalAs(UnmanagedType.LPWStr)] string rootMarker);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
#if FEATURE_SECURITY_PERMISSIONS
[SuppressUnmanagedCodeSecurity]
#endif
private delegate int EndTrackingContextDelegate();
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
#if FEATURE_SECURITY_PERMISSIONS
[SuppressUnmanagedCodeSecurity]
#endif
private delegate int StopTrackingAndCleanupDelegate();
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
#if FEATURE_SECURITY_PERMISSIONS
[SuppressUnmanagedCodeSecurity]
#endif
private delegate int SuspendTrackingDelegate();
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
#if FEATURE_SECURITY_PERMISSIONS
[SuppressUnmanagedCodeSecurity]
#endif
private delegate int ResumeTrackingDelegate();
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
#if FEATURE_SECURITY_PERMISSIONS
[SuppressUnmanagedCodeSecurity]
#endif
private delegate int WriteAllTLogsDelegate([In, MarshalAs(UnmanagedType.LPWStr)] string intermediateDirectory, [In, MarshalAs(UnmanagedType.LPWStr)] string tlogRootName);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
#if FEATURE_SECURITY_PERMISSIONS
[SuppressUnmanagedCodeSecurity]
#endif
private delegate int WriteContextTLogsDelegate([In, MarshalAs(UnmanagedType.LPWStr)] string intermediateDirectory, [In, MarshalAs(UnmanagedType.LPWStr)] string tlogRootName);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
#if FEATURE_SECURITY_PERMISSIONS
[SuppressUnmanagedCodeSecurity]
#endif
private delegate int SetThreadCountDelegate(int threadCount);
#endregion // Delegates for the tracking functions
#region Public API
internal static void StartTrackingContext([In, MarshalAs(UnmanagedType.LPWStr)] string intermediateDirectory, [In, MarshalAs(UnmanagedType.LPWStr)] string taskName)
{
int hresult = FileTrackerDllStub.startTrackingContextDelegate(intermediateDirectory, taskName);
Marshal.ThrowExceptionForHR(hresult, new IntPtr(-1));
}
internal static void StartTrackingContextWithRoot([In, MarshalAs(UnmanagedType.LPWStr)] string intermediateDirectory, [In, MarshalAs(UnmanagedType.LPWStr)] string taskName, [In, MarshalAs(UnmanagedType.LPWStr)] string rootMarker)
{
int hresult = FileTrackerDllStub.startTrackingContextWithRootDelegate(intermediateDirectory, taskName, rootMarker);
Marshal.ThrowExceptionForHR(hresult, new IntPtr(-1));
}
internal static void EndTrackingContext()
{
int hresult = FileTrackerDllStub.endTrackingContextDelegate();
Marshal.ThrowExceptionForHR(hresult, new IntPtr(-1));
}
internal static void StopTrackingAndCleanup()
{
int hresult = FileTrackerDllStub.stopTrackingAndCleanupDelegate();
Marshal.ThrowExceptionForHR(hresult, new IntPtr(-1));
}
internal static void SuspendTracking()
{
int hresult = FileTrackerDllStub.suspendTrackingDelegate();
Marshal.ThrowExceptionForHR(hresult, new IntPtr(-1));
}
internal static void ResumeTracking()
{
int hresult = FileTrackerDllStub.resumeTrackingDelegate();
Marshal.ThrowExceptionForHR(hresult, new IntPtr(-1));
}
internal static void WriteAllTLogs([In, MarshalAs(UnmanagedType.LPWStr)] string intermediateDirectory, [In, MarshalAs(UnmanagedType.LPWStr)] string tlogRootName)
{
int hresult = FileTrackerDllStub.writeAllTLogsDelegate(intermediateDirectory, tlogRootName);
Marshal.ThrowExceptionForHR(hresult, new IntPtr(-1));
}
internal static void WriteContextTLogs([In, MarshalAs(UnmanagedType.LPWStr)] string intermediateDirectory, [In, MarshalAs(UnmanagedType.LPWStr)] string tlogRootName)
{
int hresult = FileTrackerDllStub.writeContextTLogsDelegate(intermediateDirectory, tlogRootName);
Marshal.ThrowExceptionForHR(hresult, new IntPtr(-1));
}
internal static void SetThreadCount(int threadCount)
{
int hresult = FileTrackerDllStub.setThreadCountDelegate(threadCount);
Marshal.ThrowExceptionForHR(hresult, new IntPtr(-1));
}
#endregion // Public API
#pragma warning restore format
private static class FileTrackerDllStub
{
private static readonly Lazy<string> fileTrackerDllName = new Lazy<string>(() => RuntimeInformation.ProcessArchitecture == Architecture.Arm64 ? "FileTrackerA4.dll" : (IntPtr.Size == sizeof(Int32)) ? "FileTracker32.dll" : "FileTracker64.dll");
// Handle for FileTracker.dll itself
[SecurityCritical]
private static SafeHandle s_fileTrackerDllHandle;
#pragma warning disable format // region formatting is different in net7.0 and net472, and cannot be fixed for both
#region Function pointers to native functions
internal static StartTrackingContextDelegate startTrackingContextDelegate;
internal static StartTrackingContextWithRootDelegate startTrackingContextWithRootDelegate;
internal static EndTrackingContextDelegate endTrackingContextDelegate;
internal static StopTrackingAndCleanupDelegate stopTrackingAndCleanupDelegate;
internal static SuspendTrackingDelegate suspendTrackingDelegate;
internal static ResumeTrackingDelegate resumeTrackingDelegate;
internal static WriteAllTLogsDelegate writeAllTLogsDelegate;
internal static WriteContextTLogsDelegate writeContextTLogsDelegate;
internal static SetThreadCountDelegate setThreadCountDelegate;
#endregion // Function pointers to native functions
#region Declarations of Windows API needed to load the native library
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, BestFitMapping = false)]
#if FEATURE_RESOURCE_EXPOSURE
[ResourceExposure(ResourceScope.Process)]
#endif
[SecurityCritical]
private static extern IntPtr GetProcAddress(SafeHandle moduleHandle, String procName);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
#if FEATURE_RESOURCE_EXPOSURE
[ResourceExposure(ResourceScope.Machine)]
#endif
[SecurityCritical]
private static extern SafeLibraryHandle LoadLibrary(String libPath);
#endregion // Declarations of Windows API needed to load the native library
#region Initialization code
/// <summary>
/// Loads FileTracker.dll into a handle that we can use subsequently to grab the exported methods we're interested in.
/// </summary>
private static void LoadFileTrackerDll()
{
// Get the FileTracker in our directory that matches the currently running process
string buildToolsPath = FrameworkLocationHelper.GeneratePathToBuildToolsForToolsVersion(MSBuildConstants.CurrentToolsVersion, DotNetFrameworkArchitecture.Current);
string fileTrackerPath = Path.Combine(buildToolsPath, fileTrackerDllName.Value);
if (!FileSystems.Default.FileExists(fileTrackerPath))
{
throw new DllNotFoundException(fileTrackerDllName.Value);
}
SafeLibraryHandle handle = LoadLibrary(fileTrackerPath);
if (handle.IsInvalid)
{
Int32 hresult = Marshal.GetHRForLastWin32Error();
Marshal.ThrowExceptionForHR(hresult, new IntPtr(-1));
// If Marshal.ThrowExceptionForHR did not throw, we still need to make sure to throw:
throw new InvalidOperationException();
}
s_fileTrackerDllHandle = handle;
}
/// <summary>
/// Generic code to grab the function pointer for a function exported by FileTracker.dll, given
/// that function's name, and transform that function pointer into a callable delegate.
/// </summary>
[SecurityCritical]
private static DT CreateDelegate<DT>(String entryPointName)
{
IntPtr entryPoint = GetProcAddress(s_fileTrackerDllHandle, entryPointName);
if (IntPtr.Zero == entryPoint)
{
throw new EntryPointNotFoundException(fileTrackerDllName.Value + "!" + entryPointName);
}
return (DT)(Object)Marshal.GetDelegateForFunctionPointer(entryPoint, typeof(DT));
}
/// <summary>
/// Actually generate all of the delegates that will be called by our public (or rather, internal) surface area methods.
/// </summary>
private static void InitDelegates()
{
ErrorUtilities.VerifyThrow(s_fileTrackerDllHandle != null, "fileTrackerDllHandle should not be null");
ErrorUtilities.VerifyThrow(!s_fileTrackerDllHandle.IsInvalid, "Handle for FileTracker.dll should not be invalid");
startTrackingContextDelegate = CreateDelegate<StartTrackingContextDelegate>("StartTrackingContext");
startTrackingContextWithRootDelegate = CreateDelegate<StartTrackingContextWithRootDelegate>("StartTrackingContextWithRoot");
endTrackingContextDelegate = CreateDelegate<EndTrackingContextDelegate>("EndTrackingContext");
stopTrackingAndCleanupDelegate = CreateDelegate<StopTrackingAndCleanupDelegate>("StopTrackingAndCleanup");
suspendTrackingDelegate = CreateDelegate<SuspendTrackingDelegate>("SuspendTracking");
resumeTrackingDelegate = CreateDelegate<ResumeTrackingDelegate>("ResumeTracking");
writeAllTLogsDelegate = CreateDelegate<WriteAllTLogsDelegate>("WriteAllTLogs");
writeContextTLogsDelegate = CreateDelegate<WriteContextTLogsDelegate>("WriteContextTLogs");
setThreadCountDelegate = CreateDelegate<SetThreadCountDelegate>("SetThreadCount");
}
/// <summary>
/// Static constructor -- generates the delegates for all of the export methods from
/// FileTracker.dll that we care about.
/// </summary>
[SecuritySafeCritical]
static FileTrackerDllStub()
{
LoadFileTrackerDll();
InitDelegates();
}
#endregion // Initialization code
#pragma warning restore format
// Specialized handle to make sure we free FileTracker.dll
[SecurityCritical]
private class SafeLibraryHandle : SafeHandle
{
internal SafeLibraryHandle()
: base(IntPtr.Zero, true)
{
}
public override bool IsInvalid
{
[SecurityCritical]
get
{ return IntPtr.Zero == handle; }
}
#if FEATURE_CONSTRAINED_EXECUTION
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
#endif
[SecurityCritical]
protected override bool ReleaseHandle()
{
// FileTracker expects to continue to exist even through ExitProcess -- if we forcibly unload it now,
// bad things can happen when the CLR attempts to call the (still detoured?) ExitProcess.
return true;
}
} // private class SafeLibraryHandle
}
}
}
#endif
|