我们从Blutter恢复的部分IL中可以看到Dart调用函数的逻辑
// 0x180490: r16 = <int>
// 0x180490: ldr x16, [PP, #0x8a0] ; [pp+0x8a0] TypeArguments: <int>
// 0x180494: r30 = Instance_MethodChannel
// 0x180494: ldr lr, [PP, #0x78c0] ; [pp+0x78c0] Obj!MethodChannel@3719a1
// 0x180498: stp lr, x16, [SP, #8]
// 0x18049c: r16 = "getBatteryLevel"
// 0x18049c: ldr x16, [PP, #0x78c8] ; [pp+0x78c8] "getBatteryLevel"
// 0x1804a0: str x16, [SP]
// 0x1804a4: r4 = const [0x1, 0x2, 0x2, 0x2, null]
// 0x1804a4: ldr x4, [PP, #0x58] ; [pp+0x58] List(5) [0x1, 0x2, 0x2, 0x2, Null]
// 0x1804a8: r0 = invokeMethod()
// 0x1804a8: bl #0x1805ac ; [package:flutter/src/services/platform_channel.dart] MethodChannel::invokeMethod
这段汇编代码展示了在ARM64架构上,Dart如何准备并执行methodChannel.invokeMethod(‘getBatteryLevel’)方法调用的完整流程。
首先是加载类型参数,从对象池(PP)加载泛型类型参数到x16寄存器,这指定了invokeMethod返回值的类型
// 0x180490: r16 = <int>
// 0x180490: ldr x16, [PP, #0x8a0] ; [pp+0x8a0] TypeArguments: <int>
然后是加载第二个参数,从对象池加载MethodChannel实例到lr(x30)寄存器,这是调用方法的this指针
// 0x180494: r30 = Instance_MethodChannel
// 0x180494: ldr lr, [PP, #0x78c0] ; [pp+0x78c0] Obj!MethodChannel@3719a1
然后就是将参数放入栈中,这里同时压入两个栈,存储this和类型参数到SP+8位置
// 0x180498: stp lr, x16, [SP, #8]
然后加载"getBatteryLevel"字符串,存储方法名到栈顶
// 0x18049c: r16 = "getBatteryLevel"
// 0x18049c: ldr x16, [PP, #0x78c8] ; [pp+0x78c8] "getBatteryLevel"
// 0x1804a0: str x16, [SP]
这里遵循了Dart的ARM64调用约定:
- 第一个参数(方法名)放在栈顶 [SP]
- 第二个参数(this)放在 [SP+8]
- 第三个参数(类型参数)放在 [SP+16] (由stp指令自动处理)
然后在Dart的函数调用阶段需要加载并传入参数描述符 可以参考SDK的源码:runtime/vm/dart_entry.h
// 0x1804a4: r4 = const [0x1, 0x2, 0x2, 0x2, null]
// 0x1804a4: ldr x4, [PP, #0x58] ; [pp+0x58] List(5) [0x1, 0x2, 0x2, 0x2, Null]
这个关键数据结构描述了调用的元数据:
- 0x1 - 有1个类型参数()
- 0x2 - 总共有2个参数(方法名和this)
- 0x2 - 参数大小(总共占用空间)
- 0x2 - 位置参数数量
- null - 没有命名参数
最后调用函数
// 0x1804a8: r0 = invokeMethod()
// 0x1804a8: bl #0x1805ac ; [package:flutter/src/services/platform_channel.dart] MethodChannel::invokeMethod