最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

ART虛擬機method tracing技術(shù)解析

2023-07-21 14:10 作者:補給站Linux內(nèi)核  | 我要投稿

一、method tracing介紹

概述

這個是谷歌提供的對java的函數(shù)級trace工具,和systrace只支持打點不同,method tracing能支持到函數(shù),看到具體的函數(shù)執(zhí)行時間,準(zhǔn)確的分析出來執(zhí)行的時間短板。



1.生成trace的方式


sampling方式:

sampling方式采用sample任務(wù),定期抓取各個線程的調(diào)用棧,采集精度和采集的頻次正相關(guān),同時由于java stack采集的時候需要做suspend,因此還是有一部分的效率損失。



我們可以看到,原生單次采集使用的是suspendall,而不是對threadlist上的線程逐個做getStackTrace,因此效率損失會比較嚴(yán)重。


trace方式:

通過在執(zhí)行流程插入enter-exit來觀測:

相比于sample 方式,trace可以準(zhǔn)確的獲取到每個函數(shù)的進入和退出時間,精度可以非常高。


由于art虛擬機執(zhí)行特點,這個方案相較于sample方式復(fù)雜度要高不少,下文會著重介紹trace方式的實現(xiàn)原理


2.trace啟動流程


我們從trace方式的啟動入口開始看起


幾個關(guān)鍵的流程分別是


1.停用掉JIT GC,這個是防止stub方式替換之后,因為JIT GC引起的重新指定執(zhí)行方式,釋放JIT code和entry之間存在競爭。

2.進行suspend all,這是因為后續(xù)真正開啟trace的時候,會對所有的函數(shù)入口做重新指定,必然要對整個java世界進行停頓,保證安全性。

3.注冊listener

然后進入EnableMethodTracing,真正發(fā)起tracing的核心流程。


根據(jù)是否要回切解釋執(zhí)行,有兩種不同的處理方式。



具體內(nèi)部流程有兩個關(guān)鍵的處理:


1.構(gòu)造一個InstallStubsClassVisitor,這個的作用是遍歷所有類,然后對每個類做執(zhí)行方法入口的重定向,也就是stub回填。

2.對各個線程的當(dāng)前棧做一下處理,主要是植入exit frame。為什么exit point要單獨處理,我們后文詳細(xì)介紹,這個地方谷歌采用了一個非常trick的方式。


接下來我們繼續(xù)看InstallStubsClassVisitor遍歷class替換入口的處理:



真正的核心處理流程其實是下述:



如果是解釋執(zhí)行方式,則把入口都換成GetQuickToInterpreterBridge

如果是stub方式,則換成了GetQuickInstrumentationEntryPoint


3.trace采集的分類


從前面的代碼流程中,我們能發(fā)現(xiàn),分成了兩個類型。


采集的方式分類


interpretor only:這是最簡單粗暴的方式,直接強制整個系統(tǒng)回退到解釋執(zhí)行。


stubs方式:這個方式是希望提升tracing開啟之后的性能表現(xiàn),因此在支持解釋執(zhí)行的基礎(chǔ)上,對JIT和AOT的函數(shù),也做了特殊處理進行支持,而不需要強制回退到解釋執(zhí)行。相比純解釋執(zhí)行,這部分的技術(shù)細(xì)節(jié)更豐富,使用了一些“奇技淫巧”,本文后續(xù)著重介紹stub對JIT和AOT支持的方式。


trace執(zhí)行主要是在函數(shù)進出的地方植入enter-exit對來實現(xiàn)對函數(shù)執(zhí)行流程的打點。


因為要在一個java 方法的入口和出口植入事件的記錄,所以trace的實現(xiàn)就和虛擬機的執(zhí)行方式強相關(guān),我們先簡單介紹下虛擬機的幾種執(zhí)行方式。


虛擬機的執(zhí)行方式


解釋執(zhí)行:解釋執(zhí)行ART能夠全程介入java函數(shù)的執(zhí)行,這就包括了函數(shù)的入棧和出棧,因此設(shè)置觀測點非常容易,直接在虛擬機執(zhí)行流程中增加enter/exit埋點即可。


JIT:經(jīng)過JIT編譯的dex code其實target已經(jīng)是asm了,這個時候的java函數(shù)調(diào)用和arm64的native函數(shù)是非常類似的。


AOT:同JIT,區(qū)別在AOT是提前構(gòu)建而JIT是運行時構(gòu)建的。


我們看到啟動階段的實現(xiàn),是直接插入了enter,那真正的函數(shù)入口是怎么路由處理的,這里面其實由于虛擬機設(shè)計的特殊性,直接插入wrapper有一些問題,具體的下文先補充一些虛擬機的相關(guān)知識,然后結(jié)合這些背景知識慢慢道來。


【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)? ??



二、背景補充

要知道enter和exit的具體植入和運行原理,我們先補充一點art虛擬機的知識。


1.java函數(shù)入口

