自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

基于Rust的Android Native內(nèi)存分析方案

移動(dòng)開(kāi)發(fā) Android
高德地圖車機(jī)版運(yùn)行的車載系統(tǒng)環(huán)境絕大部分都是基于安卓的定制系統(tǒng),且高德車機(jī)版底層代碼均為C/C++ Native代碼。因此,在安卓上需要有一種通用的Native內(nèi)存性能分析方案。

背景:高德地圖車機(jī)版運(yùn)行的車載系統(tǒng)環(huán)境絕大部分都是基于安卓的定制系統(tǒng),且高德車機(jī)版底層代碼均為C/C++ Native代碼。因此,在安卓上需要有一種通用的Native內(nèi)存性能分析方案。內(nèi)存塔(MemTower)是一個(gè)基于開(kāi)源項(xiàng)目memory-profiler并移植安卓且優(yōu)化改進(jìn)后的方案,解決了之前方案存在的痛點(diǎn)問(wèn)題,滿足了通用Native內(nèi)存性能分析需求。該項(xiàng)目采用Rust語(yǔ)言編寫(xiě),并利用了Rust的一些特性來(lái)完成對(duì)Native內(nèi)存訪問(wèn)的Hook.

1. Android Native內(nèi)存分析痛點(diǎn)與訴求

這一節(jié)主要介紹我們?yōu)槭裁匆鲞@件事以及對(duì)于這件事我們期望達(dá)到什么樣的目標(biāo)。

1.1 現(xiàn)有工具缺陷

Android在Java層面有很完善的性能分析工具,但是在Native層面沒(méi)有完整的解決方案。主要表現(xiàn)在:

  • 不支持Android 4.x,線上統(tǒng)計(jì)數(shù)據(jù)顯示4.x版本的車機(jī)仍占有較大比重,因此這點(diǎn)成為了無(wú)法忽視的問(wèn)題。
  • 安卓自帶的malloc_debug功能在不同的版本上行為不同,而且車機(jī)安卓系統(tǒng)大多經(jīng)過(guò)了系統(tǒng)廠商的定制,不能保證這些功能可用。

因此, 無(wú)法基于Android系統(tǒng)自有的功能做到Native內(nèi)存性能分析。

我們團(tuán)隊(duì)之前也在這方面做出了一些成果,但還是存在下面幾個(gè)問(wèn)題:

  • 通過(guò)修改編譯參數(shù)對(duì)Native代碼函數(shù)入口/結(jié)束位置插樁來(lái)進(jìn)行Hook,導(dǎo)致了性能嚴(yán)重下降;
  • 由于是侵入式分析,對(duì)內(nèi)存問(wèn)題分析需要單獨(dú)編譯出包分析,解決效率大幅降低,一個(gè)內(nèi)存泄漏問(wèn)題的排查成本按天計(jì)算。
  • 缺少精準(zhǔn)內(nèi)存使用數(shù)據(jù)。

1.2 打造一套完整的Native內(nèi)存性能分析方案

結(jié)合上門(mén)的問(wèn)題痛點(diǎn),我們希望能夠有一套完整的Native內(nèi)存性能分析方案。具體訴求表現(xiàn)在下面幾點(diǎn):

  • 支持安卓4.x在內(nèi)的絕大多數(shù)安卓系統(tǒng)。
  • 無(wú)侵入式分析,內(nèi)存問(wèn)題的發(fā)現(xiàn)與精準(zhǔn)定位同時(shí)完成。
  • 性能優(yōu)異,overhead低。
  • 支持長(zhǎng)時(shí)間內(nèi)存泄漏壓測(cè)。包括車廠客戶在內(nèi)的研發(fā)團(tuán)隊(duì)都會(huì)對(duì)導(dǎo)航進(jìn)行壓測(cè),需要能夠支持長(zhǎng)時(shí)間的壓測(cè)并定位內(nèi)存泄漏問(wèn)題。
  • 函數(shù)級(jí)內(nèi)存使用數(shù)據(jù)。原先的方案重點(diǎn)在于解決內(nèi)存泄漏的問(wèn)題,獲取的內(nèi)存使用數(shù)據(jù)不夠精確。而我們希望新的方案能夠獲得詳細(xì)的內(nèi)存使用數(shù)據(jù),用來(lái)支持內(nèi)存性能優(yōu)化。

