Last active
January 12, 2024 01:38
-
-
Save nkoneko/050df446615d0f3d001af99bf372f9a4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
commented
Oct 25, 2019
やっと理解した。Invoke関数は三つ引数を受け取ってるけど、結局三つ目のパラメータは使ってないな。関数ポインタは事前に.ctorで登録してあった奴を使ってる。
.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