具有冲突的“静态性”的CIL代表行为目标方法的

发布于 2024-10-07 03:28:06 字数 1235 浏览 5 评论 0原文

这个问题需要一些介绍。

我正在开发一个安全项目,该项目将分析 CIL 程序集并拒绝那些执行某些定义的“坏”事情的程序集,同时还允许托管应用程序为某些方法提供“门”,以允许过滤某些调用。 (这是该项目功能的一小部分,但这是我将在此处询问的部分。)

该项目扫描程序集中每个方法中的所有指令,并查找 call、callvirt、ldftn、 ldvirtftn 和 newobj 操作码,因为这些是唯一最终可以导致方法调用的操作码。 ldftn 操作码在构造委托时使用,如下所示:

ldarg.1
ldftn instance bool string::EndsWith(string)
newobj instance void class [System.Core]System.Func`2<string, bool>::'.ctor'(object, native int)

在此序列的末尾,Func 位于堆栈顶部。

假设我想拦截对 String.EndsWith(String) 的所有调用。对于 call 和 callvirt,我可以将实例调用替换为签名 Boolean(String,String) 的静态调用——第一个参数将是最初调用该方法的字符串实例。在 CIL 级别上,行为将是明确且定义明确的,因为这就是调用静态方法的方式。

但是对于ldftn呢?我尝试将 ldftn 指令的操作数替换为用于替换 call/callvirt 操作数的相同静态方法:

ldarg.1
ldftn bool class Prototype.Program::EndsWithGate(string, string)
newobj instance void class [System.Core]System.Func`2<string, bool>::'.ctor'(object, native int)

我完全期望这会失败,因为委托在传递静态方法指针时被赋予了目标对象(非空)。令我惊讶的是,这实际上适用于 Microsoft .NET 运行时和 Mono。我知道 target/this 参数只是该方法的第一个参数,并且对于实例方法是隐藏的。 (该项目是基于这些知识的。)但是,代表们实际上在这些情况下工作这一事实让我有点困惑。

所以,我的问题是:这是定义和记录的行为吗?委托在调用时是否总是将其目标推入堆栈(如果它不为空)?构建一个捕获目标并“正确”调用静态方法的闭包类是否会更好,即使这会更加复杂和烦人?

This question will take a bit of introduction.

I am working on a security project that will analyze CIL assemblies and reject those that do certain defined "bad" things while also allowing the hosting application to supply "gates" for some methods, to allow some calls to be filtered. (This is a small subset of the functionality of the project, but it's the part I'll be inquiring about here.)

The project scans all of the instructions in every method in the assembly, and looks for the call, callvirt, ldftn, ldvirtftn, and newobj opcodes, since these are the only opcodes that can ultimately result in a method call. The ldftn opcodes are used when constructing delegates, like so:

ldarg.1
ldftn instance bool string::EndsWith(string)
newobj instance void class [System.Core]System.Func`2<string, bool>::'.ctor'(object, native int)

At the end of this sequence, a Func<string, bool> is on the top of the stack.

Let's say that I want to intercept all calls to String.EndsWith(String). For call and callvirt, I can just replace the instance call with a static call of the signature Boolean(String,String) -- the first argument will be the string instance the method was originally invoked on. On a CIL level the behavior will be unambiguous and well-defined, since this is how static methods are called.

But for ldftn? I tried just replacing the operand of the ldftn instruction with the same static method used to replace call/callvirt's operand:

ldarg.1
ldftn bool class Prototype.Program::EndsWithGate(string, string)
newobj instance void class [System.Core]System.Func`2<string, bool>::'.ctor'(object, native int)

I was fully expecting this to fail, since the delegate is given a target object (not null) while handed a static method pointer. To my surprise, this actually works on both the Microsoft .NET runtime and Mono. I understand that the target/this parameter is just the first parameter to the method, and is hidden for instance methods. (The project is based on this knowledge.) But the fact that delegates actually work under these circumstances is a bit puzzling to me.

So, my question: is this defined and documented behavior? Will delegates, when invoked, always push their target onto the stack if it's not null? Would it be better to construct a closure class that will capture the target and "properly" call the static method, even though this will be a lot more complex and annoying?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

荒芜了季节 2024-10-14 03:28:06

