一文技术解析ART虚拟机method tracing

news2024/10/6 12:21:01

一、method tracing介绍

概述

这个是谷歌提供的对java的函数级trace工具,和systrace只支持打点不同,method tracing能支持到函数,看到具体的函数执行时间,准确的分析出来执行的时间短板。

 

1.生成trace的方式

sampling方式:

sampling方式采用sample任务,定期抓取各个线程的调用栈,采集精度和采集的频次正相关,同时由于java stack采集的时候需要做suspend,因此还是有一部分的效率损失。

我们可以看到,原生单次采集使用的是suspendall,而不是对threadlist上的线程逐个做getStackTrace,因此效率损失会比较严重。

trace方式:

通过在执行流程插入enter-exit来观测:

相比于sample 方式,trace可以准确的获取到每个函数的进入和退出时间,精度可以非常高。

由于art虚拟机执行特点,这个方案相较于sample方式复杂度要高不少,下文会着重介绍trace方式的实现原理

2.trace启动流程

我们从trace方式的启动入口开始看起

几个关键的流程分别是

1.停用掉JIT GC,这个是防止stub方式替换之后,因为JIT GC引起的重新指定执行方式,释放JIT code和entry之间存在竞争。

2.进行suspend all,这是因为后续真正开启trace的时候,会对所有的函数入口做重新指定,必然要对整个java世界进行停顿,保证安全性。

3.注册listener

然后进入EnableMethodTracing,真正发起tracing的核心流程。

根据是否要回切解释执行,有两种不同的处理方式。

具体内部流程有两个关键的处理:

1.构造一个InstallStubsClassVisitor,这个的作用是遍历所有类,然后对每个类做执行方法入口的重定向,也就是stub回填。

2.对各个线程的当前栈做一下处理,主要是植入exit frame。为什么exit point要单独处理,我们后文详细介绍,这个地方谷歌采用了一个非常trick的方式。

接下来我们继续看InstallStubsClassVisitor遍历class替换入口的处理:

真正的核心处理流程其实是下述:

如果是解释执行方式,则把入口都换成GetQuickToInterpreterBridge

如果是stub方式,则换成了GetQuickInstrumentationEntryPoint

 资料直通车:Linux内核源码技术学习路线+视频教程内核源码

学习直通车:Linux内核源码内存调优文件系统进程管理设备驱动/网络协议栈

3.trace采集的分类

从前面的代码流程中,我们能发现,分成了两个类型。

采集的方式分类

interpretor only:这是最简单粗暴的方式,直接强制整个系统回退到解释执行。

stubs方式:这个方式是希望提升tracing开启之后的性能表现,因此在支持解释执行的基础上,对JIT和AOT的函数,也做了特殊处理进行支持,而不需要强制回退到解释执行。相比纯解释执行,这部分的技术细节更丰富,使用了一些“奇技淫巧”,本文后续着重介绍stub对JIT和AOT支持的方式。

trace执行主要是在函数进出的地方植入enter-exit对来实现对函数执行流程的打点。

因为要在一个java 方法的入口和出口植入事件的记录,所以trace的实现就和虚拟机的执行方式强相关,我们先简单介绍下虚拟机的几种执行方式。

虚拟机的执行方式

解释执行:解释执行ART能够全程介入java函数的执行,这就包括了函数的入栈和出栈,因此设置观测点非常容易,直接在虚拟机执行流程中增加enter/exit埋点即可。

JIT:经过JIT编译的dex code其实target已经是asm了,这个时候的java函数调用和arm64的native函数是非常类似的。

AOT:同JIT,区别在AOT是提前构建而JIT是运行时构建的。

我们看到启动阶段的实现,是直接插入了enter,那真正的函数入口是怎么路由处理的,这里面其实由于虚拟机设计的特殊性,直接插入wrapper有一些问题,具体的下文先补充一些虚拟机的相关知识,然后结合这些背景知识慢慢道来。

二、背景补充

