C/C++ Mono 虚拟机运行一个可执行的 .NET Assembly
本文是嵌入 Mono 虚拟机中一片基础的启蒙篇幅,它影响到后期对于描述 Mono 虚拟机引擎嵌入应用方面的内容,另外本文建议读者至少对 .NET 是什么有一个清晰的认识。 参考: Mono API Documentation http://docs.go-mono.com/?link=root: Embedding Mono https://www.mono-...
本文是嵌入 Mono 虚拟机中一片基础的启蒙篇幅,它影响到后期对于描述 Mono 虚拟机引擎嵌入应用方面的内容,另外本文建议读者至少对 .NET 是什么有一个清晰的认识。
参考:
Mono API Documentation http://docs.go-mono.com/?link=root:
Embedding Mono https://www.mono-project.com/docs/advanced/embedding/
我们先来琢磨琢磨,嵌入 Mono 虚拟机引擎大致可以应用于那些方面,我们仔细用小脑袋好好去想一想,其实就能够发现可以应用到很多方面。
例如:
1、程序集加固【抗反调试、反译编、程序集 dump】
2、即时调试器(可不利用 PDB,例:dnSpy)
3、元数据分析
4、IL 代码植入
5、AppDomain 分析
6、高性能的热插拔 .NET 脚本或插件化(例:Unity3d)【例:扩展 InternalCall 函数】
....
上述的利用方向都无疑是正向友好的,反向当然是干坏事,不过这不是本文需要探讨的内容,想想上面提出的应用点能带来多少的好处,可以说很多,它能够解决很多在 .NET /CLR 内不容易解决的问题。
当然它本身就没有缺陷了?缺陷倒是有,它相对于 .NET(Win32k)之间效率上还是有一些差距的,当然不会有五倍十倍那么夸张,另外就是框架库还存在一些的 BUG,当然这不是主要的,无论是 Java 运行库还是 .NET 运行库基本都存在一些 BUG,但是并不是太影响一般性的利用,当然高度复杂的利用,遇到这种坑坑,大概自己造个轮子就解决掉了,要是虚拟机本身的问题这个就发 issue 或者自己改代码然后重新build,所以这并不是什么太值得令人注意的东西。
回到正题,既然我们需要运行一个可执行的 .NET Assembly,那么我们势必需要先找到具体的程序集的入口点函数,我们从文档中似乎可以看到如下图所示的函数。
mono_image_get_entry_point 从名称上看与定义的结构来看 它意思是说从一个 MonoImage 镜像中返回入口点函数的 token,这个 token 主要指 metadatatoken = RID,你可以从下述列出的 _CorTokenType 枚举中参考元数据类型范围的定义。
public enum CorTokenType : uint
{
mdtModule = 0u,
mdtTypeRef = 0x1000000,
mdtTypeDef = 0x2000000,
mdtFieldDef = 0x4000000,
mdtMethodDef = 100663296u,
mdtParamDef = 0x8000000,
mdtInterfaceImpl = 150994944u,
mdtMemberRef = 167772160u,
mdtCustomAttribute = 201326592u,
mdtPermission = 234881024u,
mdtSignature = 285212672u,
mdtEvent = 335544320u,
mdtProperty = 385875968u,
mdtModuleRef = 436207616u,
mdtTypeSpec = 452984832u,
mdtAssembly = 0x20000000,
mdtAssemblyRef = 587202560u,
mdtFile = 637534208u,
mdtExportedType = 654311424u,
mdtManifestResource = 671088640u,
mdtGenericParam = 704643072u,
mdtMethodSpec = 721420288u,
mdtGenericParamConstraint = 738197504u,
mdtString = 1879048192u,
mdtName = 1895825408u,
mdtBaseType = 1912602624u
}
mono_image_get_entry_point 函数访问一个 image 有意思的它可能返回一个 0u 的值(可以视作 NULL)表明它无法获取到入口点函数的 RID,但是这种情况主要发生在 “.NET Assembly” 并不是一个有效的 “可执行程序” 的上面。
MonoMethod* mono_image_get_entry_point_ex2(MonoImage* image)
{
uint32_t metadataken = image ? mono_image_get_entry_point(image) : 0u;
if (!mono_metadata_token_index(metadataken))
{
return NULL;
}
return mono_ldtoken(image, mono_image_get_entry_point(image), NULL, NULL);
}
但是上面的方法必须要 “.NET Assembly” 内部显示定义了 entrypoint 入口点函数才可以,但是有的时候编译的代码是包含 Main 函数的,但我们却不小心编译成了 DLL 那么我们应该怎么去获取这个未被显示定义的入口点函数?
我们知道在 .NET 派系里面的语言都显示的约定了一个入口点函数 即 “Main(void) or Main(string[])” 两个签名,所以我们只要把这两个方法在程序集中找出来就可以了,那么有几种方式可以选择 “反射” 、“mono_method_desc_search_in_image” 那么为什么不考虑 “mono_method_desc_search_in_class”?这个很好理解,我们都知道 .NET 派系里的语言并不一定区分 Main 函数需要规定在哪个类里一个固定的值而是一个未知的值,而 “mono_method_desc_search_in_class” 建立在已知类的 “MonoClass*” 时才成立的,另外这两个函数支持统配符模糊查找,用在一般性的查找上面真的比较方便。
MonoMethod* mono_image_get_entry_point_ex(MonoImage* image)
{
MonoMethod* method = NULL;
MonoMethodDesc* desc;
if (image)
{
static char* pattern[] = { "*:Main()", "*:Main(string[])" };
for (int i = 0; i < 2; i++)
{
desc = mono_method_desc_new(pattern[i], TRUE);
if (!desc)
{
continue;
}
method = mono_method_desc_search_in_image(desc, image);
mono_method_desc_free(desc);
if (method)
{
break;
}
}
}
return method;
}
那么获取到入口点的 MonoMethod* 我们应该如何去正确的运行它?其实若仅仅只是运行一个托管的“ 可执行程序” 的话,有一个比较简单的方法完成。
int main(int argc, char* argv[])
{
MonoAssembly* assembly;
MonoDomain* domain;
int retval;
domain = mono_jit_init(argv[1]);
assembly = mono_domain_assembly_open(domain, argv[1]);
mono_jit_exec(domain, assembly, argc, argv);
retval = mono_environment_exitcode_get();
mono_jit_cleanup(domain);
return retval;
}
如上述代码所示,若你仅仅只是利用 mono 执行你的可执行应用程序的话,按上述代码就可以了 当然至于 mono 虚拟机层面的优化,AOT、LLVM 方面的东西,还是需要你自己慢慢处理。
int main(int argc, char* argv[])
{
MonoAssembly* assembly;
MonoDomain* domain;
MonoMethod* method;
MonoImage* image;
int retval;
domain = mono_jit_init(argv[1]);
assembly = mono_domain_assembly_open(domain, argv[1]);
image = mono_assembly_get_image(assembly);
method = mono_image_get_entry_point_ex2(image);
mono_runtime_run_main(method, argc, argv, NULL);
retval = mono_environment_exitcode_get();
mono_jit_cleanup(domain);
return retval;
}
还有别的方法,例如:mono_runtime_invoke 函数,但本文就不在这里继续累赘这些了,有兴趣的朋友可以自己私下自己去调用来玩玩,或者等本文的后续更新~~~当然到了现在本文都没有提供 include 方面的声明,本文在下述把东西给贴上,顺带一提本人的代码是链接的 “mono” 静态库,若要改成动态链接请注意调整相应的 lib 的 link,另外你可以从官方的提供的现在连接获取到各个稳定发行版 “https://www.mono-project.com/download/stable/”
#include <mono/jit/jit.h>
#include <mono/metadata/object.h>
#include <mono/metadata/environment.h>
#include <mono/metadata/assembly.h>
#include <mono/metadata/debug-helpers.h>
#include <string.h>
#include <stdlib.h>
#pragma comment(lib, "libmono-static-sgen.lib")
#pragma comment(lib, "libmono-static-boehm.lib")
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "msvcrt.lib")
#pragma comment(lib, "winmm.lib")
#pragma comment(lib, "psapi.lib")
#pragma comment(lib, "mincore.lib")
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif
更多推荐
所有评论(0)