WinAPI – Random IT Utensils https://blog.adamfurmanek.pl IT, operating systems, maths, and more. Sat, 02 Jan 2021 19:10:46 +0000 en-US hourly 1 https://wordpress.org/?v=6.6.2 .NET Inside Out Part 15 — Starting process on different desktop https://blog.adamfurmanek.pl/2020/02/29/net-inside-out-part-15/ https://blog.adamfurmanek.pl/2020/02/29/net-inside-out-part-15/#comments Sat, 29 Feb 2020 09:00:21 +0000 https://blog.adamfurmanek.pl/?p=3249 Continue reading .NET Inside Out Part 15 — Starting process on different desktop]]>

This is the fifteenth part of the .NET Inside Out series. For your convenience you can find other parts in the table of contents in Part 1 – Virtual and non-virtual calls in C#

Windows supports multiple desktops for a long time. We may want to run an application on different one than the current but it isn’t easy in C#. There is an lpDesktop property but it isn’t directly exposed to the C# wrapper. Let’s see what we can do about that.

First, let’s see if it works. I’m using Sysinternals Desktops application to create new desktop. It is named “Sysinternals Desktop 1”. To start a process there I can use the following C++ code:

#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <tchar.h>

void _tmain(int argc, TCHAR *argv[])
{
	STARTUPINFO si;
	PROCESS_INFORMATION pi;

	ZeroMemory(&si, sizeof(si));
	si.cb = sizeof(si);
	ZeroMemory(&pi, sizeof(pi));
	wchar_t* name = L"Sysinternals Desktop 1";
	si.lpDesktop = name;

	if (argc != 2)
	{
		printf("Usage: %s [cmdline]\n", argv[0]);
		return;
	}

	// Start the child process. 
	if (!CreateProcess(NULL,   // No module name (use command line)
		argv[1],        // Command line
		NULL,           // Process handle not inheritable
		NULL,           // Thread handle not inheritable
		FALSE,          // Set handle inheritance to FALSE
		0,              // No creation flags
		NULL,           // Use parent's environment block
		NULL,           // Use parent's starting directory 
		&si,            // Pointer to STARTUPINFO structure
		&pi)           // Pointer to PROCESS_INFORMATION structure
		)
	{
		printf("CreateProcess failed (%d).\n", GetLastError());
		return;
	}

	// Wait until child process exits.
	WaitForSingleObject(pi.hProcess, INFINITE);

	// Close process and thread handles. 
	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);
}

Now, we somehow need to get an access to STARTUPINFO record in C#. Let’s take this code:

var process = new Process();
var startInfo = new ProcessStartInfo
{
	WindowStyle = ProcessWindowStyle.Hidden,
	FileName = "notepad.exe",
	Arguments = "",
	RedirectStandardOutput = true,
	UseShellExecute = false,
	StandardOutputEncoding = Encoding.UTF8
};
process.StartInfo = startInfo;
process.Start();

When we debug Start method internals we get to the following:

private bool StartWithCreateProcess(ProcessStartInfo startInfo)
{
	if (startInfo.StandardOutputEncoding != null && !startInfo.RedirectStandardOutput)
	{
		throw new InvalidOperationException(SR.GetString("StandardOutputEncodingNotAllowed"));
	}
	if (startInfo.StandardErrorEncoding != null && !startInfo.RedirectStandardError)
	{
		throw new InvalidOperationException(SR.GetString("StandardErrorEncodingNotAllowed"));
	}
	if (this.disposed)
	{
		throw new ObjectDisposedException(base.GetType().Name);
	}
	StringBuilder stringBuilder = Process.BuildCommandLine(startInfo.FileName, startInfo.Arguments);
	NativeMethods.STARTUPINFO startupinfo = new NativeMethods.STARTUPINFO();
	SafeNativeMethods.PROCESS_INFORMATION process_INFORMATION = new SafeNativeMethods.PROCESS_INFORMATION();
	SafeProcessHandle safeProcessHandle = new SafeProcessHandle();
	SafeThreadHandle safeThreadHandle = new SafeThreadHandle();
	int num = 0;
	SafeFileHandle handle = null;
	SafeFileHandle handle2 = null;
	SafeFileHandle handle3 = null;
	GCHandle gchandle = default(GCHandle);
	object obj = Process.s_CreateProcessLock;
	lock (obj)
	{
		try
		{
			if (startInfo.RedirectStandardInput || startInfo.RedirectStandardOutput || startInfo.RedirectStandardError)
			{
				if (startInfo.RedirectStandardInput)
				{
					this.CreatePipe(out handle, out startupinfo.hStdInput, true);
				}
				else
				{
					startupinfo.hStdInput = new SafeFileHandle(NativeMethods.GetStdHandle(-10), false);
				}
				if (startInfo.RedirectStandardOutput)
				{
					this.CreatePipe(out handle2, out startupinfo.hStdOutput, false);
				}
				else
				{
					startupinfo.hStdOutput = new SafeFileHandle(NativeMethods.GetStdHandle(-11), false);
				}
				if (startInfo.RedirectStandardError)
				{
					this.CreatePipe(out handle3, out startupinfo.hStdError, false);
				}
				else
				{
					startupinfo.hStdError = new SafeFileHandle(NativeMethods.GetStdHandle(-12), false);
				}
				startupinfo.dwFlags = 256;
			}
			int num2 = 0;
			if (startInfo.CreateNoWindow)
			{
				num2 |= 134217728;
			}
			IntPtr intPtr = (IntPtr)0;
			if (startInfo.environmentVariables != null)
			{
				bool unicode = false;
				if (ProcessManager.IsNt)
				{
					num2 |= 1024;
					unicode = true;
				}
				byte[] value = EnvironmentBlock.ToByteArray(startInfo.environmentVariables, unicode);
				gchandle = GCHandle.Alloc(value, GCHandleType.Pinned);
				intPtr = gchandle.AddrOfPinnedObject();
			}
			string text = startInfo.WorkingDirectory;
			if (text == string.Empty)
			{
				text = Environment.CurrentDirectory;
			}
			bool flag2;
			if (startInfo.UserName.Length != 0)
			{
				if (startInfo.Password != null && startInfo.PasswordInClearText != null)
				{
					throw new ArgumentException(SR.GetString("CantSetDuplicatePassword"));
				}
				NativeMethods.LogonFlags logonFlags = (NativeMethods.LogonFlags)0;
				if (startInfo.LoadUserProfile)
				{
					logonFlags = NativeMethods.LogonFlags.LOGON_WITH_PROFILE;
				}
				IntPtr intPtr2 = IntPtr.Zero;
				try
				{
					if (startInfo.Password != null)
					{
						intPtr2 = Marshal.SecureStringToCoTaskMemUnicode(startInfo.Password);
					}
					else if (startInfo.PasswordInClearText != null)
					{
						intPtr2 = Marshal.StringToCoTaskMemUni(startInfo.PasswordInClearText);
					}
					else
					{
						intPtr2 = Marshal.StringToCoTaskMemUni(string.Empty);
					}
					RuntimeHelpers.PrepareConstrainedRegions();
					try
					{
					}
					finally
					{
						flag2 = NativeMethods.CreateProcessWithLogonW(startInfo.UserName, startInfo.Domain, intPtr2, logonFlags, null, stringBuilder, num2, intPtr, text, startupinfo, process_INFORMATION);
						if (!flag2)
						{
							num = Marshal.GetLastWin32Error();
						}
						if (process_INFORMATION.hProcess != (IntPtr)0 && process_INFORMATION.hProcess != NativeMethods.INVALID_HANDLE_VALUE)
						{
							safeProcessHandle.InitialSetHandle(process_INFORMATION.hProcess);
						}
						if (process_INFORMATION.hThread != (IntPtr)0 && process_INFORMATION.hThread != NativeMethods.INVALID_HANDLE_VALUE)
						{
							safeThreadHandle.InitialSetHandle(process_INFORMATION.hThread);
						}
					}
					if (flag2)
					{
						goto IL_416;
					}
					if (num == 193 || num == 216)
					{
						throw new Win32Exception(num, SR.GetString("InvalidApplication"));
					}
					throw new Win32Exception(num);
				}
				finally
				{
					if (intPtr2 != IntPtr.Zero)
					{
						Marshal.ZeroFreeCoTaskMemUnicode(intPtr2);
					}
				}
			}
			RuntimeHelpers.PrepareConstrainedRegions();
			try
			{
			}
			finally
			{
				flag2 = NativeMethods.CreateProcess(null, stringBuilder, null, null, true, num2, intPtr, text, startupinfo, process_INFORMATION);
				if (!flag2)
				{
					num = Marshal.GetLastWin32Error();
				}
				if (process_INFORMATION.hProcess != (IntPtr)0 && process_INFORMATION.hProcess != NativeMethods.INVALID_HANDLE_VALUE)
				{
					safeProcessHandle.InitialSetHandle(process_INFORMATION.hProcess);
				}
				if (process_INFORMATION.hThread != (IntPtr)0 && process_INFORMATION.hThread != NativeMethods.INVALID_HANDLE_VALUE)
				{
					safeThreadHandle.InitialSetHandle(process_INFORMATION.hThread);
				}
			}
			if (!flag2)
			{
				if (num == 193 || num == 216)
				{
					throw new Win32Exception(num, SR.GetString("InvalidApplication"));
				}
				throw new Win32Exception(num);
			}
		}
		finally
		{
			if (gchandle.IsAllocated)
			{
				gchandle.Free();
			}
			startupinfo.Dispose();
		}
	}
	IL_416:
	if (startInfo.RedirectStandardInput)
	{
		this.standardInput = new StreamWriter(new FileStream(handle, FileAccess.Write, 4096, false), Console.InputEncoding, 4096);
		this.standardInput.AutoFlush = true;
	}
	if (startInfo.RedirectStandardOutput)
	{
		Encoding encoding = (startInfo.StandardOutputEncoding != null) ? startInfo.StandardOutputEncoding : Console.OutputEncoding;
		this.standardOutput = new StreamReader(new FileStream(handle2, FileAccess.Read, 4096, false), encoding, true, 4096);
	}
	if (startInfo.RedirectStandardError)
	{
		Encoding encoding2 = (startInfo.StandardErrorEncoding != null) ? startInfo.StandardErrorEncoding : Console.OutputEncoding;
		this.standardError = new StreamReader(new FileStream(handle3, FileAccess.Read, 4096, false), encoding2, true, 4096);
	}
	bool result = false;
	if (!safeProcessHandle.IsInvalid)
	{
		this.SetProcessHandle(safeProcessHandle);
		this.SetProcessId(process_INFORMATION.dwProcessId);
		safeThreadHandle.Close();
		result = true;
	}
	return result;
}