2. 內(nèi)存塔(MemTower)方案

本節(jié)主要介紹memory-profiler項(xiàng)目的實(shí)現(xiàn)和內(nèi)存塔(MemTower)方案在移植該項(xiàng)目至Android平臺(tái)上的過(guò)程和對(duì)原方案的改進(jìn)。闡述我們是如何實(shí)現(xiàn)并滿足上述的訴求。

2.1 選擇Rust & Memory-profiler

針對(duì)上門(mén)的訴求,期望能夠找到一種新的解決方案。當(dāng)時(shí)正好在研究Rust,因此在GitHub上結(jié)合關(guān)鍵字搜索便發(fā)現(xiàn)了memory-profiler(以下簡(jiǎn)稱mp)項(xiàng)目,作者koute是前Nokia工程師。接著才有了后面的內(nèi)存塔。本節(jié)主要闡述mp如何結(jié)合Rust實(shí)現(xiàn)內(nèi)存Profile的相關(guān)原理和功能。

2.1.1 Hook實(shí)現(xiàn)

通常對(duì)Native內(nèi)存性能分析使用的方案是Hook malloc 和 free 等內(nèi)存調(diào)用請(qǐng)求。mp的原理也是如此,利用LD_PRELOAD 預(yù)加載自定義庫(kù)實(shí)現(xiàn)對(duì)內(nèi)存操作函數(shù)的Hook。這種方案最大的問(wèn)題是容易引發(fā)循環(huán)malloc調(diào)用。如下圖,Hook了程序內(nèi)存請(qǐng)求后,Hook業(yè)務(wù)自身的內(nèi)存請(qǐng)求也會(huì)觸發(fā)內(nèi)存請(qǐng)求,從而造成了malloc循環(huán)調(diào)用,引發(fā)棧崩潰。

mp的做法利用了Rust的可自定義內(nèi)存分配器(Allocator)的特性,將曾經(jīng)的Rust默認(rèn)內(nèi)存分配器jemalloc作為自定義分配器,并在jemalloc-sys的c代碼中將最終的內(nèi)存申請(qǐng)mmap替換成自定義的函數(shù)入口(從而也區(qū)分應(yīng)用和自身的mmap調(diào)用),最終調(diào)用mmap系統(tǒng)調(diào)用。

將Rust內(nèi)存請(qǐng)求轉(zhuǎn)發(fā)給系統(tǒng)調(diào)用后,還需要將應(yīng)用的內(nèi)存請(qǐng)求繼續(xù)傳遞給系統(tǒng)libc. mp的做法是通過(guò)Rust的feature開(kāi)關(guān),可以自行選擇兩種方式處理應(yīng)用內(nèi)存請(qǐng)求,這兩種方式都是通過(guò)在Rust中指定link_name 屬性實(shí)現(xiàn):

  • 直接通過(guò)__libc_malloc的link_name將應(yīng)用內(nèi)存請(qǐng)求轉(zhuǎn)發(fā)給libc
  • 通過(guò)指定成jemallocator的函數(shù)入口 _rjem_malloc,使應(yīng)用和Rust共用jemalloc.
  • 最終可以使Hook業(yè)務(wù)使用完整的Rust語(yǔ)言功能而不用擔(dān)心Rust自身代碼引起的循環(huán)調(diào)用崩潰。

2.1.2 高性能堆棧反解

除了利用Rust系統(tǒng)編程語(yǔ)言特性避開(kāi)內(nèi)存循環(huán)調(diào)用之外,作者還利用Rust的高性能特點(diǎn)實(shí)現(xiàn)了幾種高性能堆棧反解。

利用ELF的.eh_frame 節(jié)(C++異常處理機(jī)制)提供的棧回溯信息。

基于.ARM.exidx + .ARM.extab的棧回溯,這個(gè)是ARM提供的unwind table.

