home ホーム search 検索 -  login ログイン  | reload edit datainfo version cmd icon diff delete  | help ヘルプ

日記/2011/02/28/System.Reflection.Emitでの動的コード生成が謎の挙動を示す。

日記/2011/02/28/System.Reflection.Emitでの動的コード生成が謎の挙動を示す。

日記 / 2011 / 02 / 28 / System.Reflection.Emitでの動的コード生成が謎の挙動を示す。
id: 926 所有者: msakamoto-sf    作成日: 2011-02-28 17:32:58
カテゴリ: .NET 

読書メモ/"Advanced .NET Debugging"で紹介した"ADND本"だが、Chapter4のサンプル、04CodeGenがおかしな挙動を示す。
System.Reflection.Emitを使って、DynamicMethodとILGeneratorとで実行時にCLRのコードを出力しメソッドを作り、JITコンパイルを経て実際に呼んでみるサンプル。どんなコードを出力するかというと、int引数を二つ取り、足し算した結果を返すだけのシンプルなコードだ。
04CodeGen.cs:

using System;
using System.Text;
using System.Reflection.Emit;

namespace Advanced.NET.Debugging.Chapter4
{
    class CodeGen
    {
        private delegate int Add(int a, int b);

        public static void Main()
        {
            Type[] args={typeof(int), typeof(int)};
            DynamicMethod dyn = new 
                DynamicMethod("Add", 
                              typeof(int), 
                              new Type[] { typeof(int), typeof(int) }, 
                              typeof(CodeGen), 
                              true);
            ILGenerator gen = dyn.GetILGenerator();
            gen.Emit(OpCodes.Ldarg_1);
            gen.Emit(OpCodes.Ldarg_2);
            gen.Emit(OpCodes.Add);
            gen.Emit(OpCodes.Ret);

            Add a= (Add) dyn.CreateDelegate(typeof(Add));
            Console.WriteLine("Press any key to invoke Add method");
            Console.ReadKey();
            int ret = a(1, 2);
            Console.WriteLine("1+2={0}", ret);
        }
    }
}

・・・が。

> 04CodeGen.exe
Press any key to invoke Add method
1+2=4
( ゚д゚)ポカーン

(つд⊂)ゴシゴシ

( ゚Д゚)・・・はぁぁああ!!???

デバッグしてみる。環境はWin7(32bit)でCLR2.0(.NET 2.0, 3.0, 3.5) + CLR4.0両方入り。

Microsoft (R) Windows Debugger Version 6.12.0002.633 X86
Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: 04CodeGen.exe
Symbol search path is: ...
Executable search path is: 
ModLoad: 00e40000 00e48000   04CodeGen.exe
ModLoad: 77ac0000 77bfd000   ntdll.dll
ModLoad: 71ed0000 71f1a000   C:\Windows\SYSTEM32\MSCOREE.DLL
ModLoad: 76440000 76514000   C:\Windows\system32\KERNEL32.dll
ModLoad: 75d50000 75d9a000   C:\Windows\system32\KERNELBASE.dll
(1ca8.284): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000000 ecx=0028f3d8 edx=77b06344 esi=fffffffe edi=00000000
eip=77b5ebbe esp=0028f3f4 ebp=0028f420 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!LdrpDoDebuggerBreak+0x2c:
77b5ebbe cc              int     3
0:000> g
ModLoad: 761c0000 76260000   C:\Windows\system32\ADVAPI32.dll
...
ModLoad: 6c5a0000 6c5fb000   C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorjit.dll

"Press any key to invoke Add method"が表示されたところでCTRL-BREAKでDebuggerに戻す。

(1ca8.1c34): Break instruction exception - code 80000003 (first chance)
eax=7ffdb000 ebx=00000000 ecx=00000000 edx=77b5d7eb esi=00000000 edi=00000000
eip=77af3370 esp=03c8ff44 ebp=03c8ff70 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!DbgBreakPoint:
77af3370 cc              int     3
0:003> .loadby sos mscorwks
0:003> .load sosex
0:003> bp mscorjit!CILJit::compileMethod
0:003> g
Breakpoint 0 hit
eax=6c5f7268 ebx=0028e7f8 ecx=6c5e514c edx=00000011 esi=003763d0 edi=00000000
eip=6c5a5dbb esp=0028e63c ebp=0028e6a4 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
mscorjit!CILJit::compileMethod:
6c5a5dbb 55              push    ebp
0:000> kb 3
ChildEBP RetAddr  Args to Child              
0028e638 6967cacc 6c5f7268 0028e7f8 0028e770 mscorjit!CILJit::compileMethod
0028e6a4 6967cb65 00391f18 0028e7f8 0028e770 mscorwks!invokeCompileMethodHelper+0x72
0028e6e8 6967cbd8 00391f18 0028e7f8 0028e770 mscorwks!invokeCompileMethod+0x31
0:000> dd 0028e770 
0028e770  00153208 001535f9 00392978 00000004
          ^^^^^^^^ これを"!dumpmd"する
...
0:000> !dumpmd 00153208 
Method Name: DynamicClass.Add(Int32, Int32)
Class: 00153164
MethodTable: 001531c8
mdToken: 06000000
Module: 00152c5c
IsJitted: no
CodeAddr: ffffffff
0:000> !dumpil 00153208 
This is dynamic IL. Exception info is not reported at this time.
If a token is unresolved, run "!do <addr>" on the addr given
in parenthesis. You can also look at the token table yourself, by
running "!DumpArray 01a5755c".