It’s quite a lot of code which we don’t want to duplicate. Crucial line is:

NativeMethods.STARTUPINFO startupinfo = new NativeMethods.STARTUPINFO();

Let’s go deeper:

// Token: 0x020006AD RID: 1709
[StructLayout(LayoutKind.Sequential)]
internal class STARTUPINFO
{
	// Token: 0x06003FC2 RID: 16322 RVA: 0x0010B934 File Offset: 0x00109B34
	public STARTUPINFO()
	{
		this.cb = Marshal.SizeOf(this);
	}

	// Token: 0x06003FC3 RID: 16323 RVA: 0x0010B9B4 File Offset: 0x00109BB4
	public void Dispose()
	{
		if (this.hStdInput != null && !this.hStdInput.IsInvalid)
		{
			this.hStdInput.Close();
			this.hStdInput = null;
		}
		if (this.hStdOutput != null && !this.hStdOutput.IsInvalid)
		{
			this.hStdOutput.Close();
			this.hStdOutput = null;
		}
		if (this.hStdError != null && !this.hStdError.IsInvalid)
		{
			this.hStdError.Close();
			this.hStdError = null;
		}
	}

	// Token: 0x04002EB6 RID: 11958
	public int cb;

	// Token: 0x04002EB7 RID: 11959
	public IntPtr lpReserved = IntPtr.Zero;

	// Token: 0x04002EB8 RID: 11960
	public IntPtr lpDesktop = IntPtr.Zero;

	// Token: 0x04002EB9 RID: 11961
	public IntPtr lpTitle = IntPtr.Zero;

	// Token: 0x04002EBA RID: 11962
	public int dwX;

	// Token: 0x04002EBB RID: 11963
	public int dwY;

	// Token: 0x04002EBC RID: 11964
	public int dwXSize;

	// Token: 0x04002EBD RID: 11965
	public int dwYSize;

	// Token: 0x04002EBE RID: 11966
	public int dwXCountChars;

	// Token: 0x04002EBF RID: 11967
	public int dwYCountChars;

	// Token: 0x04002EC0 RID: 11968
	public int dwFillAttribute;

