Skip to content

Instantly share code, notes, and snippets.

@nkoneko
Last active January 12, 2024 01:38
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nkoneko/050df446615d0f3d001af99bf372f9a4 to your computer and use it in GitHub Desktop.
Save nkoneko/050df446615d0f3d001af99bf372f9a4 to your computer and use it in GitHub Desktop.
Action<T>の関数ポインターにブレークポイントを張りたい...
Il2CppDumperをかけてみた。
// Namespace: System
public sealed class Action`1 : MulticastDelegate // TypeDefIndex: 139
{
// Methods
public void .ctor(object object, IntPtr method); // RVA: 0x101166128 Offset: 0x1166128
public virtual void Invoke(T obj); // RVA: 0x101166138 Offset: 0x1166138
public virtual IAsyncResult BeginInvoke(T obj, AsyncCallback callback, object object); // RVA: 0x101162F50 Offset: 0x1162F50
public virtual void EndInvoke(IAsyncResult result); // RVA: 0x101163010 Offset: 0x1163010
}
Actionの.ctorを逆アセンブルしてみる
LDR X8, [X2]
STR X8, [X0,#0x10]
STP X1, X2, [X0,#0x20]
RET
x0はActionインスタンスを指してx0はActionインスタンスを指していて、x1はobject, x2はIntPtrのmethodを指しているだろうから、Actionインスタンスのオフセット0x10のところに実際に呼び出されるメソッドの実体(関数ポインター)が入っていて、あとはインスタンスの先頭のアドレスにはobjectのアドレス、それから0x20離れたところにはIntPtrのdereferenceする前の値が入っているだろうと予想される。
実際にInvoke関数の呼び出しで、x0(Actionインスタンスの先頭アドレス)から0x10離れたところの関数がjumpやcall命令で呼ばれていれば、この仮説が裏付けられる。
ざっとInvokeのアセンブリを読んでみる。
SUB SP, SP, #0x70
STP X28, X27, [SP,#0x60+var_50]
STP X26, X25, [SP,#0x60+var_40]
STP X24, X23, [SP,#0x60+var_30]
STP X22, X21, [SP,#0x60+var_20]
STP X20, X19, [SP,#0x60+var_10]
STP X29, X30, [SP,#0x60+var_s0]
ADD X29, SP, #0x60
この辺りはただのFunction Prologuesなので気にしない。
MOV X19, X1
MOV X20, X0
x0がActionインスタンス、x1がobj。
STR X0, [SP,#0x60+var_58]
[SP,#0x60+var_58]に一旦Actionインスタンスのアドレスを退避しているのかな。
LDR X8, [X0,#0x68]
この#0x68というのは、親クラスのMulticastDelegateのフィールド delegates へのオフセット。Il2CppDumperでダンプした結果からMulticastDelegateを抜き出すと
// Namespace: System
[ComVisibleAttribute] // RVA: 0x10002D91C Offset: 0x2D91C
[Serializable]
public abstract class MulticastDelegate : Delegate // TypeDefIndex: 364
{
// Fields
private Delegate[] delegates; // 0x68
...snip...
Action.Invoke(obj) の続き
CBZ X8, loc_10116617C
this.delegates == nullならば、loc_10116617Cへ。という分岐ですね。多分loc_10116617Cへ飛ぶんじゃないかな。
this.delegatesが空じゃない場合は下の通りの処理を進める
LDR X24, [X8,#0x18]
CBZ X24, loc_1011664B0
delegatesから 0x18 離れたところにある値をx24に詰め込んで、NULLかどうか。NULLじゃない前提で進むと、
ADD X25, X8, #0x20 ; ' '
x25にはdelegatesのアドレス + 0x20 の値が入ってくる。
B loc_101166184
さて
先ほどのthis.delegatesがnullだった場合はここから。
loc_10116617C:
ADD X25, SP, #0x60+var_58
MOV W24, #1
すなわち、x25には先ほど退避しておいた、Actionインスタンスのアドレスを書き込んだアドレス、x24には 1 が書き込まれる。
this.delegates != nullの場合は、この2行はスキップしているが、何れにせよ次の命令へ進む。
loc_101166184:
MOV X26, #0
MOV W27, #0xFFFF
B loc_101166330
loc_101166330:
LDR X8, [X25,X26,LSL#3]
LDR X23, [X8,#0x10]
LDP X22, X21, [X8,#0x20]
LDRSH W8, [X21,#0x48]
CMN W8, #1
B.NE loc_101166350
MOV X0, X21
BL sub_100D3D714
LDR x8, [x25, x26, LSL#3]
あたりから、this.delegatesが空っぽの場合は単純にActionインスタンスのアドレスをx8にロードするだけだし、this.delegatesが空っぽじゃなかった場合には0x20離れたところ(0x00〜0x20には配列長と型の情報が入っていて配列実体は0x20からとかなんじゃないのかなUnity->Il2Cppの配列はきっと)から0x00, 0x01, 0x02って
インデックスを変えながら取り出しているだけじゃないかな多分。
で、そこから0x10離れたところ!これですわ。Actionインスタンスから0x10離れたところにあるのが関数ポインターだったはず。これがx23に書き込まれる。
おっと。同時に書き込んでいた0x0, 0x20離れたところの値もx22, x21に書き込まれているな。オブジェクトのメタ情報だろう多分x21は。で、0x48離れたところに書き込まれている値が1か否かをチェックして分岐しているように見える。なんかのbooleanのフラグかな。
よくわからんから実行時の値をデバッガで取ってみる。0x1184d7480はActionインスタンスの先頭アドレスとする。
(lldb) mem read --size 8 --format x 0x1184d7480
0x1184d7480: 0x00000001161bca70 0x0000000000000000
0x1184d7490: 0x00000001031cb650 0x0000000000000000
0x1184d74a0: 0x00000001184e2ba0 0x0000000118988d68
0x1184d74b0: 0x0000000000000000 0x0000000000000000
(lldb) dis --start-address=0x00000001031cb650
`___lldb_unnamed_symbol96496:
0x1031cb650 <+0>: stp x22, x21, [sp, #-0x30]!
0x1031cb654 <+4>: stp x20, x19, [sp, #0x10]
0x1031cb658 <+8>: stp x29, x30, [sp, #0x20]
0x1031cb65c <+12>: add x29, sp, #0x20 ; =0x20
0x1031cb660 <+16>: mov x20, x2
0x1031cb664 <+20>: mov x21, x1
0x1031cb668 <+24>: mov x19, x0
0x1031cb66c <+28>: ldr x22, [x0, #0x18]
うん。やはり0x10離れたところには関数ポインターが入っているっぽいね。じゃあ、x21に書き込まれているはずの0x00000001031cb650をhexdumpしてみる。
(lldb) mem read --size 8 --count 16 --format x 0x00000001184e2ba0
0x1184e2ba0: 0x0000000118988b78 0x0000000000000000
0x1184e2bb0: 0x0000000000000001 0x000000011850a600
0x1184e2bc0: 0x0000000000000000 0x0000000000000000
0x1184e2bd0: 0x000000010d47d8e0 0x0000000000000000
0x1184e2be0: 0x00000000ffffffff 0x0000000118a91900
0x1184e2bf0: 0x00000001184e2c60 0x0000000000000000
0x1184e2c00: 0x000000010ccf1458 0x0000000000000000
0x1184e2c10: 0x0000000116850320 0x0000000300000000
0x48離れたところの値は0x0000000118a91900になっていて、まあ0x1ではなさそう。何だろうねこれ。まあいいや。じゃあNot Equalの方の分岐へ進む。
loc_101166350:
MOV X0, X21
BL sub_100D3D670
LDRB W8, [X21,#0x4A]
CBZ W0, loc_10116636C
CMP W8, #1
B.NE loc_101166400
B loc_101166498
sub_100D3D670:
STP X29, X30, [SP,#-0x10+var_s0]!
MOV X29, SP
BL _il2cpp_method_is_instance_0
EOR W0, W0, #1
LDP X29, X30, [SP+var_s0],#0x10
RET
多分これは単純にメソッドのメタ情報を渡して、インスタンスメソッドかをみている。それから、0x4A離れたところの値は、_il2cpp_method_get_param_countという関数で参照されていることから、おそらく引数がいくつかって情報。
で、あるなら、インスタンスメソッドであって、かつ引数が1つの場合は
CBZ W0, loc_10116636C
でW0が0じゃないので分岐せず、
CMP W8, #1
B.NE loc_101166400
でw8==0x01なので、ここでも分岐しないはず。よって、loc_101166498へとジャンプ。
loc_101166498:
MOV X0, X19
MOV X1, X21
BLR X23
はい、たどり着きました。
やはり、呼び出される関数ポインター x23 にobjとメソッドのメタ情報を渡すという実装になっています。
@nevermoe
Copy link

// System.Void System.Action`1<System.Object>::Invoke(T)
extern "C" IL2CPP_METHOD_ATTR void Action_1_Invoke_mB86FC1B303E77C41ED0E94FC3592A9CF8DA571D5_gshared (Action_1_t551A279CEADCF6EEAE8FA2B1E1E757D0D15290D0 * __this, RuntimeObject * ___obj0, const RuntimeMethod* method)
{
	il2cpp_codegen_raise_execution_engine_exception_if_method_is_not_found((RuntimeMethod*)(__this->get_method_3()));
	DelegateU5BU5D_tDFCDEE2A6322F96C0FE49AF47E9ADB8C4B294E86* delegatesToInvoke = __this->get_delegates_11();
	if (delegatesToInvoke != NULL)
	{
		il2cpp_array_size_t length = delegatesToInvoke->max_length;
		for (il2cpp_array_size_t i = 0; i < length; i++)
		{
			Delegate_t* currentDelegate = (delegatesToInvoke)->GetAtUnchecked(static_cast<il2cpp_array_size_t>(i));
			Il2CppMethodPointer targetMethodPointer = currentDelegate->get_method_ptr_0();
			RuntimeMethod* targetMethod = (RuntimeMethod*)(currentDelegate->get_method_3());
			RuntimeObject* targetThis = currentDelegate->get_m_target_2();
			il2cpp_codegen_raise_execution_engine_exception_if_method_is_not_found(targetMethod);
			bool ___methodIsStatic = MethodIsStatic(targetMethod);
			int ___parameterCount = il2cpp_codegen_method_parameter_count(targetMethod);
			if (___methodIsStatic)
			{
				if (___parameterCount == 1)
				{
					// open
					typedef void (*FunctionPointerType) (RuntimeObject *, const RuntimeMethod*);
					((FunctionPointerType)targetMethodPointer)(___obj0, targetMethod);
				}
				else
				{
					// closed
					typedef void (*FunctionPointerType) (void*, RuntimeObject *, const RuntimeMethod*);
					((FunctionPointerType)targetMethodPointer)(targetThis, ___obj0, targetMethod);
				}
			}
			else if (___parameterCount != 1)
			{
				// open
				if (il2cpp_codegen_method_is_virtual(targetMethod) && !il2cpp_codegen_object_is_of_sealed_type(targetThis) && il2cpp_codegen_delegate_has_invoker((Il2CppDelegate*)__this))
				{
					if (il2cpp_codegen_method_is_generic_instance(targetMethod))
					{
						if (il2cpp_codegen_method_is_interface_method(targetMethod))
							GenericInterfaceActionInvoker0::Invoke(targetMethod, ___obj0);
						else
							GenericVirtActionInvoker0::Invoke(targetMethod, ___obj0);
					}
					else
					{
						if (il2cpp_codegen_method_is_interface_method(targetMethod))
							InterfaceActionInvoker0::Invoke(il2cpp_codegen_method_get_slot(targetMethod), il2cpp_codegen_method_get_declaring_type(targetMethod), ___obj0);
						else
							VirtActionInvoker0::Invoke(il2cpp_codegen_method_get_slot(targetMethod), ___obj0);
					}
				}
				else
				{
					typedef void (*FunctionPointerType) (RuntimeObject *, const RuntimeMethod*);
					((FunctionPointerType)targetMethodPointer)(___obj0, targetMethod);
				}
			}
			else
			{
				// closed
				if (il2cpp_codegen_method_is_virtual(targetMethod) && !il2cpp_codegen_object_is_of_sealed_type(targetThis) && il2cpp_codegen_delegate_has_invoker((Il2CppDelegate*)__this))
				{
					if (il2cpp_codegen_method_is_generic_instance(targetMethod))
					{
						if (il2cpp_codegen_method_is_interface_method(targetMethod))
							GenericInterfaceActionInvoker1< RuntimeObject * >::Invoke(targetMethod, targetThis, ___obj0);
						else
							GenericVirtActionInvoker1< RuntimeObject * >::Invoke(targetMethod, targetThis, ___obj0);
					}
					else
					{
						if (il2cpp_codegen_method_is_interface_method(targetMethod))
							InterfaceActionInvoker1< RuntimeObject * >::Invoke(il2cpp_codegen_method_get_slot(targetMethod), il2cpp_codegen_method_get_declaring_type(targetMethod), targetThis, ___obj0);
						else
							VirtActionInvoker1< RuntimeObject * >::Invoke(il2cpp_codegen_method_get_slot(targetMethod), targetThis, ___obj0);
					}
				}
				else
				{
					typedef void (*FunctionPointerType) (void*, RuntimeObject *, const RuntimeMethod*);
					((FunctionPointerType)targetMethodPointer)(targetThis, ___obj0, targetMethod);
				}
			}
		}
	}
	else
	{
		Il2CppMethodPointer targetMethodPointer = __this->get_method_ptr_0();
		RuntimeMethod* targetMethod = (RuntimeMethod*)(__this->get_method_3());
		RuntimeObject* targetThis = __this->get_m_target_2();
		il2cpp_codegen_raise_execution_engine_exception_if_method_is_not_found(targetMethod);
		bool ___methodIsStatic = MethodIsStatic(targetMethod);
		int ___parameterCount = il2cpp_codegen_method_parameter_count(targetMethod);
		if (___methodIsStatic)
		{
			if (___parameterCount == 1)
			{
				// open
				typedef void (*FunctionPointerType) (RuntimeObject *, const RuntimeMethod*);
				((FunctionPointerType)targetMethodPointer)(___obj0, targetMethod);
			}
			else
			{
				// closed
				typedef void (*FunctionPointerType) (void*, RuntimeObject *, const RuntimeMethod*);
				((FunctionPointerType)targetMethodPointer)(targetThis, ___obj0, targetMethod);
			}
		}
		else if (___parameterCount != 1)
		{
			// open
			if (il2cpp_codegen_method_is_virtual(targetMethod) && !il2cpp_codegen_object_is_of_sealed_type(targetThis) && il2cpp_codegen_delegate_has_invoker((Il2CppDelegate*)__this))
			{
				if (il2cpp_codegen_method_is_generic_instance(targetMethod))
				{
					if (il2cpp_codegen_method_is_interface_method(targetMethod))
						GenericInterfaceActionInvoker0::Invoke(targetMethod, ___obj0);
					else
						GenericVirtActionInvoker0::Invoke(targetMethod, ___obj0);
				}
				else
				{
					if (il2cpp_codegen_method_is_interface_method(targetMethod))
						InterfaceActionInvoker0::Invoke(il2cpp_codegen_method_get_slot(targetMethod), il2cpp_codegen_method_get_declaring_type(targetMethod), ___obj0);
					else
						VirtActionInvoker0::Invoke(il2cpp_codegen_method_get_slot(targetMethod), ___obj0);
				}
			}
			else
			{
				typedef void (*FunctionPointerType) (RuntimeObject *, const RuntimeMethod*);
				((FunctionPointerType)targetMethodPointer)(___obj0, targetMethod);
			}
		}
		else
		{
			// closed
			if (il2cpp_codegen_method_is_virtual(targetMethod) && !il2cpp_codegen_object_is_of_sealed_type(targetThis) && il2cpp_codegen_delegate_has_invoker((Il2CppDelegate*)__this))
			{
				if (il2cpp_codegen_method_is_generic_instance(targetMethod))
				{
					if (il2cpp_codegen_method_is_interface_method(targetMethod))
						GenericInterfaceActionInvoker1< RuntimeObject * >::Invoke(targetMethod, targetThis, ___obj0);
					else
						GenericVirtActionInvoker1< RuntimeObject * >::Invoke(targetMethod, targetThis, ___obj0);
				}
				else
				{
					if (il2cpp_codegen_method_is_interface_method(targetMethod))
						InterfaceActionInvoker1< RuntimeObject * >::Invoke(il2cpp_codegen_method_get_slot(targetMethod), il2cpp_codegen_method_get_declaring_type(targetMethod), targetThis, ___obj0);
					else
						VirtActionInvoker1< RuntimeObject * >::Invoke(il2cpp_codegen_method_get_slot(targetMethod), targetThis, ___obj0);
				}
			}
			else
			{
				typedef void (*FunctionPointerType) (void*, RuntimeObject *, const RuntimeMethod*);
				((FunctionPointerType)targetMethodPointer)(targetThis, ___obj0, targetMethod);
			}
		}
	}
}

@nevermoe
Copy link

やっと理解した。Invoke関数は三つ引数を受け取ってるけど、結局三つ目のパラメータは使ってないな。関数ポインタは事前に.ctorで登録してあった奴を使ってる。

@nevermoe
Copy link

.ctorもついでに貼ろう

// System.Void System.Action`1<System.Object>::.ctor(System.Object,System.IntPtr)
extern "C" IL2CPP_METHOD_ATTR void Action_1__ctor_mAFC7442D9D3CEC6701C3C5599F8CF12476095510_gshared (Action_1_t551A279CEADCF6EEAE8FA2B1E1E757D0D15290D0 * __this, RuntimeObject * ___object0, intptr_t ___method1, const RuntimeMethod* method)
{
  __this->set_method_ptr_0(il2cpp_codegen_get_method_pointer((RuntimeMethod*)___method1));
  __this->set_method_3(___method1);
  __this->set_m_target_2(___object0);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment