崩潰堆棧還原技術(shù)大揭秘

0x00 前言

當(dāng)應(yīng)用出現(xiàn)崩潰的時(shí)候枢纠,程序員的第一反應(yīng)肯定是:在我這好好的,肯定不是我的問題黎棠,不信我拿日志來定位一下晋渺,于是千辛萬苦找出用戶日志,符號(hào)表脓斩,提取出崩潰堆棧木西,拿命令開干,折騰好一個(gè)多小時(shí)随静,拿到了下面的結(jié)果:


addr2line -ipfCe libxxx.so 007da904 007da9db 007d7895 00002605 007dbdf1

logging::Logging::~Logging() LINE: logging.cc:856

logging::ErrLogging::~ErrLogging() LINE: logging..cc:993

base::internal::XXXX::Free(int) LINE: scoped____.cc:54

base::___Generic<int, base::internal::_____loseTraits>::_____sary() LINE: scoped_______.h:153

base::___Generic<int, base::internal::_____loseTraits>::_____eric() LINE: scoped_______.h:90

如果是接入了岳鷹全景監(jiān)控平臺(tái)八千,場(chǎng)景就完全不一樣了。測(cè)試同學(xué):發(fā)來一個(gè)鏈接燎猛,附言研發(fā)哥哥恋捆,這是你的bug,請(qǐng)注意查收重绷。研發(fā)哥哥:點(diǎn)開鏈接沸停,就可以在平臺(tái)看到這條崩潰信息啦,如下圖:

image.png

那么問題來了昭卓,岳鷹上有這么多的應(yīng)用版本愤钾,再加上海量的日志,對(duì)于Native崩潰候醒,總不能每個(gè)崩潰點(diǎn)都用addr2line或者相關(guān)的命令去符號(hào)化吧绰垂?
岳鷹的符號(hào)化系統(tǒng)正是為了解決該問題而設(shè)計(jì)。岳鷹最初上線的版本1.0火焰,支持同時(shí)符號(hào)化解析數(shù)量有限劲装,對(duì)iOS符號(hào)化時(shí)依賴Mac系統(tǒng),不支持容器化部署昌简,消耗機(jī)器資源較多占业。為了更好的滿足用戶業(yè)務(wù)需求,岳鷹在年初啟動(dòng)了2.0版本的改造纯赎,并且制定以下目標(biāo):

  • 同時(shí)解析不限數(shù)量的符號(hào)表

  • 提升符號(hào)化的效率

  • 解除Mac系統(tǒng)依賴谦疾,支持全容器化部署

那這樣一個(gè)分布式的符號(hào)化系統(tǒng)該如何設(shè)計(jì)呢?接下來小編就來詳細(xì)介紹下犬金。

0x01 方案的選擇

結(jié)合當(dāng)前系統(tǒng)設(shè)計(jì)以及業(yè)界常見方案念恍,我們有以下幾條路可以走:

  1. 岳鷹1.0方案六剥,用大磁盤,高CPU性能的機(jī)器搭建符號(hào)化機(jī)器峰伙,符號(hào)文件存放到磁盤疗疟,需要符號(hào)化時(shí)再調(diào)用addr2line;

  2. 建立一個(gè)中央存儲(chǔ)瞳氓,把符號(hào)文件上傳到中央存儲(chǔ)策彤,符號(hào)化機(jī)器需要符號(hào)化的時(shí)候再過去拉,然后用addr2line符號(hào)化匣摘;

  3. 把符號(hào)信息按key-value方式提取出來店诗,存入hbase或者其它中間件,符號(hào)化時(shí)通過類sql查詢實(shí)現(xiàn)音榜。

結(jié)合岳鷹2.0的目標(biāo)庞瘸,我們對(duì)三個(gè)方案進(jìn)行對(duì)比:

image.png

