【逆向工程】生成能够用dnSpy调试的mono-2.0-bdwgc.dll(二)

接上文【逆向工程】生成能够用dnSpy调试的mono-2.0-bdwgc.dll(一)

umpacther在src\umpatcher\umpatcher\V40\SourceCodePatcher.cs中,对拷贝后的代码进行修改,总共修改了7个文件,10处代码

		public void Patch() {
			Patch_mono_metadata_icall_c();
			Patch_mono_mini_debugger_agent_c();
			Patch_mono_mini_mini_runtime_c();
			Add_mono_mini_dnSpy_c();
			Patch_masm_fixed_props();
			Patch_bdwgc_gc_atomic_ops_h();
			Patch_bdwgc_gcconfig_h();
		}

在此就不一一分析了,我们着重看一下导致crash的修改。将每处修改回滚之后发现只要回滚Patch_mono_mini_mini_runtime_c 函数就可以正常打开游戏,而这个函数的作用就是在mini-runtime.c中引入了dnSpy_debugger_init ()

		void Patch_mono_mini_mini_runtime_c() {
			var filename = Path.Combine(solutionOptions.UnityVersionDir, "mono", "mini", "mini-runtime.c");
			var textFilePatcher = new TextFilePatcher(filename);
			int index = textFilePatcher.GetIndexesOfLine(line => line.Text.Contains("CHECKED_MONO_INIT ();")).Single();
			textFilePatcher.Insert(++index, string.Empty);
			textFilePatcher.Insert(++index, "\tdnSpy_debugger_init ();");
			textFilePatcher.Write();
		}

也就是说真正的crash的地方在dnSpy_debugger_init中,因此检查dnSpy.c中的dnSpy_debugger_init 函数:

// 0 = .NET 2.0-3.5 Unity
// 1 = .NET 4.0+ Unity
#ifndef DNUNITYRT
#define DNUNITYRT 0
#endif
dnSpy_debugger_init ()
{
	gboolean fixDefer = FALSE;
	char* envVal = getenv (ENV_VAR_NAME);

#if DNUNITYRT != 0
	if (!envVal) {
		envVal = getenv (ENV_VAR_NAME_V0);
		fixDefer = TRUE;
	}
#endif

	if (!envVal) {
		envVal = "--debugger-agent=transport=dt_socket,server=y,address=127.0.0.1:55555,defer=y";
		fixDefer = TRUE;
	}

#if DNUNITYRT != 0

#define defer_y		"defer=y"
#define suspend_n	"suspend=n"

	const char* s = strstr (envVal, defer_y);
	if (s && fixDefer) {
		int envVal_len = strlen (envVal);
		int defer_y_len = strlen (defer_y);
		int suspend_n_len = strlen (suspend_n);
		int newStr_len = envVal_len - defer_y_len + suspend_n_len;
		char* newStr = (char*)malloc (newStr_len + 1);
		memcpy (newStr, envVal, s - envVal);
		memcpy (newStr + (s - envVal), suspend_n, suspend_n_len);
		memcpy (newStr + (s - envVal) + suspend_n_len, s + defer_y_len, envVal_len - (s + defer_y_len - envVal));
		newStr [newStr_len] = 0;
		envVal = newStr;
	}
#endif

	char* argv[] = { envVal };
	mono_jit_parse_options (1, (char**)argv);
	mono_debug_init (MONO_DEBUG_FORMAT_MONO);
}

经过一番排查后,发现最后一行 mono_debug_init (MONO_DEBUG_FORMAT_MONO); 是导致crash的罪魁祸首。看一下mono_debug_init函数:

mono_debug_init (MonoDebugFormat format)
{
	g_assert (!mono_debug_initialized);
	if (format == MONO_DEBUG_FORMAT_DEBUGGER)
		g_error ("The mdb debugger is no longer supported.");

	mono_debug_initialized = TRUE;
	mono_debug_format = format;
	...
}

注意这个 g_assert (!mono_debug_initialized);
通过输出log调试可以发现在调用dnSpy_debugger_init之前,mono_profiler_enable_coverage 已经调用了mono_debug_init ,因此第二次调用会导致assert。

mono_profiler_enable_coverage (void)
{
	if (mono_profiler_state.startup_done)
		return FALSE;

	mono_os_mutex_init (&mono_profiler_state.coverage_mutex);

	if (!mono_debug_enabled ())
		mono_debug_init (MONO_DEBUG_FORMAT_MONO);

	return mono_profiler_state.code_coverage = TRUE;
}

