<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Jack Jiang

    我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
    posts - 494, comments - 13, trackbacks - 0, articles - 1

    1、引言

    隨著Android系統的不斷升級,即時通訊網技術群和社區里的IM和推送開發的程序員們,對于進程?;钸@件事是越來越悲觀,必竟系統對各種?;詈诳萍嫉南拗圃絹碓蕉嗔耍氤较到y的摯肘,難度越來越大。

    但?;钸@件事就像“激情”之后的余味,總是讓人欲罷不能,想放棄又不甘心。那么,除了像上篇《2020年了,Android后臺?;钸€有戲嗎?看我如何優雅的實現!》這樣的正經白名單方式,不正經的“黑科技”是否還有發揮的余地?

    答案是肯定的,“黑科技”仍發揮的余地。不是“黑科技”不行,而是技術沒到位。

    研究TIM的保活是一次偶然機會,發現在安全中心關閉了它的自啟動功能的情況下, 一鍵清理、強力清理等各大招都無法徹底殺掉TIM,系統的自啟動攔截也沒能阻止TIM的永生,這引起了我強烈的興趣,于是便有了本文。

    本文將從Andriod系統層面為你深入剖析騰訊TIM這款IM應用的超強保活能力,希望能給你帶來更多Android方面的靈感。

     

    * 特別申明:本文的技術研究和分析過程,僅供技術愛好者學習的用途,請勿用作非法用途。

    擴展知識:騰訊TIM是什么?(以下文字來自百度百科)

    TIM是由騰訊公司于2016年11月發布的多平臺IM客戶端應用。TIM是在QQ輕聊版的基礎上加入了協同辦公服務的支持,可QQ號登錄,以及好友、消息同步等,適合辦公使用。

    (本文同步發布于:http://www.52im.net/thread-2893-1-1.html

    2、本文作者 

    袁輝輝:2019年5月加入字節跳動移動平臺部。畢業于西安電子科技大,曾就職于小米、聯想、IBM。

    之前主要經歷從事Android手機系統研發,在上一份小米MIUI系統組工作期間主要負責小米手機Android Framework架構優化、系統穩定、技術預研、平臺建設等工作。熱衷于研究Android系統內核技術,對Android系統框架有著深刻理解與豐富的實戰經驗,編寫近200篇高質量文章,多次受邀參加業內Android技術大會演講。

    3、保活技術回顧

    Android?;罴夹g的進化,可以分為幾個階段。

    第一個階段:也就是各種“黑科技”盛行的時代,比如某Q搞出來的1像素、后臺無聲音樂(某運動計步APP就干過)等等。

    這個階段的一些典型主要技術手段,可以看以下這幾篇文章:

    1. 應用?;罱K極總結(一):Android6.0以下的雙進程守護?;顚嵺`
    2. Android進程?;钤斀猓阂黄恼陆鉀Q你的所有疑問
    3. 微信團隊原創分享:Android版微信后臺?;顚崙鸱窒?進程?;钇?

    第二個階段:到了Android 6.0時代以后,Android?;罹烷_始有點技術難度了,之前的各種無腦?;罘椒ㄩ_始慢慢失效。

    這個階段的一些典型技術手段,可以讀讀以下這幾篇文章:

    1. 應用?;罱K極總結(二):Android6.0及以上的?;顚嵺`(進程防殺篇)
    2. 應用?;罱K極總結(三):Android6.0及以上的?;顚嵺`(被殺復活篇)

    第三個階段:進入Android 8.0時代,Android直接在系統層面進行了各種越來越嚴格的管控,可以用的?;钍侄卧絹碓缴?,?;罴夹g的發展方向已發分化為兩個方向——要么用白名單的方式走正經的?;盥窂健⒁丛絹碓?#8220;黑”一“黑”到底(比如本文將要介紹的TIM的保活手段)。

    這個階段可以用的?;钜呀浭侄尾欢嗔?,以下幾篇盤點了目前的一些技術可行性現狀等:

    1. Android P正式版即將到來:后臺應用保活、消息推送的真正噩夢
    2. 全面盤點當前Android后臺?;罘桨傅恼鎸嵾\行效果(截止2019年前)
    3. 2020年了,Android后臺保活還有戲嗎?看我如何優雅的實現!

    4、什么是?;??

    ?;罹褪窃谟脩糁鲃託⑦M程,或者系統基于當前內存不足狀態而觸發清理進程后,該進程設法讓自己免于被殺的命運或者被殺后能立刻重生的手段。

    ?;钍?#8221;應用的蜜罐,系統的腫瘤“,應用高?;盥式o自己贏得在線時長,甚至做各種應用想做而用戶不期望的行為,給系統帶來的是不必要的耗電,以及系統額外的性能負擔。

    ?;罘桨敢恢本蛯映霾桓F,APP開發們不斷地絞盡腦汁讓自己的應用能存活得時間更長, 主要思路有以下兩個。

    提升進程優先級,降低被殺概率:

    • 1)比如監聽SCREEN_ON/OFF廣播,啟動一像素的透明Activity;
    • 2)啟動空通知,提升fg-service;
    • ... ...

    進程被殺后,重新拉起進程:

    • 1)監聽系統或者第3方廣播拉起進程。但目前安全中心/Whetstone已攔截;
    • 2)Native fork進程相互監聽,監聽到父進程被殺,則通過am命令啟動進程。force-stop會殺整個進程組,所以這個方法幾乎很難生效了。

    5、初步分析

    5.1 初識TIM

    執行命令adb shell ps | grep tencent.tim,可見TIM共有4個進程, 其父進程都是Zygote:

    root@gityuan:/ # ps | grep tencent.tim

    u0_a146   27965 551   1230992 43964 SyS_epoll_ 00f6df4bf0 S com.tencent.tim:Daemon

    u0_a146   27996 551   1252492 54032 SyS_epoll_ 00f6df4bf0 S com.tencent.tim:MSF

    u0_a146   28364 551   1348616 89204 SyS_epoll_ 00f6df4bf0 S com.tencent.tim:mail

    u0_a146   31587 551   1406128 147976 SyS_epoll_ 00f6df4bf0 S com.tencent.tim

    5.2 一鍵清理看現象,排查初步懷疑

    以下是對TIM執行一鍵清理后的日志:

    12-21 21:12:20.265  1053  1075 I am_kill : [0,4892,com.tencent.tim:Daemon,5,stop com.tencent.tim: from pid 4617]

    12-21 21:12:20.272  1053  1075 I am_kill : [0,5276,com.tencent.tim:mail,2,stop com.tencent.tim: from pid 4617]

    12-21 21:12:20.305  1053  1075 I am_kill : [0,4928,com.tencent.tim,2,stop com.tencent.tim: from pid 4617]

    12-21 21:12:20.330  1053  1075 I am_kill : [0,4910,com.tencent.tim:MSF,0,stop com.tencent.tim: from pid 4617]

    12-21 21:13:59.920  1053  1466 I am_proc_start: [0,5487,10146,com.tencent.tim:MSF,service,com.tencent.tim/com.tencent.mobileqq.app.DaemonMsfService]

    12-21 21:13:59.984  1053  1604 I am_proc_start: [0,5516,10146,com.tencent.tim,content provider,com.tencent.tim/com.tencent.mqq.shared_file_accessor.ContentProviderImpl]

    Force-stop是系統提供的殺進程最為徹底的方式,詳見文章《Android進程絕殺技–forceStop》。從日志可以發現一鍵清理后TIM的4個進程全部都已被Force-stop。但進程com.tencent.tim:MSF立刻就被DaemonMsfService服務啟動過程而拉起。

    問題1:安全中心已配置了禁止TIM的自啟動, 并且安全中心和系統都有對進程自啟動以及級聯啟動的嚴格限制,為何會有漏網之魚?

    懷疑1: 是否安全中心自啟動沒能有效限制,以及微信/QQ跟TIM有所級聯,比如com.tencent.mobileqq.app.DaemonMsfService服務名中以com.tencent.mobileqq(QQ的包名)開頭。

    經過dumpsys以及反復驗證后排除了這種可能性,如下:

    12-21 21:12:20.266  1053  1075 I AutoStartManagerService: MIUILOG- Reject RestartService packageName :com.tencent.tim uid : 10146

    12-21 21:12:20.291  1053  1075 I AutoStartManagerService: MIUILOG- Reject RestartService packageName :com.tencent.tim uid : 10146

    12-21 21:12:20.323  1053  1075 I AutoStartManagerService: MIUILOG- Reject RestartService packageName :com.tencent.tim uid : 10146

    12-21 21:12:20.323  1053  1075 I AutoStartManagerService: MIUILOG- Reject RestartService packageName :com.tencent.tim uid : 10146

    12-21 21:12:20.331  1053  1075 I AutoStartManagerService: MIUILOG- Reject RestartService packageName :com.tencent.tim uid : 10146

    12-21 21:12:20.332  1053  1075 I AutoStartManagerService: MIUILOG- Reject RestartService packageName :com.tencent.tim uid : 10146

    懷疑2: 是否在TIM進程被殺后, 收到BinderDied后的死亡回調過程中將Service再次拉起,這個情況也很快就被排除, 因為force-stop這種冷面強力殺手, 并不會等到死亡回調再去清理進程相關信息,而是直接連根拔起,并不會走到AMS的死亡回調。

    懷疑3: TIM設置了alarm機制,在callApp為空符合特征, 但經過分析這里就是普通的startService, 非startServiceInPackage(), 也排除了這種可能性:

    //啟動DaemonAssistService時,callApp為空,只有通過PendingIntent方式才可能出現這種情況

    12-21 21:56:54.653 3181 3195 I am_start_service: [-1,NULL,10146,com.tencent.tim:Daemon,com.tencent.tim/com.tencent.mobileqq.app.DaemonAssistService,{cmp=com.tencent.tim/com.tencent.mobileqq.app.DaemonAssistService}]

    12-21 21:56:56.666 3181 3827 I am_start_service: [-1,NULL,10146,com.tencent.tim:MSF,com.tencent.tim/com.tencent.mobileqq.app.DaemonMsfService,{cmp=com.tencent.tim/com.tencent.mobileqq.app.DaemonMsfService}]

    既然排除以上3種可能,直接上斷點來看看吧。

    5.3 Android Studio斷點分析

    一上斷點就發現了意外的一幕: 

    問題2:startService()的callingPid怎么可能等于0?

    5.3.1)分析callingPid=0:

    為什么說上面是意外的一幕呢?這需要對binder底層原理有一定深入理解,才能看出一些端倪,那就是此處callingPid=0是不合理邏輯的。很多人可能不太理解為何就不合乎邏輯, 這要從Binder原理說起, startService()這個Binder call是屬于同步binder調用, 對于binder調用過程,只有異步Binder調用的情況下callingPid=0才會為空, 因為不需要reply應答數據給發送binder請求的那一端。 但如果是同步的,則必須要給出callingPid,否則無法將應答數據回傳給發送方。 這是由Binder Driver所決定的,見如下Binder Driver核心代碼。

    (1) Binder發起端:根據當前ONE_WAY來決定是否設置from線程

    binder_transaction(...) {

        ...

        if(!reply && !(tr->flags & TF_ONE_WAY))

            t->from = thread;

        else

            t->from = NULL;

        }

        ...

    }

    (2) Binder接收端: 根據from線程是否為空, 來決定sender_pid是否為0. 這便是Java層所說的callingPid

    binder_thread_read(...) {

        ...

        t_from = binder_get_txn_from(t);

        if(t_from) {

            structtask_struct *sender = t_from->proc->tsk;

     

            tr.sender_pid = task_tgid_nr_ns(sender,

                            task_active_pid_ns(current));

        } else{

            tr.sender_pid = 0;

        }

        ...

    }

    上述代碼表明: 同步的Binder調用的情況下則callingPid必定不等于0。

    下面告訴大家如何看一個Binder調用是否同步, 如下圖最后一個參數代表的是FLAG_ONEWAY值,等于0則代表的是同步, 等于1則代表的是異步。

    以上代碼是framework的框架代碼,startService最終都會調用到這里來,所以callingPid必然是不可能出現為0的情況,讓我們看不透到底哪個進程把com.tencent.tim: Daemon拉起的。

    5.3.2)揭秘:

    從前面的分析來看callingPid是不可能為0的, 但從結果來看的確是0, 出現矛盾就一定有反常規存在,難道是存在同步的Binder調用,也存在同時callingPid=0的case?答案是No.

    從源碼角度來看是沒有這種可能性存在,后面再進一步追蹤flags值的變化,從如下的flags=17,可以確定的是此處的startService的binder call是ONE_WAY的,這就可以確定的確是發起了異步的Binder調用。

    代碼如下: 

    雖然callingPid=0,但從callUid=10146可以確定的一點是com.tencent.tim: Daemon進程是被來自TIM應用自身的某個進程所拉起的。

    5.4 小結

    通過前面的初步分析,先整理一下思路,有以下初步結論:

    • 1)TIM至少有4個進程,且都是由Zygote進程fork, ?;钍峭ㄟ^startService被拉起;
    • 2)排除 安全中心的對TIM限制自啟動功能失效的情況;
    • 3)排除 TIM進程被殺后的Binder死亡回調過程通過Service重新拉起進程;
    • 4)排除 alarm機制 拉起進程;
    • 5)從callingPid=0,可以得出TIM沒有走常規的系統框架中提供的startService()接口來啟動服務,而是自定義的方式;
    • 6)從callingUid=10146, 可以得出TIM救活自己的方式,是通過TIM自身,而非系統或者第三方應用拉起。

    到此不難得出一個猜想: 首先TIM應用能做到監聽應用進程被殺的情況, 其次是TIM應用自身替換掉或者自定義一套Binder調用,主動跟Binder驅動進行數據交互。

    6、深入分析

    6.1 尋求規律

    TIM應用有4個進程,不斷反復地嘗試殺TIM每一個進程后,觀察自啟動的情況后。 發現了一個規律:com.tencent.tim: Daemon和com.tencent.tim:MSF進程任一被殺,都會先把對方進程拉起,然后跟著自殺后,再重啟。

    接下來就把范圍鎖定在這兩個進程,然后來tracing信號處理情況。

    6.2 從signal角度來分析

    打開signal開關:

    root@gityuan:/ # echo 1 > /d/tracing/events/signal/enable

    root@gityuan:/ # echo 1 > /d/tracing/tracing_on

    執行如下命令抓取tracing log:

    root@cancro/: cat/d/tracing/trace_pipe

    日志如下:

    //通過adb shell kill-9 10649,  將com.tencent.tim:Daemon進程殺掉

           sh-22775 [000] d..2 18844.276419: signal_generate: sig=9 errno=0 code=0 comm=cent.tim:Daemon pid=10649 grp=1 res=0

    //線程Thread-89 將tencent.tim:MSF進程也殺掉了

          Thread-89-10712 [000] dn.2 18844.340735: signal_generate: sig=9 errno=0 code=0 comm=tencent.tim:MSF pid=10669 grp=1 res=0

      Binder:14682_4-14845 [000] d..2 18844.340779: signal_deliver: sig=9 errno=0 code=0 sa_handler=0 sa_flags=0

      Binder:14682_1-14694 [000] d..2 18844.341418: signal_deliver: sig=9 errno=0 code=0 sa_handler=0 sa_flags=0

      Binder:14682_2-14697 [000] d..2 18844.345075: signal_deliver: sig=9 errno=0 code=0 sa_handler=0 sa_flags=0

     tencent.tim:MSF-14682 [000] dn.2 18844.345115: signal_deliver: sig=9 errno=0 code=0 sa_handler=0 sa_flags=

    從這里,可以發現com.tencent.tim: Daemon進程是由于其中一個線程Thread-89所殺,但從名字來看Thread-xxx,很明顯是系統自動生成的編號。

    問題3:進程內的名叫“Thread-89”的線程具有什么特點,如何做到把進程殺掉?

    從下面的截圖,可以看出MSF進程的這個特殊的線程當前在執行flock_lock操作,這個明顯是一個文件加鎖的操作, 這個方法很快就引起了我的注意。同理Daemon進程也有一個這樣的線程, 離真相有近了一步。 

     

    再來看看調用棧情況:

    Cmd line: com.tencent.tim:Daemon

    "Thread-89"prio=10 tid=12 Native

      | group="main"sCount=1 dsCount=0 obj=0x32c07460 self=0xf3382000

      | sysTid=10712 nice=-8 cgrp=bg_non_interactive sched=0/0handle=0xee824930

      | state=S schedstat=( 44972457 14188383 124 ) utm=1 stm=3 core=0 HZ=100

      | stack=0xee722000-0xee724000 stackSize=1038KB

      | held mutexes=

      kernel: __switch_to+0x74/0x8c

      kernel: flock_lock_file_wait+0x2a4/0x318

      kernel: SyS_flock+0x19c/0x1a8

      kernel: el0_svc_naked+0x20/0x28

      native: #00 pc 000423d4  /system/lib/libc.so (flock+8)

      native: #01 pc 0000195d  /data/app/com.tencent.tim-1/lib/arm/libdaemon_acc.so (_Z9lock_filePc+64)

     ...

      native: #29 pc 0000191f  /data/app/com.tencent.tim-1/lib/arm/libdaemon_acc.so (_Z9lock_filePc+2)

      native: #30 pc 0000191d  /data/app/com.tencent.tim-1/lib/arm/libdaemon_acc.so (_Z9lock_filePc)

      native: #31 pc 0000191b  /data/app/com.tencent.tim-1/lib/arm/libdaemon_acc.so (_Z18notify_and_waitforPcS_+102)

      ...

      native: #63 pc 000018d1  /data/app/com.tencent.tim-1/lib/arm/libdaemon_acc.so (_Z18notify_and_waitforPcS_+28)

      at com.libwatermelon.WaterDaemon.doDaemon2(Native method)

      at com.libwatermelon.strategy.WaterStrategy2$2.run(WaterStrategy2.java:111)

    從這個線程的調用棧中的名字, notify_and_waitfor讓我想到了這極有可能用于監聽文件來獲知進程是否存活。 為了進一步觀察這個特殊線程的工作使命, 這里還不需要GDB, 祭出strace大招應該就差不多。

    6.3 利用strace分析

    root@gityuan:/ # strace -CttTip 22829 -CttTip 22793

    結果如下:

    flock基礎知識簡介:

    flock是Linux文件鎖,用于多個進程同時操作同一個文件時,通過加鎖機制保證數據的完整,flock使用場景之一,便是用于檢測進程是否存在。flock屬于建議性的鎖,而非強制性鎖,只是進程可以直接操作正被另一個進程用flock鎖住的文件, 原因在于flock只檢測文件是否加鎖,內核并不會強制阻塞其他進程的讀寫操作,這便是建議性鎖的內核策略。

    方法原型: intflock(intfd, intoperation)

    第一個參數是文件描述符,第二參數指定鎖的類型,有以下3個可選值:

    • 1)LOCK_SH: 共享鎖, 同一時間運行多個進程同時持有該共享鎖;
    • 2)LOCK_EX: 排它鎖,只允許一個進程持有該鎖;
    • 3)LOCK_UN: 移除該進程的該文件所持有的鎖。

    從strace可以推測出:com.tencent.tim:MSF進程的監控線程執行排它鎖LOCK_EX類型的flock,嘗試去獲取某個文件,而該文件已被com.tencent.tim: Daemon進程所持有,所以MSF進程會被阻塞知道鎖的釋放,而一旦Daemon進程被殺,系統就會回收所有資源(包括文件),這是Linux內核負責完成的。

    當Daemon進程的文件被回收,就會釋放flock, 從而MSF進程可以獲取該鎖,從而吐出“lock file success”的信息。 MSF得知Daemon進程被殺,然后執行一行ioctl(11, BINDER_WRITE_READ, 0xffffffffee823ed0) = 0 <0.000867> 。

    這個應該就是TIM進程自身實現了一套執行startService的Binder調用,向Binder驅動發送 BINDER_WRITE_READ的ioctl命令。 再然后發送kill SIGKILL將自身MSF進程殺掉,同樣的道理可以再次被拉起。

    分析到這里,看執行了writev操作, 應該就是Log操作, 有一個關鍵詞到 Watermelon 吸引了我的注意力, 搜索 Watermelon 關鍵詞,果然找到新的一片天地。

    6.4 TIM日志

    //舊的MSF進程

    24538 24562 D Watermelon: lock filesuccess  >> /data/user/0/com.tencent.tim/app_indicators/indicator_p2

    24538 24562 E Watermelon: Watch >>>>Daemon<<<<< Daed !!

    24538 24562 E Watermelon: java_callback:onDaemonDead

    24538 24562 V Watermelon: onDaemonDead

    24576 24576 D Watermelon: lock filesuccess  >> /data/user/0/com.tencent.tim/app_indicators/indicator_d1

    24576 24576 E Watermelon: Watch >>>>Daemon<<<<< Daed !!

    24576 24576 E Watermelon: process exit

    //新daemon進程

    25103 25103 V Watermelon: initDaemon processName=com.tencent.tim:Daemon

    25103 25103 E Watermelon: onDaemonAssistantCreate

    25134 25134 D Watermelon: start daemon24=/data/user/0/com.tencent.tim/app_bin/daemon2

    //app_d進程

    25137 25137 D Watermelon: pipe readdatasize >> 316 <<

    25137 25137 D Watermelon: indicator_self_path >> /data/user/0/com.tencent.tim/app_indicators/indicator_d1

    25137 25137 D Watermelon: observer_daemon_path >> /data/user/0/com.tencent.tim/app_indicators/observer_p1

    25137 25137 I Watermelon: sIActivityManager==NULL

    25137 25137 I Watermelon: BpActivityManager init

    //新daemon

    25103 25120 D Watermelon: start try to lock file>> /data/user/0/com.tencent.tim/app_indicators/indicator_p2

    25103 25120 D Watermelon: lock filesuccess  >> /data/user/0/com.tencent.tim/app_indicators/indicator_p2

    25137 25137 I Watermelon: BpActivityManager init end

    //app_d進程

    25137 25137 D Watermelon: start try to lock file>> /data/user/0/com.tencent.tim/app_indicators/indicator_d1

    25137 25137 D Watermelon: lock filesuccess  >> /data/user/0/com.tencent.tim/app_indicators/indicator_d1

    //新MSF進程

    25119 25119 V Watermelon: initDaemon processName=com.tencent.tim:MSF

    25119 25119 V Watermelon: mConfigurations.PERSISTENT_CONFIG.PROCESS_NAME=com.tencent.tim:MSF

    25119 25119 E Watermelon: onPersistentCreate

    25153 25153 D Watermelon: start daemon24=/data/user/0/com.tencent.tim/app_bin/daemon2

    25119 25144 D Watermelon: pipe write len=324

    25159 25159 D Watermelon: pipe readdatasize >> 324 <<

    25159 25159 D Watermelon: indicator_self_path >> /data/user/0/com.tencent.tim/app_indicators/indicator_p1

    25159 25159 D Watermelon: observer_daemon_path >> /data/user/0/com.tencent.tim/app_indicators/observer_d1

    25159 25159 I Watermelon: sIActivityManager==NULL

    25159 25159 I Watermelon: BpActivityManager init

    25119 25144 D Watermelon: start try to lock file>> /data/user/0/com.tencent.tim/app_indicators/indicator_d2

    25119 25144 D Watermelon: lock filesuccess  >> /data/user/0/com.tencent.tim/app_indicators/indicator_d2

    25159 25159 I Watermelon: BpActivityManager init end

    //各進程進入監聽就緒狀態

    25159 25159 D Watermelon: start try to lock file>> /data/user/0/com.tencent.tim/app_indicators/indicator_p1

    25159 25159 D Watermelon: lock filesuccess  >> /data/user/0/com.tencent.tim/app_indicators/indicator_p1

    25119 25144 E Watermelon: Watched >>>>OBSERVER<<<< has been ready...

    25119 25144 D Watermelon: start try to lock file>> /data/user/0/com.tencent.tim/app_indicators/indicator_p2

    25159 25159 E Watermelon: Watched >>>>OBSERVER<<<< has been ready...

    25159 25159 D Watermelon: start try to lock file>> /data/user/0/com.tencent.tim/app_indicators/indicator_d1

    25137 25137 E Watermelon: Watched >>>>OBSERVER<<<< has been ready...

    25137 25137 D Watermelon: start try to lock file>> /data/user/0/com.tencent.tim/app_indicators/indicator_p1

    25103 25120 E Watermelon: Watched >>>>OBSERVER<<<< has been ready...

    25103 25120 D Watermelon: start try to lock file>> /data/user/0/com.tencent.tim/app_indicators/indicator_d2

    再從其中的截取核心片段:

    25159 25159 I Watermelon: BpActivityManager init

    25119 25144 D Watermelon: start try to lock file>> /data/user/0/com.tencent.tim/app_indicators/indicator_d2

    25119 25144 D Watermelon: lock filesuccess >> /data/user/0/com.tencent.tim/app_indicators/indicator_d2

    不難看出:

    • 1)TIM自身通過向servicemanager查詢來獲取AMS的代理BpActivityManager, 然后自己去寫startService通信過程的數據;
    • 2)TIM通過兩個進程通過flock來相互監聽對方進程存活狀態;
    • 3)監聽的文件有比如:/data/user/0/com.tencent.tim/app_indicators/indicator_d2。

    6.5 indicator文件

    進一步查看TIM所監聽的路徑下/data/user/0/com.tencent.tim/app_indicators/, 發現有4個監聽文件: 

    問題4:為何需要4個indicator文件?

    進一步延伸:通過查看flock,再次發現新大陸,原來除了Daemon和MSF進程各有一個監聽文件的線程, 還有兩個由init進程作為父進程的app_d進程也監聽文件:

    gityuan@13203:~/gityuan$ adb shell ps-t | grep-i flock

    u0_a146   10668 10649 1143304 85876 flock_lock 00f6e1e3d8 S Thread-85

    u0_a146   10712 10669 1158552 89664 flock_lock 00f6e1e3d8 S Thread-89

    u0_a146   10687 1     12768  564   flock_lock 00f73113d8 S app_d

    u0_a146   10717 1     12768  560   flock_lock 00f74353d8 S app_d

    不難發現,以上幾個進程/線程的uid=10146,進一步通過ps命名查找。

    再一次刷新對TIM應用的認識:原來TIM有6個進程,其中還有2個是掛在init進程下,名字跟tencent沒有關系,差點錯過了這兩個特殊的進程。

    這兩個app_d進程其實也是做著同樣的相互監聽的工作, 應該是備選方案。當有概率恰巧Daemon和MSF進程同時被殺而來不及互保的情況下,那么可以走緊急通道app_d 將TIM進程拉起??芍^是暗藏玄機, 6個進程中有4個進程可以相互?;?, 以保證TIM進程永生。

    問題5: 這4個進程到達是什么如何相互監聽的呢?

    通過不斷分析被殺與重啟前后的規律與特征,得出進程與監聽文件的關系圖: 

    進一步揭露面紗,得到如下結論:

    • 1)Daemon與MSF兩進程等待對方所持有的鎖,兩個app_d進程相互等待對方所持有的鎖;
    • 2)app_d1進程被殺, 則app_d2觀察后通過拉起DaemonMsfService服務來啟動MSF進程,然后跟著被殺;
    • 3)app_d2進程被殺,則app_d1觀察后通過拉起DaemonAssistService服務來啟動Daemon進程,然后跟著被殺;
    • 4)Daemon與MSF兩進程, 如果殺掉其中一個,則另個一個進程觀察后通過拉起服務方式來啟動對方進程,然后跟著被殺;然后app_d兩個進程也跟著重啟。

    另外猜想:監測indicator_p1和indicator_p2的兩個進程有關聯,indicator_d1和indicator_d2的進程有關聯,后面會驗證。

    到這里又有出現新的疑問:Daemon進程死后,MSF進程通過flock能監測到該事件,可是app_d進程又是如何得知的呢? app_d得知之后,又為何要再次自殺重啟?

    6.6 從cgroup角度來分析

    root@gityuan:/acct/uid_10146/pid_10649# cat cgroup.procs                       

    10649    //Daemon

    10687    //app_d

    root@gityuan:/acct/uid_10146/pid_10669# cat cgroup.procs                       

    10669   //MSF

    10717  //app_d

    從而,進一步獲取更多關于TIM深層次的關聯,通過查看cgroup發現,Daemon和app_d1是同一個group的, MSF和app_d2是同一個group的。

    問題6: app_d到底是如何創建出來?又是如何成為init進程的子進程的?

    從進程創建與退出的角度來看看來看:

    //5170(MSF進程) --> 5192 --> 5201(退出) --> 5211(存活)

    tencent.tim:MSF-5170  [001] ...1 55659.446062: sched_process_fork: comm=tencent.tim:MSF pid=5170 child_comm=tencent.tim:MSF child_pid=519

    Thread-300-5192  [000] ...1 55659.489621: sched_process_fork: comm=Thread-300 pid=5192 child_comm=Thread-300 child_pid=5201

    <...>-5201  [003] ...1  55659.501074: sched_process_exec: filename=/data/user/0/com.tencent.tim/app_bin/daemon2pid=5201 old_pid=5201

    daemon2-5201  [009] ...1  55659.533492: sched_process_fork: comm=daemon2 pid=5201 child_comm=daemon2 child_pid=5211

    daemon2-5201  [009] ...1  55659.535169: sched_process_exit: comm=daemon2 pid=5201 prio=120

    daemon2-5201  [009] d..3  55659.535341: signal_generate: sig=17 errno=0 code=262145 comm=Thread-300 pid=5192 grp=1 res=1

    說明:其中一個app_d進程是由MSF進程,通過兩次fork,然后父進程退出,從而成為了孤兒進程,然后托孤給init進程,這是Linux進程機制所保證的。 同理,另一個app_d進程是由Daemon進程所fork。到這里,那么總算是認清的app_d的由來。 app_d是由于cgroup關聯所以可以得知Daemon進程的情況。 關于重啟的原因是為了重新建立互動的關系。

    問題7:為何單殺daemon,會牽連app_d進程被殺,這是什么原理?

    解答:從殺進程的日志上來是調用killProcessGroup()殺進程,可事實上adb只調用kill -9 pid的方式,單殺一個進程,怎么就牽連了app_d進程。 這是由于當daemon進程被殺后,死亡回調會回來后,在binderDied()的過程執行了killProcessGroup()。

    如果從Linux內核層面,研究過Binder死亡回調機制的童鞋,到這里還就會有想到一個新的疑問如下。

    問題8:app_d是由daemon進程間接fork出來的, 會共享binder fd,所以即便daemon進程被殺,死亡回調也不會觸發,這又是何觸發的呢?

    解答:由于app_d進程被fork后,馬上執行了exec()系的函數, 而在ProcessState打開Binder驅動的時候, 有一個非常重要的flag, 那就是O_CLOEXEC。

    采用O_CLOEXEC方式打開的問題,當新創建的進程調用exec()函數成功后,文件描述符會自動關閉, 代碼如下:

    6.7 剖根問底

    問題9:TIM到底對Binder框架做了什么級別的修改?這4個互保進程,既然callingPid=0,有沒有辦法知道到底是由誰拉起誰的?

    前面既然說了,TIM強行修改了ONEWAY的方式??梢匀サ粼揻lags, 為了調試,這里就針對TIM,并且code=34(即START_SERVICE_TRANSACTION), 并且修改flag的case下:

    從實驗結果來看,通過修改IPCThreadState.cpp代碼, 完成control住了 TIM的所有修改, 這里可以說明:

    TIM分別在Java層和Native層,主動向ServiceManager進程查詢AMS后,獲取BpActivityManager代理對象,然后繼續使用框架中的IPCThreadState跟Binder驅動交互,并沒有替換掉libbinder.so。

    其實,還可以更高級的玩法,連IPCThreadState這些框架通信代碼也不使用, 徹底地去自定義Binder交互代碼,類似于servicemanager的方式??梢宰约悍庋bioctl(),直接talkWithDriver。TIM保活還有改進空間, 提供保活變種方案,這樣的話,上面的調試代碼也攔截不了其對flags修改為ONEWAY的過程。 即使如此,一切都在Control之中, 完全可以在Binder Driver中攔截再定位其策略, 玩得再高級也主要活動在用戶態, 內核態的策略還是相對安全的, 此所謂“魔高一座,道高一尺”。

    另外,通過增加上面的臨時代碼,再次多次實驗對比,可以得出如下關系圖:

    二度fork是指前面介紹了,fork后再fork,然后托孤,無論如何跟最初的進程都屬于同一個group,有著級聯被殺關系。

    • 1)殺掉Daemon進程,則MSF進程觀察到會去拉起Daemon進程; 同時app_d1因為同一個group而被殺,則app_d2進程觀察到也拉起Daemon進程,這就是雙保險;
    • 2)殺掉app_d1進程, 則app_d2進程觀察到會拉起MSF進程;
    • 3)直接force-stop進程, 則6個進程都會被殺,只是殺的過程并非所有進程同一時刻點被殺,而是有前后順序,所以造成能自啟。

    6.8 分析思路歸納

    我們來回顧一下上面的過程:

    • 1)先有了初步分析過程中對一些常規套路的可能性的排除,并嗅到callingPid=0的異常舉動;
    • 2)沿著蛛絲馬跡,不斷反復嘗試殺進程,從中尋找更多的規律,不斷地向自己提出疑問;
    • 3)結合signal,strace, traces,ps,binder,linux,kill等技能 不斷地解答自己的疑惑。

    解系統層的問題,更像是偵探破案的感覺,要有敏銳的嗅覺,抓住蛛絲馬跡,加上”大膽猜想,小心驗證“ , 終究能找到案件的真相。 此所謂”點動成線,線動成面,面動成體“, 從零星的點滴勾畫出全方面立體化的真相。

    歸納下,主要提出過這些疑惑:

    • 問題1:安全中心已配置了禁止TIM的自啟動, 并且安全中心和Whetstone都有對進程自啟動以及級聯啟動的嚴格限制, 為何會有漏網之魚?
    • 問題2:startService()的callingPid怎么可能等于0?
    • 問題3:進程內的名叫“Thread-89”的線程具有什么特點,如何做到把進程殺掉?
    • 問題4:為何需要4個indicator文件?
    • 問題5: 這4個進程到達是什么如何相互監聽的呢?
    • 問題6: app_d到底是如何創建出來?又是如何成為init進程的子進程的?
    • 問題7:為何單殺daemon,會牽連app_d進程被殺,這是什么原理?
    • 問題8:app_d是由daemon進程間接fork出來的, 會共享binder fd,所以即便daemon進程被殺,死亡回調也不會觸發,這又是何觸發的呢?
    • 問題9:TIM到底對Binder框架做了什么級別的修改?這4個互保進程,既然callingPid=0,有沒有辦法知道到底是由誰拉起誰的?

    7、本文總結

    總結一下TIM的?;罴夹g要點,我們可以得出以下經驗:

    • 1)通過flock的文件排它鎖方式來監聽進程存活狀態
    • 1.1)先采用一對普通的進程Daemon和MSF相互監聽文件的方式來獲得對方進程是否存活的狀態;
    • 1.2)同時再采用一對退孤給init進程的app_d進程相互監聽文件的方式來獲得對方進程是否存活的狀態; 而這兩個進程都有間接由Daemon和MSF進程所fork而來;雙重保險。
    • 2)不采用系統框架中startService的Binder框架代碼,而是自身在Native層通過自己去查詢獲取BpActivityManager代理對象, 然后自己實現startService接口,并修改為ONEWAY的binder調用,既增加分析問題的難度,也進一步隱藏自身策略;
    • 3)當監聽進程死亡,則通過自身實現的StartService的Binder call去拉起對方進程,系統對于這種方式啟動進程并沒有攔截機制。

    這種flock的方式至少比網上常說的通過循環監聽的方式,要強很多。

    比往常的互保更厲害的是TIM共有6個進程(說明:使用過程也還會創建一些進程),其中4個進程,形成兩組互動進程,其中一組利用Linux進程托孤原理,可謂是隱藏得很深來互保,進一步確保進程永生。

    當然,進程收到signal信號后,如果恰巧這四個進程在同一個時刻點退出,那么還是有概率會被殺。 

    不走系統框架代碼,自己去實現啟動服務的binder call也是一大亮點,不過還有更高級的玩法,直接封裝ioctl跟驅動交互。之前針對這個問題,做過反?;罘桨福髞頌榱四承┕δ芫壒视址砰_對這個的限制,這里就不再繼續展開了。

    附錄:有關IM/推送的進程?;?網絡?;罘匠傻奈恼聟R總

    應用?;罱K極總結(一):Android6.0以下的雙進程守護?;顚嵺`

    應用?;罱K極總結(二):Android6.0及以上的?;顚嵺`(進程防殺篇)

    應用?;罱K極總結(三):Android6.0及以上的保活實踐(被殺復活篇)

    Android進程保活詳解:一篇文章解決你的所有疑問

    Android端消息推送總結:實現原理、心跳?;?、遇到的問題等

    深入的聊聊Android消息推送這件小事

    為何基于TCP協議的移動端IM仍然需要心跳保活機制?

    微信團隊原創分享:Android版微信后臺保活實戰分享(進程?;钇?

    微信團隊原創分享:Android版微信后臺?;顚崙鸱窒?網絡?;钇?

    移動端IM實踐:實現Android版微信的智能心跳機制

    移動端IM實踐:WhatsApp、Line、微信的心跳策略分析

    Android P正式版即將到來:后臺應用保活、消息推送的真正噩夢

    全面盤點當前Android后臺?;罘桨傅恼鎸嵾\行效果(截止2019年前)

    一文讀懂即時通訊應用中的網絡心跳包機制:作用、原理、實現思路等

    融云技術分享:融云安卓端IM產品的網絡鏈路?;罴夹g實踐

    正確理解IM長連接的心跳及重連機制,并動手實現(有完整IM源碼)

    2020年了,Android后臺?;钸€有戲嗎?看我如何優雅的實現!

    史上最強Android保活思路:深入剖析騰訊TIM的進程永生技術

    >> 更多同類文章 ……

    (本文同步發布于:http://www.52im.net/thread-2893-1-1.html



    作者:Jack Jiang (點擊作者姓名進入Github)
    出處:http://www.52im.net/space-uid-1.html
    交流:歡迎加入即時通訊開發交流群 215891622
    討論:http://www.52im.net/
    Jack Jiang同時是【原創Java Swing外觀工程BeautyEye】【輕量級移動端即時通訊框架MobileIMSDK】的作者,可前往下載交流。
    本博文 歡迎轉載,轉載請注明出處(也可前往 我的52im.net 找到我)。


    只有注冊用戶登錄后才能發表評論。


    網站導航:
     
    Jack Jiang的 Mail: jb2011@163.com, 聯系QQ: 413980957, 微信: hellojackjiang
    主站蜘蛛池模板: 亚洲国产精品线观看不卡| jizz免费一区二区三区| 久操免费在线观看| 日本免费观看网站| 亚洲精品在线免费观看视频| caoporm碰最新免费公开视频| 免费做爰猛烈吃奶摸视频在线观看| 国产亚洲A∨片在线观看| 亚洲av日韩av永久在线观看| 67194成手机免费观看| 亚洲中文字幕无码一区二区三区| 亚洲综合欧美色五月俺也去| 99re6热视频精品免费观看| 国产午夜亚洲精品理论片不卡| 亚洲国产aⅴ成人精品无吗| 在线观看www日本免费网站| 国产亚洲综合色就色| 黄色三级三级三级免费看| AV免费网址在线观看| 337p欧洲亚洲大胆艺术| 中文字幕免费不卡二区| 亚洲日韩在线观看| 国产AV日韩A∨亚洲AV电影| 在线免费观看a级片| 亚洲av无码国产综合专区| 99久久免费观看| 亚洲AV永久精品爱情岛论坛| 久久久久免费视频| 亚洲欧洲中文日韩久久AV乱码| 亚洲成a人无码亚洲成www牛牛 | 亚洲国产成人久久综合| 思思re热免费精品视频66 | 中文字幕无码视频手机免费看 | 亚洲一区二区三区乱码A| 国产亚洲午夜精品| 国产又大又粗又硬又长免费| 亚洲精品久久无码| 午夜时刻免费入口| 亚洲国产精品成人午夜在线观看 | 免费无码又爽又刺激高潮的视频 | 婷婷亚洲天堂影院|