C# .NET CIL对求值堆栈的操作

C# .NET CIL对求值堆栈的操作,c#,.net,cil,mono.cecil,C#,.net,Cil,Mono.cecil,我通过使用Mono.Cecil注入了这个CIL代码序列。但是,修改后的.NET C#应用程序将不会运行 目标: 手动从堆栈加载并弹出值,以显示在控制台中。WriteLine for (int i = 0; i < 3; i++) { int z = some value popped manually from stack; Console.WriteLine(z); }

我通过使用
Mono.Cecil
注入了这个CIL代码序列。但是,修改后的.NET C#应用程序将不会运行

目标: 手动从堆栈加载并弹出值,以显示在控制台中。WriteLine

 for (int i = 0; i < 3; i++)
        {
            int z = some value popped manually from stack;                 
            Console.WriteLine(z);
        }
但是,上述代码无法运行。我尝试将
blt.s
更改为
clt
br_true.s
,但也不起作用。有人知道我的目标能否实现吗?谢谢。

编辑: 根据ECMA-335,III.1.7.5,可能存在向后分支约束。不确定是否是这样

特别是,如果单通道分析到达一条指令,将其称为位置X,即 紧跟在无条件分支之后,其中X不是早期分支的目标 指令,则X处计算堆栈的状态显然不能从现有的 信息。在这种情况下,CLI要求X处的计算堆栈为空


您的IL代码看起来不错,但我认为CLR可能无法在方法完成后检查堆栈是否损坏。当某些内容被推送到堆栈上时,CLR会检查该值是否也从堆栈中弹出


因此,如果将3个值推送到堆栈上,CLR可能无法检查循环是否运行了三次,因此当方法返回时,CLR不知道堆栈上是否还有值

非常有趣的问题。您正试图使用IL执行堆栈来存储任意数据项队列。与传统的IL代码相比,这引入了一种不寻常的情况,在这种情况下,堆栈的正确平衡主要取决于运行时循环迭代次数,该迭代次数与烧录到IL中的ILAsm时间数据项的数量完全匹配。正如您所指出的,该程序(此处重复)不起作用

