自定义Rotor: 给Rotor添加一个CIL指令

快速指南

下载我修改的文件,找一份新的Rotor代码,覆盖,而后Build,运行本文的例子即可

引言:

CIL = Common Intermediate Language, 在.Net Framework中,我们称他为MSIL

还不知道Rotor? 看看Wiki的解释 (注:我们这里的Rotor如果没有特别注明都是指SSCLI 2.0)

关于添加CIL的文章已经不少了这里,还有这里. 然而中文的,很可惜,无,虽然有前辈(Flier's Sky - (Flier Lu), NeoRAGEx2002's Weblog)做过一些探索,然而终于是没有给俺们看到,于是有了俺的不断探索,虽频遭头痛,偶有心得,分享之。:)

思路概述:

我们先那一个指令来搞清楚CIL的执行过程, 而后在对应的地方作相应的操作,模仿即可.  : )

线索:

  • IL指令涉及到Verify, Compile, 和运行期验证的过程. 他是JIT一部分.
  • 在rotor目录中有 clr/src/fjit 这个目录

从拿一个指令开刀说起

很多地方的例子都是乘法,我们也拿他开始. 例子程序

// Sample Entrypoint and a sample function here, 
// Please add your own code in your environment
public static void Main()
{
    
double x = 2;
    
double y = 5;
    
double z = x * y;

    Console.Read();
}

按照我之前的如何调试SSCLI的步骤, 到PAL_Initialize的位置。步骤如下

cd E:/Research/Rotor  // 切换到SSCLI所在目录
env // 使用环境设置env.bat
csc /debug E:/Practices/cil/MULTest.cs // 编译
devenv /debugexe clix MulTest.exe // 运行调试

我们在Visual Studio的类试图中搜索MUL指令, 而且我们知道他是JIT的一部分,于是找到了如下图这个指令,

image

我们给这个指令加上一个断点,按F5执行。哈哈,抓到断点了 Bingo!

image

查看Call Stack, 就可以轻易的看到执行过程了,双击 / 按右键 Go to source 也可以轻易的跳转到对应代码了 :D

image

而后根据call stack跳转到compileCEE_MUL方法之前执行的步骤

image 

有case, 哈哈,那就有switch. :)

image

看到CEE_PREFIX1, CEE_LDARG_0这种东西你是不是和我一样兴奋,意味着可以在某个地方添加我们的CEE_MYCUSTOM_IL_OPCODE

接着看看MUL的实现代码

image

聪明的你应该看明白了,至少代码的解释应该是清楚的, 得到栈上(topOp)上第0个和第一个参数,进行运算而后进栈,

BINARY_NUMBERIC_RESULT:  查看两个参数是否为同一类型

TYPE_SWITCH_ARITH:           得到指令的详细格式,例如ldloc.i4 或者 ldloc.i8 等,这里这是emit_mul_i4

关于emit_mul或者CEE_Prefix1等等操作指令定义在fjitdef.h中,我们查看MUL_I4的操作指令

/************* MUL ************************/

#ifndef emit_MUL_I4
#define emit_MUL_I4()                       \
{                                           \
    callInfo.reset();                       \
    emit_tos_arg( 
1, INTERNAL_CALL );       \
    emit_tos_arg( 
2, INTERNAL_CALL );       \
    emit_callhelper_I4I4_I4(MUL_I4_helper); \
    emit_pushresult_I4();                   \
}
#ifdef DECLARE_HELPERS
int HELPER_CALL MUL_I4_helper(int i, int j) {
    
return j * i;
}
#endif
#endif

到这里,我们对一个指令的执行过程有了一个认识,于是,开始我们的新指令之旅

添加一门新武器 – EXPON

修改分为如下步骤

  • 写一个测试程序
  • 添加新指令到Rotor源码
  • 编译运行

测试程序

.assembly extern mscorlib {} 
  .assembly expi4demo {} 
  .method 
static void main() { 
    .entrypoint 
    ldc.i4 
3 
    ldc.i4 
3 
    expon
    call 
void [mscorlib]System.Console::WriteLine(int32) 
    ret 
  }

添加指令到Rotor

给clr\src\inc\opcode.def 模仿CEE_MUL添加上EXPON.

OPDEF(CEE_EXPON, "expon", PopR8+PopR8, PushR8, InlineNone, IPrimitive, 1,  0xFF0xA6, NEXT)

 并在Reflection.Emit添加这个指令 clr\src\bcl\system\reflection\emit\opcodes.cs

public static readonly OpCode Expon = new OpCode("expon", StackBehaviour.Pop1_pop1,
StackBehaviour.Push1, OperandType.InlineNone, OpCodeType.Primitive, 
1, (byte)0xff, (byte)0xa6,
FlowControl.Next, 
false-1);

接着要进行JIT指令验证的过程了. 验证的代码在clr\src\vm\validator.cpp中,我们只需要添加指令到vertable.h

VEROPCODE(CEE_EXPON,                 "N=:-")