要知道enter和exit的具体植入和运行原理,我们先补充一点art虚拟机的知识。

1.java函数入口

每个java方法,在虚拟机层面都维持着一个ArtMethod数据结构,每次调用一个方法,实际上是通过ArtMethod找到真正的入口,然后进行调用的。

java动态性的方式也是通过:

object->class->art method ->entrypoint来实现的

我们每次对一个对象call function,实际上就是找到对象的类型,类型里面回填了真正的artmethod,然后查找到正确的入口。

这个布局我们在看替换stub的整体流程的时候就发现了,替换stub就是沿着遍历class-遍历method的方式来完成的执行入口重定向。

在只有一个入口可以插入的情况下,我们很容易想到做一个wrapper,在wrapper中调用art_method同时完成跟踪:

图示中的stack frame 1 2 3就是对应了我们栈上的栈帧,可以看到如果要使用wrapper方式,会在caller和真正的执行函数之间引入一个新的wrapper栈帧,我们结合下面一个点,就会发现问题。

2.walkstack

在anr,抛出异常的时候,都会对java调用栈进行遍历,此种遍历的逻辑主要在walkstack中完成的,这个如果加入了wrapper,会导致穿透的情况变得复杂如下图:

这种栈结构要兼容起来就非常的痛苦,在已有的JNI-解释,JNI-quick,quik-quik,quik-解释之上每种都要考虑栈内有wrapper的场景。

总结

通过上述的虚拟机的特征有如下两个问题:

1.art_method的入口只有一个挂载点,JIT和AOT处理后的java函数调用方式也并不能提供exit事件的记录时机。

2.最好不要导致stack结构发生变化,否则在进行栈遍历的时候会带来非常大的兼容负担。

1和2看似是矛盾的,因为常规的手段,只有一个函数入口的话,需要使用wrapper,但是如果使用wrapper函数,栈结构就会发生改变。这个矛盾android使用了一个非常巧妙的方法解决,我们下文就对stub的解决方法做个详细的介绍。

三、stub技术原理探究

因为jit和odex执行的对象实际上都是汇编,我们在汇编中调用一个函数,实际上只能insert一个entrypoint,那出栈如何实现呢?

此处其实就是使用了arm64的calling conversion偷鸡,我们先看下替换的函数art_quick_instrumentation_entry,这个函数是纯汇编写的,我们看下汇编的核心处理:

汇编中使用bl指令调用了artInstrumentationMethodEntryFromCode(BL指令在函数结束后,ret会回到此处,而BR则是直接基于当前的contexts做跳转,ret后就回到caller了),在artInstrumentationMethodEntryFromCode中主要做了三个事情

1.抓取并且查询到了真实java函数的入口地址

2.记录enter事件

3.记录返回地址的PC(LR寄存器)

artInstrumentationMethodEntryFromCode通过x0把真正java方法的入口返回,然后art_quick_instrumentation_entry做了如下两个事情:

1.把x30设置为art_quick_instrumentation_exit的入口地址(adr x30, 0x21a6a0)

2.通过BR跳转到获取的java方法入口( br x16)

这样,在真正的被调函数完成之后ret,就会定向到exit的汇编上下文中:

在exit函数里面

1.记录了出栈事件

2.还原了caller PC