具體實(shí)現(xiàn)可以看作者的這個(gè)Crate not-perf。這里選擇第二種做說(shuō)明,如圖下,對(duì)每個(gè)線程的堆棧都用線程局部存儲(chǔ)維護(hù)了一套棧幀緩存,這個(gè)緩存來(lái)自于ELF文件中的unwind table信息,當(dāng)堆棧的幀在緩存未命中時(shí)會(huì)把對(duì)應(yīng)二進(jìn)制的unwind表被加載到內(nèi)存,而命中的時(shí)候,就不需要去讀取文件。通常二進(jìn)制被加載后它的地址空間就不會(huì)發(fā)生變化,所以緩存的效率很高。缺點(diǎn)是每個(gè)線程都有一套完整的緩存。從系統(tǒng)層面看占用的內(nèi)存overhead很大。

2.1.3 強(qiáng)大的數(shù)據(jù)分析功能

從mp的頁(yè)面可以看到它除了內(nèi)存Profile外,還有一個(gè)對(duì)應(yīng)的數(shù)據(jù)分析Server端,采用actix-web框架,且具備一個(gè)非常強(qiáng)大的分析功能。主要特性有下面幾點(diǎn):

  • 內(nèi)存使用量和泄漏兩種視角的時(shí)序曲線非常直觀。
  • 搭配了一個(gè)非常強(qiáng)大的過(guò)濾器,可以實(shí)現(xiàn)針對(duì)內(nèi)存生命周期、函數(shù)、時(shí)間等多維度做過(guò)濾查詢及其對(duì)應(yīng)的內(nèi)存火焰圖功能。
  • 所有功能具備RESTful API接口,可以非常容易的實(shí)現(xiàn)定制。

詳細(xì)的使用說(shuō)明這里不做過(guò)多的介紹。

 

 


 

2.2 移植

了解完mp的基本原理后,本節(jié)我們主要闡述在移植安卓平臺(tái)過(guò)程中遇到的各種問(wèn)題(坑)。

2.2.1 自定義Allocator

mp的Hook方案在Android平臺(tái)上存在較多問(wèn)題,主要體現(xiàn)在下面幾點(diǎn):

  • Jemalloc本身也才是Android 5.0開(kāi)始引入安卓,mp自帶的jemalloc-sys會(huì)導(dǎo)致一個(gè)應(yīng)用里存在兩個(gè)jemalloc,最終表現(xiàn)為在不同的版本上有著各種各樣的異常崩潰,問(wèn)題排查成了阻礙。
  • __libc_malloc是glibc提供的malloc函數(shù)入口別名,但在Android平臺(tái)沒(méi)有對(duì)應(yīng)這類實(shí)現(xiàn)。

因此,我們采用最原始的dlsym 方法獲取內(nèi)存相關(guān)函數(shù)入口,再將其封裝成Rust Allocator. 應(yīng)用的內(nèi)存請(qǐng)求也使用這些函數(shù)地址。如下圖,最終所有內(nèi)存請(qǐng)求都傳給libc,這樣Rust的業(yè)務(wù)代碼對(duì)libc來(lái)說(shuō)是透明的。

2.2.2 棧回溯

?;厮葸@塊同樣有一些移植修改。上面說(shuō)到作者提供了基于C++異常處理機(jī)制的?;厮莘椒ǎ沁@個(gè)方案要求依賴C++庫(kù)。而C在Android 8.0之后才會(huì)成為默認(rèn)依賴。這要求在8.0之前的版本運(yùn)行時(shí)應(yīng)用必須也依賴C++庫(kù)。因此我們移除了這個(gè)?;厮莘桨福崛チ诉@個(gè)依賴。

2.2.3 地址空間重載

在程序啟動(dòng)或調(diào)用dlopen/dlclose時(shí)鏈接器會(huì)加載(或卸載)ELF文件,相應(yīng)的,程序的地址空間會(huì)發(fā)生變化,這時(shí)候?;厮菥彺胬锏牡刂房臻g就可能會(huì)失效,需要重新加載(reload),reload操作掃描整個(gè)地址空間的變更,這個(gè)成本很高。與此同時(shí)還需要一種低成本獲取地址空間變化的方式. mp的實(shí)現(xiàn)主要有兩種方式:

libc提供的接口dl_iterate_phdr. Android API_LEVEL低于21(即5.0之前)沒(méi)有,5.0之后這個(gè)函數(shù)的結(jié)構(gòu)體和在高版本Android的實(shí)現(xiàn)不同。所以Rust定義的單一C結(jié)構(gòu)體格式會(huì)導(dǎo)致讀取到臟數(shù)據(jù)作為reload依據(jù),導(dǎo)致非常高頻繁地reload.;

Perf的 PERF_RECORD_MMAP2 事件,這個(gè)要求內(nèi)核版本大于3.16。因此這在Android 4.x上也不具備。

實(shí)際運(yùn)行過(guò)程中程序在加載完所有依賴ELF后,地址空間幾乎很少再變。因此,我們修改為只有在新的ELF被加載時(shí)才進(jìn)行地址空間重載?;鹧鎴D結(jié)果顯示可以大幅降低Hook時(shí)的計(jì)算成本。

2.3 改進(jìn)

到目前為止, 內(nèi)存塔已經(jīng)可以在支持 LD_PRELOAD 的Android版本上正確運(yùn)行了(含4.x)。但是上面訴求中還有一點(diǎn)無(wú)法滿足:長(zhǎng)時(shí)間內(nèi)存泄漏壓測(cè)。而且在數(shù)據(jù)分析過(guò)程中,我們希望有更多維度的信息。因此,本小節(jié)主要介紹我們對(duì)內(nèi)存塔的改進(jìn)。

2.3.1 內(nèi)存泄漏壓測(cè)

mp原先的定位正如它的名稱表述,是一款內(nèi)存性能分析工具,它記錄的是全量?jī)?nèi)存信息。這點(diǎn)決定了它的數(shù)據(jù)量規(guī)模。在長(zhǎng)時(shí)間壓測(cè)一小時(shí)的多個(gè)業(yè)務(wù)場(chǎng)景中,根據(jù)內(nèi)存使用量不同,生成的采樣數(shù)據(jù)文件有1GB~7GB之多。這樣的數(shù)據(jù)量無(wú)法滿足業(yè)務(wù)的需要。

因此,我們?cè)黾恿藘?nèi)存泄漏檢測(cè)模式(ONLY_LEAKED),這個(gè)模式的原理如下:

  • 將記錄到內(nèi)存開(kāi)辟的每一層棧幀記錄到一個(gè)字典樹(shù)(Trie Tree)中,同時(shí)記錄開(kāi)辟的內(nèi)存大小。
  • 內(nèi)存釋放時(shí)更新字典樹(shù)對(duì)應(yīng)的節(jié)點(diǎn)信息。當(dāng)前泄漏是否達(dá)到某個(gè)閾值(如100MB), 是則停止采樣。
  • 在結(jié)束采樣時(shí)把整個(gè)字典樹(shù)存儲(chǔ)的未釋放內(nèi)存記錄寫(xiě)入文件。

這種模式的優(yōu)點(diǎn)是最終的數(shù)據(jù)量非常的小,實(shí)際壓測(cè)一小時(shí)數(shù)據(jù)文件大小在100~200MB之間。再進(jìn)過(guò)mp自帶的postprocess 子命令壓縮后,大小不足100MB。不足之處是內(nèi)存塔需要在內(nèi)存中緩存一個(gè)全量的堆棧歷史數(shù)據(jù),當(dāng)沒(méi)有新的棧幀記錄出現(xiàn)后這個(gè)內(nèi)存增長(zhǎng)才會(huì)趨于穩(wěn)定。

2.3.2 增強(qiáng)分析過(guò)濾器

導(dǎo)航的業(yè)務(wù)模塊劃分和線程很多,因此增加了按線程和庫(kù)正則篩選過(guò)濾器選項(xiàng)。

2.3.3 內(nèi)存火焰圖完善

