Mono源码学习1

起因

结束了一年多的出差,回到了公司上班,最近一直在想着博客的改版,连博客也没怎么写了,正好这两天不怎么忙,便想起看看mono的源码,至于为什么看mono源码而不是选择.Net Core源码,Mono源码是纯c的,看起来更容易一些,不需要c++的知识.其实.Net Core源码直接用VS调试还是很方便的.不过source insight对c++支持的不是很好.阅读源码使用source insight还是很方便的.

在Github看Mono已经把zlib源码加入到Mono源码,在编译源码的时候,不用手动链接zlib静态库了.

阅读源码从mini/main.c开始.

函数名以g_开头的,是eglib封装的工具函数.主要用于简化操作.如g_new0/g_free

屏蔽系统API差异

这里以mono_pagesize为例.先认识Windows的源码.
//获取系统页大小
int mono_pagesize(void)
{
	SYSTEM_INFO info;
	static int saved_pagesize = 0;	    //saved_pagesize 使用static声明
	if (saved_pagesize)
		return saved_pagesize;	    //第二次调用mono_pagesize函数,直接返回saved_pagesize
	GetSystemInfo(&info);		    //第一次调用mono_pagesize函数,调用GetSystemInfo系统函数
	saved_pagesize = info.dwPageSize;   //将获取到系统页大小赋值给saved_pagesize 
	return saved_pagesize;
}

Linux下

int
mono_pagesize (void)
{
	static int saved_pagesize = 0;

	if (saved_pagesize)
		return saved_pagesize;

	// Prefer sysconf () as it's signal safe.
#if defined (HAVE_SYSCONF) && defined (_SC_PAGESIZE)
	saved_pagesize = sysconf (_SC_PAGESIZE);
#else
	saved_pagesize = getpagesize ();
#endif

	return saved_pagesize;
}

根据宏判断是否支持sysconf系统调用.

linux 系统页大小默认为4096

其实Windows默认的页大小也是4096

mono_main 主函数

初学mono源码,mono加载过程
main.c
	int main(void)			               //Windows
	int main(int argc, char* argv[])               //Linux或Unix及Mac
		int mono_main_with_options(argc, argv)
driver.c
			int mono_main(argc, argv)      //主函数
coree.c
			        //加载mscoree.dll,Windows独有的, c# exe(Windows下)执行的时候通过ntdll->mscoree.dll->mscorlib.dll
				void mono_load_coree(const char* exe_file_name)  
mono-config.c	
				//设置lib和etc,具体可以看 https://www.qiufengblog.com/articles/mono-config.html
				void mono_config_parse (const char *filename)    
mini-runtime.c                                //mono CLR运行时初始化,返回MonoDomain
				MonoDomain* mini_init(const char* filename, const char* runtime_version) 
dirver.c
					static void main_thread_handler (gpointer user_data)
domain.c
						//根据domain,加载程序集(并没有执行)
						MonoAssembly* mono_domain_assembly_open(MonoDomain* domain, const char* name)
driver.c
						//根据domain,执行程序集
						int mono_jit_exec (MonoDomain *domain, MonoAssembly *assembly, int argc, char *argv[])
mini-runtime.c
						//清理domain,释放资源
						void mini_cleanup(MonoDomain* domain)

看简化之后的代码:

//函数原型
MonoDomain* mini_init(const char* filename, const char* runtime_version);

//mono 主函数
//函数去除很多代码
int mono_main(int argc, char* argv[])
{
	//
	//去除解析参数处理,后面用到单独说明
	//
	MonoThreadArgs main_args;
	int i;
	int mixed_mode = FALSE;

	if (mixed_mode)
	{
		mono_load_coree(argv[i]);
	}
	mono_config_parse(config_file);

	mono_set_defaults(mini_verbose_level, opt);

	MonoDomain* domain = mini_init(argv[i], forced_version);

	main_args.domain = domain;
	main_args.file = aname;
	main_args.argc = argc - i;
	main_args.argv = argv + i;
	main_args.opts = opt;
	main_args.aot_options = aot_options;

	main_thread_handler(&main_args);
	mono_thread_manage();

	mini_cleanup(domain);

	i = mono_environment_exitcode_get();

	return i;
}

MonoDomain* mini_init(const char* filename, const char* runtime_version)
{
	//1.初始化很多东西
	//2.注册回调函数
	MonoDomain* domain;

	//通过mono program.exe 没有指定--runtime=v4.0.30319 ,runtime_version不为真的
	if (runtime_version)		  
	{
		domain = mono_init_version(filename, runtime_version);
	}
	else
	{
		domain = mono_init_from_assembly(filename, filename);
	}

	//还要处理很多东西
	//xxx        //xxx

	return domain;
}

在fixed模式下,mono如何加载mscoree.dll

基于mono_load_coree函数源码,改动而来,主要是动手练习一下.也顺便一些系统函数的调用.
typedef unsigned __int16 gunichar;

//1. 根据GetSystemDirectory系统函数,获取系统目录
//2. 将获取到系统目录和mscoree.dll进行拼接
//3. 通过LoadLibrary加载到内存
HMODULE load_coree()
{
	UINT len = GetSystemDirectory(NULL, 0);
	printf("%d\n", len);

	printf("unsigned __int16 size:%d\n", sizeof(gunichar));
	gunichar* dir = calloc(1, (len + 12) * sizeof(gunichar));

	GetSystemDirectory(dir, len);
	//打印系统路径
	printf("system dir:%ls\n", dir);

	if (dir[len - 1] != L'\\')
	{
		dir[len - 1] = L'\\';
	}

	//拼接dll路径  
	memcpy(&dir[len], L"mscoree.dll", 12 * sizeof(gunichar));

	printf("dir :%ls\n", dir);

	//LoadLibrary (将dll加载内存中,将文件的起始位置返回) 
	//LoadLibrary和FreeLibrary 配对使用,
	HMODULE module = LoadLibraryW(dir);

	return module;
}

//验证该句柄是否位pe文件的起始位置
int validate_pe(HMODULE hModule)
{
	PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)hModule;
	DWORD magic = dos_header->e_magic;
	char* a = (char*)&magic;
	printf("%s\n", a);
	if (magic == IMAGE_DOS_SIGNATURE)
	{
		printf("pe dos header Signature:MZ\n");
	}

	PIMAGE_NT_HEADERS nt_header = (PIMAGE_NT_HEADERS)((char*)dos_header + dos_header->e_lfanew);
	if (nt_header->Signature == IMAGE_NT_SIGNATURE)
	{
		printf("pe dos header Signature:PE\n");
	}

	return 0;
}

要不是看了源码,怎么也没想到LoadLibrary返回的HMODULE,竟然是文件在内存中的起始地址

秋风 2019-12-18