所谓”N=:-” N – Number, = - 接着另一个Number : - 结果, - - 入栈.

关于格式的详细解释,在vertable.h的注释中有详尽的解释,有兴趣的朋友不妨一看。

接下来则要给他添加执行过程了, 在clr/src/vm/fjit/fjit.cpp 中的jitCompile方法添加一个switch判断

case CEE_EXPON:
    JitResult 
= compileCEE_EXPON();
    
break;

接着在本类中和头文件中添加对应的实现代码

fjit.cpp

FJitResult FJit::compileCEE_EXPON()
{
    OpType result_xf;
    BINARY_NUMERIC_RESULT(topOp(), topOp(
1), CEE_EXPON, result_xf);
    TYPE_SWITCH_ARITH(topOp(), emit_EXPON, ());
    POP_STACK(
2);
    pushOp(result_xf);
    
return FJIT_OK;
}

fjit.h

FJitResult compileCEE_EXPON();

TYPE_SWITCH_ARITH中定义了对应的实现方法,于是我们在clr\src\jit\fjit.def 中添加对应的定义

#ifndef emit_EXPON_I4
#define emit_EXPON_I4()                       \
{                                           \
    callInfo.reset();                       \
    emit_tos_arg( 
1, INTERNAL_CALL );       \
    emit_tos_arg( 
2, INTERNAL_CALL );       \
    emit_callhelper_I4I4_I4(EXPON_I4_helper); \
    emit_pushresult_I4();                   \
}
#ifdef DECLARE_HELPERS
int HELPER_CALL EXPON_I4_helper(int i, int j) {
    
return j * i;
}
#endif
#endif

#ifndef emit_EXPON_I8
#define emit_EXPON_I8()   \
{                                                                            \
    callInfo.reset();                                                        \
    
int NumRegUsed = 0;                                                      \
    emit_tos_fixedsize_arg( 
18, NumRegUsed, INTERNAL_CALL );               \
    emit_tos_fixedsize_arg( 
1 + NumRegUsed, 8, NumRegUsed, INTERNAL_CALL );  \
    emit_callhelper_I8I8_I8(EXPON_I8_helper);                                  \
    emit_pushresult_I8();                                                    \
}
#ifdef DECLARE_HELPERS
__int64 HELPER_CALL EXPON_I8_helper(__int64 i, __int64 j) {
    
return j * i;
}
#endif
#endif

#ifndef emit_EXPON_R4
#define emit_EXPON_R4()   \
{                                           \
    callInfo.reset();                       \
    emit_tos_arg( 
1, INTERNAL_CALL );       \
    emit_tos_arg( 
2, INTERNAL_CALL );       \
    emit_callhelper_I4I4_I4(EXPON_R4_helper); \
    emit_pushresult_I4();                   \
}
#ifdef DECLARE_HELPERS
unsigned 
int HELPER_CALL EXPON_R4_helper(int i, int j) {
    
float result = (*(float *)&j) * (*(float *)&i);
    
return *(unsigned int*)&result;
}
#endif
#endif

#ifndef emit_EXPON_R8
#define emit_EXPON_R8()                                                        \
{                                                                            \
    callInfo.reset();                                                        \
    
int NumRegUsed = 0;                                                      \
    emit_tos_fixedsize_arg( 
18, NumRegUsed, INTERNAL_CALL );               \
    emit_tos_fixedsize_arg( 
1 + NumRegUsed, 8, NumRegUsed, INTERNAL_CALL );  \
    emit_callhelper_R8R8_R8(EXPON_R8_helper);                                  \
    emit_pushresult_I8();                                                    \
}
#ifdef DECLARE_HELPERS
unsigned __int64 HELPER_CALL EXPON_R8_helper(__int64 i, __int64 j) {
    
double result = (*(double *)&j) * (*(double *)&i);
    
return *(unsigned __int64*)&result;
}
#endif
#endif

重新编译Rotor, 切换到开始的IL所在目录并运行

ILAsm expon.il
clix expon

编译结果:

image

在编译的过程中,碰到一些问题,需要注意如下:

  • 文件编码要正确
  • 如果编译错误,删除原来编译结果,而后重新编译

在这里,我把我所修改的文件共享下,覆盖原来的代码文件即可

我修改的文件

结语:

修改本来是件Easy的事情,不幸运的是俺第一次编译失败,而后面对一堆的错误,不知道哪里入手,检查一次又一次代码之后,确定代码无错误,尝试着删除某个编译结果后错误不一样了, 俺兴奋了,俺强烈的兴奋了。 在经过几次尝试和漫长的编译等待之后,

env

buildall

等待…… Zzzz Orz…

编译错误, 查看clr/src/build.err

env

buildall

修改过程虽然简单,可惜俺那稳定性不高的编码速度实在害人,总结下,发发牢骚而已 :)

 

Have Fun :)

posted on 2009-01-01 15:07  xwang  阅读(1922)  评论(6编辑  收藏  举报

导航