	// Token: 0x04002EC1 RID: 11969
	public int dwFlags;

	// Token: 0x04002EC2 RID: 11970
	public short wShowWindow;

	// Token: 0x04002EC3 RID: 11971
	public short cbReserved2;

	// Token: 0x04002EC4 RID: 11972
	public IntPtr lpReserved2 = IntPtr.Zero;

	// Token: 0x04002EC5 RID: 11973
	public SafeFileHandle hStdInput = new SafeFileHandle(IntPtr.Zero, false);

	// Token: 0x04002EC6 RID: 11974
	public SafeFileHandle hStdOutput = new SafeFileHandle(IntPtr.Zero, false);

	// Token: 0x04002EC7 RID: 11975
	public SafeFileHandle hStdError = new SafeFileHandle(IntPtr.Zero, false);
}

Okay, so we have a class with two interesting fields. One of them is the size of the object — standard WinAPI approach. The other one is lpDesktop which is initialized to null pointer and cannot be directly modified. What can we do?

Well, as usual — let’s hijack the constructor and do some magic.

using System;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace DesktopHack
{
	unsafe class Program
	{
		public static byte[] tempDesktopName = Encoding.UTF8.GetBytes("Sysinternals Desktop 1");
		public static byte[] desktopName = tempDesktopName.SelectMany(b => new byte[] { b, 0 }).Concat(new byte[] { 0, 0 }).ToArray();
		public static GCHandle handle = GCHandle.Alloc(desktopName, GCHandleType.Pinned);

		static void Main(string[] args)
		{
			var matchingType = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).First(t => t.Name.Contains("STARTUPINFO"));
			var constructor = matchingType.GetConstructor(new Type[0]);
			var newConstructor = typeof(Program).GetMethod(nameof(NewConstructor), BindingFlags.Static | BindingFlags.Public);

			HijackMethod(constructor, newConstructor);
			
			var process = new Process();
			var startInfo = new ProcessStartInfo
			{
				WindowStyle = ProcessWindowStyle.Hidden,
				FileName = "notepad.exe",
				Arguments = "",
				RedirectStandardOutput = true,
				UseShellExecute = false,
				StandardOutputEncoding = Encoding.UTF8
			};
			process.StartInfo = startInfo;
			process.Start();
		}

		public static void NewConstructor(object startupInfo)
		{
			startupInfo.GetType().GetField("cb", BindingFlags.Instance | BindingFlags.Public).SetValue(startupInfo, Marshal.SizeOf(startupInfo));
			startupInfo.GetType().GetField("lpDesktop", BindingFlags.Instance | BindingFlags.Public).SetValue(startupInfo, handle.AddrOfPinnedObject());
		}

		public enum Protection
		{
			PAGE_NOACCESS = 0x01,
			PAGE_READONLY = 0x02,
			PAGE_READWRITE = 0x04,
			PAGE_WRITECOPY = 0x08,
			PAGE_EXECUTE = 0x10,
			PAGE_EXECUTE_READ = 0x20,
			PAGE_EXECUTE_READWRITE = 0x40,
			PAGE_EXECUTE_WRITECOPY = 0x80,
			PAGE_GUARD = 0x100,
			PAGE_NOCACHE = 0x200,
			PAGE_WRITECOMBINE = 0x400
		}

		[DllImport("kernel32.dll", SetLastError = true)]
		static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect);

		private static void UnlockPage(int address)
		{
			uint old;
			VirtualProtect((IntPtr)address, 4096, (uint)Protection.PAGE_EXECUTE_READWRITE, out old);
		}

		public static void HijackMethod(ConstructorInfo source, MethodInfo target)
		{
			RuntimeHelpers.PrepareMethod(source.MethodHandle);
			RuntimeHelpers.PrepareMethod(target.MethodHandle);

			var sourceAddress = source.MethodHandle.GetFunctionPointer();
			var targetAddress = (long)target.MethodHandle.GetFunctionPointer();

			UnlockPage((int)sourceAddress);
			UnlockPage((int)targetAddress);


			int offset = (int)(targetAddress - (long)sourceAddress - 4 - 1); // four bytes for relative address and one byte for opcode

			byte[] instruction = {
				0xE9, // Long jump relative instruction
				(byte)(offset & 0xFF),
				(byte)((offset >> 8) & 0xFF),
				(byte)((offset >> 16) & 0xFF),
				(byte)((offset >> 24) & 0xFF)
			};

			Marshal.Copy(instruction, 0, sourceAddress, instruction.Length);
		}
	}
}

We start with creating a string for desktop name (lines 13-15). We need to pin it to make sure it is not moved by the GC. Next, we get the type used by .NET (line 19) and its constructor (line 20). Finally, we have a method for new constructor (39-43) which we get through reflection in line 21.

So what do we do? We use reflection to get method handles for old constructor and a new dummy method which we want to inject in the old place. We then do the trick with jump to hijack the constructor and execute NewConstructor method in place of the old one.

Now, in NewConstructor we use reflection to modify both fields. First, we set the size in line 41. Finally, we just get the pointer to the byte array and put it in the field (line 42).

Rest of the code should be pretty familiar, we just unlock the page, calculate relative address and do the jump. Nothing special here.

One important thing to note is line 14: we get normal bytes of the desktop name and then encode it in UTF 16 because application runs as a Unicode. So if we have string like abcde we need to introduce zeroes after each letter to get something like a 0 b 0 c 0 d 0 e 0. Obviously, letters must be replaced with ASCII codes. At the very end we need to add two more zeroes to represent the zero terminating the string (to get the null-terminated string).

That’s it. Changes for x64 or Ansii application should be pretty obvious now.

]]>
https://blog.adamfurmanek.pl/2020/02/29/net-inside-out-part-15/feed/ 1
Concurrency Part 10 — Reentrant mutex https://blog.adamfurmanek.pl/2019/11/02/concurrency-part-10/ https://blog.adamfurmanek.pl/2019/11/02/concurrency-part-10/#respond Sat, 02 Nov 2019 09:00:31 +0000 https://blog.adamfurmanek.pl/?p=3157 Continue reading Concurrency Part 10 — Reentrant mutex]]>

This is the tenth part of the Concurrency series. For your convenience you can find other parts in the table of contents in Part 1 – Mutex performance in .NET

We have already seen how to implement custom mutex using memory mapped files and CAS operation. It has one drawback — it is not reentrant. Actually, if we try to take if recursively we will end up with deadlock. Today we will fix it.

Since our mutex protocol requires a delegate to execute while holding mutex (kind of a RAII pattern or try-with-resources approach) we don’t need to worry about taking the mutex and not releasing it. Conceptually, this is not allowed:

mutex.Lock()
mutex.Lock() // again
...
mutex.Release()
// Not releasing again

It is guaranteed that mutex will be released once it’s taken. Well, this is not 100% true as there are AccessViolation exceptions resulting in finally block not being executed but we will ignore this fact. So, we don’t need to take lock for the second time if we can guarantee that we won’t try releasing it.

Let’s see this code:

using System;
using System.Diagnostics;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;

namespace MutexUtils
{
    public static class MutexUtils
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        unsafe public static extern long InterlockedCompareExchange64(long* destination, long exchange, long comperand);

