.Net CLR R2R编译的原理简析
前言
躺平了好一段时间了,都懒得动了。本文均为个人理解所述,如有疏漏,请指正。
楔子
金庸武侠天龙八部里面,少林寺至高无上的镇寺之宝,武林人士梦寐以求的内功秘笈易筋经被阿朱偷了,但是少林寺也没有大张旗鼓的派出高手去寻找,为啥?
这种少林寺至高无上的内功秘笈,一般的江湖人士根本看不懂。除非内功深厚的高手。
来看看.Net里面看不懂的内功秘笈R2R原理。
概念:
R2R编译实质上就是把方法运行的结果存储在二进制的动态链接库里面,在调用这个方法的时候,直接从动态链接库里面获取到方法的结果。而不需要经过RyuJit繁琐的编译,提升程序的性能。是一种AOT的预编译形式。
编译
dotnet publish -c Release -r win-x64 -p:PublishReadyToRun=true
整体过程:
当CLI命令里面标记了PublishReadyToRun,Rosyln重新编译生成的动态链接里面会生成Native Header,里面保存了当前模块的方法的运行结果。此后在CLR加载它的时候,CLR会查找动态链接库里的Native Header是否存在,如果存在,则在调用方的时候,直接获取到此方法的结果。
由于过程过于复杂此处只是提纲:
CLI(PublishReadyToRun:true)->Rosyln(Native Header) -> CLR (Get NH)
预编译存储结构
typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY {
DWORD BeginAddress;
DWORD EndAddress;
union {
DWORD UnwindInfoAddress;
DWORD UnwindData;
} DUMMYUNIONNAME;
} _IMAGE_RUNTIME_FUNCTION_ENTRY, *_PIMAGE_RUNTIME_FUNCTION_ENTRY;
构成方式:
动态链接库里面会分配一段内存空间,称之为Nativie Header。里面存储了包括如下内容:
1.编译器标识符(CompilerIdentifier)
2.导入方法段(ImportSections)
3.运行时方法(RuntimeFunctions)
4.方法入口点(MethodDefEntryPoints)
5.异常信息(ExceptionInfo)
6.调试信息(DebugInfo)
7.延迟方法加载调用快(DelayLoadMethodCallThunks)
等等总共高达18项信息,由于这些东西过于复杂此处只列出其中的前面几个。构成了Native Header。
加载R2R
CLR在进行一个模块加载的时候,它会初始化R2R,如果判断此模块有Native Header,那么把里面的18项信息加入到内存当中。代码如下(过于复杂,省略了大部分)
PTR_ReadyToRunInfo ReadyToRunInfo::Initialize(Module * pModule, AllocMemTracker *pamTracker)
{
// 此处省略一百万行代码
return new (pMemory) ReadyToRunInfo(pModule, pModule->GetLoaderAllocator(), pLayout, pHeader, nativeImage, pamTracker);
}
ReadyToRunInfo::ReadyToRunInfo(Module * pModule, LoaderAllocator* pLoaderAllocator, PEImageLayout * pLayout, READYTORUN_HEADER * pHeader, NativeImage *pNativeImage, AllocMemTracker *pamTracker)
: m_pModule(pModule),
m_pHeader(pHeader),
m_pNativeImage(pNativeImage),
m_readyToRunCodeDisabled(FALSE),
m_Crst(CrstReadyToRunEntryPointToMethodDescMap),
m_pPersistentInlineTrackingMap(NULL)
{
// pHeader就是动态链接库里面的native header,它包含了Signature,MajorVersion,CoreHeader等。
STANDARD_VM_CONTRACT;
if (pNativeImage != NULL)
{
// 此处省略
}
else
{
m_pCompositeInfo = this;
m_component = ReadyToRunCoreInfo(pLayout, &pHeader->CoreHeader);
m_pComposite = &m_component;
m_isComponentAssembly = false;
}
//获取运行时R2R方法的内存虚拟地址和所占的长度,后面用获取到的索引得到R2R方法的入口地址
IMAGE_DATA_DIRECTORY * pRuntimeFunctionsDir = m_pComposite->FindSection(ReadyToRunSectionType::RuntimeFunctions);
if (pRuntimeFunctionsDir != NULL)
{
m_pRuntimeFunctions = (T_RUNTIME_FUNCTION *)m_pComposite->GetLayout()->GetDirectoryData(pRuntimeFunctionsDir);
m_nRuntimeFunctions = pRuntimeFunctionsDir->Size / sizeof(T_RUNTIME_FUNCTION);
}
else
{
m_nRuntimeFunctions = 0;
}
调用过程:
当你在C#代码里面调用方法的时候,CLR检测当前方法所在的模块是否包含R2R信息,如果包含则获取到R2R信息,通过R2R信息,获取到Native Header里面的RuntimeFunctions和MethodDefEntryPoints。然后通过这两项计算出这个方法在RuntimeFunctions内存块里面的索引,通过这个索引计算出方法在RuntimeFunctions内存块的偏移值,通过偏移值获取属性BeginAddress,也就是方法在二进制动态链接库里面存储的结果。过程比较复杂,下面贴出部分代码。
PCODE MethodDesc::GetPrecompiledR2RCode(PrepareCodeConfig* pConfig)
{
STANDARD_VM_CONTRACT;
PCODE pCode = NULL;
#ifdef FEATURE_READYTORUN
Module * pModule = GetModule(); //获取被调用的方法所在模块
if (pModule->IsReadyToRun()) //检测此模块思否包含R2R信息
{
//如果包含,则获取到R2R信息,然后获取被调用方法的入口点
pCode = pModule->GetReadyToRunInfo()->GetEntryPoint(this, pConfig, TRUE /* fFixups */);
}
}
//获取被调用方法入口点
PCODE ReadyToRunInfo::GetEntryPoint(MethodDesc * pMD, PrepareCodeConfig* pConfig, BOOL fFixups)
{
mdToken token = pMD->GetMemberDef();
int rid = RidFromToken(token);//获取被调用方法的MethodDef索引
if (rid == 0)
goto done;
uint offset;
if (pMD->HasClassOrMethodInstantiation())
{
//此处省略一万字
}
else
{
// 这个m_methodDefEntryPoints就是Native Header里面的方法入口点项。通过函数入口点项获取到被调用方法所在运行时方法(RuntimeFunctions)的索引
if (!m_methodDefEntryPoints.TryGetAt(rid - 1, &offset))
goto done;
}
uint id;
offset = m_nativeReader.DecodeUnsigned(offset, &id);
if (id & 1)
{
if (id & 2)
{
uint val;
m_nativeReader.DecodeUnsigned(offset, &val);
offset -= val;
}
if (fFixups)
{
BOOL mayUsePrecompiledNDirectMethods = TRUE;
mayUsePrecompiledNDirectMethods = !pConfig->IsForMulticoreJit();
if (!m_pModule->FixupDelayList(dac_cast<TADDR>(GetImage()->GetBase()) + offset, mayUsePrecompiledNDirectMethods))
{
pConfig->SetReadyToRunRejectedPrecompiledCode();
goto done;
}
}
id >>= 2;
}
else
{
id >>= 1;
}
_ASSERTE(id < m_nRuntimeFunctions);
//上面经过了一系列的计算,把这个真正的索引id作为m_pRuntimeFunctions也就是native header项RuntimeFunctions的内存块的索引,然后获取到属性BeginAddress,也就是被调用方法的入口点。
pEntryPoint = dac_cast<TADDR>(GetImage()->GetBase()) + m_pRuntimeFunctions[id].BeginAddress;
这个地方是更新了下被调用方法的入口点
m_pCompositeInfo->SetMethodDescForEntryPointInNativeImage(pEntryPoint, pMD);
return pEntryPoint;
}
以上参考如下:
1.https://github.com/dotnet/runtime/blob/main/src/coreclr/gc/gchandletable.cpp
2.https://github.com/dotnet/runtime/blob/main/src/coreclr/gc/gc.cpp
3.https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/readytoruninfo.cpp
4.https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/prestub.cpp
5.https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/nativeformatreader.h
结尾:
一直认为技术是可以无限制的免费分享和随意攫取,如果你喜欢可以随意转载修改。微信公众号:jianghupt QQ群:676817308。欢迎大家一起讨论。
.Net CLR R2R编译的原理简析的更多相关文章
- PHP的错误报错级别设置原理简析
原理简析 摘录php.ini文件的默认配置(php5.4): ; Common Values: ; E_ALL (Show all errors, warnings and notices inclu ...
- Java Annotation 及几个常用开源项目注解原理简析
PDF 版: Java Annotation.pdf, PPT 版:Java Annotation.pptx, Keynote 版:Java Annotation.key 一.Annotation 示 ...
- Java Android 注解(Annotation) 及几个常用开源项目注解原理简析
不少开源库(ButterKnife.Retrofit.ActiveAndroid等等)都用到了注解的方式来简化代码提高开发效率. 本文简单介绍下 Annotation 示例.概念及作用.分类.自定义. ...
- [转载] Thrift原理简析(JAVA)
转载自http://shift-alt-ctrl.iteye.com/blog/1987416 Apache Thrift是一个跨语言的服务框架,本质上为RPC,同时具有序列化.发序列化机制:当我们开 ...
- Spring系列.@EnableRedisHttpSession原理简析
在集群系统中,经常会需要将Session进行共享.不然会出现这样一个问题:用户在系统A上登陆以后,假如后续的一些操作被负载均衡到系统B上面,系统B发现本机上没有这个用户的Session,会强制让用户重 ...
- SIFT特征原理简析(HELU版)
SIFT(Scale-Invariant Feature Transform)是一种具有尺度不变性和光照不变性的特征描述子,也同时是一套特征提取的理论,首次由D. G. Lowe于2004年以< ...
- 基于IdentityServer4的OIDC实现单点登录(SSO)原理简析
写着前面 IdentityServer4的学习断断续续,兜兜转转,走了不少弯路,也花了不少时间.可能是因为没有阅读源码,也没有特别系统的学习资料,相关文章很多园子里的大佬都有涉及,有系列文章,比如: ...
- 动态代理 原理简析(java. 动态编译,动态代理)
动态代理: 1.动态编译 JavaCompiler.CompilationTask 动态编译想理解自己查API文档 2.反射被代理类 主要使用Method.invoke(Object o,Object ...
- 【超精简JS模版库/前端模板库】原理简析 和 XSS防范
使用jsp.php.asp或者后来的struts等等的朋友,不一定知道什么是模版,但一定很清楚这样的开发方式: <div class="m-carousel"> < ...
随机推荐
- 免费yum源镜像地址
收集的镜像,yum源等网站地址 阿里巴巴开源镜像站 https://opsx.alibaba.com/mirror http://mirrors.aliyun.com/centos/ 网易开源镜像站 ...
- linux篇-linux mysql数据库定时备份
1在linux上面创建一个文件夹,并且进行备份 cd /home mkdir backup cd backup 2创建一个脚本 Vi imaginebase.sh #!/bin/bash mysqld ...
- 20 HTTP 长连接与短连接
20 HTTP 长连接与短连接 每日一句 纸上得来终觉浅,绝知此事要躬行. 每日一句 Never give up until the fight is over. 永远不要放弃,要一直战斗到最后一秒. ...
- TypeError: this.getOptions is not a function
我在vue ui界面中安装版本依赖包后报这个错误 less-loader/sass-loader安装的版本过高 解决办法 删除原有的版本依赖包,安装更低版本的依赖包. 如 @6.0.1为选择安装的版本 ...
- 行列式&矩阵树定理
行列式: 参考 oi-wiki 定义 对于一个\(n*n\)的矩阵A行列式取值(标量) \(det(A)=|A|=\sum\limits_p(-1)^{\tau(p)}\prod\limits_{i= ...
- mysql 开启binlog日志,恢复误删的表、数据、mysql库
linux下开启mysql的binlog日志功能 1.配置mysql配置文件my.cnf(内容如下). #配置文件储存的位置log-bin=mysql-bin#5.7以及以上版本需要配置这一行(保证唯 ...
- re学习笔记
re学习笔记 学习链接: https://regexlearn.com/zh-cn/learn \w: 数字字母下划线 \W: 非\w \d \D: !\d \s: space cha \S: !\s ...
- 28.MysQL的日志管理及备份与恢复
MySQL 索引.事务与存储引擎 目录 MySQL 索引.事务与存储引擎 MySQL 索引 索引的概念 索引的作用及副作用 索引的作用 索引的副作用 创建索引的原则依据 索引的分类和创建 普通索引 唯 ...
- 19.Tomcat多实例部署及负载均衡、动静分离
Tomcat多实例部署及负载均衡.动静分离 目录 Tomcat多实例部署及负载均衡.动静分离 Tomcat多实例部署 安装jdk 设置jdk环境变量 安装tomcat 配置 tomcat 环境变量 修 ...
- JS:eval
定义和用法: eval() 函数计算 JavaScript 字符串,并把它作为脚本代码来执行.eval()函数并不会创建一个新的作用域,并且它的作用域就是它所在的作用域. 如果参数是一个表达式,eva ...