5

を使用して挿入したこの一連のCILコードがありMono.Cecilます。ただし、変更された.NET C#アプリケーションは実行されません。

目的:スタックから値を手動でロードしてポップし、表示するConsole.WriteLine

 for (int i = 0; i < 3; i++)
        {
            int z = some value popped manually from stack;                 
            Console.WriteLine(z);
        }

これは私が変更した単純なmain()プログラムです。

.method private hidebysig static void Main(string[] args) cil managed
{

    .entrypoint
    .maxstack 5
    .locals init (
        [0] int32 num,
        [1] int32 num2)
    L_0000: ldc.i4.6 //manually push value 6 to stack
    L_0001: ldc.i4.5 //manually push value 5 to stack
    L_0002: ldc.i4.4 //manually push value 4 to stack
    L_0003: ldc.i4.0 //push int i initial value 0 to stack 
    L_0004: stloc.0 //pop and store to int i variable to variable num
    L_0005: br.s L_0013
    L_0007: nop 
    L_0008: stloc.1 //pop the pushed values 6,5 and 4 to variable num2
    L_0009: ldloc.1 //load value of num2 to stack
    L_000a: call void [mscorlib]System.Console::WriteLine(int32) //pop value of num2 and print
    L_000f: ldloc.0 //load previous value in variable num to stack
    L_0010: ldc.i4.1 //load incremental value 1 to stack
    L_0011: add //pop and add the top 2 values, result is pushed to stack
    L_0012: stloc.0 //store the new result to variable num. (int i)
    L_0013: ldloc.0 //push int i variable value to stack
    L_0014: ldc.i4.3 //push value 3 to stack as number of times to loop
    L_0015: blt.s L_0007 //branch less than (pop and cmp the top 2 values in stack)
    L_0017: ret 
}

ただし、上記のコードは実行できません。に変更blt.sしてcltみましbr_true.sたが、うまくいきません。私の目的を達成することが可能かどうか誰かが知っていますか?ありがとう。

編集:ECMA-335、III.1.7.5によると、後方分岐制約がある可能性があります。これが当てはまるかどうかはわかりません。

特に、そのシングルパス分析が命令に到達した場合、それをロケーションXと呼びます。これは、無条件分岐の直後に続き、Xが以前の分岐命令のターゲットではない場合、Xでの評価スタックの状態は明らかに、既存の情報から導出することはできません。この場合、CLIは、Xの評価スタックが空であることを要求します。

4

2 に答える 2

2

ILコードは問題ないように見えますが、メソッドの完了後にCLRがスタックが破損しているかどうかを確認できない可能性があると思います。何かがスタックにプッシュされると、CLRは値もスタックからポップされるかどうかをチェックします。

したがって、3つの値をスタックにプッシュすると、CLRはループが3回実行されているかどうかを確認できない可能性があるため、メソッドが戻ってきたときにCLRはスタックにまだ値があるかどうかを認識しません。

于 2012-10-17T09:20:48.233 に答える
1

非常に興味深い質問です。任意のデータ項目のキューを格納する目的でIL実行スタックを使用しようとしています。これにより、従来のILコードとは異なり、スタックの正しいバランスが、ILに焼き付けられたILAsm時間データ項目の数と正確に一致するランタイムループの反復回数に大きく依存するという異常な状態が発生します。お気づきのように、プログラム(ここで繰り返されます)は機能しません。

link.exe(実際、 withを使用する私のビルドでは/LTCG、リンカーはアセンブリを生成することすらできず、「fatal error C1352: Invalid or corrupt MSIL in function。」を与えます)

.method public static void ExecStackResidual()      // !!! FAILS - BAD EXAMPLE - NO !!!
{
    .locals init (int32 i, int32 cur)

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

    ldc.i4.0
    stloc i
    br _next

_more:
    stloc cur       // de-queue item  -- NO!

    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 
}

問題は、コードの単純な論理的欠陥または1つずつのバグによるn̲o̲t̲です。これは、物議を醸している部分を次のようにコメントアウトすると、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を発見しました。これは、ここで関連しているように思われます。これは、動作例と失敗例の主な違いは、後者では空でない評価スタックが存在する必要があることです(別名「シーケンスポイント」)場所_moreで、そしてその場所は確かに、仕様を引用するために...

「...無条件の分岐[ここ、br _next]の直後をたどります。ここで、[ _more]は以前の分岐命令のターゲットではありません。」

ただし、残念ながら、これは完全な説明ではないようです。静的に識別できるバランスの取れた方法でキューに入れられたアイテムを削除する限り、評価スタックは場所で空である必要はない_moreようです。これは、 ECMA-335、III.1.7.5の脆弱な場所の実行スタックにいくつかの項目があるにもかかわらず、3つのゼロを出力して正常に機能する次のコードによって示されます。_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の説明とは対照的に、問題のより基本的な要約は、すべての失敗の例では、メソッドの命令の1つ(または複数)が経験する実行スタック上のアイテムの数が固定されていないことを意味すると思います。メソッドの実行中に異なる時間に異なる値を取得します。これは、どのように達成しても、常に厳密に許可されない根本的な状況です。私には、それはより一般的な不可侵の制約のように思えます。

たとえば_more、デモメソッドの命令では、すべて1回の呼び出しの範囲内で、実行スタックに最初に2つ、次に1つ、次に0の「超過」アイテムがあります(反復ごとに1つを減算したことに注意してください)。予想していたかもしれませんが、これは、個々のループの反復内で、特定の場所、つまり推定デキュー操作で1つのアイテム適切に期待され、必要であるという事実を強調するために、直前に「超過」という単語を使用したためです。 )。_morestloc cur

ILメソッドが有効であるためには、その命令のいずれかが実行スタックの一般的な深さの変動を経験する方法がないはずだと私は推測します。つまり、静的に決定してメソッドの各命令に割り当てることができるスタック深度値は1つだけである必要があります。直感的には、そうしないと、JITタスクが非常に手に負えなくなるか、おそらく不可能になる可能性があります。

于 2019-02-11T02:38:14.233 に答える