技术社区
安全培训
技术社群
积分商城
先知平台
漏洞库
历史记录
清空历史记录
相关的动态
相关的文章
相关的用户
相关的圈子
相关的话题
注册
登录
追踪Android方法调用1
youncyb11
发表于 湖北
移动安全
902浏览 · 2025-05-28 12:10
返回文档
1. 前言
本文是为了学习如何在 AOSP 源码中插桩,以便 Trace Java 函数和 Native 函数的调用关系。以 Android10.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
。
进入
art_quick_generic_jni_trampoline
,这里我们以 arm64 为例。如下所示,通过
artQuickGenericJniTrampoline
计算 Native 函数所需要的栈空间,准备 Native 函数的参数,然后将 native 函数的地址保存到 x0 寄存器,再通过
blr xIP0
执行。
继续跟进
artQuickGenericJniTrampoline
函数,如下所示,其会设置
cookie=JniMethodStart
,然后将其存储到栈空间上,所以每个 Native 函数执行前都会执行
JniMethodStart
函数。相应的结束后,也会执行一个名为
JniMethodEnd
函数。
然后通过
void const* nativeCode = called->GetEntryPointFromJni();
获取
nativeCode
地址,如果其地址为
art_jni_dlsym_lookup_stub
,说明此时 Native 函数是第一次调用,还没有被注册过。然后会通过
artFindNativeMethod
进行注册。
继续跟进
artFindNativeMethod
,
其通过
FindCodeForNativeMethod
函数,使用
dlsym
对每个加载的 so 进行查找,
最终通过
method->RegisterNative(native_code)
进行注册。
所以,当一个 native 函数被调用过一次后,则不会再发生上述查找过程,即不会进入
art_jni_dlsym_lookup_stub
,而是直接进入
ArtMethod
对象的机器码然后再跳转到 JNI 机器码的入口点。
2.2 Native 函数注册总结
根据《深入理解 Android Java 虚拟机 ART》可知:
●
dex2oat 编译这个 Java native 方法后将会生成一段 机器码。
ArtMethod
对象的机器码入口地址会指向这段 生成的机器码。这段机器码本身会跳转到这个
ArtMethod
对象的 JNI 机器码入口地址。如果这个 JNI 方 法没有注册过(即这个 native 方法还未和 Native 层对应 的函数相关联), 这个 JNI 机器码入口地址是
art_jni_dlsym_lookup_stub
。否则, JNI 机器码入口地址 指向 Native 层对应的函数。
●
如果 dex2oat 没有编译过这个 Java native 方法, 则
ArtMethod
对象的机器码入口地址为跳转代码
art_quick_generic_jni_trampoline
。同样, 如果这个 JNI 方 法没有注册过, 则 JNI 机器码入口地址为跳转代码
art_jni_dlsym_lookup_stub
。否则, JNI 机器码入口地址 指向 Native 层对应的函数。
art_quick_generic_jni_trampoline
和 dex2oat 过程中为 native 函数准备参数类似,是一段 native 函数执行前必须经历的机器码。
注:图来自《深入理解 Android Java 虚拟机 ART》
2.3 调用 Native 函数
调用 Native 函数,有两种情况:
●
native 调用 native
●
Java 调用 native
Java 调用 native,我们称发起调用的 Java 函数为 A,被调用的 native 函数为 B。首先 A 会进入
ArtMethod::Invoke
,如以下代码所示。
(这里我们假设 A 没有被 oat 编译,走的仍然是解释模式)A 会进入
art::interpreter::EnterInterpreterFromInvoke
,然后经历:
●
Execute(self, accessor, *shadow_frame, JValue(), stay_in_interpreter)
●
ExecuteMterpImpl(self, accessor.Insns(), &shadow_frame, &result_register)
或者
ExecuteSwitchImpl<false, false>(self, accessor, shadow_frame, result_register, false)
●
MterpInvokexxxx
或者
ExecuteSwitchImplCpp 的switch模式:Invoke-xxxx
●
DoInvoke
●
Docall
●
DoCallCommon
继续跟进
DoInvoke
,其代码如下,其中参数的
shadow_frame
保存了 A 的
ArtMethod
对象 和
B
的参数,
inst
和
inst_data
代表 B 的 smali 指令。
当
use_fast_path = true
,则继续通过
ExecuteXXXImpl
执行 B 方法,否则调用
DoCall
,
Docall
调用
DoCallCommon
。所以,当我们对 Docall 以后的函数进行 hook 时,需要确保
use_fast_path = false
。
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 层函数流程
以下是两个 JNI 调用 Java 层方法的例子,其中一个是调用返回值为
static int
,另一个是调用返回值为
int
。这些 Call 开头的函数最终都会调用
InvokeWithArgArray
函数。
以 CallIntMethod 为例,其会调用
InvokeVirtualOrInterfaceWithVarArgs
函数。
如下所示,该函数首先通过
ObjPtr<mirror::Object> receiver = soa.Decode<mirror::Object>(obj)
获取 Java 对象。
然后通过
jni::DecodeArtMethod(mid)
获取 ArtMethod 指针,从以下代码可知,jmethodID 对象其实就是 ArtMethod 对象。
然后获取了函数的短签名
shorty
和参数
arag_array
,最后调用
InvokeWithArgArray(soa, method, &arg_array, &result, shorty);
。
InvokeWithArgArray
会调用
ArtMethod::Invoke
,如下所示,在解释模式下,通过
art::interpreter::EnterInterpreterFromInvoke
执行代码;在 quick 模式下,通过
art_quick_invoke_stub
和
art_quick_invoke_static_stub
执行代码。
继续跟进
EnterInterpreterFromInvoke
,该函数会调用
Execute(self, accessor, *shadow_frame, JValue(), stay_in_interpreter);
,需要注意的是,当处于解释模式时,
stay_in_interpreter = true
。
继续进入
Execute
函数,重要代码如下所示,当需要访问权限检测
AccessChecks()
,即使指定了解释器为
kMterpImplKind
,也是通过
ExecuteSwitchImpl
执行 dex 指令。如果不需要权限检测且
transaction_active = false
,则使用
ExecuteMterpImpl
执行 dex 指令。
根据《深入理解 Android Java 虚拟机 ART》,
transaction_active
与 dex2oat 编译逻辑有关,在完整的虚拟机运行时返回 false。
LIKELY(method->SkipAccessChecks())
也表明大概率是跳过访问权限检测。结合以下 aosp 源码,默认情况下,Android10 的解释模式走
ExecuteMterpImpl
。
当然,ART 运行了多种代码执行的模式,例如:假设一个类还没有被编译为 oat 文件,其中一个 Java 函数 A 调用了 Native 函数,则会由解释模式切换到 quick 模式。假设该 Java 函数调用的另一个 Java 函数 B,该函数已经被 JIT 编译了,则也会从解释模式切换到 quick 模式。
4. 参考
1
《深入理解 Android Java 虚拟机 ART》
1
人收藏
1
人喜欢
转载
分享
0
条评论
某人
表情
可输入
255
字
评论
发布投稿
热门文章
1
从零掌握java内存马大全(基于LearnJavaMemshellFromZero复现重组)
2
突破网络限制,Merlin Agent助你轻松搭建跳板网络!
3
从白帽角度浅谈SRC业务威胁情报挖掘与实战
4
基于规则的流量加解密工具-CloudX
5
从0到1大模型MCP自动化漏洞挖掘实践
近期热点
一周
月份
季度
1
从零掌握java内存马大全(基于LearnJavaMemshellFromZero复现重组)
2
突破网络限制,Merlin Agent助你轻松搭建跳板网络!
3
从白帽角度浅谈SRC业务威胁情报挖掘与实战
4
基于规则的流量加解密工具-CloudX
5
从0到1大模型MCP自动化漏洞挖掘实践
暂无相关信息
暂无相关信息
优秀作者
1
T0daySeeker
贡献值:38700
2
一天
贡献值:24800
3
Yale
贡献值:21000
4
1674701160110592
贡献值:18000
5
1174735059082055
贡献值:16000
6
Loora1N
贡献值:13000
7
bkbqwq
贡献值:12800
8
手术刀
贡献值:11000
9
lufei
贡献值:11000
10
xsran
贡献值:10600
目录
1. 前言
2. Java 层调用 Native 函数流程
2.1 Native 函数注册流程
2.2 Native 函数注册总结
2.3 调用 Native 函数
3. Native 层调用 Java 层函数流程
4. 参考
转载
标题
作者:
你好
http://www.a.com/asdsabdas
文章
转载
自
复制到剪贴板