ECMA 335 规范第 2 部分 14.6.2 有一段关于此内容:
T和D的调用约定必须完全匹配,忽略静态方法和实例方法的区别。 (即this参数如果有的话,不做特殊处理)。

在我看来,静态方法将允许有两种变体:

  • 没有这个,在这种情况下应该传递 NULL
  • 使用额外的第一个参数,假设类型与传入 newobj 调用的类型匹配。

ECMA 335 spec part 2 14.6.2 has a paragraph about this:
The calling convention of T and D shall match exactly, ignoring the distinction between static and instance methods. (i.e. the this parameter if any, is not treated specially).

What this sounds to me like that for static methods will be allowed in two variations:

  • Without this, in which case NULL should be passed
  • With the extra first parameter, presuming the type matches what was fed into the newobj call.
|煩躁 2024-10-14 03:28:06

值得指出的是,这不是滥用。这是一种称为“委托柯里化”的技术。它来自函数式编程语言中称为“柯里化”的更通用技术,其中具有 N+1 个参数的函数被转换为具有 N 个参数的函数。C# 等效项看起来像这样:

Func<T2, R> CurryFirst<T1, T2, R>(
    Func<T1, T2, R> f,
    T1 arg
)
{
    return (x) => f(arg, x);
}

Func<T1, R> CurrySecond<T1, T2, R>(
    Func<T1, T2, R> f,
    T2 arg
)
{
    return (x) => f(x, arg);
}

CLR 为“柯里化”提供了特殊支持first” 的情况,主要是因为在机器代码级别,柯里化静态方法调用看起来几乎与实例方法调用一模一样(this 参数作为隐式第一个参数传入)。

这使得委托柯里化的实现相当高效。它最初是与 DynamicMethod 一起实现的,以支持 Iron Python。它还用于其他目的,例如允许委托透明地引用扩展方法。

It's worth pointing out that this isn't an abuse. It's a technique known as "delegate currying". It's comes from a more general technique called "currying" in functional programming languages, where a function with N+1 arguments is converted into a function with N arguments.The C# equivalent would look something like:

Func<T2, R> CurryFirst<T1, T2, R>(
    Func<T1, T2, R> f,
    T1 arg
)
{
    return (x) => f(arg, x);
}

Func<T1, R> CurrySecond<T1, T2, R>(
    Func<T1, T2, R> f,
    T2 arg
)
{
    return (x) => f(x, arg);
}

The CLR provides special support for the "curry first" case, mainly because at the machine code level, a curried static method invocation looks almost exactly like an instance method invocation (the this parameter is passed in as the implicit first argument).

That makes implementing delegate currying fairly efficient. It was originally implemented, along with DynamicMethod to support Iron Python. It's also used for other purposes, such as allowing delegates to refer to extension methods transparently.

清风无影 2024-10-14 03:28:06

好吧,我不认为我自己会回答我的第一个问题...

#mono 的一位同事 (Ck ) 已通知我 Delegate.CreateDelegate 的相关行为:(强调我的)

如果提供了firstArgument,则每次调用委托时都会将其传递给方法; firstArgument 被认为是绑定到委托的,并且委托被认为对其第一个参数是封闭的。 如果方法是静态的(在 Visual Basic 中为共享),则调用委托时提供的参数列表包括除第一个参数之外的所有参数;如果方法是实例方法,则将firstArgument传递给隐藏的实例参数(在C#中用this表示,在Visual Basic中用Me表示)。

对我来说,从这个文档中得出的结论似乎是合乎逻辑的:在委托构造过程中(滥用)使用 ldftn 并结合非空目标,实际上是定义明确的行为。

Well, I didn't think I'd be answering my first question myself...

A colleague in #mono (Ck) has informed me of the relevant behavior of Delegate.CreateDelegate: (emphasis mine)

If firstArgument is supplied, it is passed to method every time the delegate is invoked; firstArgument is said to be bound to the delegate, and the delegate is said to be closed over its first argument. If method is static (Shared in Visual Basic), the argument list supplied when invoking the delegate includes all parameters except the first; if method is an instance method, then firstArgument is passed to the hidden instance parameter (represented by this in C#, or by Me in Visual Basic).

It seems logical to me to conclude from this documentation, that this (ab)use of ldftn during delegate construction, combined with a non-null target, is actually well-defined behavior.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文