Skip to content

Distinguish OOM and failure to reprotect in thunks APIs #116010

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

Merged
merged 8 commits into from
May 30, 2025

Conversation

MichalStrehovsky
Copy link
Member

Resolves #113114.

I wasn't sure how much cleanup to do here, the failure handling is a bit odd, I assume for historical reasons.

Cc @dotnet/ilc-contrib

Resolves dotnet#113114.

I wasn't sure how much cleanup to do here, the failure handling is a bit odd, I assume for historical reasons.
@Copilot Copilot AI review requested due to automatic review settings May 27, 2025 13:08
Comment on lines -91 to -92
lock (this)
{
Copy link
Member Author

Choose a reason for hiding this comment

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

I was scratching my head about this - we're in a constructor. Nobody else can have this

Copy link
Member

Choose a reason for hiding this comment

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

That's obviously wrong, but also GetNewThunksBlock doesn't seem to be thread-safe, so perhaps the intention was to protect multiple simultaneous calls to that?

Copy link
Member

@filipnavara filipnavara May 29, 2025

Choose a reason for hiding this comment

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

If we are not addressing this, should we open a follow-up issue? (or did I miss some mechanism that is making the static variable access thread safe?)

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not a fan of doing more within a PR than what's needed and this is orthogonal. Filed #116089.

Copy link
Contributor

Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas
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.

Pull Request Overview

This PR enhances the thunk-allocation APIs to distinguish out‐of‐memory failures from other reprotect failures by propagating the exact exception back to managed code.

  • Added out Exception parameters on GetNewThunksBlock, ExpandHeap, and AllocateThunk so callers can throw the correct exception.
  • Updated the native RhAllocateThunksMapping signature and implementations to set an isOOM flag.
  • Changed managed callers (CreateThunksHeap / AllocateThunk) to remove null returns and instead throw the propagated exception.

Reviewed Changes

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

Show a summary per file
File Description
ThunkPool.cs Added out Exception to block- and thunk-allocation methods and removed null-return fallbacks
RuntimeImports.cs Updated the RhAllocateThunksMapping P/Invoke to take an int* isOOM parameter
RuntimeAugments.cs Switched CreateThunksHeap / AllocateThunk to the new exception-based APIs
portable.cpp Stubbed RhAllocateThunksMapping to always set isOOM = 0
ThunksMapping.cpp Populated isOOM on allocation failure
Comments suppressed due to low confidence (3)

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/ThunkPool.cs:115

  • The new CreateThunksHeap API can now throw different exception types; add unit tests to cover both the OOM and NotSupported failure scenarios.
public static unsafe ThunksHeap CreateThunksHeap(IntPtr commonStubAddress)

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/ThunkPool.cs:172

  • [nitpick] The out parameter exception is ambiguous; consider renaming it to something like allocationException for clarity.
public unsafe IntPtr AllocateThunk(out Exception exception)

src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs:714

  • This change now throws NotSupportedException for reprotect failures instead of always OutOfMemoryException. Consider whether callers expect the original behavior or need to handle both exception types.
return ThunksHeap.CreateThunksHeap(commonStubAddress);

// The only option is for the caller to attempt and recycle unused thunks to be able to
// find some free entries.
if (isOOM == 0)
exception = new NotSupportedException();
Copy link
Contributor

Choose a reason for hiding this comment

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

Add a custom exception message?

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you have a suggestion for a message that would be actually useful (we might not have a full list of reasons why this could fail; we just found out about a new reason a couple months ago...). I mostly just wanted to distinguish OOM from "everything else".

Copy link
Member

Choose a reason for hiding this comment

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

"Dynamic entrypoint allocation is not supported in the current environment."?

@@ -100,13 +100,18 @@ FCIMPL0(int, RhpGetThunkBlockSize)
}
FCIMPLEND

EXTERN_C void* QCALLTYPE RhAllocateThunksMapping()
EXTERN_C void* QCALLTYPE RhAllocateThunksMapping(int* isOOM)
Copy link
Member

Choose a reason for hiding this comment

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

It is unusual to return error code via out argument, and only allow that error code to be OOM.

Can we return proper error code, and return the pointer via [out] argument instead?

The error code can be an HRESULT or an enum specific to this call. (I know HRESULTs are Windows-specific, but they are part of BCL Exception so they are not going away x-plat, so there is not much value in trying hard to avoid them.)

Copy link
Member Author

Choose a reason for hiding this comment

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

Good idea with reusing HRESULT! I didn't like the bool either, but I didn't want to invent a dedicated enum that we need to keep in sync between native and managed.

Copy link
Member Author

Choose a reason for hiding this comment

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

@AaronRobinsonMSFT deleted the HRESULT definitions in #115858 so this no longer compiles.

I would prefer going back to the bool if we don't want HRESULTs anymore.

Copy link
Member

Choose a reason for hiding this comment

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

I think it is fine add back what you need to get this to compile.

if (result == HResults.E_OUTOFMEMORY)
throw new OutOfMemoryException();
else if (result != HResults.S_OK)
throw new PlatformNotSupportedException();
Copy link
Member

Choose a reason for hiding this comment

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

@copilot Could you please suggest a good error message for this exception?

Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

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

Thanks

Comment on lines 344 to 359
#ifndef __GCENV_BASE_INCLUDED__
#if !defined(_INC_WINDOWS)
#ifdef _WIN32
// this must exactly match the typedef used by windows.h
typedef long HRESULT;
#else
typedef int32_t HRESULT;
#endif

#define S_OK 0x0
#define E_FAIL 0x80004005
#define E_OUTOFMEMORY 0x8007000E

#endif // !defined(_INC_WINDOWS)
#endif // __GCENV_BASE_INCLUDED__

Copy link
Member

@jkotas jkotas May 29, 2025

Choose a reason for hiding this comment

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

Suggested change
#ifndef __GCENV_BASE_INCLUDED__
#if !defined(_INC_WINDOWS)
#ifdef _WIN32
// this must exactly match the typedef used by windows.h
typedef long HRESULT;
#else
typedef int32_t HRESULT;
#endif
#define S_OK 0x0
#define E_FAIL 0x80004005
#define E_OUTOFMEMORY 0x8007000E
#endif // !defined(_INC_WINDOWS)
#endif // __GCENV_BASE_INCLUDED__
#ifdef TARGET_UNIX
typedef int32_t HRESULT;
#define S_OK 0x0
#define E_FAIL 0x80004005
#define E_OUTOFMEMORY 0x8007000E
#endif

Could you please change this to above and move it to PalRedhawk.h right after HRESULT definition`?

@MichalStrehovsky
Copy link
Member Author

/ba-g the failing test was supposedly fixed in #116062, I'm not sure about the timing but we'll see if it's really fixed in the coming days...

@MichalStrehovsky MichalStrehovsky merged commit 29b1e99 into dotnet:main May 30, 2025
135 of 142 checks passed
@MichalStrehovsky MichalStrehovsky deleted the fix113114 branch May 30, 2025 11:22
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.

ThunkPool doesn't work with PROCESS_MITIGATION_DYNAMIC_CODE_POLICY
4 participants