        public static unsafe bool DoWithMutex(string name, Action action, int timeoutInSeconds = int.MaxValue, int spinTimeout = 250)
        {
			var myPid = Process.GetCurrentProcess().Id;
			var myTid = AppDomain.GetCurrentThreadId();
			
            // Store <PID><TID> as one field
            long myIdentifier = ((long)myPid << 32) + myTid;

            // Calculate timeout wall clock
            DateTime end = DateTime.UtcNow.AddSeconds(timeoutInSeconds);

            // Open memory mapped file initialized with zeros
            using (var memoryMappedFile = MemoryMappedFile.CreateOrOpen(name, 8))
            using (var viewStream = memoryMappedFile.CreateViewStream(8, 8))
            {
                var pointer = viewStream.SafeMemoryMappedViewHandle.DangerousGetHandle();
				
				try
				{
					var currentLock = Interlocked.Read((long*)pointer);
					if(GetPid(currentLock) == myPid && GetTid(currentLock) == myTid){
						action();
						return true;
					}
				}
				catch(Exception e)
				{
					Console.WriteLine($"Something very bad happened: {e}");
					return false;
				}

                try
                {
                    int holderPid = -1;
                    int holderTid = -1;

                    while (DateTime.UtcNow < end)
                    {
                        // Take lock only if it is not taken
                        var currentLock = InterlockedCompareExchange64((long*)pointer, myIdentifier, 0);

                        if (currentLock == 0)
                        {
                            action();
                            return true;
                        }

                        // Lock is taken, let's see who holds it
                        holderTid = GetTid(currentLock);
                        holderPid = GetPid(currentLock);

                        bool exists = false;
                        try
                        {
                            exists = Process.GetProcessById(holderPid).Threads.OfType<ProcessThread>().Any(t => t.Id == holderTid);
                        }
                        catch
                        {
                        }

                        // If holding thread doesn't exist then the lock is abandoned
                        if (!exists)
                        {
                            // Clear lock only if it is still held by previous owner
                            var currentLock2 = InterlockedCompareExchange64((long*)pointer, 0, currentLock);
                            if (currentLock == currentLock2)
                            {
                               Console.WriteLine($"Mutex {name} was abandoned by pid={holderPid} tid={holderTid}");
                            }
                        }

                        Thread.Sleep(spinTimeout);
                    }

                    Console.WriteLine($"Timeout when waiting on mutex {name} held by pid={holderPid} tid={holderTid}");
                }
                finally
                {
                    // Clear lock only if I'm the one holding it
                    var currentLock = InterlockedCompareExchange64((long*)pointer, 0, myIdentifier);
                    if (currentLock != myIdentifier)
                    {
                        Console.WriteLine($"I tried to release mutex held by someone else, pid={GetPid(currentLock)} tid={GetTid(currentLock)}");
                    }
                }
            }

            return false;
        }

        private static int GetPid(long value)
        {
            return (int)(value >> 32);
        }

        private static int GetTid(long value)
        {
            return (int)(value & 0xFFFFFFFF);
        }
    }
}

We check in line 34 if the current owner of the lock is us — in that case we just execute the action and return early. If something wrong happened (we have exception) then we cannot assume anything about lock owner or executed action so we need to return immediately. The only thing we know for sure is that we didn’t modify the lock. You may be tempted to set a flag that lock is on us and return it in the exception handler — thinking that the exception was thrown by the executed action — but you cannot guarantee that you started executing the action (think about OOM thrown after setting the flag and before calling the handler). You could try enhancing this by using CER but then you constrain yourself a lot.

]]>
https://blog.adamfurmanek.pl/2019/11/02/concurrency-part-10/feed/ 0
Concurrency Part 8 — Tracking mutex owner https://blog.adamfurmanek.pl/2019/10/19/concurrency-part-8/ https://blog.adamfurmanek.pl/2019/10/19/concurrency-part-8/#respond Sat, 19 Oct 2019 08:00:51 +0000 https://blog.adamfurmanek.pl/?p=3148 Continue reading Concurrency Part 8 — Tracking mutex owner]]>

This is the eighth part of the Concurrency series. For your convenience you can find other parts in the table of contents in Part 1 – Mutex performance in .NET

We know how to use global mutexes to synchronize processes. However, there is a big drawback — we don’t know who owns the mutex and we cannot get that information easily. There is https://docs.microsoft.com/en-us/windows/win32/debug/wait-chain-traversal API for reading that information but it is not easy to follow. Can we do better?

One common trick is to use memory mapped files to store the information. Let’s see the code:

using System;
using System.Diagnostics;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;

namespace MutexUtils
{
    public static class MutexUtils
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        unsafe public static extern long InterlockedCompareExchange64(long* destination, long exchange, long comperand);