方案1:符號(hào)文件上傳倒是很快,如果需要高可用赠叼,還需要鏡像一份到備機(jī)恕洲,且在做addr2line的時(shí)候,會(huì)帶來高內(nèi)存及高cpu的占用梅割,而且不支持動(dòng)態(tài)擴(kuò)容霜第,安全性也幾乎沒有,拿到機(jī)器就拿到了源碼户辞;

方案2:符號(hào)文件存放于中央存儲(chǔ)泌类,做好備份機(jī)制后,能保障文件不會(huì)丟失底燎,但機(jī)器在符號(hào)化時(shí)刃榨,都需要去中央存儲(chǔ)拉符號(hào)文件,之后的處理同方案1双仍,查詢效率不高枢希,而且安全性也不高;

方案3:在符號(hào)入庫時(shí)朱沃,把符號(hào)信息按key-value方式提取出來苞轿,然后加密存入hbase,這里要解決符號(hào)表全量導(dǎo)出及入庫的速度及空間問題逗物。

結(jié)合岳鷹2.0目標(biāo)搬卒,我們對(duì)日志處理的及時(shí)性,可擴(kuò)展性翎卓,安全性契邀,以及海量版本同時(shí)解析的要求,我們選擇了方案3失暴。下面我們先給大家簡(jiǎn)單介紹下原理坯门,再深入看看選擇方案3要解決哪些問題微饥。

0x02 原理(大神請(qǐng)忽略這一節(jié))

國際慣例,我們先來了解一下原理古戴,符號(hào)表是什么欠橘?符號(hào)表是記錄著地址或者混淆代碼與源碼的對(duì)應(yīng)關(guān)系表。下面我們分別用一個(gè)小demo程序來講解符號(hào)表及符號(hào)化的過程允瞧。

0x02-1 iOS-OC、Android-SO符號(hào)化原理

a.示例源碼:


int add(){

int a = 1;

a ++;

int b = a+3;

return b;

}

int div(){

int a = 1;

a ++;

int b = a/0;                //這里除0會(huì)引發(fā)崩潰

return b;

}

int _tmain(int argc, _TCHAR* argv[]){

add();

sub();

return 0;

}

b.對(duì)應(yīng)符號(hào)表蛮拔,這里簡(jiǎn)化了符號(hào)表述暂,沒帶行號(hào)信息


0x00F913B0 ~ 0x00F913F0    add()

0x00F91410 ~ 0x00F91450    div()

0x00F91A90 ~ 0x00F91ACD    _tmain()

c.現(xiàn)有一崩潰堆棧


0x00F9143A

0x00F91AB0

d.進(jìn)行符號(hào)化


0x00F9143A    div()    //查找符號(hào)表,地址0x00F9143A的符號(hào)名建炫,在0x00F91410 ~ 0x00F91450范圍內(nèi)

0x00F91AB0    _tmain() //查找符號(hào)表畦韭,地址0x00F91AB0的符號(hào)名,在0x00F91A90 ~ 0x00F91ACD范圍內(nèi)

<a name="3dba1efe"></a>

0x02-2 Android-Java 符號(hào)化原理

a.示例源碼:


package com.uc.meg.wpk

class User{

    int count;

    UserDTO userDto;

    UserDTO get(int id){...}

    int set(UserDTO userDto){...}

}

class UserDTO{

    int id;

    String name;

}

b.符號(hào)表


com.a.b.c.d -> com.uc.meg.wpk.User

    int count -> a

    com.uc.meg.wpk.UserDTO -> b

    com.uc.meg.wpk.UserDTO get(int) -> c

    int set(com.uc.meg.wpk.UserDTO) -> d

com.a.b.c.e -> com.uc.meg.wpk.UserDTO

    int id -> a

    String name -> b

c.現(xiàn)有一崩潰堆棧


com.a.b.c.d.d(com.a.b.c.e)

d.進(jìn)行符號(hào)化


//符號(hào)化com.a.b.c.d.d(com.a.b.c.e)   

