|
| 1 | +# System.Security.Cryptography.Native |
| 2 | + |
| 3 | +This folder contains C# bindings for native shim (libSystem.Security.Cryptography.Native.OpenSsl.so), shimming functionality provided by the OpenSSL library. |
| 4 | + |
| 5 | +## Memory allocation hooks |
| 6 | + |
| 7 | +One extra feature exposed by the native shim is tracking of memory used by |
| 8 | +OpenSSL by hooking the memory allocation routines via |
| 9 | +`CRYPTO_set_mem_functions`. |
| 10 | + |
| 11 | +The functionality is enabled by setting |
| 12 | +`DOTNET_OPENSSL_MEMORY_DEBUG` to 1. This environment |
| 13 | +variable must be set before launching the program (calling |
| 14 | +`Environment.SetEnvironmentVariable` at the start of the program is not |
| 15 | +sufficient). The diagnostic API is not officially exposed and needs to be |
| 16 | +accessed via private reflection on the `Interop.Crypto` type located in the |
| 17 | +`System.Security.Cryptography` assembly. On this type, you can use following static |
| 18 | +methods: |
| 19 | + |
| 20 | +- `int GetOpenSslAllocatedMemory()` |
| 21 | + - Gets the total amount of memory allocated by OpenSSL |
| 22 | +- `int GetOpenSslAllocationCount()` |
| 23 | + - Gets the number of allocations made by OpenSSL |
| 24 | +- `void EnableMemoryTracking()`/`void DisableMemoryTracking()` |
| 25 | + - toggles tracking of individual live allocations via internal data |
| 26 | + structures. I.e. will keep track of live memory allocated since the start of |
| 27 | + tracking. |
| 28 | +- `void ForEachTrackedAllocation(Action<IntPtr, ulong, IntPtr, int> callback)` |
| 29 | + - Accepts an callback and calls it for each allocation performed since the |
| 30 | + last `EnableMemoryTracking` call. The order of reported information does not |
| 31 | + correspond to the order of allocation. This method holds an internal lock |
| 32 | + which prevents other threads from allocating any memory from OpenSSL. |
| 33 | + - Callback parameters are |
| 34 | + - IntPtr - The pointer to the allocated object |
| 35 | + - ulong - size of the allocation in bytes |
| 36 | + - IntPtr - Pointer to a null-terminated string (`const char*`) containing the name of the file from which the allocation was made. |
| 37 | + - int - line number within the file specified by the previous parameter where the allocation was called from. |
| 38 | + |
| 39 | +The debug functionality brings some overhead (header for each allocation, |
| 40 | +locks/synchronization during each allocation) and may cause performance penalty. |
| 41 | + |
| 42 | +### Example usage |
| 43 | + |
| 44 | +```cs |
| 45 | +// all above mentioned APIs are accessible via "private reflection" |
| 46 | +BindingFlags flags = BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Static; |
| 47 | +var cryptoInterop = typeof(RandomNumberGenerator).Assembly.GetTypes().First(t => t.Name == "Crypto"); |
| 48 | + |
| 49 | +// enable tracking, this clears up any previously tracked allocations |
| 50 | +cryptoInterop.InvokeMember("EnableMemoryTracking", flags, null, null, null); |
| 51 | + |
| 52 | +// do some work that includes OpenSSL |
| 53 | +HttpClient client = new HttpClient(); |
| 54 | +await client.GetAsync("https://www.microsoft.com"); |
| 55 | + |
| 56 | +// stop tracking (this step is optional) |
| 57 | +cryptoInterop.InvokeMember("DisableMemoryTracking", flags, null, null, null); |
| 58 | + |
| 59 | +using var process = Process.GetCurrentProcess(); |
| 60 | +Console.WriteLine($"Bytes known to GC [{GC.GetTotalMemory(false)}], process working set [{process.WorkingSet64}]"); |
| 61 | +Console.WriteLine("OpenSSL - currently allocated memory: {0} B", cryptoInterop.InvokeMember("GetOpenSslAllocatedMemory", flags, null, null, null)); |
| 62 | +Console.WriteLine("OpenSSL - total allocations since start: {0}", cryptoInterop.InvokeMember("GetOpenSslAllocationCount", flags, null, null, null)); |
| 63 | + |
| 64 | +Dictionary<(IntPtr file, int line), ulong> allAllocations = new(); |
| 65 | +Action<IntPtr, ulong, IntPtr, int> callback = (ptr, size, namePtr, line) => |
| 66 | +{ |
| 67 | + CollectionsMarshal.GetValueRefOrAddDefault(allAllocations, (namePtr, line), out _) += size; |
| 68 | +}; |
| 69 | +cryptoInterop.InvokeMember("ForEachTrackedAllocation", flags, null, null, [callback]); |
| 70 | + |
| 71 | +Console.WriteLine("Total allocated OpenSSL memory by location"); |
| 72 | +foreach (var ((filenameptr, line), total) in allAllocations.OrderByDescending(kvp => kvp.Value).Take(10)) |
| 73 | +{ |
| 74 | + string filename = Marshal.PtrToStringUTF8(filenameptr); |
| 75 | + Console.WriteLine($"{total:N0} B from {filename}:{line}"); |
| 76 | +} |
| 77 | +``` |
0 commit comments