IL_0000: ldarg.1 
IL_0001: ldarg.2 
IL_0002: add 
IL_0003: ret 

IL自体も書籍と実際のソースの通りなんだけど。

0:000> !bpmd -md 00153208 
MethodDesc = 00153208
This DynamicMethodDesc is not yet JITTED. Placing memory breakpoint at 00153228
0:000> g
Unable to insert breakpoint 2 at 0018ce80, Win32 error 0n299
    "ReadProcessMemory 要求または WriteProcessMemory 要求の一部だけを完了しました。"
The breakpoint was set with BP.  If you want breakpoints
to track module load/unload state you must use BU.
bp2 at 0018ce80 failed
WaitForEvent failed
eax=00000000 ebx=002e00a8 ecx=00153228 edx=0018ce80 esi=00000000 edi=00000008
eip=695319c4 esp=0028eadc ebp=0028eae8 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
mscorwks!CompareExchangeMP+0x8:
695319c4 c20400          ret     4

メッセージが気になるけど、とりあえず止まってくれたのでもう一度"!dumpmd"してJITされたコードを逆アセンブルしてみる。

0:002> !dumpmd 00153208 
Method Name: DynamicClass.Add(Int32, Int32)
Class: 00153164
MethodTable: 001531c8
mdToken: 06000000
Module: 00152c5c
IsJitted: yes
CodeAddr: 002e00a8
          ^^^^^^^^ これを"!U"してみる。
0:002> !U 002e00a8
Normal JIT generated code
DynamicClass.Add(Int32, Int32)
Begin 002e00a8, size 24
>>> 002e00a8 55              push    ebp
002e00a9 8bec            mov     ebp,esp
002e00ab 83ec08          sub     esp,8
002e00ae 894dfc          mov     dword ptr [ebp-4],ecx
002e00b1 8955f8          mov     dword ptr [ebp-8],edx
002e00b4 833d142e150000  cmp     dword ptr ds:[152E14h],0
002e00bb 7405            je      002e00c2
002e00bd e867b24a69      call    mscorwks!JIT_DbgIsJustMyCode (6978b329)
002e00c2 8b45f8          mov     eax,dword ptr [ebp-8]
^^^^^^^^ ここにbpしてみる。
002e00c5 030424          add     eax,dword ptr [esp]
002e00c8 8be5            mov     esp,ebp
002e00ca 5d              pop     ebp
002e00cb c3              ret
0:002> bl
 0 e 6c5a5dbb     0001 (0001)  0:**** mscorjit!CILJit::compileMethod
 2 e 0018ce80     0001 (0001)  0:**** 
0:002> bc 0
0:002> bc 2
0:002> bl
0:002> bp 002e00c2 
0:002> g
Breakpoint 0 hit
eax=00153208 ebx=0028ed2c ecx=00000001 edx=00000002 esi=003763d0 edi=00000000
eip=002e00c2 esp=0028ec60 ebp=0028ec68 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
002e00c2 8b45f8          mov     eax,dword ptr [ebp-8] ss:0023:0028ec60=00000002
0:000> p
eax=00000002 ebx=0028ed2c ecx=00000001 edx=00000002 esi=003763d0 edi=00000000
eip=002e00c5 esp=0028ec60 ebp=0028ec68 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
002e00c5 030424          add     eax,dword ptr [esp]  ss:0023:0028ec60=00000002
0:000> p
eax=00000004 ebx=0028ed2c ecx=00000001 edx=00000002 esi=003763d0 edi=00000000
eip=002e00c8 esp=0028ec60 ebp=0028ec68 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
002e00c8 8be5            mov     esp,ebp

・・・この時点でEAX=4となっている。
詳しいデバッグログは省くが、このルーチンに入った時点ではECX = 第一引数, EDX=第二引数がセットされている。つまり
ECX = 1, EDX = 2
となっている。

sub     esp,8
mov     dword ptr [ebp-4],ecx
mov     dword ptr [ebp-8],edx

で、この時点でECX, EDX がそれぞれEBPからの相対位置を使ってコピーされる。
なので、

mov     eax,dword ptr [ebp-8]

この次の

add     eax,dword ptr [esp]

は、本来は

add     eax,dword ptr [ebp-4]

じゃないとおかしくないか?

Visual C# 2008 Express Edition (.NET Framework 3.5)でコンパイルしてみる:

> csc 04CodeGen.cs
Microsoft (R) Visual C# 2008 Compiler version 3.5.30729.4926
for Microsoft (R) .NET Framework version 3.5
Copyright (C) Microsoft Corporation. All rights reserved.

> 04CodeGen.exe
Press any key to invoke Add method
1+2=71
(  д )   ゚ ゚

Visual C# 2010 Express Edition (CLR 4)でコンパイルしてみる:

> csc 04CodeGen.cs
Microsoft (R) Visual C# 2010 Compiler version 4.0.30319.1
Copyright (C) Microsoft Corporation. All rights reserved.

> 04CodeGen.exe
Press any key to invoke Add method
1+2=75
(    )       д         ゚ ゚

謎。とりあえずこの機能、使わないでおこう・・・。


プレーンテキスト形式でダウンロード
現在のバージョン : 1
更新者: msakamoto-sf
更新日: 2011-02-28 17:43:15
md5:1b59621ad47439ffe98a297841d8dbaf
sha1:7b82cadfd3ccb22eafab82eb73f86ee42b54fe57
コメント
コメントを投稿するにはログインして下さい。