//查找com.a.b.c.d肛跌, 命中com.uc.meg.wpk.User

//查找com.uc.meg.wpk.User.d 命中 set()

//查找com.a.b.c.e艺配,命中 com.uc.meg.wpk.UserDTO

//符號(hào)化結(jié)果為com.uc.meg.wpk.User.set(com.uc.meg.wpk.UserDTO)

<a name="0a3a7298"></a>

0x03 新的難題

選擇方案3后,主要瓶頸在符號(hào)表上傳之后處理衍慎,這里主要工作是要把符號(hào)表轉(zhuǎn)換為key-value转唉,然后再寫入hbase。現(xiàn)在主流的app開發(fā)有android的java及C++稳捆,iOS的OC赠法,我們下面主要討論這三種符號(hào)。因?yàn)閍ndroid的java符號(hào)化有g(shù)oogle的開源工具支持乔夯,這里就不再展開砖织。OC因?yàn)槭莍OS系統(tǒng),封閉系統(tǒng)末荐,標(biāo)準(zhǔn)統(tǒng)一侧纯,上架AppStrore的應(yīng)用,只用XCode進(jìn)行編譯甲脏,沒有各種定制的需求眶熬。我們?cè)瓉碛幸粋€(gè)OC實(shí)現(xiàn)的符號(hào)表kv提取程序,但是只能用于OSX系統(tǒng)块请,不便于線上布署聋涨,所以我們選擇了用java重寫了提取符號(hào)kv的功能。但是對(duì)于Android的C++庫so符號(hào)表负乡,即ELF格式牍白,存在著各種版本,各種定制下不同的編譯參數(shù)抖棘,會(huì)大幅增加用java重寫的成本茂腥,所以我們使用了Java跟C++結(jié)合的方式去實(shí)現(xiàn)ELF的符號(hào)表kv的提取狸涌,先用Java程序把ELF的基礎(chǔ)信息,地址表讀取出來最岗,然后再用addr2line去遍歷這個(gè)地址表帕胆,然后再把結(jié)果存入hbase,這個(gè)為100%的符號(hào)化成功率打下基礎(chǔ)般渡。

0x03-1 addr2line的問題

改進(jìn)前后的對(duì)比

image.png

當(dāng)然懒豹,這個(gè)addr2line,是要經(jīng)過改造才能達(dá)到我們的要求驯用,原來的addr2line是給開發(fā)者以單條命令去使用脸秽,不是給程序做批量查詢的,每次查詢都是要把整個(gè)ELF文件加載到內(nèi)存蝴乔,像UC內(nèi)核记餐,還有一些游戲的so文件,大小要到幾百M(fèi)的級(jí)別薇正,每個(gè)addr2line進(jìn)程都要一份獨(dú)立的內(nèi)存片酝。假設(shè)一個(gè)500M的so符號(hào),一臺(tái)64核的機(jī)器挖腰,假如用60核去100%跑addr2line雕沿,加上其它開銷,它就需要35G的內(nèi)存猴仑。面對(duì)這么高的cpu和內(nèi)存占用晦炊,而且是一個(gè)較低的QPS,單核大約100QPS宁脊,我們也嘗試去優(yōu)化addr2line的binutils中的bfd部分断国,但是最終的接口都是調(diào)用系統(tǒng)內(nèi)核的,這條路榆苞,短期好像走不通稳衬。面對(duì)這樣的性能問題,期間也多次嘗試用Java去重寫這部分邏輯坐漏,但是最終結(jié)果只能實(shí)現(xiàn)與addr2line的90%匹配度薄疚,而且還有很多未知的兼容性問題,最后還是選擇了改造addr2line赊琳,改造點(diǎn)主要有以下三點(diǎn):

  • 從文件讀取地址表街夭,使用批量請(qǐng)求去addr2line,減少bfd初始化的次數(shù)躏筏,因?yàn)檫@個(gè)過程中板丽,bfd接口在調(diào)用一些特定的地址轉(zhuǎn)換后,會(huì)導(dǎo)致qps降到個(gè)位數(shù),需要重啟進(jìn)程才行埃碱;

  • 減少額外的內(nèi)存開銷猖辫;

  • 支持多進(jìn)程,多容器分布式任務(wù)調(diào)度砚殿,支持動(dòng)態(tài)擴(kuò)縮容啃憎,提高資源利用率。

