惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

IT之家
IT之家
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
A
About on SuperTechFans
博客园 - 聂微东
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
B
Blog RSS Feed
U
Unit 42
Stack Overflow Blog
Stack Overflow Blog
Recent Announcements
Recent Announcements
雷峰网
雷峰网
罗磊的独立博客
Microsoft Security Blog
Microsoft Security Blog
Hugging Face - Blog
Hugging Face - Blog
L
LangChain Blog
人人都是产品经理
人人都是产品经理
The GitHub Blog
The GitHub Blog
F
Fortinet All Blogs
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
H
Help Net Security
P
Proofpoint News Feed
The Cloudflare Blog
D
Docker
大猫的无限游戏
大猫的无限游戏

博客园 - xwang

更好的执行 Better Communication – 你在听吗? 时间管理 or 能量管理 Stress Managment - 压力管理 Understanding WPF Template 调试Rotor: 启动过程 sscli google group launched Understanding Static Constructor, 理解静态构造函数 - xwang PAL 阅读笔记 Metadata Tables, 元数据表 [Coding For Fun] 您有运行许可证吗? How To: TeamBuild 编译之前的版本 How To: Team Build 自定义版本号 混淆的概念与实践 [Debugging Tool]Windbg调试托管代码 The Anatomy Of Basic Data Structure, 基本数据结构解析 Understanding GetHashCode, 理解GetHashCode 基本数据结构解析之List 试探讨泛型实现过程
自定义Rotor: 给Rotor添加一个CIL指令
xwang · 2009-01-01 · via 博客园 - xwang

快速指南

下载我修改的文件,找一份新的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 :)