        public static unsafe bool DoWithMutex(string name, Action action, int timeoutInSeconds = int.MaxValue, int spinTimeout = 250)
        {
            // Store <PID><TID> as one field
            long myIdentifier = ((long)Process.GetCurrentProcess().Id << 32) + AppDomain.GetCurrentThreadId();

            // Calculate timeout wall clock
            DateTime end = DateTime.UtcNow.AddSeconds(timeoutInSeconds);

            // Open memory mapped file initialized with zeros
            using (var memoryMappedFile = MemoryMappedFile.CreateOrOpen(name, 8))
            using (var viewStream = memoryMappedFile.CreateViewStream(8, 8))
            {
                var pointer = viewStream.SafeMemoryMappedViewHandle.DangerousGetHandle();

                try
                {
                    int holderPid = -1;
                    int holderTid = -1;

                    while (DateTime.UtcNow < end)
                    {
                        // Take lock only if it is not taken
                        var currentLock = InterlockedCompareExchange64((long*)pointer, myIdentifier, 0);

                        if (currentLock == 0)
                        {
                            action();
                            return true;
                        }

                        // Lock is taken, let's see who holds it
                        holderTid = GetTid(currentLock);
                        holderPid = GetPid(currentLock);

                        bool exists = false;
                        try
                        {
                            exists = Process.GetProcessById(holderPid).Threads.OfType<ProcessThread>().Any(t => t.Id == holderTid);
                        }
                        catch
                        {
                        }

                        // If holding thread doesn't exist then the lock is abandoned
                        if (!exists)
                        {
                            // Clear lock only if it is still held by previous owner
                            var currentLock2 = InterlockedCompareExchange64((long*)pointer, 0, currentLock);
                            if (currentLock == currentLock2)
                            {
                               Console.WriteLine($"Mutex {name} was abandoned by pid={holderPid} tid={holderTid}");
                            }
                        }

                        Thread.Sleep(spinTimeout);
                    }

                    Console.WriteLine($"Timeout when waiting on mutex {name} held by pid={holderPid} tid={holderTid}");
                }
                finally
                {
                    // Clear lock only if I'm the one holding it
                    var currentLock = InterlockedCompareExchange64((long*)pointer, 0, myIdentifier);
                    if (currentLock != myIdentifier)
                    {
                        Console.WriteLine($"I tried to release mutex held by someone else, pid={GetPid(currentLock)} tid={GetTid(currentLock)}");
                    }
                }
            }

            return false;
        }

        private static int GetPid(long value)
        {
            return (int)(value >> 32);
        }

        private static int GetTid(long value)
        {
            return (int)(value & 0xFFFFFFFF);
        }
    }
}

Magic, a lot. Let’s go part by part.

First, we want to store process ID and thread ID somewhere in the lock to be able to read it easily later. In line 18 we encode those in one 64-bit long variable which we can later replace using CAS operation.

In line 21 we just calculate the time when we should stop trying to get lock. This is for timeouts.

Next, we create memory mapped file (lines 24, 25) not backed by any physical file. This is to avoid permission problems — we cannot map the same file in two processes without copy-on-write semantics. We will need separate reader for debugging.

Next, we spin in the loop. Each time we try to take lock (line 37). If current lock value is zero (line 39) then it means that our lock is available and we have just locked it. We execute the action and then return (which is jump to finally).

However, if lock wasn’t available, we now have the owner of the lock (lines 46 and 47). So we need to check if the owner is still alive. We read process and look for thread in line 52.

If it didn’t exist, we try to clear the lock (line 62). We clear it only if it is still held by the same owner. And here is big warning — process ID and thread ID can be recycled so here we may inadvertently release still used mutex!

Then in line 69 we sleep for some timeout and loop again.

Ultimately, in line 77 we try to clear the lock if it is taken by us. We may end up in this line of code if some exception appears so we cannot just blindly release the mutex.

That’s all, you can verify that it should work pretty well. Just be aware of this chance of clearing up some other mutex, you may come up with different identifier if needed.

In theory, this can be solved by using CAS for 128 bits:

public static class MutexUtils
{
	public static void DoWithSemaphore(string name, int semaphoresCount, Action action)
	{
		bool checkedAll = false;
		string fullName;

		for (int index = 0; ; index = (index + 1) % semaphoresCount)
		{
			fullName = name + index;
			if (DoWithMutex(fullName, action, checkedAll ? 20 : 1))
			{
				break;
			}

			if (index == semaphoresCount - 1)
			{
				checkedAll = true;
			}
		}
	}

	[DllImport("kernel32.dll", SetLastError = true)]
	unsafe public static extern bool InterlockedCompareExchange128(long* destination, long exchangeHigh, long exchangeLow, long* comperand);

