自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java代碼引起的NATIVE野指針問題(上)

開發(fā) 后端 開發(fā)工具
所謂野指針就是一個(gè)對象被釋放后又被使用,可能是釋放的問題,也可能是使用的問題。我們已經(jīng)知道使用的位置,接下來要找出是從哪釋放的。找到釋放對象的最笨的方法,是在free()函數(shù)里打印調(diào)用棧。

 

[[177042]]

樸英敏,小米MIUI部門。從事嵌入式開發(fā)和調(diào)試工作8年多,擅長逆向分析方法,主要負(fù)責(zé)解決安卓系統(tǒng)穩(wěn)定性問題。

 

上周音樂組同事反饋了一個(gè)必現(xiàn)Native Crash問題,tombstone如下:

  1. pid: 5028, tid: 5028, name: com.miui.player  >>> com.miui.player <<< 
  2. signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 79801f28 
  3.     r0 7ac59c98  r1 00000000  r2 bea7b174  r3 400fc1b8 
  4.     r4 774c4c88  r5 79801f28  r6 bea7b478  r7 40c12bb8 
  5.     r8 7c1b68e8  r9 778781e8  sl bea7b478  fp bea7b414 
  6.     ip 00000001  sp bea7b148  lr 40c07031  pc 79801f28  cpsr 600f0010 
  7. backtrace: 
  8.     #00  pc 0000bf28  <unknown> 
  9.     #01  pc 0002302f  /system/lib/libhwui.so (android::uirenderer::OpenGLRenderer::callDrawGLFunction(android::Functor*, android::uirenderer::Rect&)+322) 
  10.     #02  pc 00015d91  /system/lib/libhwui.so (android::uirenderer::DrawFunctorOp::applyDraw(android::uirenderer::OpenGLRenderer&, android::uirenderer::Rect&)+28) 
  11.     #03  pc 00014527  /system/lib/libhwui.so (android::uirenderer::DrawBatch::replay(android::uirenderer::OpenGLRenderer&, android::uirenderer::Rect&, int)+74) 
  12.     #04  pc 00014413  /system/lib/libhwui.so (android::uirenderer::DeferredDisplayList::flush(android::uirenderer::OpenGLRenderer&, android::uirenderer::Rect&)+218) 
  13.     #05  pc 0001d1cf  /system/lib/libhwui.so (_ZN7android10uirenderer14OpenGLRenderer15drawDisplayListEPNS0_11DisplayListERNS0_4RectEi.part.47+230) 
  14.     #06  pc 0006820d  /system/lib/libandroid_runtime.so 

 

崩潰的原因是pc指向了一個(gè)沒有可執(zhí)行權(quán)限的內(nèi)存地址上。

初步分析:

對應(yīng)的代碼如下:

  1. status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) { 
  2.  
  3. if (mSnapshot->isIgnored()) return DrawGlInfo::kStatusDone; 
  4.  
  5. detachFunctor(functor); 
  6.  
  7. ... 
  8.  
  9. interrupt(); 
  10.  
  11. => status_t result = (*functor)(DrawGlInfo::kModeDraw, &info); 

 

其中,F(xiàn)unctor類重載了()操作符:

  1. class Functor { 
  2.  
  3. public
  4.  
  5. Functor() {} 
  6.  
  7. virtual ~Functor() {} 
  8.  
  9. => virtual status_t operator ()(int /*what*/, void* /*data*/) { return NO_ERROR; } 
  10.  
  11. }; 

 

因此,()操作其實(shí)就是調(diào)用了Functor類的一個(gè)虛函數(shù),它的具體實(shí)現(xiàn)目前還不清楚。

對應(yīng)的匯編代碼如下:

  1. 23028: aa0b add r2, sp, #44 
  2.  
  3. 2302a: 6803 ldr r3, [r0, #0] ; r0是functor,r3 = [r0] = functor.vtlb 
  4.  
  5. 2302c: 689d ldr r5, [r3, #8] ; r5 = [r3 + 8] = [functor.vtlb + 8] = Functor.operator() 
  6.  
  7. 2302e: 47a8 blx r5 ; call Functor.operator() 

 

崩潰時(shí)的寄存器值如下:

  1. r0 7ac59c98 r1 00000000 r2 bea7b174 r3 400fc1b8 
  2.  
  3. r4 774c4c88 r5 79801f28 r6 bea7b478 r7 40c12bb8 
  4.  
  5. r8 7c1b68e8 r9 778781e8 sl bea7b478 fp bea7b414 
  6.  
  7. ip 00000001 sp bea7b148 lr 40c07031 pc 79801f28 cpsr 600f0010 

 

可以看到,r5和pc值是相等的,可以知道,確定是崩潰在2302e這一行匯編代碼中。

而查看寄存器對應(yīng)的內(nèi)存值,發(fā)現(xiàn)有點(diǎn)問題:

  1. memory near r0: 
  2.     7ac59c78 00000018 0000001b 735a9b38 23831ef0   
  3.     7ac59c88 23831ef0 735a9b50 00000018 00000011   
  4.     7ac59c98 79822328 77768698 00000010 00000022   
  5.     7ac59ca8 00000000 00000000 00000000 00000003   
  6.  
  7. memory near r3: 
  8.     400fc198 7c74c000 00200000 00000077 0d44acd8   
  9.     400fc1a8 00000000 00000000 400fc1a8 400fc1a8   
  10.     400fc1b8 400fc1b0 400fc1b0 7c04acb8 7c78f008   
  11.     400fc1c8 7c021d98 7c78ffc0 7983bbf0 7c04bfa8 

 

[r0] = [7ac59c98] = 798223298,這個(gè)和r3值(400fc1b8)不一樣,

同樣

[r3+8] = [400fc1b8 + 8] = 7c04acb8,這個(gè)值也和r5值(79801f28)不一樣。

這在平時(shí)的tombstone里是非常少見的!

乍一看非常不可思議,但仔細(xì)想想tombstone的生成過程,就能發(fā)現(xiàn)其中的問題。

原來寄存器信息是錯(cuò)位崩潰時(shí)的cpu context,保存在崩潰時(shí)的線程私有的信號棧和內(nèi)核棧中,直到debuggerd去獲取這個(gè)值,它是不會被修改的。

而內(nèi)存是進(jìn)程中的各個(gè)線程共享的,所以在發(fā)生異常到debuggerd打印內(nèi)存信息這段過程中(其實(shí)是相對很長的一個(gè)過程),別的線程是有可能修改內(nèi)存值的。

為了證明別的線程在改這個(gè)內(nèi)存值,在callDrawGLFunction()函數(shù)中的若干處打印了Functor和它的vtbl(虛函數(shù)表地址)值:

  1. status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) { 
  2.  
  3. AOGI("functor=%p,vtbl=%p"); 
  4.  
  5. sleep(1); 
  6.  
  7. if (mSnapshot->isIgnored()) return DrawGlInfo::kStatusDone; 
  8.  
  9. AOGI("functor=%p,vtbl=%p"); 
  10.  
  11. sleep(1); 
  12.  
  13. detachFunctor(functor); 
  14.  
  15. ... 
  16.  
  17. AOGI("functor=%p,vtbl=%p"); 
  18.  
  19. sleep(1); 
  20.  
  21. interrupt(); 
  22.  
  23. AOGI("functor=%p,vtbl=%p"); 
  24.  
  25. sleep(1); 
  26.  
  27. status_t result = (*functor)(DrawGlInfo::kModeDraw, &info); 

 

抓到的log如下:

  1. 10-27 21:19:45.794 8027 8027 I OpenGLRenderer: functor=0x7a7b8530,vtbl=0x73648de0 
  2.  
  3. 10-27 21:19:47.801 8027 8027 I OpenGLRenderer: functor=0x7a7b8530,vtbl=0x73648de0 
  4.  
  5. 10-27 21:19:48.801 8027 8027 I OpenGLRenderer: functor=0x7a7b8530,vtbl=0x73648de0 
  6.  
  7. 10-27 21:19:49.801 8027 8027 I OpenGLRenderer: functor=0x7a7b8530,vtbl=0x73648de0 
  8.  
  9. 10-27 21:19:50.804 8027 8027 I OpenGLRenderer: functor=0x7a7b8530,vtbl=0x73648de0 
  10.  
  11. 10-27 21:19:51.804 8027 8027 I OpenGLRenderer: functor=0x7a7b8530,vtbl=0x400fc1b8 

 

可以確定確實(shí)有別的線程在修改這個(gè)值。

這里就存在兩個(gè)可能性了:

1、別的線程也持有functor指針,并修改內(nèi)容

2、functor是野指針,對應(yīng)的內(nèi)存已經(jīng)還回系統(tǒng),其他模塊可任意使用。

而對象的vtbl一般是不會修改的,所以2的可能性更大一些。

為了查明是哪個(gè)線程在改,對functor指向的內(nèi)存做了寫保護(hù)操作:

  1. static int** s_saved_vtbl = NULL
  2. static void* s_saved_functor = NULL
  3.  
  4. static void  mprotect_local(int** p) { 
  5.     // 一旦發(fā)現(xiàn)vtbl有變化就將對應(yīng)內(nèi)存設(shè)置為只讀 
  6.     if(p != s_saved_vtbl) {  
  7.         mprotect((void*)((unsigned int)s_saved_functor&0xfffff000), 4096, PROT_READ); 
  8.     } 
  9.     sleep(1); 
  10.  
  11. status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) { 
  12.     int* ptr = (int*)functor; 
  13.     s_saved_functor = (void*)ptr; 
  14.     s_saved_vtbl = (int**)*ptr; 
  15.  
  16.     if (mSnapshot->isIgnored()) return DrawGlInfo::kStatusDone;  
  17.  
  18.     mprotect_local((int**)*ptr); 
  19.     detachFunctor(functor); 
  20.     mprotect_local((int**)*ptr); 
  21.     ... 
  22.     mprotect_local((int**)*ptr); 
  23.     interrupt(); 
  24.   
  25.     status_t result = (*functor)(DrawGlInfo::kModeDraw, &info); 

 

push到手機(jī)中復(fù)現(xiàn)問題,很容易抓到訪問權(quán)限引起的crash。

而每次的crash的線程和位置都不一樣,也就是不同的線程在不同的函數(shù)中讀寫這個(gè)地址。

這樣基本上就確定是野指針問題,進(jìn)入下一階段的分析。

關(guān)于野指針:

所謂野指針就是一個(gè)對象被釋放后又被使用,可能是釋放的問題,也可能是使用的問題。

我們已經(jīng)知道使用的位置,接下來要找出是從哪釋放的。

找到釋放對象的最笨的方法,是在free()函數(shù)里打印調(diào)用棧。

但這么做有兩個(gè)問題:

1、log太量多,一秒內(nèi)可能會有成千上萬的malloc/free函數(shù)被調(diào)用。

2、打印調(diào)用棧的函數(shù)本身會調(diào)用free函數(shù),這樣會陷入死循環(huán)。

為了解決上面兩個(gè)問題,需要用到hook技術(shù)。

關(guān)于hook技術(shù):

要了解hook技術(shù),得先了解外部函數(shù)的調(diào)用過程。

所謂外部函數(shù)就是外部模塊中定義的函數(shù)。比如,libhwui.so中的某個(gè)源文件中調(diào)用了malloc函數(shù),而這個(gè)malloc函數(shù)是libc.so中定義的。

當(dāng)編譯libhwui.so的這個(gè)源文件時(shí),對應(yīng)調(diào)用malloc的地方會生成如下的匯編代碼:

 

  1. blx addr 

這里blx是arm的跳轉(zhuǎn)指令,addr是目標(biāo)地址,也就是malloc函數(shù)的地址,那這個(gè)malloc函數(shù)的地址如何確定?

這個(gè)編譯的階段是無法確定的,只有當(dāng)運(yùn)行時(shí)進(jìn)程加載完libc.so以后,malloc函數(shù)的地址才能被確定。

所以編譯器在編譯的時(shí)候會在libbinder.so中留出一部分空間作為地址表,專門用于存放外部函數(shù)的地址,這個(gè)區(qū)域叫g(shù)ot表。

每一個(gè)本模塊調(diào)用到的外部函數(shù)都對應(yīng)got表中的一項(xiàng)。

當(dāng)然got表里面的內(nèi)容是在進(jìn)程啟動階段,加載動態(tài)庫時(shí)被連接器linker填充的。

而編譯階段我們只需要將代碼寫成:

1、從got表對應(yīng)位置獲取外部函數(shù)地址

2、跳轉(zhuǎn)到這個(gè)外部函數(shù)的地址

這個(gè)動作需要由若干的指令來完成,所以跳轉(zhuǎn)指令blx addr中的addr其實(shí)指向本模塊的一組指令:

  1. blx cb74 <malloc@plt> 

這組指令所在的區(qū)域就是elf文件結(jié)構(gòu)里的plt表,plt表中每一個(gè)外部函數(shù)都對應(yīng)一個(gè)表項(xiàng),如:

0000cb74 <malloc@plt>:

cb74: e28fc600 add ip, pc, #0, 12

cb78: e28cca29 add ip, ip, #167936 ;

cb7c: e5bcf1e8 ldr pc, [ip, #488]! ;

0000c8bc <free@plt>:

c8bc: e28fc600 add ip, pc, #0, 12

c8c0: e28cca29 add ip, ip, #167936 ;

c8c4: e5bcf3b8 ldr pc, [ip, #952]! ;

每一個(gè)plt表項(xiàng)都是做相同操作:

1、先獲取got表中外目標(biāo)函數(shù)對應(yīng)的地址(前兩行);

2、從got表中獲取地址目標(biāo)函數(shù)的地址,并賦給pc寄存器(第三行)。

下面給出got表和plt表在so文件中的位置:

readelf -S libhwui.so

[Nr] Name Type Addr Off Size ES Flg Lk Inf Al

[ 0] NULL 00000000 000000 000000 00 0 0 0

[ 1] .interp PROGBITS 00000134 000134 000013 00 A 0 0 1

[ 2] .dynsym DYNSYM 00000148 000148 002420 10 A 3 1 4

[ 3] .dynstr STRTAB 00002568 002568 0056a4 00 A 0 0 1

[ 4] .hash HASH 00007c0c 007c0c 001134 04 A 2 0 4

[ 5] .rel.dyn REL 00008d40 008d40 002bc8 08 A 2 0 4

[ 6] .rel.plt REL 0000b908 00b908 000a78 08 A 2 7 4

=>[ 7] .plt PROGBITS 0000c380 00c380 000fc8 00 AX 0 0 4

[ 8] .text PROGBITS 0000d348 00d348 01ef30 00 AX 0 0 8

[ 9] .ARM.exidx ARM_EXIDX 0002c278 02c278 001fb8 08 AL 8 0 4

[10] .ARM.extab PROGBITS 0002e230 02e230 000930 00 A 0 0 4

[11] .rodata PROGBITS 0002eb60 02eb60 0036a4 00 A 0 0 4

[12] .fini_array FINI_ARRAY 00034010 033010 000004 00 WA 0 0 4

[13] .data.rel.ro PROGBITS 00034018 033018 001910 00 WA 0 0 8

[14] .init_array INIT_ARRAY 00035928 034928 00000c 00 WA 0 0 4

[15] .dynamic DYNAMIC 00035934 034934 000140 08 WA 3 0 4

=>[16] .got PROGBITS 00035a74 034a74 00058c 00 WA 0 0 4

[17] .data PROGBITS 00036000 035000 00025c 00 WA 0 0 4

[18] .bss NOBITS 0003625c 03525c 000068 00 WA 0 0 4

[19] .comment PROGBITS 00000000 03525c 000010 01 MS 0 0 1

[20] .note.gnu.gold-ve NOTE 00000000 03526c 00001c 00 0 0 4

[21] .ARM.attributes ARM_ATTRIBUTES 00000000 035288 00003e 00 0 0 1

[22] .gnu_debuglink PROGBITS 00000000 0352c6 000010 00 0 0 1

[23] .shstrtab STRTAB 00000000 0352d6 0000dc 00 0 0 1

我們的hook技術(shù)就是通過修改so的got表來截獲so中的某些外部函數(shù)調(diào)用。

so的代碼段是多個(gè)進(jìn)程共享的,但它的數(shù)據(jù)段私有的,而got表就是數(shù)據(jù)段。

所以我們只修改music應(yīng)用進(jìn)程的libhwui.so的got表中free函數(shù)對應(yīng)的項(xiàng),影響范圍將大大減少。

那改成什么值呢?一般是我們自己定義的函數(shù),比如:

  1. void inject_free(void *ptr)   { 
  2.     ALOGI("free ptr=%p",ptr); 
  3.     dumpNativeStack(); 
  4.     dumpJavaStack(); 
  5.     free(ptr); 

 

為了不影響原來的邏輯,打印完debug信息,還是要調(diào)用原來被hook的函數(shù)。

有了hook技術(shù)后能完美的解決野指針中的兩個(gè)問題,下面繼續(xù)分析問題。

【本文是51CTO專欄“小米開放平臺”的原創(chuàng)文章,“小米開放平臺”微信公眾號:xiaomideveloper】

 

責(zé)任編輯:龐桂玉 來源: 小米開放平臺
相關(guān)推薦

2016-11-24 15:39:03

JavaNATIVE野指針

2021-08-06 13:48:53

C語言野指針內(nèi)存

2023-12-26 12:13:31

野指針C++編程

2023-05-29 18:33:30

得物H5容器

2021-07-29 20:28:24

靜態(tài)代碼Hdfs

2017-05-03 16:26:24

MySQL并發(fā)死鎖

2010-05-19 10:00:17

2022-08-05 11:55:13

FlutteriOS

2025-02-14 08:59:09

2016-12-12 12:37:45

結(jié)構(gòu)C代碼賦值

2011-07-12 17:33:09

PHP

2018-04-10 13:02:51

HBase寫入流程數(shù)據(jù)

2014-06-04 09:34:36

2021-09-02 07:56:46

HDFSHIVE元數(shù)據(jù)

2025-01-08 08:47:44

Node.js內(nèi)存泄露定時(shí)器

2022-07-10 07:51:46

元宇宙3DWeb

2022-11-03 16:10:29

groovyfullGC

2024-01-03 16:39:07

2024-04-25 10:06:03

內(nèi)存泄漏

2010-09-14 10:41:59

無線網(wǎng)絡(luò)配置
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號