mp原方案的內(nèi)存火焰圖是以內(nèi)存大小(allocated)作為火焰圖維度,在分析內(nèi)存性能時(shí)內(nèi)存開(kāi)辟次數(shù)(allocations)也是一個(gè)很重要的指標(biāo),因此加入內(nèi)存開(kāi)辟次數(shù)火焰圖。這是當(dāng)初最早改進(jìn)的功能,而且火焰圖的形狀類似塔狀,就把該項(xiàng)目重命名為:內(nèi)存塔(MemTower)。

最后一點(diǎn)是原方案的火焰圖信息沒(méi)有以線程為單位劃分,我們把堆棧信息按線程區(qū)分后會(huì)更加直觀。

分配次數(shù)火焰圖

分配大小火焰圖

3. 內(nèi)存塔的能力及更多可能

最后一節(jié)介紹下內(nèi)存塔提供了什么樣的能力、收益以及還有哪些可能。

3.1 能力

內(nèi)存塔(MemTower)在Android 8.0以下依賴setprop wrap.com.xxx.xxx 和 root權(quán)限的能力,8.0以上版本如果沒(méi)有root權(quán)限還可以通過(guò)配置Android項(xiàng)目wrap.sh來(lái)加載內(nèi)存塔庫(kù)。另外,由于mp原生支持Linux的原因,我們也成功適配了奔馳戴姆勒這類嵌入式Linux項(xiàng)目車機(jī)。

  • 支持平臺(tái):Android 4.x、5.1.1和7或更高以上版本(5.0和6系統(tǒng)存在Bug, 無(wú)法設(shè)置setprop ). Linux x86_64, AArch64, Arm.
  • 采樣方式: 非侵入式. 非Root設(shè)備可選侵入式方式。
  • 采樣模式: 常規(guī)性能分析模式和內(nèi)存泄漏壓測(cè)模式。
  • 特點(diǎn): 高性能堆棧反解、完善的內(nèi)存分析Insight體驗(yàn)(多維度過(guò)濾器分析、內(nèi)存火焰圖等)。

原先發(fā)現(xiàn)內(nèi)存泄漏問(wèn)題重新出包二次壓測(cè)分析,再推斷可能泄漏點(diǎn)的流程耗費(fèi)時(shí)間按天計(jì)算。利用內(nèi)存塔(MemTower)做一遍測(cè)試后幾分鐘即可解析出精細(xì)化數(shù)據(jù),大幅降低了內(nèi)存性能問(wèn)題分析成本。mp提供的這套Hook思路和高性能堆棧反解其實(shí)可以不僅僅局限在內(nèi)存方面的分析,還可以針對(duì)IO性能分析或其它問(wèn)題上。

責(zé)任編輯:未麗燕 來(lái)源: 高德技術(shù)
相關(guān)推薦

2024-07-03 11:28:15

2013-08-02 10:06:36

Android內(nèi)存溢出

2024-07-08 10:56:34

Rust進(jìn)程內(nèi)存

2012-08-13 10:14:36

IBMdW

2020-06-17 16:38:22

Rust業(yè)務(wù)架構(gòu)

2020-09-24 10:57:52

Frida 和 QB

2020-10-26 10:58:39

Volatility的

2021-11-08 12:44:48

AndroidC++內(nèi)存

2013-07-23 06:56:12

Android內(nèi)存機(jī)制APP內(nèi)存使用情況Android開(kāi)發(fā)學(xué)習(xí)

2022-09-16 07:40:17

CloudWeGo開(kāi)源Rust

2022-12-05 09:55:01

漏洞AndroidRust

2025-02-05 08:43:40

2023-11-28 08:29:31

Rust內(nèi)存布局

2016-11-23 16:48:20

react-nativandroidjavascript

2013-07-23 06:47:55

Android內(nèi)存機(jī)制Android堆和棧Android開(kāi)發(fā)學(xué)習(xí)

2024-01-15 11:56:55

lintersESLint

2022-07-20 10:33:50

RustGo內(nèi)存管理

2012-04-16 17:20:31

IBM收購(gòu)智慧商務(wù)

2015-09-22 09:50:36

FacebookAndroid

2011-11-23 13:39:32

VPNVPN管理VPN管理方案
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)