改造后似炎,單核的QPS大約提升到800QPS辛萍,上面舉的500M的so符號(hào)的例子,大約需要15分鐘羡藐,基本能滿足我們的需求贩毕。

0x03-2 存儲(chǔ)的問題

解決完提取的問題,接下來就是存儲(chǔ)的問題传睹。符號(hào)表都是經(jīng)過精心設(shè)計(jì)的高度壓縮的數(shù)據(jù)結(jié)構(gòu)耳幢,我們通過上面的方案把它提取出kv的格式岸晦,容量上增加了10+倍欧啤,而且很多信息都是重復(fù)的,如函數(shù)名启上,文件名這些邢隧,雖然空間對(duì)于hbase來說不是什么問題,但是在追求極致的面前冈在,我們還可以再折騰折騰倒慧。前面提到我們因?yàn)橐紤]數(shù)據(jù)的安全性,需要把存入hbase的數(shù)據(jù)做加密包券,所以不能直接用hbase本身的壓縮功能纫谅,要求在加密前先做好壓縮,如果是按行壓縮再加密溅固,總體的壓縮比不會(huì)太高付秕,我們可以把00006740000069eb這一段當(dāng)成一個(gè)大段,把它們壓縮在一起再加密侍郭,這樣因?yàn)橹貜?fù)信息較多询吴,壓縮比會(huì)很高,最終的體積可以縮小5+倍亮元,相當(dāng)于只是比原始符號(hào)表大34倍猛计。hbase rowkey的設(shè)計(jì),因?yàn)楹竺娴牟樵儠?huì)需要用到scan爆捞,我們把符號(hào)表kv的結(jié)束地址作為rowkey的一部分奉瘤,至于為什么這么設(shè)計(jì),往下讀煮甥,你就明白了毛好。

0x03-3 查詢的問題

根據(jù)0x01原理望艺,對(duì)hbase的查詢,需要get肌访,scan的支持找默,get的話相對(duì)簡(jiǎn)單,直接通過rowkey命中就好了吼驶,適用于java符號(hào)化的場(chǎng)景惩激,對(duì)于C++/OC的符號(hào)化,就需要scan的支持蟹演,因?yàn)榈刂肥且粋€(gè)范圍风钻,不能用get直接命中,下面用偽代碼舉例說明scan的流程:


//1. 掃描libxxx.so符號(hào)酒请,地址范圍0x00001234 ~ 0xffffffff骡技, 只取一條結(jié)果

//這里利用了scan的特性,我們存的rowkey是符號(hào)的結(jié)束地址羞反,所以掃描出的第一個(gè)布朦,

//就是最接近0x00001234的一個(gè)符號(hào)

raw = scan("libxxx.so", 0x00001234, 0xffffffff, limit=1);

//2. 解密,解壓昼窗,判斷有效性預(yù)處理

data = pre(raw);

//3. 精確定位地址是趴,根據(jù)0x04-2的打包存入,再做切割拆分

result = splitData(data);

舊系統(tǒng)我們只用了應(yīng)用級(jí)的緩存澄惊,每次重啟緩存就會(huì)丟失唆途,為了減小hbase的壓力,我們?cè)黾右患?jí)分布式緩存掸驱,使用redis作為緩存肛搬,進(jìn)一步減少了末端的查詢QPS。

0x03-4 如果保證100%的符號(hào)化成功率