	public static unsafe bool DoWithMutex(string name, Action action, int timeoutInSeconds = int.MaxValue, int spinTimeout = 250)
	{
		// Store <PID><TID> as one field
		long myIdentifier = ((long)Process.GetCurrentProcess().Id << 32) + AppDomain.GetCurrentThreadId();
		long myTime = DateTime.UtcNow.Ticks;

		// Calculate timeout wall clock
		DateTime end = DateTime.UtcNow.AddSeconds(timeoutInSeconds);

		// Open memory mapped file initialized with zeros
		using (var memoryMappedFile = MemoryMappedFile.CreateOrOpen(name, 8))
		using (var viewStream = memoryMappedFile.CreateViewStream(8, 8))
		{
			var pointer = viewStream.SafeMemoryMappedViewHandle.DangerousGetHandle();
			long* currentLock = stackalloc long[2];
			currentLock[0] = 0;
			currentLock[1] = 0;

			try
			{
				int holderPid = -1;
				int holderTid = -1;

				while (DateTime.UtcNow < end)
				{
					// Take lock only if it is not taken
					var isLockFree = InterlockedCompareExchange128((long*)pointer, myIdentifier, myTime, currentLock);

					if (isLockFree)
					{
						action();
						return true;
					}

					// Lock is taken, let's see who holds it
					holderTid = GetTid(currentLock[0]);
					holderPid = GetPid(currentLock[1]);

					bool exists = false;
					try
					{
						exists = Process.GetProcessById(holderPid).Threads.OfType<ProcessThread>().Any(t => t.Id == holderTid);
					}
					catch
					{
					}

					// If holding thread doesn't exist then the lock is abandoned
					if (!exists)
					{
						// Clear lock only if it is still held by previous owner
						var isLockStillOwnedByTheSameOwner = InterlockedCompareExchange128((long*)pointer, 0, 0, currentLock);
						if (isLockStillOwnedByTheSameOwner)
						{
						   Console.WriteLine($"Mutex {name} was abandoned by pid={holderPid} tid={holderTid}");
						}
					}

					Thread.Sleep(spinTimeout);
				}

				Console.WriteLine($"Timeout when waiting on mutex {name} held by pid={holderPid} tid={holderTid}");
			}
			finally
			{
				// Clear lock only if I'm the one holding it
				currentLock[0] = myIdentifier;
				currentLock[1] = myTime;
				if (!InterlockedCompareExchange128((long*)pointer, 0, 0, currentLock))
				{
					Console.WriteLine($"I tried to release mutex held by someone else, pid={GetPid(currentLock[0])} tid={GetTid(currentLock[1])}");
				}
			}
		}

		return false;
	}

	private static int GetPid(long value)
	{
		return (int)(value >> 32);
	}

	private static int GetTid(long value)
	{
		return (int)(value & 0xFFFFFFFF);
	}
}

However, it doesn’t work on my machine, my kernel32.dll doesn’t support this CAS operation.

And since this is playing with very dangerous primitives, I take no responsibility for any failures in your systems if it doesn’t work. I tested it in my situation and it behaved correctly, though.

]]>
https://blog.adamfurmanek.pl/2019/10/19/concurrency-part-8/feed/ 0
Concurrency Part 5 – WinAPI mutex https://blog.adamfurmanek.pl/2018/05/26/concurrency-part-5/ https://blog.adamfurmanek.pl/2018/05/26/concurrency-part-5/#comments Sat, 26 May 2018 08:00:37 +0000 https://blog.adamfurmanek.pl/?p=2434 Continue reading Concurrency Part 5 – WinAPI mutex]]>

This is the fifth part of the Concurrency series. For your convenience you can find other parts in the table of contents in Part 1 – Mutex performance in .NET

Last time we called WinAPI through P/Invoke, today we are going to call it directly.

Code

#include "stdafx.h"
#include <windows.h>
#include <ctime>
#include <cstdio>

using namespace std;

int main()
{
	HANDLE mutex = CreateMutex(NULL, FALSE, L"mutex_cpp");

	while (true) {
		clock_t begin = clock();
		for (int i = 0;i < 100000;++i) {
			WaitForSingleObject(mutex, INFINITE);
			ReleaseMutex(mutex);
		}
		clock_t end = clock();
		double elapsed = double(end - begin) / CLOCKS_PER_SEC * 1000.0;
		printf("%lf\n", elapsed);
	}

	CloseHandle(mutex);

    return 0;
}

Results

The results are:

    \[ \begin{array}{cc} Number\ of\ processes & Time [ms] \\ 1 & 110\\ 2 & 605\\ 3 & 905\\ 4 & 1269\\ 5 & 1589\\ 6 & 1920\\ 7 & 2313\\ 8 & 2548\\ 9 & 2950\\ 10 & 3250\\ \end{array} \]

As you can see, the results are very similar to the P/Invoke ones.

]]>
https://blog.adamfurmanek.pl/2018/05/26/concurrency-part-5/feed/ 1