如何处理受约束执行区域中的分配?

ant*_*duh 9 .net c# pinvoke

约束执行区域是C#/ .Net的一个特性,它允许开发人员尝试从关键的代码区域中提升"三大"异常--OutOfMemory,StackOverflow和ThreadAbort.

CER通过推迟ThreadAborts,准备调用图中​​的所有方法来实现这一点(因此不会发生JIT,这可能导致分配),并确保有足够的堆栈空间来适应随后的调用堆栈.

典型的不间断区域可能如下所示:

public static void GetNativeFlag()
{
    IntPtr nativeResource = new IntPtr();
    int flag;

    // Remember, only the finally block is constrained; try is normal.
    RuntimeHelpers.PrepareConstrainedRegions();
    try
    { }
    finally
    {
        NativeMethods.GetPackageFlags( ref nativeResource );

        if ( nativeResource != IntPtr.Zero ) {
            flag = Marshal.ReadInt32( nativeResource );
            NativeMethods.FreeBuffer( nativeResource );
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

上述内容大部分都很好,因为CER内部没有任何规则被破坏 - 所有.Net分配都在CER之外,Marshal.ReadInt32()具有兼容性ReliabilityContract,我们假设我的NativeMethods被类似地标记,以便VM可以正确地考虑他们在准备CER时.

因此,除了所有这些之外,您如何处理分配必须在CER内部发生的情况?分配违反规则,因为很有可能获得OutOfMemoryException.

在查询本机API(SSPI的QuerySecurityPackageInfo)时,我遇到了这个问题,这会迫使我违反这些规则.本机API确实执行自己的(本机)分配,但如果失败,我只得到一个空的结果,所以没有什么大不了的.但是,在它分配的结构中,它存储了一些未知大小的C字符串.

当它返回指向它所分配的结构的指针时,我必须复制整个事物,并分配空间来存储这些c字符串作为.Net字符串对象.毕竟,我应该告诉它释放分配.

但是,由于我在CER中执行.Net分配,我违反规则并可能泄漏一个句柄.

处理这个问题的正确方法是什么?

对于它的价值,这是我天真的方法:

internal static SecPkgInfo GetPackageCapabilities_Bad( string packageName )
{
    SecPkgInfo info;

    IntPtr rawInfoPtr;

    rawInfoPtr = new IntPtr();
    info = new SecPkgInfo();

    RuntimeHelpers.PrepareConstrainedRegions();
    try
    { }
    finally
    {
        NativeMethods.QuerySecurityPackageInfo( packageName, ref rawInfoPtr );

        if ( rawInfoPtr != IntPtr.Zero )
        {
            // This performs allocations as it makes room for the strings contained in the result.
            Marshal.PtrToStructure( rawInfoPtr, info );

            NativeMethods.FreeContextBuffer( rawInfoPtr );
        }
    }

    return info;
}
Run Code Online (Sandbox Code Playgroud)

编辑

我应该提一下,在这种情况下我的"成功"是我从不泄漏手柄; 如果我执行失败的分配,并释放句柄,然后向我的调用者返回一个错误,指示分配失败,那就没问题.只是不能泄漏手柄.

编辑以回应Frank Hileman

当我们执行互操作调用时,我们无法控制所需的内存分配.

取决于你的意思 - 可能被分配用于执行调用调用的内存,或者被调用的调用创建的内存?

我们完全控制分配用于执行调用的内存 - 这是由JIT创建的内存,用于编译所涉及的方法,以及堆栈执行调用所需的内存.JIT编译存储器在CER准备期间分配; 如果失败,整个CER永远不会被执行.CER准备工作还计算CER执行的静态调用图中需要多少堆栈空间,如果没有足够的堆栈,则中止CER准备.

巧合的是,这涉及到任何try-catch-finally框架的堆栈空间准备,甚至是嵌套的try-catch-finally框架,这些框架恰好定义并参与CER.在CER中嵌套try-catch-finally是完全合理的,因为JIT可以计算记录try-catch-finally上下文所需的堆栈内存量,如果需要太多则中止CER准备工作.

调用本身可以在.net堆之外进行一些内存分配; 我很惊讶在CER内部允许本地通话.

如果您的意思是由调用的调用执行本机内存分配,那么这也不是CER的问题.本机内存分配成功或返回状态代码.OOM不是由本机内存分配生成的.如果本机分配失败,可能是我正在调用的本机API通过返回状态代码或空指针来处理它.这个电话仍然具有确定性.唯一的副作用是,由于内存压力增加,可能导致后续的托管分配失败.但是,如果我们要么从不执行分配,要么可以确定地处理失败的托管分配,那么它仍然不是问题.

因此,CER中唯一不好的分配是托管分配,因为它可能导致"异步"OOM异常.所以现在的问题是如何确定性地处理CER内部失败的托管分配.

但这完全有可能.CER可以嵌套try-catch-finally块.CER的所有调用,以及CER所需的所有堆栈空间,即使是最终在CER中记录嵌套try-finally的上下文,也可以在整个CER的准备过程中确定性地计算,在我的任何代码实际执行之前.

ant*_*duh 9

只要CER准备好处理失败的分配,就可以在CER内部执行管理分配.

首先,这是破碎的代码:

SecPkgInfo info;
SecurityStatus status = SecurityStatus.InternalError;
SecurityStatus freeStatus;

IntPtr rawInfoPtr;

rawInfoPtr = new IntPtr();
info = new SecPkgInfo();

RuntimeHelpers.PrepareConstrainedRegions();
try
{ }
finally
{
    status = NativeMethods.QuerySecurityPackageInfo( packageName, ref rawInfoPtr );

    if ( rawInfoPtr != IntPtr.Zero  )
    {
        if ( status == SecurityStatus.OK )
        {
            // *** BWOOOP **** BWOOOP ***
            // This performs allocations as it makes room for the strings contained 
            // in the SecPkgInfo class. That means that we're performing managed 
            // allocation inside of a CER. This CER is broken and may cause a leak because
            // it never calls FreeContextBuffer if an OOM is caused by the Marshal.
            Marshal.PtrToStructure( rawInfoPtr, info );
        }

        freeStatus = NativeMethods.FreeContextBuffer( rawInfoPtr );
    }
}
Run Code Online (Sandbox Code Playgroud)

由于的try-catch-终于真实可以被嵌套,并且通过所需的任何额外的堆栈空间嵌套的try-catch-finally程序是CER的prepration期间预先计算,我们可以用一个try-finally里面我们的核证减排量的主要最终确保我们FreeContextBuffer是绝不外泄:

SecPkgInfo info;
SecurityStatus status = SecurityStatus.InternalError;
SecurityStatus freeStatus;

IntPtr rawInfoPtr;

rawInfoPtr = new IntPtr();
info = new SecPkgInfo();

RuntimeHelpers.PrepareConstrainedRegions();
try
{ }
finally
{
    status = NativeMethods.QuerySecurityPackageInfo( packageName, ref rawInfoPtr );

    if ( rawInfoPtr != IntPtr.Zero  )
    {
        try
        {
            if ( status == SecurityStatus.OK )
            {
                // This may fail but the finally will make sure we always free the native pointer.
                Marshal.PtrToStructure( rawInfoPtr, info );
            }
        }
        finally
        {
            freeStatus = NativeMethods.FreeContextBuffer( rawInfoPtr );
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我还整理了一个演示程序,可在http://www.antiduh.com/tests/LeakTest.zip上找到.它有一个跟踪分配的小型自定义本机DLL,以及一个调用该DLL的托管应用程序.它显示了使用嵌套try-finally的CER如何能够确定性地释放非托管资源,即使部分CER导致OOM异常.

  • 感谢您向其他开发人员提供所有这些信息.这是一个不起眼的话题. (2认同)