而mono_profiler_enable_coverage 的调用来源又是哪,在此就不深究了
总之注释掉dnSpy_debugger_init 中的mono_debug_init 就可以避免crash了,更稳妥的改法是将mono_debug_init中的 g_assert (!mono_debug_initialized); 改成:

mono_debug_init (MonoDebugFormat format)
{
	//g_assert (!mono_debug_initialized);
	if (mono_debug_initialized) return;
	
	if (format == MONO_DEBUG_FORMAT_DEBUGGER)
		g_error ("The mdb debugger is no longer supported.");

	mono_debug_initialized = TRUE;
	mono_debug_format = format;
	...
}

为了之后的通用性,还可以修改src\umpatcher\umpatcher\V40\SourceCodePatcher.cs

		public void Patch() {
			...
			Patch_mono_metadata_mono_debug_c();
		}
		
		void Patch_mono_metadata_mono_debug_c() {
			if (solutionOptions.UnityVersion.CompareTo(new UnityVersion(2019, 1, 0, "-mbe")) < 0)
				return;
			var filename = Path.Combine(solutionOptions.UnityVersionDir, "mono", "metadata", "mono-debug.c");
			var textFilePatcher = new TextFilePatcher(filename);
			var lines = textFilePatcher.Lines;
			int index = textFilePatcher.GetIndexOfLine("\tg_assert (!mono_debug_initialized);");
			lines[index] = lines[index].Replace("\tif (mono_debug_initialized) return;");
			textFilePatcher.Write();
		}

这样对于2020版本unity应该都可以顺利生成mono-2.0-bdwgc.dll了。将生成的dll替换掉原本的文件,然后就可以成功在dnSpy中进行断点调试了。
在这里插入图片描述

此外,dnSpy_debugger_init也还有一定的优化空间
dnSpy_debugger_init中envVal分为两类,分别对应.NET 2.0-3.5版本和.NET 4.0+版本
因此可以以一种更清晰的方式对代码进行整合:

#define ENV_VAR_NAME_V0 "DNSPY_UNITY_DBG"
#define ENV_VAR_NAME_V1 "DNSPY_UNITY_DBG2"
dnSpy_debugger_init ()
{
#if DNUNITYRT == 0
	char* envVal = getenv(ENV_VAR_NAME_V0);

	if (!envVal) {
		envVal = "--debugger-agent=transport=dt_socket,server=y,address=127.0.0.1:55555,defer=y";
	}
#else
	char* envVal = getenv(ENV_VAR_NAME_V1);

//	if (!envVal) {
//		envVal = getenv(ENV_VAR_NAME_V0);
//
//#define defer_y		"defer=y"
//#define suspend_n	"suspend=n"
//
//		const char* s = strstr(envVal, defer_y);
//		if (s) {
//			int defer_pos = s - envVal;
//			int envVal_len = strlen(envVal);
//			int defer_y_len = strlen(defer_y);
//			int suspend_n_len = strlen(suspend_n);
//			int newStr_len = envVal_len - defer_y_len + suspend_n_len;
//			char* newStr = (char*)malloc(newStr_len + 1);
//			memcpy(newStr, envVal, defer_pos);
//			memcpy(newStr + defer_pos, suspend_n, suspend_n_len);
//			memcpy(newStr + defer_pos + suspend_n_len, s + defer_y_len, envVal_len - defer_pos - defer_y_len);
//			newStr[newStr_len] = 0;
//			envVal = newStr;
//		}
//	}

	if (!envVal) {
		envVal = "--debugger-agent=transport=dt_socket,server=y,address=127.0.0.1:55555,suspend=n";
	}
#endif
	char* argv[] = { envVal };
	mono_jit_parse_options (1, (char**)argv);
	mono_debug_init (MONO_DEBUG_FORMAT_MONO);
}

上面注释掉的一大段是为了适配老版本的dnSpy,在原本的代码中作用是将defer=y替换成suspend=n,如果使用最新的dnSpy版本可以直接无视

dnSpy对Unity有两种调试模式,一种是dnSpy启动
dnSpyz中启动
另一种是外部启动,然后dnSpy附加
dnSpy附加
如果是dnSpy启动的话,envVal的值为:
–debugger-agent=transport=dt_socket,server=y,address=127.0.0.1:36663,suspend=n
如果是dnSpy附加的话,envVal的值为:
–debugger-agent=transport=dt_socket,server=y,address=127.0.0.1:55555,suspend=n
可以看出唯一的区别就是地址端口,dnSpy启动是随机分配的,而附加的话是固定的

本文链接:
dnSpy-Unity-mono官方仓库
mono官方仓库
我的个人仓库

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值