(事实上,在我使用
link.exe
/LTCG
的构建中,链接器甚至无法生成程序集,导致“
致命错误C1352:函数中的MSIL无效或损坏”

问题在于n̲o̲t̲是由于一个简单的逻辑缺陷造成的,或者是由于代码中的一个bug造成的。这可以通过如下事实来证明:注释有争议的部分效果很好,打印3个零

    //-- ldc.i4.6       // commented out
    //-- ldc.i4.5       // commented out
    //-- ldc.i4.4       // commented out

    ldc.i4.0
    stloc i
    br _next

_more:
    //-- stloc cur      // commented out

    ldloc cur
    box int32
    call void Debug::WriteLine(object)

    ldloc i
    ldc.i4.1
    add
    stloc i

_next:
    ldloc i
    ldc.i4.3
    blt _more
    ret 
OP进行了一些调查并发现了ECMA-335,III.1.7.5,这似乎与此相关,因为工作示例和失败示例之间的主要区别在于后者存在要求在
\u more
位置有一个非空评估堆栈(也称为“序列点”),那个位置确实,引用规范

“…立即跟随无条件分支[此处,
br\u next
],其中[
\u more
]不是早期分支指令的目标。”

然而,不幸的是,这似乎不是完全的解释,因为只要您以一种可以静态识别的平衡方式删除排队的项目,计算堆栈显然不必在位置
\u more
处为空。以下代码证明了这一点,也可以正常工作,打印3个零,尽管在执行堆栈上的ECMA-335,III.1.7.5-易受攻击位置
\u more

    ldc.i4.6       // enqueue item  -- ok
    ldc.i4.5       // enqueue item  -- ok
    ldc.i4.4       // enqueue item  -- ok

    ldc.i4.0
    stloc i
    br _next

_more:
    //-- stloc cur      // de-queue item  -- still commented out

    ldloc cur
    box int32
    call void Debug::WriteLine(object)

    ldloc i
    ldc.i4.1
    add
    stloc i

_next:
    ldloc i
    ldc.i4.3
    blt _more

    pop         // de-queue item  -- required
    pop         // de-queue item  -- required
    pop         // de-queue item  -- required
    ret 
OP还使用了术语“向后分支约束”,但不清楚该短语是在规范中找到的,还是原始贡献。这似乎可能是因为规范中出现了短语“…早期分支指令”。无论哪种方式,它都提出了一个诱人的问题,即是否可以通过重新排列代码来避免错误,以便在ECMA-335中没有(技术上)与“早期”(技术性)匹配的位置,III.1.7.5约束

一个相关的想法是,规范中的“无条件分支”仅表示
br
系列指令。为了绕过
br
,我们可以在方法体中嵌入
ret
指令,如下所示。正如你可能猜到的,这是徒劳的。尽管规范没有明确说明这一点,但它显然打算将
ret
作为“无条件分支”包括在内。这是常识,因此下面的示例仍然不起作用:

    // !!! FAILS - BAD EXAMPLE - NO
    ldc.i4.6        // enqueue item  -- NO!
    ldc.i4.5        // enqueue item  -- NO!
    ldc.i4.4        // enqueue item  -- NO!

    ldc.i4.0
    stloc i

_next:
    ldloc i
    ldc.i4.3
    blt _more
    ret 

_more:
    stloc cur       // de-queue item  -- NO! -- still follows an "unconditional branch"

    ldloc cur
    box int32
    call void Debug::WriteLine(object)

    ldloc i
    ldc.i4.1
    add
    stloc i
    br _next
总而言之,我不认为这项技术会起作用,因为它的基本要求是(a)硬编码到IL中的事实(即排队数据项的数量)必须与(b)需要运行时解释的事实(即循环迭代的数量)完全对应

我认为,与ECMA描述相反,问题的一个更基本的总结是,所有失败示例都意味着执行堆栈上一条(或多条)方法指令所经历的项数不是固定的,相反,当方法执行时,在不同的时间获得不同的值,这是基本情况,无论您如何实现它,它都将被严格禁止。在我看来,这似乎是更普遍的不可侵犯的限制

例如,在demo方法中的指令
\u more
中,在一次调用的范围内,执行堆栈上首先有2个,然后是1个,然后是0个“多余”项(请注意,我从您可能期望的每个迭代中减去一个,这是因为我使用了“多余”一词)在此之前,我们试图强调一个事实,即在每个单独的循环迭代中,在位置
\u more
处正确地预期和需要一个项目,即假定的出列操作
    ldc.i4.6       // enqueue item  -- ok
    ldc.i4.5       // enqueue item  -- ok
    ldc.i4.4       // enqueue item  -- ok

    ldc.i4.0
    stloc i
    br _next

_more:
    //-- stloc cur      // de-queue item  -- still commented out

    ldloc cur
    box int32
    call void Debug::WriteLine(object)

    ldloc i
    ldc.i4.1
    add
    stloc i

_next:
    ldloc i
    ldc.i4.3
    blt _more

    pop         // de-queue item  -- required
    pop         // de-queue item  -- required
    pop         // de-queue item  -- required
    ret 
    // !!! FAILS - BAD EXAMPLE - NO
    ldc.i4.6        // enqueue item  -- NO!
    ldc.i4.5        // enqueue item  -- NO!
    ldc.i4.4        // enqueue item  -- NO!

    ldc.i4.0
    stloc i

_next:
    ldloc i
    ldc.i4.3
    blt _more
    ret 

_more:
    stloc cur       // de-queue item  -- NO! -- still follows an "unconditional branch"

    ldloc cur
    box int32
    call void Debug::WriteLine(object)

    ldloc i
    ldc.i4.1
    add
    stloc i
    br _next