src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/README.md
This folder contains C# bindings for native shim (libSystem.Security.Cryptography.Native.OpenSsl.so), shimming functionality provided by the OpenSSL library.
One extra feature exposed by the native shim is tracking of memory used by
OpenSSL by hooking the memory allocation routines via
CRYPTO_set_mem_functions.
The functionality is enabled by setting
DOTNET_OPENSSL_MEMORY_DEBUG to 1. This environment
variable must be set before launching the program (calling
Environment.SetEnvironmentVariable at the start of the program is not
sufficient). The diagnostic API is not officially exposed and needs to be
accessed via private reflection on the Interop.Crypto type located in the
System.Security.Cryptography assembly. On this type, you can use following static
methods:
int GetOpenSslAllocatedMemory()
int GetOpenSslAllocationCount()
void EnableMemoryTracking()/void DisableMemoryTracking()
void ForEachTrackedAllocation(Action<IntPtr, ulong, IntPtr, int> callback)
EnableMemoryTracking call. The order of reported information does not
correspond to the order of allocation. This method holds an internal lock
which prevents other threads from allocating any memory from OpenSSL.const char*) containing the name of the file from which the allocation was made.The debug functionality brings some overhead (header for each allocation, locks/synchronization during each allocation) and may cause performance penalty.
// all above mentioned APIs are accessible via "private reflection"
BindingFlags flags = BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Static;
var cryptoInterop = typeof(RandomNumberGenerator).Assembly.GetTypes().First(t => t.Name == "Crypto");
// enable tracking, this clears up any previously tracked allocations
cryptoInterop.InvokeMember("EnableMemoryTracking", flags, null, null, null);
// do some work that includes OpenSSL
HttpClient client = new HttpClient();
await client.GetAsync("https://www.microsoft.com");
// stop tracking (this step is optional)
cryptoInterop.InvokeMember("DisableMemoryTracking", flags, null, null, null);
using var process = Process.GetCurrentProcess();
Console.WriteLine($"Bytes known to GC [{GC.GetTotalMemory(false)}], process working set [{process.WorkingSet64}]");
Console.WriteLine("OpenSSL - currently allocated memory: {0} B", cryptoInterop.InvokeMember("GetOpenSslAllocatedMemory", flags, null, null, null));
Console.WriteLine("OpenSSL - total allocations since start: {0}", cryptoInterop.InvokeMember("GetOpenSslAllocationCount", flags, null, null, null));
Dictionary<(IntPtr file, int line), ulong> allAllocations = new();
Action<IntPtr, ulong, IntPtr, int> callback = (ptr, size, namePtr, line) =>
{
CollectionsMarshal.GetValueRefOrAddDefault(allAllocations, (namePtr, line), out _) += size;
};
cryptoInterop.InvokeMember("ForEachTrackedAllocation", flags, null, null, [callback]);
Console.WriteLine("Total allocated OpenSSL memory by location");
foreach (var ((filenameptr, line), total) in allAllocations.OrderByDescending(kvp => kvp.Value).Take(10))
{
string filename = Marshal.PtrToStringUTF8(filenameptr);
Console.WriteLine($"{total:N0} B from {filename}:{line}");
}