背景
之前用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指針並不會用到。
總結
- 如果直接訪問空指針指向的內存,如內建類型,則會立即發生崩潰
- 訪問空指針對象指向的成員函數時,則不一定會發生崩潰
- 使用引用或者常量引用類型的參數時,內存訪問會延遲,內存訪問異常也會延遲