C/C++ Mono 虛擬機器執行一個可執行的 .NET Assembly
本文是嵌入 Mono 虛擬機器中一片基礎的啟蒙篇幅,它影響到後期對於描述 Mono 虛擬機器引擎嵌入應用方面的內容,另外本文建議讀者至少對 .NET 是什麼有一個清晰的認識。
參考:
我們先來琢磨琢磨,嵌入 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