我們知道毕贼,如果符號(hào)化失敗温赔,就會(huì)出現(xiàn)不一樣的崩潰點(diǎn),這樣就不能把這些崩潰點(diǎn)聚合在一起帅刀,會(huì)把一些嚴(yán)重的問題分散掉让腹,同時(shí)會(huì)產(chǎn)生很多新的崩潰點(diǎn),導(dǎo)致開發(fā)扣溺,測(cè)試無法分辨真實(shí)的崩潰情況骇窍,我們使用以下技術(shù)保障成功率:

  • 高并發(fā),低延遲的符號(hào)化查詢服務(wù)锥余,保障解析效率腹纳,防止超時(shí)出現(xiàn)符號(hào)化失敗的情況;

  • 多級(jí)緩存保障,減少hbase的scan操作嘲恍;

  • 使用原生addr2line提取符號(hào)kv足画;

  • 重試機(jī)制。

<a name="bb113271"></a>

0x04 總結(jié)

0x04-1 符號(hào)化系統(tǒng)的核心能力

通過幾個(gè)平臺(tái)的符號(hào)化反能力對(duì)比佃牛,我們可以看到岳鷹2.0取得的階段性成果淹辞。

image.png

0x04-2 運(yùn)行效果的提升

image.png

0x05 歡迎免費(fèi)試用

岳鷹為阿里集團(tuán)眾多使用UC內(nèi)核的app(如手淘,支付寶俘侠,天貓象缀,釘釘,優(yōu)酷等)提供內(nèi)核so的崩潰符號(hào)化功能爷速,實(shí)現(xiàn)了Java央星,Native C++的質(zhì)量監(jiān)控完整閉環(huán),并在Native C++上的支持上明顯優(yōu)于其它競(jìng)品惫东,開發(fā)者能快速地還原現(xiàn)場(chǎng)并找出問題莉给,同時(shí)整個(gè)系統(tǒng)支持動(dòng)態(tài)擴(kuò)縮容,為更多業(yè)務(wù)接入打下了堅(jiān)實(shí)的基礎(chǔ)廉沮。更多功能颓遏,歡迎來岳鷹全景監(jiān)控平臺(tái)平臺(tái)體驗(yàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市废封,隨后出現(xiàn)的幾起案子州泊,更是在濱河造成了極大的恐慌丧蘸,老刑警劉巖漂洋,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異力喷,居然都是意外死亡刽漂,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門弟孟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贝咙,“玉大人,你說我怎么就攤上這事拂募⊥バ桑” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵陈症,是天一觀的道長(zhǎng)蔼水。 經(jīng)常有香客問我,道長(zhǎng)录肯,這世上最難降的妖魔是什么趴腋? 我笑而不...
    開封第一講書人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上优炬,老公的妹妹穿的比我還像新娘颁井。我一直安慰自己,他們只是感情好蠢护,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開白布雅宾。 她就那樣靜靜地躺著,像睡著了一般葵硕。 火紅的嫁衣襯著肌膚如雪秀又。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,158評(píng)論 1 308
  • 那天贬芥,我揣著相機(jī)與錄音吐辙,去河邊找鬼。 笑死蘸劈,一個(gè)胖子當(dāng)著我的面吹牛昏苏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播威沫,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼贤惯,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了棒掠?” 一聲冷哼從身側(cè)響起孵构,我...
    開封第一講書人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎烟很,沒想到半個(gè)月后颈墅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡雾袱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年恤筛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芹橡。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡毒坛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出林说,到底是詐尸還是另有隱情煎殷,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布腿箩,位于F島的核電站豪直,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏度秘。R本人自食惡果不足惜顶伞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一饵撑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧唆貌,春花似錦滑潘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至酪刀,卻和暖如春粹舵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背骂倘。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來泰國打工眼滤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人历涝。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓诅需,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親荧库。 傳聞我的和親對(duì)象是個(gè)殘疾皇子堰塌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359