每個java方法,在虛擬機層面都維持著一個ArtMethod數(shù)據(jù)結(jié)構(gòu),每次調(diào)用一個方法,實際上是通過ArtMethod找到真正的入口,然后進行調(diào)用的。


java動態(tài)性的方式也是通過:

object->class->art method ->entrypoint來實現(xiàn)的

我們每次對一個對象call function,實際上就是找到對象的類型,類型里面回填了真正的artmethod,然后查找到正確的入口。


這個布局我們在看替換stub的整體流程的時候就發(fā)現(xiàn)了,替換stub就是沿著遍歷class-遍歷method的方式來完成的執(zhí)行入口重定向。


在只有一個入口可以插入的情況下,我們很容易想到做一個wrapper,在wrapper中調(diào)用art_method同時完成跟蹤:

圖示中的stack frame 1 2 3就是對應(yīng)了我們棧上的棧幀,可以看到如果要使用wrapper方式,會在caller和真正的執(zhí)行函數(shù)之間引入一個新的wrapper棧幀,我們結(jié)合下面一個點,就會發(fā)現(xiàn)問題。

?

2.walkstack


在anr,拋出異常的時候,都會對java調(diào)用棧進行遍歷,此種遍歷的邏輯主要在walkstack中完成的,這個如果加入了wrapper,會導(dǎo)致穿透的情況變得復(fù)雜如下圖:

這種棧結(jié)構(gòu)要兼容起來就非常的痛苦,在已有的JNI-解釋,JNI-quick,quik-quik,quik-解釋之上每種都要考慮棧內(nèi)有wrapper的場景。


總結(jié)


通過上述的虛擬機的特征有如下兩個問題:

1.art_method的入口只有一個掛載點,JIT和AOT處理后的java函數(shù)調(diào)用方式也并不能提供exit事件的記錄時機。

2.最好不要導(dǎo)致stack結(jié)構(gòu)發(fā)生變化,否則在進行棧遍歷的時候會帶來非常大的兼容負(fù)擔(dān)。


1和2看似是矛盾的,因為常規(guī)的手段,只有一個函數(shù)入口的話,需要使用wrapper,但是如果使用wrapper函數(shù),棧結(jié)構(gòu)就會發(fā)生改變。這個矛盾android使用了一個非常巧妙的方法解決,我們下文就對stub的解決方法做個詳細(xì)的介紹。


三、stub技術(shù)原理探究


因為jit和odex執(zhí)行的對象實際上都是匯編,我們在匯編中調(diào)用一個函數(shù),實際上只能insert一個entrypoint,那出棧如何實現(xiàn)呢?


此處其實就是使用了arm64的calling conversion偷雞,我們先看下替換的函數(shù)art_quick_instrumentation_entry,這個函數(shù)是純匯編寫的,我們看下匯編的核心處理:

匯編中使用bl指令調(diào)用了artInstrumentationMethodEntryFromCode(BL指令在函數(shù)結(jié)束后,ret會回到此處,而BR則是直接基于當(dāng)前的contexts做跳轉(zhuǎn),ret后就回到caller了),在artInstrumentationMethodEntryFromCode中主要做了三個事情


1.抓取并且查詢到了真實java函數(shù)的入口地址

2.記錄enter事件

3.記錄返回地址的PC(LR寄存器)


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

1.把x30設(shè)置為art_quick_instrumentation_exit的入口地址(adr? ? ?x30, 0x21a6a0)

2.通過BR跳轉(zhuǎn)到獲取的java方法入口(?br? ? ? x16)

這樣,在真正的被調(diào)函數(shù)完成之后ret,就會定向到exit的匯編上下文中:

在exit函數(shù)里面

1.記錄了出棧事件

2.還原了caller PC

通過改寫棧上位置(str? ? ?x0, [sp, #504]),然后restore的時候(?ldp? ? ?x29, x30, [sp, #496]),就自然讀到目標(biāo)lr了,同時這樣不會有寄存器污染的問題

還原lr之后,直接使用br指令跳轉(zhuǎn)到caller原始的位置。

以上就是android利用arm callingconversion實現(xiàn)的exit植入。


總結(jié)


如下圖所示,android通過篡改調(diào)用前的lr,結(jié)合BL和BR指令的不同ret方式,完成了單入口,在破壞棧結(jié)構(gòu)的情況下,記錄了enter和exit事件對。


原文作者:內(nèi)核工匠




ART虛擬機method tracing技術(shù)解析的評論 (共 條)

分享到微博請遵守國家法律
温州市| 宝丰县| 江阴市| 崇阳县| 巴中市| 平泉县| 垫江县| 南宁市| 金溪县| 宝清县| 库尔勒市| 泽库县| 浦北县| 桐庐县| 兰坪| 海晏县| 周口市| 甘南县| 黄龙县| 济阳县| SHOW| 玉山县| 左权县| 桐梓县| 集安市| 江华| 昌邑市| 疏勒县| 兰溪市| 那曲县| 绥化市| 深圳市| 玛纳斯县| 沽源县| 莱芜市| 措美县| 灯塔市| 色达县| 西宁市| 彭山县| 永寿县|