背景
之前用AddressSanitizer工具驗證阿裡雲短視訊SDK的穩定性時出現了一個崩潰問題,報錯堆棧在一個空指針對象通路其成員函數處,但是從整體代碼執行流程分析發現其對象指針一直是空的,詭異的是不使用工具運作時無論如何都不會崩潰。
ASAN工具報錯發生在如下行,此處
mAudioRenderServicePtr
為NULL。
this->OnStop(false, mAudioRenderServicePtr->GetAddr());
下面對正常運作和工具調試出現的結果一緻進行分析,以及延伸的讨論一下關于空指針通路的一些陷阱。
分析
空指針即未指向任何對象,于是從語言層面來講在通路空指針時應該一定發生崩潰,但在多函數調用及參數傳遞過程中,以及C++語言的封裝特性、編譯器是否優化的情況下,則不一定發生崩潰或者崩潰延遲。
先看看
GetAddr
這個成員函數的聲明:
const MdfAddr& GetAddr(){ return mAddr; }
這裡發現傳回值為成員變量mAddr的常量引用,如下是反彙編對應的指令
0xad0b9880 <+512>: ldr.w r0, [r5, #0x124]
0xad0b9884 <+516>: movs r1, #0x0
0xad0b9886 <+518>: add.w r2, r0, #0x3c
從彙編指令看到,最後一行直接将this指針加上成員變量的偏移值0x3c并傳回,也即傳回的是mAddr的位址0x3c。
以及成員函數OnStop的函數聲明
void OnStop(bool isAsync, const MdfAddr &srcAddr);
第二個參數實際使用的是MdfAddr的常量引用,是以參數會直接傳遞其對象位址0x3c,而不會通路0x3c指向的記憶體值,後面函數執行過程中也并未通路mAddr的成員變量,是以無工具運作時不會發生崩潰。
至于有工具時會發生崩潰,是因為ASAN會對所有位址通路進行監控,當出現異常時就會中斷崩潰。
其它情況
- 如上述的情況,如果我們在OnStop裡實際通路了第二個參數裡的成員變量,即實際通路了0x3c指針指向的記憶體,就會立即産生崩潰,比如OnStop的聲明是這樣
void OnStop(bool isAsync, MdfAddr srcAddr);
第二個參數會直接傳遞MdfAddr的值,是以會通路0x3c指向的實際記憶體,進而會立即導緻崩潰,輸出類似如下的資訊
Build fingerprint: 'Xiaomi/cancro_wc_lte/cancro:6.0.1/MMB29M/8.9.13:user/release-keys'
02-21 16:43:27.589 290-290/? A/DEBUG: Revision: '0'
02-21 16:43:27.589 290-290/? A/DEBUG: ABI: 'arm'
02-21 16:43:27.589 290-290/? A/DEBUG: pid: 19854, tid: 19933, name: Thread-3246 >>> com.aliyun.aliyunvideosdkpro <<<
02-21 16:43:27.589 290-290/? A/DEBUG: signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
02-21 16:43:27.610 290-290/? A/DEBUG: r0 00000000 r1 00004ddd r2 00000006 r3 99c11978
02-21 16:43:27.610 290-290/? A/DEBUG: r4 99c11980 r5 99c11930 r6 00000019 r7 0000010c
02-21 16:43:27.610 290-290/? A/DEBUG: r8 0000000b r9 b3472bd0 sl 00000000 fp 133822cc
02-21 16:43:27.610 290-290/? A/DEBUG: ip 00000006 sp a8260188 lr b6816c69 pc b6819058 cpsr 400d0010
02-21 16:43:27.646 290-290/? A/DEBUG: backtrace:
02-21 16:43:27.646 290-290/? A/DEBUG: #00 pc 00042058 /system/lib/libc.so (tgkill+12)
02-21 16:43:27.646 290-290/? A/DEBUG: #01 pc 0003fc65 /system/lib/libc.so (pthread_kill+32)
02-21 16:43:27.646 290-290/? A/DEBUG: #02 pc 0001c403 /system/lib/libc.so (raise+10)
02-21 16:43:27.646 290-290/? A/DEBUG: #03 pc 000195b5 /system/lib/libc.so (__libc_android_abort+34)
02-21 16:43:27.646 290-290/? A/DEBUG: #04 pc 00017508 /system/lib/libc.so (abort+4)
02-21 16:43:27.647 290-290/? A/DEBUG: #05 pc 000a18b3 /system/lib/libclang_rt.asan-arm-android.so (_ZN11__sanitizer5AbortEv+40)
02-21 16:43:27.647 290-290/? A/DEBUG: #06 pc 000a6449 /system/lib/libclang_rt.asan-arm-android.so (_ZN11__sanitizer3DieEv+60)
02-21 16:43:27.647 290-290/? A/DEBUG: #07 pc 0008ffc0 /system/lib/libclang_rt.asan-arm-android.so (_ZN6__asan19ScopedInErrorReportD2Ev+352)
02-21 16:43:27.647 290-290/? A/DEBUG: #08 pc 000900b8 /system/lib/libclang_rt.asan-arm-android.so (_ZN6__asan18ReportDeadlySignalEiRKN11__sanitizer13SignalContextE+160)
02-21 16:43:27.647 290-290/? A/DEBUG: #09 pc 0008f0fc /system/lib/libclang_rt.asan-arm-android.so (_ZN6__asan18AsanOnDeadlySignalEiPvS0_+188)
02-21 16:43:27.647 290-290/? A/DEBUG: #10 pc 0036d2e1 /system/lib/libart.so (_ZN3art12FaultManager11HandleFaultEiP7siginfoPv+208)
02-21 16:43:27.647 290-290/? A/DEBUG: #11 pc 0001756c /system/lib/libc.so
- 考慮另一種場景,如何實作的代碼
class MyClass
{
public:
int calcValue(int x, int y) const
{
return x + y;
}
};
如果我們将一些實際未使用類成員變量的全局函數作為類的成員函數,那麼在對象指針為NULL時通路這一類函數,則不會發生崩潰,因為在執行calcValue時this指針并不會用到。
總結
- 如果直接通路空指針指向的記憶體,如内建類型,則會立即發生崩潰
- 通路空指針對象指向的成員函數時,則不一定會發生崩潰
- 使用引用或者常量引用類型的參數時,記憶體通路會延遲,記憶體通路異常也會延遲