本文分析基于Android S(12)
當App發(fā)生ANR或是System觸發(fā)watchdog時掸掸,系統(tǒng)都希望生成一份trace文件采盒,用來記錄各個線程的調(diào)用棧信息背伴,以及一些進程/線程的狀態(tài)信息树姨。這份文件通常存放在/data/anr
目錄下蹈集,APP開發(fā)者拿不到。不過從Android R(11)開始犀暑,App便可以通過AMS的getHistoricalProcessExitReasons
接口讀取該文件的詳細信息驯击。以下是一份典型trace文件中的內(nèi)容。
----- pid 8331 at 2021-11-26 09:10:03 -----
Cmd line: com.hangl.test
Build fingerprint: xxx
ABI: 'arm64'
Build type: optimized
Zygote loaded classes=9118 post zygote classes=475
Dumping registered class loaders
#0 dalvik.system.PathClassLoader: [], parent #1
#1 java.lang.BootClassLoader: [], no parent
...
(進程整體的一些狀態(tài)耐亏,譬如GC的統(tǒng)計數(shù)據(jù))
suspend all histogram: Sum: 161us 99% C.I. 2us-60us Avg: 16.100us Max: 60us
DALVIK THREADS (14):
"Signal Catcher" daemon prio=5 tid=7 Runnable
| group="system" sCount=0 dsCount=0 flags=0 obj=0x14dc0298 self=0x7c4c962c00
...
"main" prio=5 tid=1 Native
| group="main" sCount=1 dsCount=0 flags=1 obj=0x7263ee78 self=0x7c4c7dcc00
| sysTid=8331 nice=-10 cgrp=default sched=0/0 handle=0x7c4dd45ed0
| state=S schedstat=( 387029514 32429484 166 ) utm=28 stm=10 core=6 HZ=100
| stack=0x7feacb5000-0x7feacb7000 stackSize=8192KB
| held mutexes=
native: #00 pc 00000000000d0f48 /apex/com.android.runtime/lib64/bionic/libc.so (__epoll_pwait+8)
native: #01 pc 00000000000180bc /system/lib64/libutils.so (android::Looper::pollInner(int)+144)
native: #02 pc 0000000000017f8c /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+56)
native: #03 pc 000000000013b920 /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44)
at android.os.MessageQueue.nativePollOnce(Native method)
at android.os.MessageQueue.next(MessageQueue.java:336)
at android.os.Looper.loop(Looper.java:174)
at android.app.ActivityThread.main(ActivityThread.java:7397)
at java.lang.reflect.Method.invoke(Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:935)
"Jit thread pool worker thread 0" daemon prio=5 tid=2 Native
| group="main" sCount=1 dsCount=0 flags=1 obj=0x14dc0220 self=0x7bb9a05000
...
本文既無意于討論ANR的觸發(fā)類型徊都,也無意于流水賬式地展示每塊內(nèi)容的生成順序,因為這些已經(jīng)有不少文章寫過广辰,而且其中不乏精品暇矫。鑒于此,本文將重點分析調(diào)用棧的生成流程择吊,而它將幫助我們更好地理解trace信息李根。
前言
不論是ANR還是Watchdog,trace的生成過程都放在target process中進行几睛。以ANR為例房轿,它的判定過程發(fā)生在system_server(AMS)中,而trace的生成過程卻發(fā)生在APP中所森。那么如何讓APP開始這個過程呢囱持?答案是給它發(fā)送SIGQUIT(信號3)。之所以這樣處理必峰,是因為跨進程的信息收集通常采用ptrace方案洪唐,它要求收集方要么擁有特殊權(quán)限钻蹬,要么滿足進程間父子關(guān)系吼蚁,而這些都沒有進程內(nèi)收集方便。
因此问欠,分析的第一步便是去查看信號3在進程中的處理方式肝匆。
1. Signal Catcher線程
"Signal Catcher"線程在每個Java進程中都會存在。正常運行時顺献,它將掛起等待信號3(以及信號10)的到來旗国。當該進程接收到信號3時,將會交由"Signal Catcher"線程處理注整,處理的函數(shù)為HandleSigQuit
能曾。
void SignalCatcher::HandleSigQuit() {
Runtime* runtime = Runtime::Current();
std::ostringstream os;
os << "\n"
<< "----- pid " << getpid() << " at " << GetIsoDate() << " -----\n";
DumpCmdLine(os);
// Note: The strings "Build fingerprint:" and "ABI:" are chosen to match the format used by
// debuggerd. This allows, for example, the stack tool to work.
std::string fingerprint = runtime->GetFingerprint();
os << "Build fingerprint: '" << (fingerprint.empty() ? "unknown" : fingerprint) << "'\n";
os << "ABI: '" << GetInstructionSetString(runtime->GetInstructionSet()) << "'\n";
os << "Build type: " << (kIsDebugBuild ? "debug" : "optimized") << "\n";
runtime->DumpForSigQuit(os);
if ((false)) {
std::string maps;
if (android::base::ReadFileToString("/proc/self/maps", &maps)) {
os << "/proc/self/maps:\n" << maps;
}
}
os << "----- end " << getpid() << " -----\n";
Output(os.str());
}
中間的跳轉(zhuǎn)過程就不展示了度硝,直接進入我們關(guān)心的議題:調(diào)用棧的收集過程。通過ThreadList::Dump
函數(shù)寿冕,我們可以收集所有線程的調(diào)用棧信息蕊程。
void ThreadList::Dump(std::ostream& os, bool dump_native_stack) {
Thread* self = Thread::Current();
{
MutexLock mu(self, *Locks::thread_list_lock_);
os << "DALVIK THREADS (" << list_.size() << "):\n";
}
if (self != nullptr) {
DumpCheckpoint checkpoint(&os, dump_native_stack);
size_t threads_running_checkpoint;
{
// Use SOA to prevent deadlocks if multiple threads are calling Dump() at the same time.
ScopedObjectAccess soa(self);
threads_running_checkpoint = RunCheckpoint(&checkpoint);
}
if (threads_running_checkpoint != 0) {
checkpoint.WaitForThreadsToRunThroughCheckpoint(threads_running_checkpoint);
}
} else {
DumpUnattachedThreads(os, dump_native_stack);
}
}
其中關(guān)鍵的環(huán)節(jié)是執(zhí)行RunCheckpoint函數(shù)。它將每個線程的信息收集分為單獨的任務(wù):**如果該線程正處在Runnable狀態(tài)(運行java代碼)驼唱,則將收集的任務(wù)派發(fā)給線程自己處理藻茂;如果該線程處于其他狀態(tài),則由"Signal Catcher"線程代為完成玫恳。**請記住這句話辨赐,因為下面2、3小節(jié)分析的就是它的兩種不同情況京办。
2. Checkpoint機制
將任務(wù)派發(fā)給Runnable狀態(tài)的線程采用的是checkpoint機制掀序,它分為兩個部分:
-
"Signal Catcher"線程調(diào)用RequestCheckpoint去改變目標線程的art::Thread對象的內(nèi)部數(shù)據(jù),具體而言改變的是以下兩個字段惭婿。
tls32_.state_and_flags.as_struct.flags |= kCheckpointRequest; tlsPtr_.checkpoint_function = function; (tls32_和tlsPtr_均是art::Thread對象的內(nèi)部數(shù)據(jù))
對ART虛擬機而言森枪,目標線程在每個方法的起始位置和循環(huán)語句的跳轉(zhuǎn)位置都會去檢查state_and_flags字段,如果checkpoint位被置上审孽,則執(zhí)行相應(yīng)的checkpoint函數(shù)县袱。這樣安插檢查點可以保證線程”及時“地處理checkpoint任務(wù):因為所有向前執(zhí)行(直線型、帶條件分支都算)的代碼都會在有限時間內(nèi)執(zhí)行完佑力,而可能導(dǎo)致長時間執(zhí)行的代碼式散,要么是循環(huán),要么是方法調(diào)用打颤,所以只要在這兩個地方插入檢查點就可以保證及時性了暴拄。(參考R大知乎回答)
關(guān)于目標線程的檢查點,這里我還想舉個例子编饺,讓大家真切地感受到它的存在乖篷。
字節(jié)碼在ART虛擬機中可以解釋執(zhí)行,也可以編譯成機器碼執(zhí)行透且。當一個方法被編譯為機器碼后(如下所示)撕蔼,我們可以在函數(shù)的入口處看到有檢測state_and_flags的操作。當有標志位被置上時秽誊,則執(zhí)行pTestSuspend動作鲸沮。
CODE: (code_offset=0x003f9ae0 size=788)...
0x003f9ae0: d1400bf0 sub x16, sp, #0x2000 (8192)
0x003f9ae4: b940021f ldr wzr, [x16]
StackMap[0] (native_pc=0x3f9ae8, dex_pc=0x0, register_mask=0x0, stack_mask=0b)
0x003f9ae8: f8180fe0 str x0, [sp, #-128]!
0x003f9aec: a9035bf5 stp x21, x22, [sp, #48]
0x003f9af0: a90463f7 stp x23, x24, [sp, #64]
0x003f9af4: a9056bf9 stp x25, x26, [sp, #80]
0x003f9af8: a90673fb stp x27, x28, [sp, #96]
0x003f9afc: a9077bfd stp x29, lr, [sp, #112]
0x003f9b00: b9008fe2 str w2, [sp, #140]
0x003f9b04: 79400270 ldrh w16, [tr] ; state_and_flags
0x003f9b08: 350016f0 cbnz w16, #+0x2dc (addr 0x3f9de4) //如果state_and_flags不為0,則跳轉(zhuǎn)到0x3f9de4的位置
...
0x003f9de4: 940e62c3 bl #+0x398b0c (addr 0x7928f0) ; pTestSuspend //跳轉(zhuǎn)進入pTestSuspend
幾經(jīng)跳轉(zhuǎn)锅论,pTestSuspend最終會調(diào)用Thread::CheckSuspend函數(shù)讼溺。當checkpoint位被置上時,則執(zhí)行相應(yīng)的checkpoint函數(shù)(RunCheckpointFunction)最易。
inline void Thread::CheckSuspend() {
DCHECK_EQ(Thread::Current(), this);
for (;;) {
if (ReadFlag(kCheckpointRequest)) {
RunCheckpointFunction();
} else if (ReadFlag(kSuspendRequest)) {
FullSuspendCheck();
} else if (ReadFlag(kEmptyCheckpointRequest)) {
RunEmptyCheckpoint();
} else {
break;
}
}
}
下面舉一個Runnable線程自己收集調(diào)用棧的例子怒坯,2292行正好是writeNoException方法的第一行炫狱,與上述"在每個方法的起始位置插入檢查點"的描述相吻合。
"Binder:2278_C" prio=5 tid=97 Runnable
| group="main" sCount=0 ucsCount=0 flags=0 obj=0x16104b20 self=0xb400007117c7afb0
| sysTid=2890 nice=0 cgrp=foreground sched=0/0 handle=0x6eafe24cb0
| state=R schedstat=( 47445156223 266433061959 175792 ) utm=1623 stm=3121 core=4 HZ=100
| stack=0x6eafd2d000-0x6eafd2f000 stackSize=991KB
| held mutexes= "mutator lock"(shared held)
at android.os.Parcel.writeNoException(Parcel.java:2292)
at android.os.IPowerManager$Stub.onTransact(IPowerManager.java:474)
at android.os.Binder.execTransactInternal(Binder.java:1184)
at android.os.Binder.execTransact(Binder.java:1143)
2291 public final void writeNoException() {
2292 AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);
3. Suspend標志位
對于那些非Runnable狀態(tài)的線程剔猿,收集的工作由"Signal Catcher"代為完成毕荐。這里我梳理了一下為單個線程”代工“的流程,總共分為4步艳馒。
thread->ModifySuspendCount(self, +1, nullptr, SuspendReason::kInternal);
checkpoint_function->Run(thread);
thread->ModifySuspendCount(self, -1, nullptr, SuspendReason::kInternal);
Thread::resume_cond_->Broadcast(self);
- 增加target thread的suspend count(+1)憎亚,且給它置上suspend標志位。
- 運行相應(yīng)的函數(shù)替target thread收集信息弄慰。
- 減少target thread的suspend count(-1)第美,如果suspend count減為0,則清除suspend標志位陆爽。
- 調(diào)用resume_cond_條件變量的Broadcast函數(shù)什往,它會喚醒所有等待在它上面的線程。
流程的梳理總是簡單慌闭,難的是理解流程設(shè)計背后的原因别威,下面來條分縷析。
-
為什么在執(zhí)行信息收集前需要給target thread置上suspend標志位驴剔?
在回答這個問題前省古,我們需要補充些基礎(chǔ)知識。每個Java線程本質(zhì)上都是一個pthread線程丧失,而它在內(nèi)核中又對應(yīng)一個task_struct對象豺妓,該對象是CPU調(diào)度的基本單元。從CPU的視角來看布讹,該線程可以是R態(tài)琳拭、S態(tài)、D態(tài)等描验,它們的含義如下所示白嘁。不過虛擬機中又為Java線程記錄了另一套狀態(tài),它反映的是虛擬機視角下的狀態(tài)膘流,具體分類如下絮缅。
R running or runnable (on run queue) D uninterruptible sleep (usually IO) S interruptible sleep (waiting for an event to complete)
enum ThreadState { // Java // Thread.State JDWP state kTerminated = 66, // TERMINATED TS_ZOMBIE Thread.run has returned, but Thread* still around kRunnable, // RUNNABLE TS_RUNNING runnable kTimedWaiting, // TIMED_WAITING TS_WAIT in Object.wait() with a timeout kSleeping, // TIMED_WAITING TS_SLEEPING in Thread.sleep() kBlocked, // BLOCKED TS_MONITOR blocked on a monitor kWaiting, // WAITING TS_WAIT in Object.wait() kWaitingForLockInflation, // WAITING TS_WAIT blocked inflating a thin-lock kWaitingForTaskProcessor, // WAITING TS_WAIT blocked waiting for taskProcessor kWaitingForGcToComplete, // WAITING TS_WAIT blocked waiting for GC kWaitingForCheckPointsToRun, // WAITING TS_WAIT GC waiting for checkpoints to run kWaitingPerformingGc, // WAITING TS_WAIT performing GC kWaitingForDebuggerSend, // WAITING TS_WAIT blocked waiting for events to be sent kWaitingForDebuggerToAttach, // WAITING TS_WAIT blocked waiting for debugger to attach kWaitingInMainDebuggerLoop, // WAITING TS_WAIT blocking/reading/processing debugger events kWaitingForDebuggerSuspension, // WAITING TS_WAIT waiting for debugger suspend all kWaitingForJniOnLoad, // WAITING TS_WAIT waiting for execution of dlopen and JNI on load code kWaitingForSignalCatcherOutput, // WAITING TS_WAIT waiting for signal catcher IO to complete kWaitingInMainSignalCatcherLoop, // WAITING TS_WAIT blocking/reading/processing signals kWaitingForDeoptimization, // WAITING TS_WAIT waiting for deoptimization suspend all kWaitingForMethodTracingStart, // WAITING TS_WAIT waiting for method tracing to start kWaitingForVisitObjects, // WAITING TS_WAIT waiting for visiting objects kWaitingForGetObjectsAllocated, // WAITING TS_WAIT waiting for getting the number of allocated objects kWaitingWeakGcRootRead, // WAITING TS_WAIT waiting on the GC to read a weak root kWaitingForGcThreadFlip, // WAITING TS_WAIT waiting on the GC thread flip (CC collector) to finish kNativeForAbort, // WAITING TS_WAIT checking other threads are not run on abort. kStarting, // NEW TS_WAIT native thread started, not yet ready to run managed code kNative, // RUNNABLE TS_RUNNING running in a JNI native method kSuspended, // RUNNABLE TS_RUNNING suspended by GC or debugger };
一個處于R態(tài)的線程表明它邏輯上正在運行(由于調(diào)度的關(guān)系,他可能暫時未被執(zhí)行睡扬,但總會被執(zhí)行[一定時間內(nèi)])盟蚣,而它運行的代碼可能位于kernel層黍析,native層或java層卖怜。只有當它運行在java層時,虛擬機中記載的狀態(tài)才是Runnable阐枣。
如果target thread處于非Runnable狀態(tài)马靠,也就意味它并未處于java層奄抽。可是不處在java層并不表示它不運行甩鳄。在"Signal Catcher"代理target thread進行收集的過程中逞度,target thread隨時可能返回java層(結(jié)束了native層的工作或是發(fā)起了對java方法的調(diào)用)。一旦返回java層妙啃,java層的調(diào)用棧形態(tài)就會被改變档泽。這樣"Signal Catcher"和target thread之間對于調(diào)用棧整體形態(tài)就會存在競爭關(guān)系。
因此我們需要一種方案去解決這種競爭揖赴。
所有返回到j(luò)ava層的操作都需要進行線程狀態(tài)切換馆匿,也即調(diào)用TransitionFromSuspendedToRunnable函數(shù)。該函數(shù)內(nèi)部會判斷suspend標志位燥滑,一旦它被置上渐北,target thread就會等待在resume_cond_條件變量上。因此铭拧,置上suspend標志位可以保證target thread無法返回java層赃蛛,也即無法改變java層的調(diào)用棧形態(tài)。(值得注意的是搀菩,網(wǎng)上有些言論認為置上suspend標志位是為了暫停線程呕臂,這其實是一種不嚴謹?shù)恼J識。對于不想返回java層的線程而言肪跋,置上suspend標志位絲毫不影響它的運行诵闭。)
-
為什么需要在信息收集結(jié)束后調(diào)用resume_cond_條件變量的Broadcast函數(shù)?
因為有些準備返回java層的線程此時正等待在resume_cond_條件變量上(處于S態(tài))澎嚣,當執(zhí)行完收集操作后疏尿,我們有必要喚醒它們讓它們繼續(xù)工作。
-
分析了這么多易桃,舉個實際案例吧褥琐。通過native的#2可以知道,主線程已經(jīng)結(jié)束了native層的工作晤郑,希望返回到j(luò)ava層敌呈。不過從堆棧中我們找不到TransitionFromSuspendedToRunnable的身影,原因是它被inline(內(nèi)聯(lián))到GoToRunnable函數(shù)內(nèi)部了造寝。而#1 WaitingHoldingLocks等待的就是resume_cond_條件變量磕洪。
"main" prio=5 tid=1 Native | group="main" sCount=1 ucsCount=0 flags=1 obj=0x71a33c18 self=0xb400006f417a1380 | sysTid=14756 nice=-10 cgrp=top-app sched=0/0 handle=0x71027344f8 | state=S schedstat=( 603683604122 79803215759 1916541 ) utm=43513 stm=16854 core=6 HZ=100 | stack=0x7fe8361000-0x7fe8363000 stackSize=8188KB | held mutexes= native: #00 pc 000000000004dff0 /apex/com.android.runtime/lib64/bionic/libc.so (syscall+32) native: #01 pc 000000000028dc74 /apex/com.android.art/lib64/libart.so (art::ConditionVariable::WaitHoldingLocks(art::Thread*)+152) native: #02 pc 000000000074c4ec /apex/com.android.art/lib64/libart.so (art::GoToRunnable(art::Thread*)+412) native: #03 pc 000000000074c318 /apex/com.android.art/lib64/libart.so (art::JniMethodEnd(unsigned int, art::Thread*)+28) at android.os.BinderProxy.transactNative(Native method) at android.os.BinderProxy.transact(BinderProxy.java:571) at com.android.internal.telephony.ISub$Stub$Proxy.getAvailableSubscriptionInfoList(ISub.java:1543) at android.telephony.SubscriptionManager.getAvailableSubscriptionInfoList(SubscriptionManager.java:1640)
不過需要注意,這種trace只會在如下的時序條件下生成诫龙。如果運行在native層的函數(shù)沒有結(jié)束析显,那么也就不需要返回java層,同時也就不會調(diào)用GoToRunnable签赃。
因此谷异,當我們發(fā)生ANR時看到主線程的調(diào)用棧如上所示分尸,千萬不要認為GoToRunnable才是ANR的元兇。它僅僅表明線程在執(zhí)行過程中希望回到j(luò)ava層歹嘹,而真正導(dǎo)致ANR的可能是一個消息的整體耗時箩绍。
4. Java調(diào)用棧收集
(這一小節(jié)較為粗略,如無興趣可跳過)
通過調(diào)用StackDumpVisitor::WalkStack函數(shù)尺上,我們可以收集到j(luò)ava層的調(diào)用棧信息材蛛。這個函數(shù)的內(nèi)部比較復(fù)雜,想要完整的了解還要補充ArtMethod及DexFile等一系列知識怎抛。本文不打算完整介紹仰税,只是概括性地總結(jié)。
機器碼的每條指令都有編號抽诉,它在運行時表現(xiàn)為PC值陨簇。同樣,Dex字節(jié)碼的每條指令也有編號迹淌,它在Dex文件中表現(xiàn)為dex_pc(每個方法的dex_pc都從0開始編號)河绽。譬如下方文件中的0x0003、0x0008等就是dex_pc唉窃。
DEX CODE:
0x0000: 7010 5350 0100 | invoke-direct {v1}, void android.media.IPlayer$Stub.<init>() // method@20563
0x0003: 2200 791f | new-instance v0, java.lang.ref.WeakReference // type@TypeIndex[8057]
0x0005: 7020 84fa 2000 | invoke-direct {v0, v2}, void java.lang.ref.WeakReference.<init>(java.lang.Object) // method@64132
0x0008: 5b10 582e | iput-object v0, v1, Ljava/lang/ref/WeakReference; android.media.PlayerBase$IPlayerWrapper.mWeakPB // field@11864
0x000a: 0e00 | return-void
字節(jié)碼在實際運行時耙饰,可能被解釋執(zhí)行,也可能被編譯成機器碼執(zhí)行(AOT或JIT)纹份,而這兩種執(zhí)行方式的調(diào)用椆豆颍回溯方法是不同的。原因是機器碼執(zhí)行時蔓涧,java方法在棧幀結(jié)構(gòu)上表現(xiàn)得就像純native方法(S上引入了新的解釋器nterp件已,它的棧幀結(jié)構(gòu)和機器碼執(zhí)行也是一致的,因此性能比之前的mterp更好)元暴;而解釋執(zhí)行(這里指mterp解釋器)會有專門的數(shù)據(jù)結(jié)構(gòu)來記錄dex_pc值篷扩。
當我們希望回溯出一幀java調(diào)用棧信息時,其實是想得到三個信息:方法名茉盏,文件名以及行號(至于鎖信息鉴未,它并非每一幀都有,因此屬于另一個話題鸠姨,這里不做闡述)铜秆。
at android.os.Looper.loop(Looper.java:174)
想要得到這三個信息,其實依賴的數(shù)據(jù)也有三個:ArtMethod對象讶迁、DexFile信息及dex_pc值连茧。由于DexFile信息可以通過ArtMethod間接得到,因此我們在回溯的過程中,主要目的就是為每一幀尋找它的ArtMehtod對象和dex_pc值梅屉。
這個尋找對于解釋執(zhí)行來說很簡單值纱,因為解釋執(zhí)行會有專門的數(shù)據(jù)結(jié)構(gòu)來記錄它鳞贷,這個特定的數(shù)據(jù)結(jié)構(gòu)就是ShadowFrame坯汤。
可是對于機器碼執(zhí)行來說,問題就變得復(fù)雜了很多搀愧。好在每一幀的機器碼執(zhí)行都遵循一個規(guī)律:棧頂存放當前執(zhí)行方法的ArtMethod指針惰聂。因此當一連串的方法調(diào)用發(fā)生時,我們可以僅憑最后一幀的sp值就解析出所有信息咱筛,原理如下:
- 通過sp值搓幌,我們兩次解引用可以獲得當前運行方法的ArtMethod對象。
- 通過ArtMethod進一步獲取FrameInfo迅箩,其中可知frame size溉愁。
- sp+frame size便可以得知上一幀的sp值。
- 通過上一幀的sp也可以獲知返回地址的值饲趋,通常存于x30寄存器拐揭,方法調(diào)用時會壓入棧中固定偏移的位置。
因此奕塑,我們可以獲取每一幀的ArtMethod對象及pc值(最頂上的一幀要么是native方法堂污,要么是runtime方法,都不需要恢復(fù)行號)龄砰。通過如下方法盟猖,便可以進一步得到dex_pc值,這樣每一幀的詳細信息便可以解析出來换棚。
uint32_t StackVisitor::GetDexPc(bool abort_on_failure) const {
if (cur_shadow_frame_ != nullptr) {
return cur_shadow_frame_->GetDexPC();
} else if (cur_quick_frame_ != nullptr) {
if (IsInInlinedFrame()) {
return current_inline_frames_.back().GetDexPc();
} else if (cur_oat_quick_method_header_ == nullptr) {
return dex::kDexNoIndex;
} else if ((*GetCurrentQuickFrame())->IsNative()) {
return cur_oat_quick_method_header_->ToDexPc(
GetCurrentQuickFrame(), cur_quick_frame_pc_, abort_on_failure);
} else if (cur_oat_quick_method_header_->IsOptimized()) {
StackMap* stack_map = GetCurrentStackMap();
DCHECK(stack_map->IsValid());
return stack_map->GetDexPc();
} else {
DCHECK(cur_oat_quick_method_header_->IsNterpMethodHeader());
return NterpGetDexPC(cur_quick_frame_);
}
} else {
return 0;
}
}
不過我們上述的描述中還省略了一種情況式镐,也即java inline的情況,它在unwind的過程中也是耗時的大戶固蚤。
5. Native調(diào)用棧收集
在正常的trace生成過程中碟案,一個線程的native調(diào)用棧是否收集取決于以下函數(shù)的判斷,如下序號表示判斷優(yōu)先級颇蜡。
static bool ShouldShowNativeStack(const Thread* thread)
REQUIRES_SHARED(Locks::mutator_lock_) {
ThreadState state = thread->GetState();
// In native code somewhere in the VM (one of the kWaitingFor* states)? That's interesting.
if (state > kWaiting && state < kStarting) {
return true;
}
// In an Object.wait variant or Thread.sleep? That's not interesting.
if (state == kTimedWaiting || state == kSleeping || state == kWaiting) {
return false;
}
// Threads with no managed stack frames should be shown.
if (!thread->HasManagedStack()) {
return true;
}
// In some other native method? That's interesting.
// We don't just check kNative because native methods will be in state kSuspended if they're
// calling back into the VM, or kBlocked if they're blocked on a monitor, or one of the
// thread-startup states if it's early enough in their life cycle (http://b/7432159).
ArtMethod* current_method = thread->GetCurrentMethod(nullptr);
return current_method != nullptr && current_method->IsNative();
}
當state是虛擬機相關(guān)的狀態(tài)時价说,需要收集native調(diào)用棧。那什么是虛擬機相關(guān)的狀態(tài)呢风秤?譬如kWaitingForGcToComplete鳖目,它表示當前線程在等待GC結(jié)束。因此我們可以理解這些狀態(tài)是因為虛擬機自身工作而影響到本線程運行的狀態(tài)缤弦。
如果state是Waiting或Sleeping相關(guān)的狀態(tài)時领迈,則省略native調(diào)用棧的收集。因為處于此狀態(tài)的線程,其native層的調(diào)用棧最終必然為futex系統(tǒng)調(diào)用狸捅,因此輸出這些調(diào)用棧并不會給調(diào)試帶來有價值的信息衷蜓,故可省略。
如果該線程沒有java層調(diào)用棧信息尘喝,則需要收集native調(diào)用棧磁浇,否則沒有任何信息可輸出。
如果java層調(diào)用棧的最后一幀為native方法朽褪,則需要收集native調(diào)用棧置吓,以便了解native層具體的動作。
接下來就要討論如何收集native調(diào)用棧缔赠,這個過程的專業(yè)術(shù)語叫做回溯或是展開(unwind)故黑,在Android中主要通過libunwindstack這個庫來完成果善。
收集native調(diào)用棧逝撬,本質(zhì)上是尋找每一幀的pc值宠蚂。當我們拿到最后一幀的sp值后,便可以通過尋找返回地址的方式踢匣,不斷回溯出其上的每一幀pc值告匠。
因此,接下來的問題可以簡化為兩個:
- 如何尋找最后一幀的寄存器(sp/pc)值符糊?
- 如何尋找每一幀的返回地址凫海?
5.1 如何尋找最后一幀的寄存器值
寄存器信息本質(zhì)上是線程相關(guān)的,因此這里分為兩種情況來討論男娄。
- 本線程收集本線程的調(diào)用棧行贪。
- "Signal Catcher"線程收集其他線程的調(diào)用棧。
本線程獲取寄存器值較為簡單模闲,只需要一些基本的匯編指令便可以完成建瘫。譬如如下代碼,可以將32個通用寄存器的值存到用戶空間特定的數(shù)據(jù)結(jié)構(gòu)中尸折。
inline __attribute__((__always_inline__)) void AsmGetRegs(void* reg_data) {
asm volatile(
"1:\n"
"stp x0, x1, [%[base], #0]\n"
"stp x2, x3, [%[base], #16]\n"
"stp x4, x5, [%[base], #32]\n"
"stp x6, x7, [%[base], #48]\n"
"stp x8, x9, [%[base], #64]\n"
"stp x10, x11, [%[base], #80]\n"
"stp x12, x13, [%[base], #96]\n"
"stp x14, x15, [%[base], #112]\n"
"stp x16, x17, [%[base], #128]\n"
"stp x18, x19, [%[base], #144]\n"
"stp x20, x21, [%[base], #160]\n"
"stp x22, x23, [%[base], #176]\n"
"stp x24, x25, [%[base], #192]\n"
"stp x26, x27, [%[base], #208]\n"
"stp x28, x29, [%[base], #224]\n"
"str x30, [%[base], #240]\n"
"mov x12, sp\n"
"adr x13, 1b\n"
"stp x12, x13, [%[base], #248]\n"
: [base] "+r"(reg_data)
:
: "x12", "x13", "memory");
}
可如果是跨線程(不是跨進程)來獲取啰脚,該如何處理呢?
答案是通過信號实夹。當目標線程運行在用戶空間時橄浓,寄存器值是不會有備份的。只有當它發(fā)生用戶態(tài)和內(nèi)核態(tài)的切換時亮航,信息才會被備份荸实。此外,切換的過程也會檢測信號缴淋,進而觸發(fā)信號處理函數(shù)准给,因此剛剛備份的寄存器信息可以進一步傳遞給處理函數(shù)泄朴。而那里,才是我們跨線程獲取寄存器值的真正位置露氮。
Android中采用信號33(THREAD_SIGNAL)來完成這項工作祖灰,它的處理函數(shù)也比較簡單,即將sigcontext中的寄存器信息拷貝到全局數(shù)據(jù)中畔规,這樣其他線程便可以獲取它局扶。
5.2 如何尋找每一幀的返回地址
當函數(shù)調(diào)用發(fā)生時,返回地址通常存在x30寄存器(AArch64)中油讯。如果被調(diào)用者內(nèi)部需要使用這個寄存器详民,那么它的起始片段肯定要將x30的值存到棧中延欠,否則返回地址將丟失陌兑。可是x30的值到底存在棧中什么位置呢由捎?
當開啟-fno-omit-frame-pointer
編譯選項時兔综,x30存儲的位置和x29(FP寄存器)相鄰,因此很容易找出狞玛∪沓郏可是沒有此編譯選項時,x30的值就得依賴更多的信息來獲取心肪。在64位庫中锭亏,這個信息稱為"Call Frame Information",它存儲在elf文件的.eh_frame段硬鞍。微信技術(shù)團隊的一篇文章關(guān)于這點描述的比較清楚慧瘤,引用如下:
當你的代碼執(zhí)行到某一“行”時,根據(jù)此時的 pc 我們可以從"Call Frame Information"中查詢到退出當前函數(shù)棧時各個寄存器該怎么進行恢復(fù)固该,比如它可能描述了寄存器的值該從當前棧的哪個位置上讀回來锅减。
除了可以unwind純native的幀,libunwindstack庫還支持AOT/JIT的幀以及解釋執(zhí)行的幀伐坏。這也就表明怔匣,通過libunwindstack收集的調(diào)用棧除了可以反映native層調(diào)用信息,還可以反映java層的調(diào)用信息桦沉,如下示例每瞒。
#00 pc 000aa0f8 /system/lib/libart.so (void std::__1::__tree_balance_after_insert<std::__1::__tree_node_base<void*>*>(std::__1::__tree_node_base<void*>*, std::__1::__tree_balance_after_insert<std::__1::__tree_node_base<void*>*>)+32)
#01 pc 001a0a35 /system/lib/libart.so (art::gc::space::LargeObjectMapSpace::Alloc(art::Thread*, unsigned int, unsigned int*, unsigned int*, unsigned int*)+180)
#02 pc 003cd4f5 /system/lib/libart.so (art::mirror::Object* art::gc::Heap::AllocLargeObject<false, art::mirror::SetLengthVisitor>(art::Thread*, art::ObjPtr<art::mirror::Class>*, unsigned int, art::mirror::SetLengthVisitor const&)+108)
#03 pc 003cb659 /system/lib/libart.so (artAllocArrayFromCodeResolvedRegionTLAB+484)
#04 pc 00411613 /system/lib/libart.so (art_quick_alloc_array_resolved16_region_tlab+82)
#05 pc 0020cfe3 /system/framework/arm/boot-core-oj.oat (offset 0x10d000) (java.lang.AbstractStringBuilder.append+242)
#06 pc 002b809b /system/framework/arm/boot-core-oj.oat (offset 0x10d000) (java.lang.StringBuilder.append+50)
#07 pc 001199b7 /system/framework/arm/boot-core-libart.oat (offset 0x76000) (org.json.JSONTokener.nextString+214)
#08 pc 00119b73 /system/framework/arm/boot-core-libart.oat (offset 0x76000) (org.json.JSONTokener.nextValue+162)
#09 pc 001195db /system/framework/arm/boot-core-libart.oat (offset 0x76000) (org.json.JSONTokener.readObject+314)
#10 pc 00119b47 /system/framework/arm/boot-core-libart.oat (offset 0x76000) (org.json.JSONTokener.nextValue+118)
#11 pc 0040d775 /system/lib/libart.so (art_quick_invoke_stub_internal+68)
#12 pc 003e72c9 /system/lib/libart.so (art_quick_invoke_stub+224)
#13 pc 000a103d /system/lib/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+136)
#14 pc 001e60f1 /system/lib/libart.so (art::interpreter::ArtInterpreterToCompiledCodeBridge(art::Thread*, art::ArtMethod*, art::ShadowFrame*, unsigned short, art::JValue*)+236)
#15 pc 001e0bdf /system/lib/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+814)
#16 pc 003e1f23 /system/lib/libart.so (MterpInvokeVirtual+442)
#17 pc 00400514 /system/lib/libart.so (ExecuteMterpImpl+14228)
#18 pc 002613ec /system/priv-app/ReusLauncherDev/ReusLauncherDev.apk (offset 0x9c9000) (com.reus.launcher.AsusAnimationIconReceiver.a+80)
#19 pc 001c535b /system/lib/libart.so (_ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_6JValueEb.llvm.866626450+378)
#20 pc 001c9a41 /system/lib/libart.so (art::interpreter::ArtInterpreterToInterpreterBridge(art::Thread*, art::CodeItemDataAccessor const&, art::ShadowFrame*, art::JValue*)+152)
#21 pc 001e0bc7 /system/lib/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+790)
#22 pc 003e2eff /system/lib/libart.so (MterpInvokeStatic+130)
#23 pc 00400694 /system/lib/libart.so (ExecuteMterpImpl+14612)
#24 pc 0028ae7a /system/priv-app/ReusLauncherDev/ReusLauncherDev.apk (offset 0x9c9000) (com.reus.launcher.d.run+1274)
#25 pc 001c535b /system/lib/libart.so (_ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_6JValueEb.llvm.866626450+378)
#26 pc 001c9987 /system/lib/libart.so (art::interpreter::EnterInterpreterFromEntryPoint(art::Thread*, art::CodeItemDataAccessor const&, art::ShadowFrame*)+82)
#32 pc 0040d775 /system/lib/libart.so (art_quick_invoke_stub_internal+68)
#33 pc 003e72c9 /system/lib/libart.so (art_quick_invoke_stub+224)
#34 pc 000a103d /system/lib/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+136)
#36 pc 00348f6d /system/lib/libart.so (art::InvokeVirtualOrInterfaceWithJValues(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jmethodID*, jvalue*)+320)
#37 pc 00369ee7 /system/lib/libart.so (art::Thread::CreateCallback(void*)+866)
#38 pc 00072131 /system/lib/libc.so (__pthread_start(void*)+22)
#39 pc 0001e005 /system/lib/libc.so (__start_thread+24)
#24是虛擬幀,也即它并非存在于棧上纯露,而是輔助調(diào)試所增加的信息剿骨,它反映的就是#23#25這幾幀解釋器正在解釋的java方法。#05#10反映的是機器碼執(zhí)行(AOT編譯)的java方法苔埋。#00~#04反映的是純native層(so庫)的函數(shù)調(diào)用懦砂。
那么這時就有一個問題縈繞在我們心頭:libunwindstack可以收集到j(luò)ava層的調(diào)用信息,那為什么trace文件中的native調(diào)用棧僅僅顯示了native層的調(diào)用信息呢?
原因是trace文件在收集調(diào)用棧時做了截斷和省略荞膘。具體的策略如下:
-
在回溯的過程中罚随,如果碰到所屬文件后綴為oat或odex的幀,則停止回溯羽资。原因是JNI的跳板函數(shù)/AOT編譯的java方法通常都在oat/odex文件中淘菩,碰到它們停止回溯,可以略去后續(xù)java方法的回溯屠升。
backtrace_map_->SetSuffixesToIgnore(std::vector<std::string> { "oat", "odex" });
-
回溯的棧幀中如果有落在"libunwindstack.so"和"libbacktrace.so"的幀潮改,則不予顯示。原因是這些幀反映的是調(diào)用棧收集過程腹暖,而非線程原始的調(diào)用邏輯汇在。
std::vector<std::string> skip_names{"libunwindstack.so", "libbacktrace.so"};
5.3 當前調(diào)用棧回溯的缺陷
仔細思考上述策略的第一條脏答,其實可以發(fā)現(xiàn)它是有缺陷的糕殉。這個缺陷主要有兩點:
-
JNI跳板函數(shù)一定在oat/odex文件中么?
其實并不是殖告。在dex2oat階段阿蝶,系統(tǒng)會為一個oat/odex文件中參數(shù)兼容(個數(shù)相同且類型相似)的native方法統(tǒng)一生成一個JNI跳板函數(shù)。這點可以展開舉個例子黄绩。
#05 pc 00000000000eeb24 /system/lib64/libandroid_runtime.so (android::nativeCreate(_JNIEnv*, _jclass*, _jstring*, int)+132) #06 pc 00000000003dff04 /system/framework/arm64/boot-framework.oat (offset 0x3d6000) (android.graphics.FontFamily.nInitBuilder [DEDUPED]+180) #07 pc 000000000091414c /system/framework/arm64/boot-framework.oat (offset 0x3d6000) (android.database.CursorWindow.<init>+172)
如上調(diào)用棧是Android S以前tombstone文件的典型輸出羡洁。通過代碼我們可以得知,#7中的CursorWindow構(gòu)造方法明明調(diào)用的是nativeCreate方法爽丹,可為什么回溯出來的#6卻是nInitBuilder筑煮?原因正是一個JNI跳板函數(shù)可以被多個native方法使用,而回溯時只是從眾多native方法中挑了一個名字顯示出來习劫。因此后面的DEDUPED正是提醒我們咆瘟,這一幀是不可信的。具體解釋如下:
## DEDUPED frames If the name of a Java method includes `[DEDUPED]`, this means that multiple methods share the same code. ART only stores the name of a single one in its metadata, which is displayed here. This is not necessarily the one that was called.
繼續(xù)查看nativeCreate和nInitBuilder的方法定義诽里,可以發(fā)現(xiàn)它們的參數(shù)個數(shù)和類型均相同袒餐,因此在dex2oat后可以共用一個JNI跳板函數(shù)。
private static native long nativeCreate(String name, int cursorWindowSize); private static native long nInitBuilder(String langs, int variant);
好在從Android S開始谤狡,這一幀不再顯示具體的方法名灸眼,而是統(tǒng)一的art_jni_trampoline,這樣可以減少對開發(fā)者的困擾墓懂。如下示例焰宣。
#05 pc 00000000004a600c /apex/com.android.art/lib64/libart.so (art::VMDebug_countInstancesOfClass(_JNIEnv*, _jclass*, _jclass*, unsigned char)+876) (BuildId: 2ede688a1cdde049a8439e413c1c41f8) #06 pc 0000000000010fb4 /apex/com.android.art/javalib/arm64/boot-core-libart.oat (art_jni_trampoline+180) (BuildId: a58ab7e35be2dda5ad3453c56bfefea6edf331bf) #07 pc 000000000064037c /system/framework/arm64/boot-framework.oat (android.os.Debug.countInstancesOfClass+44) (BuildId: e47113da18d4f822af52023fa19893d55035facd) #08 pc 0000000000812930 /system/framework/arm64/boot-framework.oat (android.view.ViewDebug.getViewRootImplCount+48) (BuildId: e47113da18d4f822af52023fa19893d55035facd)
書歸正題,dex2oat生成的JNI跳板函數(shù)其實是位于oat/odex文件中的捕仔。但還有一種情況匕积,dex2oat并不會為native方法生成JNI跳板函數(shù)盈罐,而是在運行時采用統(tǒng)一的art_quick_generic_jni_trampoline來動態(tài)執(zhí)行參數(shù)傳遞和狀態(tài)切換。這時闪唆,art_quick_generic_jni_trampoline位于libart.so中盅粪,不符合oat/odex后綴的規(guī)律,因此調(diào)用椙睦伲回溯碰到這一幀時會繼續(xù)進行票顾。而如果后續(xù)的java方法全都是解釋執(zhí)行,那么解釋執(zhí)行的幀也將全部回溯出來帆调,如下示例奠骄。
"Binder:1083_11" prio=5 tid=127 Native | group="main" sCount=1 dsCount=0 flags=1 obj=0x16002138 self=0xb40000715181e940 | sysTid=6990 nice=0 cgrp=default sched=0/0 handle=0x6f5580fcc0 | state=S schedstat=( 4739949803 13009985270 12510 ) utm=234 stm=239 core=3 HZ=100 | stack=0x6f55718000-0x6f5571a000 stackSize=995KB | held mutexes= native: #00 pc 000000000009aa34 /apex/com.android.runtime/lib64/bionic/libc.so (__ioctl+4) native: #01 pc 0000000000057564 /apex/com.android.runtime/lib64/bionic/libc.so (ioctl+156) native: #02 pc 00000000000999d4 /system/lib64/libhidlbase.so (android::hardware::IPCThreadState::transact(int, unsigned int, android::hardware::Parcel const&, android::hardware::Parcel*, unsigned int)+564) native: #03 pc 0000000000094e84 /system/lib64/libhidlbase.so (android::hardware::BpHwBinder::transact(unsigned int, android::hardware::Parcel const&, android::hardware::Parcel*, unsigned int, std::__1::function<void (android::hardware::Parcel&)>)+76) native: #04 pc 000000000000e538 /system/lib64/android.system.suspend@1.0.so (android::system::suspend::V1_0::BpHwSystemSuspend::_hidl_acquireWakeLock(android::hardware::IInterface*, android::hardware::details::HidlInstrumentor*, android::system::suspend::V1_0::WakeLockType, android::hardware::hidl_string const&)+324) native: #05 pc 0000000000003178 /system/lib64/libhardware_legacy.so (acquire_wake_lock+356) native: #06 pc 0000000000086648 /system/lib64/libandroid_servers.so (android::nativeAcquireSuspendBlocker(_JNIEnv*, _jclass*, _jstring*)+64) native: #07 pc 000000000013ced4 /apex/com.android.art/lib64/libart.so (art_quick_generic_jni_trampoline+148) native: #08 pc 00000000001337e8 /apex/com.android.art/lib64/libart.so (art_quick_invoke_static_stub+568) native: #09 pc 00000000001a8a94 /apex/com.android.art/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+228) native: #10 pc 0000000000318240 /apex/com.android.art/lib64/libart.so (art::interpreter::ArtInterpreterToCompiledCodeBridge(art::Thread*, art::ArtMethod*, art::ShadowFrame*, unsigned short, art::JValue*)+376) native: #11 pc 000000000030e56c /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+996) native: #12 pc 000000000067e098 /apex/com.android.art/lib64/libart.so (MterpInvokeStatic+548) native: #13 pc 000000000012d994 /apex/com.android.art/lib64/libart.so (mterp_op_invoke_static+20) native: #14 pc 0000000000617e00 /system/framework/services.jar (com.android.server.power.PowerManagerService.access$600) native: #15 pc 000000000067e33c /apex/com.android.art/lib64/libart.so (MterpInvokeStatic+1224) native: #16 pc 000000000012d994 /apex/com.android.art/lib64/libart.so (mterp_op_invoke_static+20) native: #17 pc 0000000000614fec /system/framework/services.jar (com.android.server.power.PowerManagerService$NativeWrapper.nativeAcquireSuspendBlocker) native: #18 pc 000000000067b3e0 /apex/com.android.art/lib64/libart.so (MterpInvokeVirtual+1520) native: #19 pc 000000000012d814 /apex/com.android.art/lib64/libart.so (mterp_op_invoke_virtual+20) native: #20 pc 00000000006152b0 /system/framework/services.jar (com.android.server.power.PowerManagerService$SuspendBlockerImpl.acquire+52) native: #21 pc 0000000000305b68 /apex/com.android.art/lib64/libart.so (art::interpreter::Execute(art::Thread*, art::CodeItemDataAccessor const&, art::ShadowFrame&, art::JValue, bool, bool) (.llvm.10833873914857160001)+268) native: #22 pc 0000000000669e48 /apex/com.android.art/lib64/libart.so (artQuickToInterpreterBridge+780) native: #23 pc 000000000013cff8 /apex/com.android.art/lib64/libart.so (art_quick_to_interpreter_bridge+88) native: #24 pc 00000000021f4bc4 /memfd:jit-cache (deleted) (offset 2000000) (com.android.server.power.PowerManagerService.updateSuspendBlockerLocked+228) native: #25 pc 000000000201cf6c /memfd:jit-cache (deleted) (offset 2000000) (com.android.server.power.PowerManagerService.updatePowerStateLocked+988) native: #26 pc 00000000021a3800 /memfd:jit-cache (deleted) (offset 2000000) (com.android.server.power.PowerManagerService.acquireWakeLockInternal+1712) native: #27 pc 000000000205640c /memfd:jit-cache (deleted) (offset 2000000) (com.android.server.power.PowerManagerService$BinderService.acquireWakeLock+524) native: #28 pc 0000000002040b64 /memfd:jit-cache (deleted) (offset 2000000) (android.os.IPowerManager$Stub.onTransact+8340) native: #29 pc 00000000020c95a4 /memfd:jit-cache (deleted) (offset 2000000) (android.os.Binder.execTransactInternal+996) native: #30 pc 00000000020b9a0c /memfd:jit-cache (deleted) (offset 2000000) (android.os.Binder.execTransact+284) native: #31 pc 0000000000133564 /apex/com.android.art/lib64/libart.so (art_quick_invoke_stub+548) native: #32 pc 00000000001a8a78 /apex/com.android.art/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+200) native: #33 pc 0000000000553c70 /apex/com.android.art/lib64/libart.so (art::JValue art::InvokeVirtualOrInterfaceWithVarArgs<art::ArtMethod*>(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, art::ArtMethod*, std::__va_list)+468) native: #34 pc 0000000000553e10 /apex/com.android.art/lib64/libart.so (art::JValue art::InvokeVirtualOrInterfaceWithVarArgs<_jmethodID*>(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jmethodID*, std::__va_list)+92) native: #35 pc 00000000003a0920 /apex/com.android.art/lib64/libart.so (art::JNI<false>::CallBooleanMethodV(_JNIEnv*, _jobject*, _jmethodID*, std::__va_list)+660) native: #36 pc 000000000009c698 /system/lib64/libandroid_runtime.so (_JNIEnv::CallBooleanMethod(_jobject*, _jmethodID*, ...)+124) native: #37 pc 0000000000124064 /system/lib64/libandroid_runtime.so (JavaBBinder::onTransact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+156) native: #38 pc 000000000004882c /system/lib64/libbinder.so (android::BBinder::transact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+232) native: #39 pc 0000000000051110 /system/lib64/libbinder.so (android::IPCThreadState::executeCommand(int)+1032) native: #40 pc 0000000000050c58 /system/lib64/libbinder.so (android::IPCThreadState::getAndExecuteCommand()+156) native: #41 pc 0000000000051490 /system/lib64/libbinder.so (android::IPCThreadState::joinThreadPool(bool)+60) native: #42 pc 00000000000773e0 /system/lib64/libbinder.so (android::PoolThread::threadLoop()+24) native: #43 pc 000000000001549c /system/lib64/libutils.so (android::Thread::_threadLoop(void*)+260) native: #44 pc 00000000000a2590 /system/lib64/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+144) native: #45 pc 0000000000014d60 /system/lib64/libutils.so (thread_data_t::trampoline(thread_data_t const*)+412) native: #46 pc 00000000000af808 /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+64) native: #47 pc 000000000004fc88 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64) at com.android.server.power.PowerManagerService.nativeAcquireSuspendBlocker(Native method) at com.android.server.power.PowerManagerService.access$600(PowerManagerService.java:125) at com.android.server.power.PowerManagerService$NativeWrapper.nativeAcquireSuspendBlocker(PowerManagerService.java:713) at com.android.server.power.PowerManagerService$SuspendBlockerImpl.acquire(PowerManagerService.java:4643) - locked <0x073deae2> (a com.android.server.power.PowerManagerService$SuspendBlockerImpl) at com.android.server.power.PowerManagerService.updateSuspendBlockerLocked(PowerManagerService.java:3067) at com.android.server.power.PowerManagerService.updatePowerStateLocked(PowerManagerService.java:1956) at com.android.server.power.PowerManagerService.acquireWakeLockInternal(PowerManagerService.java:1320) - locked <0x03f99c8c> (a java.lang.Object) at com.android.server.power.PowerManagerService.access$4600(PowerManagerService.java:125) at com.android.server.power.PowerManagerService$BinderService.acquireWakeLock(PowerManagerService.java:4780) at android.os.IPowerManager$Stub.onTransact(IPowerManager.java:421) at android.os.Binder.execTransactInternal(Binder.java:1154) at android.os.Binder.execTransact(Binder.java:1123)
可以發(fā)現(xiàn),native標記的調(diào)用棧里其實包含了java層的信息番刊,因此java層的信息輸出了兩遍(信息冗余)含鳞。如果不了解棧回溯的具體原理撵枢,恐怕很多人都會好奇:為什么nativeAcquireSuspendBlocker的java方法會調(diào)用到#47的__start_thread民晒?這其實并不是真實的調(diào)用路徑精居,而只是因為當下的trace調(diào)用棧收集方案中有些缺陷锄禽。
-
當調(diào)用棧回溯碰到位于oat/odex文件里的JNI跳板函數(shù)時靴姿,則停止回溯沃但。這種方案適用于大多數(shù)場景》鹣牛可是如果函數(shù)調(diào)用呈現(xiàn)如下的交錯情形宵晚,那么現(xiàn)行方案會丟失部分調(diào)用棧。
Java method A ↓(call) Native method B ↓(call) Java method C ↓(call) Native method D
最終回溯出的整體調(diào)用棧信息中维雇,Native method B將不見蹤影淤刃,因為native層的回溯在碰到C時就已經(jīng)結(jié)束了。以下是一個實際的案例吱型。
"Binder:1540_2" prio=5 tid=9 Blocked | group="main" sCount=1 dsCount=0 flags=1 obj=0x13700580 self=0x7e0c139800 | sysTid=1560 nice=-2 cgrp=default sched=0/0 handle=0x7df07474f0 | state=S schedstat=( 126689305075 80266662086 342299 ) utm=8978 stm=3690 core=0 HZ=100 | stack=0x7df064c000-0x7df064e000 stackSize=1009KB | held mutexes= at com.android.server.LocationManagerService.isProviderEnabledForUser(LocationManagerService.java:2813) - waiting to lock <0x07cdf9c8> (a java.lang.Object) held by thread 11 at android.location.ILocationManager$Stub.onTransact(ILocationManager.java:488) at android.os.Binder.execTransact(Binder.java:726) (---丟失了這中間的native調(diào)用棧---) at android.os.BinderProxy.transactNative(Native method) at android.os.BinderProxy.transact(BinderProxy.java:473) at android.location.IGeocodeProvider$Stub$Proxy.getFromLocation(IGeocodeProvider.java:143) at com.android.server.location.GeocoderProxy$1.run(GeocoderProxy.java:79) at com.android.server.ServiceWatcher.runOnBinder(ServiceWatcher.java:425) - locked <0x0d7e7a61> (a java.lang.Object) at com.android.server.location.GeocoderProxy.getFromLocation(GeocoderProxy.java:74) at com.android.server.LocationManagerService.getFromLocation(LocationManagerService.java:3341) at android.location.ILocationManager$Stub.onTransact(ILocationManager.java:217) at android.os.Binder.execTransact(Binder.java:726)
該線程首先發(fā)起了一個通往對端進程的binder通信逸贾,對端進程在處理的過程中又給本進程發(fā)起了新的通信〗蛑停基于binder transaction stack的設(shè)計铝侵,這個新的通信必然交給當初的線程。因此execTransact表明它正在處理這個通信触徐。在transactNative和execTransact之間咪鲜,其實漏掉了native層的幀。
這兩個缺陷其實都是小問題撞鹉,無傷大雅疟丙。跟Google工程師反饋溝通以后颖侄,他們表示大概率在T上修復(fù)這些問題。
結(jié)語
當我們在解決大多數(shù)APP問題時享郊,調(diào)用棧都是最重要的分析素材发皿。如果它總能完美地反映線程的執(zhí)行邏輯,那么是否了解細節(jié)其實不重要拂蝎⊙ㄊ可現(xiàn)實并非如此。ANR的某些場景下温自,線程可能卡在GoToRunnable中玄货;交錯調(diào)用的情況下,中間的native方法可能丟失悼泌。等等松捉。這些時候的調(diào)用棧會出現(xiàn)讓人迷惑的信息,而只有了解回溯的細節(jié)馆里,才能真正解惑隘世。
最后
您的點贊收藏就是對我最大的鼓勵! 歡迎關(guān)注我鸠踪,分享Android干貨丙者,交流Android技術(shù)。 對文章有何見解营密,或者有何技術(shù)問題械媒,歡迎在評論區(qū)一起留言討論!最后給大家分享一些Android相關(guān)的視頻教程评汰,感興趣的朋友可以去看看纷捞。
【2021 最新版】Android studio全套教程+Android(安卓)開發(fā)入門到精通(項目實戰(zhàn)篇)_嗶哩嗶哩_bilibili
Android開發(fā)進階學(xué)習—設(shè)計思想解讀開源框架 · 已更新至104集(持續(xù)更新中~)_嗶哩嗶哩_bilibili
Android音視頻開發(fā):音視頻基礎(chǔ)知識到直播推流實戰(zhàn)系列教程_嗶哩嗶哩_bilibili
Android項目實戰(zhàn)-從0開始手把手實現(xiàn)組件化路由SDK項目實戰(zhàn)_嗶哩嗶哩_bilibili
本文轉(zhuǎn)自 https://juejin.cn/post/7041834777229393934,如有侵權(quán)被去,請聯(lián)系刪除主儡。