Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hooks to debug OpenSSL memory allocations #111539

Merged
merged 47 commits into from
Feb 11, 2025

Conversation

rzikm
Copy link
Member

@rzikm rzikm commented Jan 17, 2025

This ressurects #101626. CC: @wfurt.

Changes since his PR:

  • Moved some code around, the managed part now lives in Interop.Crypto.
  • Moved tracking to native code and exposed to C# via foreach-callback

We had several cases when users complained about large memory use. For than native it is quite difficult to figure out where the memory goes. This PR aims to make that somewhat easier.

OpenSSL provides hooks for memory function so this PR adds switch to optimally hook into that.
The only one caveat that the CRYPTO_set_mem_functions works only if called before any allocations e.g. it needs to be done very early in the process. So I end up putting into initialization process.

The simple use pattern is something like

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Net.Security;
using System.Reflection;
using System.Runtime.InteropServices;
// Environment variable needs to be set before launching the process, as otherwise the native layer will
// not see the environment variable
// Environment.SetEnvironmentVariable("DOTNET_OPENSSL_MEMORY_DEBUG", "1");
//
// Once enabled, the functionality can be accessed by following methods on the Interop.Crypto class:
// - GetOpenSslAllocatedMemory - Gets the total amount of memory allocated by OpenSSL
// - GetOpenSslAllocationCount - Gets the number of allocations made by OpenSSL
// - EnableTracking/DisableTracking - toggles tracking of individual (outstanding) allocations via internal dictionary.
// - ForEachTrackedAllocation - Accepts an Action<IntPtr, int, IntPtr, int> callback and calls it for each allocation performed since the last EnableTracking call. The order of allocations is not guaranteed, the functions holds an inner lock which prevents concurrent memory operations. The parameters passed to the callback are:
//   - IntPtr to the memory allocated
//   - size of the allocation
//   - IntPtr to the filename where the allocation was made
//   - line number of the allocation

// 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}");
}

Access through Reflection should be OK since this is only last resort debug hook e.g. it does not need stable API and convenient access.

Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-security, @bartonjs, @vcsjones
See info in area-owners.md if you want to be subscribed.

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 2 out of 8 changed files in this pull request and generated 1 comment.

Files not reviewed (6)
  • src/native/libs/System.Security.Cryptography.Native/apibridge_30.h: Language not supported
  • src/native/libs/System.Security.Cryptography.Native/entrypoints.c: Language not supported
  • src/native/libs/System.Security.Cryptography.Native/openssl.c: Language not supported
  • src/native/libs/System.Security.Cryptography.Native/openssl.h: Language not supported
  • src/native/libs/System.Security.Cryptography.Native/opensslshim.h: Language not supported
  • src/native/libs/System.Security.Cryptography.Native/pal_ssl.c: Language not supported

@rzikm rzikm marked this pull request as draft January 20, 2025 08:53
@rzikm rzikm requested a review from bartonjs January 31, 2025 14:21

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 2 out of 10 changed files in this pull request and generated no comments.

Files not reviewed (8)
  • src/libraries/System.Security.Cryptography/src/ILLink/ILLink.Descriptors.LibraryBuild.xml: Language not supported
  • src/native/libs/System.Security.Cryptography.Native/CMakeLists.txt: Language not supported
  • src/native/libs/System.Security.Cryptography.Native/apibridge.h: Language not supported
  • src/native/libs/System.Security.Cryptography.Native/entrypoints.c: Language not supported
  • src/native/libs/System.Security.Cryptography.Native/memory_debug.c: Language not supported
  • src/native/libs/System.Security.Cryptography.Native/memory_debug.h: Language not supported
  • src/native/libs/System.Security.Cryptography.Native/openssl.c: Language not supported
  • src/native/libs/System.Security.Cryptography.Native/opensslshim.h: Language not supported
@rzikm
Copy link
Member Author

rzikm commented Feb 11, 2025

/ba-g wasm failures are unrelated.

@rzikm rzikm merged commit 7715391 into dotnet:main Feb 11, 2025
93 of 99 checks passed
grendello added a commit to grendello/runtime that referenced this pull request Feb 12, 2025
* main:
  [Android] Run CoreCLR functional tests on Android (dotnet#112283)
  [LoongArch64] Fix some assertion failures for Debug ILC building Debug NativeAOT testcases. (dotnet#112229)
  Fix suspicious code fragments (dotnet#112384)
  `__ComObject` doesn't support dynamic interface map (dotnet#112375)
  Native DLLs: only load imported DLLs from System32 (dotnet#112359)
  [main] Update dependencies from dotnet/roslyn (dotnet#112314)
  Update SVE instructions that writes to GC regs (dotnet#112389)
  Bring up android+coreclr windows build.  (dotnet#112256)
  Never use heap for return buffers (dotnet#112060)
  Wait to complete the test before releasing the agile reference. (dotnet#112387)
  Prevent returning disposed HTTP/1.1 connections to the pool (dotnet#112383)
  Fingerprint dotnet.js if writing import map to html is enabled (dotnet#112407)
  Remove duplicate definition of CORECLR_HOSTING_API_LINKAGE (dotnet#112096)
  Update the exception message to reflect current behavior. (dotnet#112355)
  Use enum for frametype not v table (dotnet#112166)
  Enable AltJits build for LoongArch64 and RiscV64 (dotnet#110282)
  Guard members of MonoType union & fix related bugs (dotnet#111645)
  Add optional hooks for debugging OpenSSL memory allocations (dotnet#111539)
  JIT: Optimize struct parameter register accesses in the backend (dotnet#110819)
  NativeAOT: Cover more opcodes in type preinitializer (dotnet#112073)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants