约束执行区域是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的准备过程中确定性地计算,在我的任何代码实际执行之前.
只要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异常.
| 归档时间: |
|
| 查看次数: |
412 次 |
| 最近记录: |