通过改写栈上位置(str x0, [sp, #504]),然后restore的时候( ldp x29, x30, [sp, #496]),就自然读到目标lr了,同时这样不会有寄存器污染的问题

还原lr之后,直接使用br指令跳转到caller原始的位置。

以上就是android利用arm callingconversion实现的exit植入。

总结

如下图所示,android通过篡改调用前的lr,结合BL和BR指令的不同ret方式,完成了单入口,在破坏栈结构的情况下,记录了enter和exit事件对。

四、安卓最新的演进

1.演进概述

因为复杂度和对jit的冲突,导致了不太好

目前谷歌在最新的安卓版本做出了重大的更新:

1.关闭了对odex的支持

2.在jit code生成的时候,如果开启了tracing,会生成出带有enter和exit的code,直接在code gen层面支持。

3.对于stub方式,不做全量的替换,使能trace的时候整个系统回退到解释执行,然后清理jit cache,新的jit函数会直接生成带有enter和exit的code

2.谷歌最新变更相关合入:

1.jit code中直接生成enter/exit hook调用

https://cs.android.com/android/_/android/platform/art/+/5097f83c4719a76fdfab1044ab745273841aca45

2.instrument替换掉trace odex的支持

https://cs.android.com/android/_/android/platform/art/+/890b19bd625be5d0e4a876e3eb11b8b893fb0c13

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/457666.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构初阶】第七节.树和二叉树的性质

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 前言 一、树 1.1 树的概念 1.2 树的结点分类 1.3 结点之间的关系 1.4 树的存储结构 1.5 其他相关概念 二、 二叉树 2.1 二叉树的概念 2.2 特殊的二叉树 2.3 二叉树的性质 2.4…

快速上线chatGPT软件

ChatGPT是一个由OpenAI开发的自然语言处理模型,您无法将其直接上线。但是,如果您想要在您的应用程序中集成对话系统或聊天机器人,您可以探索以下步骤: 选择开发工具:选择一个适合您的编程语言和平台的开发工具&…

C++ [STL之string的使用]

本文已收录至《C语言和高级数据结构》专栏! 作者:ARMCSKGT 文章目录 前言正文编码basic_string类说明basic_string实例成员关于string string类模块构造函数空间大小相关字符串长度及容量大小清空字符串和空串查询字符串大小和容量设置 访问与遍历头尾元…

探索【Stable-Diffusion WEBUI】的插件:界面与中文翻译

文章目录 (零)前言(一)主题(kitchen Theme)(二)对照翻译(Bilingual Localization)(三)自行翻译(四)提示词翻译…

前端02:CSS选择器等基础知识

CSS基础选择器、设置字体样式、文本样式、CSS的三种引入方式、能使用Chrome调试工具调试样式 HTML专注做结构呈现,样式交给CSS,即结构(HTML)和样式CSS相分离 CSS主要由量分布构成,选择器以及一条或多条声明 选择器&…

华为OD机试真题(Java),喊7的次数重排(100%通过+复盘思路)

一、题目描述 喊7是一个传统的聚会游戏,N个人围成一圈,按顺时针从1到N编号。 编号为1的人从1开始喊数,下一个人喊的数字为上一个人的数字加1,但是当将要喊出来的数字是7的倍数或者数字本身含有7的话,不能把这个数字直…

ML之DR:基于鸢尾花(Iris)数据集利用多种降维算法(PCA/TSVD/LDA/TSNE)实现数据降维并进行二维和三维动态可视化应用案例

ML之DR:基于鸢尾花(Iris)数据集利用多种降维算法(PCA/TSVD/LDA/TSNE)实现数据降维并进行二维和三维动态可视化应用案例 目录 基于鸢尾花(Iris)数据集利用多种降维算法(PCA/TSVD/LDA/TSNE)实现数据降维并进行二维和三维动态可视化应用案例 # 1、定义数据集 # 2、数…

华为OD机试真题(Java),密码验证合格程序(100%通过+复盘思路)

一、题目描述 密码要求: 长度超过8位包括大小写字母.数字.其它符号,以上四种至少三种不能有长度大于2的包含公共元素的子串重复 (注:其他符号不含空格或换行) 二、输入描述 一组字符串。 三、输出描述 如果符合要求输出:OK&…

Oxygen Content Fusion carck

Oxygen Content Fusion carck 输入法支持改进-对非拉丁语言输入法编辑器(IME)的支持在稳定性和性能方面得到了改进。 文件比较工具中环绕差异的精确显示-文件比较工具现在可以更好地识别和显示环绕编辑产生的差异。例如,当一段文本标记有标记时,它会识别…

pytorch深度学习框架CUDA版本环境安装记录——牛刀杀鸡——解一个非线性方程组

目录 一、前言二、安装步骤step1. 安装显卡驱动step2. 安装cudastep3. 安装cuDNNstep4. 安装pytorch环境 三、用pytorch解个非线性方程组 一、前言 在深度学习界pytorch框架用得人越来越多,无论是CV机器视觉、NLP还是自然语言处理,目前主流的大的模型如…

Matlab-报错griddedInterpolant解决方法分享

Yiinterp1 (x,Y,xi) interp1函数的用法: yiinterp1 (x,Y,xi):返回插值向量yi,每一元素对应于参量xi,同时由向量X与Y的内插值决定。 1.问题产生 用matlab做网格数据插值时遇到的问题 报错截图收录 2.分析原因 根据报错可知&#x…

胜叔说SI_PI_EMC

第一课 分享的目的 书籍推荐 第二课 什么是理论分析 仿真不是目的,仿真是验证理论分析的方法 测试不是目的,测试是验证理论分析的方法 第三课 信号完整性简介 小型化、高功率、高密度 传输线理论:传输线是由 信号路径和返回路径共同组…

【Spring Cloud】Sleuth+Zipkin全链路日志追踪接入实战

文章目录 一、背景链路追踪介绍为什么需要链路追踪?那该如何解决呢? 二、常见的链路追踪技术有下面这些:三、Sleuth3.1、Sleuth(读作/sluːθ/)介绍3.2、相关术语3.3、Sleuth入门 四、多线程传递traceId1.问题2.解决方案3. 业务组…

cookie和session—javaEE

1.cookie 1.1定义 单纯的说cookie指的是cookie技术,是客户端保存数据的一种技术 1.2保存的方式 (1)客户端写js代码 (2)服务端返回响应头set-cookie字段的值让客户端保存在本地硬盘或浏览器的相关路径中 1.3作用 …

Oracle的学习心得和知识总结(二十三)|Oracle数据库Real Application Testing之Database Replay相关视图

目录结构 注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下: 1、参考书籍:《Oracle Database SQL Language Reference》 2、参考书籍:《PostgreSQL中文手册》 3、EDB Postgres Advanced Server User Gui…

[C++]模板初阶与STL简介

目录 模板初阶与STL简介:: 1.泛型编程 2.函数模板 3.类模板 4.什么是STL 5.STL的版本 6.STL的六大组件 7.STL的缺陷 模板初阶与STL简介:: 1.泛型编程 如何实现一个通用的交换函数呢? void Swap(int& left, int& righ…

《Netty》从零开始学netty源码(四十二)之PoolChunk.runsAvailMap

runsAvailMap PoolChunk中的runsAvailMap属性用于存放可用的run的信息,PoolChunk中每一次分配内存都会更新runsAvailMap中可用的run的起始信息及末尾信息,先看下它的数据结构: 我们看下它的构造函数是如何赋值的: PoolChunk的默认…

为什么MySQL索引更适合B+树而不是二叉树、B树

概述: 在当今社会,程序员内卷非常的严重,如果没有过硬的技术,很难在众多的程序员中脱颖而出,例如,以前问数据库方面的知识,只会问些增删改查语句表面的东西,而如今却要问数据库底层…

【翻译一下官方文档】之uniapp的网络请求

uni.request(OBJECT) 发起网络请求。 参数名类型必填默认值说明平台差异说明urlString是开发者服务器接口地址dataObject/String/ArrayBuffer否请求的参数App 3.3.7 以下不支持 ArrayBuffer 类型headerObject否设置请求的 header,header 中不能设置 RefererApp、H5…

关于链表的题目—leetcode

第一题:删除链表中的指定节点 问题描述: 给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。 返回删除后的链表的头节点。 示例 1: 输入: head [4,5,1,9], val 5 输出: [4,1,9] 解释: 给定你链表中值为 5 的第二个节点…