追踪Android方法调用1
字数 3634 2025-08-29 22:41:24
Android方法调用追踪技术详解
1. 前言
本文详细讲解如何在AOSP源码中进行插桩,以追踪Java函数和Native函数的调用关系。基于Android 10.0源码,结合《深入理解Android Java虚拟机ART》的分析,重点剖析Native函数注册流程和Native函数调用Java函数的流程。
2. Java层调用Native函数流程
2.1 Native函数注册流程
类加载与函数注册
- 通过
ClassLinker::LoadClass和ClassLinker::LinkCode在解释模式下注册函数 - 包含静态方法注册和Native方法注册
- Native方法通过
GetQuickGenericJniStub()注册 - 实际调用的是平台相关的汇编代码:
art_quick_generic_jni_trampoline
ARM64架构下的执行流程
artQuickGenericJniTrampoline计算Native函数所需栈空间- 准备Native函数参数
- 将native函数地址保存到x0寄存器
- 通过
blr xIP0执行
JNI方法执行前后处理
- 设置
cookie=JniMethodStart并存储到栈空间 - 每个Native函数执行前都会执行
JniMethodStart函数 - 执行结束后会执行
JniMethodEnd函数
Native函数查找与注册
- 通过
void const* nativeCode = called->GetEntryPointFromJni()获取nativeCode地址 - 如果地址为
art_jni_dlsym_lookup_stub,说明是第一次调用,尚未注册 - 通过
artFindNativeMethod进行注册 FindCodeForNativeMethod函数使用dlsym对每个加载的so进行查找- 最终通过
method->RegisterNative(native_code)完成注册
2.2 Native函数注册总结
已编译Native方法的情况
- dex2oat编译Java native方法生成机器码
- ArtMethod对象的机器码入口地址指向生成的机器码
- 生成的机器码会跳转到ArtMethod对象的JNI机器码入口地址
- 如果JNI方法未注册,JNI机器码入口地址是
art_jni_dlsym_lookup_stub - 已注册则JNI机器码入口地址指向Native层对应函数
未编译Native方法的情况
- ArtMethod对象的机器码入口地址为跳转代码
art_quick_generic_jni_trampoline - 未注册时JNI机器码入口地址为
art_jni_dlsym_lookup_stub - 已注册则指向Native层对应函数
art_quick_generic_jni_trampoline是native函数执行前必须经历的机器码,功能类似于dex2oat过程中为native函数准备参数。
2.3 调用Native函数
调用场景
- native调用native
- Java调用native
Java调用native流程
假设:
- 发起调用的Java函数为A
- 被调用的native函数为B
- A未被oat编译,走解释模式
调用链:
ArtMethod::Invokeart::interpreter::EnterInterpreterFromInvokeExecute(self, accessor, *shadow_frame, JValue(), stay_in_interpreter)ExecuteMterpImpl或ExecuteSwitchImpl<false, false>MterpInvokexxxx或ExecuteSwitchImplCpp的switch模式Invoke-xxxxDoInvokeDocallDoCallCommon
关键函数分析
DoInvoke函数
shadow_frame保存A的ArtMethod对象和B的参数inst和inst_data代表B的smali指令use_fast_path = true时通过ExecuteXXXImpl执行B方法use_fast_path = false时调用DoCall和DoCallCommon
DoCallCommon函数
called_method代表B方法的ArtMethod对象shadow_frame属于A方法arg代表B方法的参数- 通过拷贝方式创建B方法的
shadow_frame - 通过
PerformCall进行调用
PerformCall函数
- 解释模式执行B方法:调用
ArtInterpreterToInterpreterBridge - B是Native函数或被编译过:通过
ArtInterpreterToCompiledCodeBridge执行
ArtInterpreterToInterpreterBridge
- 回到
Execute
ArtInterpreterToCompiledCodeBridge
- 回到
ArtMethod::Invoke - 通过
art_quick_invoke_stub和art_quick_invoke_static_stub执行
3. Native层调用Java层函数流程
3.1 JNI调用Java方法示例
// 调用static int方法
jint result = env->CallStaticIntMethod(clazz, mid, args...);
// 调用int方法
jint result = env->CallIntMethod(obj, mid, args...);
3.2 调用流程分析
Call开头的函数最终调用InvokeWithArgArray- 以
CallIntMethod为例:- 调用
InvokeVirtualOrInterfaceWithVarArgs - 获取Java对象:
ObjPtr<mirror::Object> receiver = soa.Decode<mirror::Object>(obj) - 获取ArtMethod指针:
jni::DecodeArtMethod(mid) - 获取函数短签名
shorty和参数arag_array - 调用
InvokeWithArgArray(soa, method, &arg_array, &result, shorty)
- 调用
3.3 InvokeWithArgArray执行路径
- 解释模式:通过
art::interpreter::EnterInterpreterFromInvoke执行 - quick模式:通过
art_quick_invoke_stub和art_quick_invoke_static_stub执行
3.4 EnterInterpreterFromInvoke细节
- 调用
Execute(self, accessor, *shadow_frame, JValue(), stay_in_interpreter) - 解释模式下
stay_in_interpreter = true
3.5 Execute函数关键逻辑
- 需要访问权限检测
AccessChecks()时:- 即使指定解释器为
kMterpImplKind - 也通过
ExecuteSwitchImpl执行dex指令
- 即使指定解释器为
- 不需要权限检测且
transaction_active = false时:- 使用
ExecuteMterpImpl执行dex指令
- 使用
3.6 执行模式说明
transaction_active与dex2oat编译逻辑相关,完整虚拟机运行时返回falseLIKELY(method->SkipAccessChecks())表明大概率跳过访问权限检测- Android10默认解释模式走
ExecuteMterpImpl
3.7 执行模式切换场景
- 未编译类中的Java函数A调用Native函数:
- 从解释模式切换到quick模式
- Java函数A调用已被JIT编译的Java函数B:
- 从解释模式切换到quick模式
4. 关键知识点总结
-
Native函数注册:
- 首次调用时通过
dlsym查找并注册 - 后续调用直接跳转到已注册地址
- 注册过程涉及
art_jni_dlsym_lookup_stub和artQuickGenericJniTrampoline
- 首次调用时通过
-
Java调用Native:
- 通过解释器或编译代码桥接
- 涉及
DoInvoke和PerformCall等关键函数 - 执行路径取决于方法是否被编译
-
Native调用Java:
- 通过JNI接口函数发起
- 最终进入解释器或编译代码执行
- 执行模式可动态切换
-
执行模式:
- 解释模式:
ExecuteMterpImpl或ExecuteSwitchImpl - Quick模式:直接执行编译后的机器码
- 模式间可动态切换
- 解释模式:
5. 参考
- 《深入理解Android Java虚拟机ART》