本文是嵌入 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

 

 

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