成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

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

 

[[177042]]

樸英敏,小米MIUI部門。從事嵌入式開發和調試工作8年多,擅長逆向分析方法,主要負責解決安卓系統穩定性問題。

 

上周音樂組同事反饋了一個必現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指向了一個沒有可執行權限的內存地址上。

初步分析:

對應的代碼如下:

  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); 

 

其中,Functor類重載了()操作符:

  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. }; 

 

因此,()操作其實就是調用了Functor類的一個虛函數,它的具體實現目前還不清楚。

對應的匯編代碼如下:

  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() 

 

崩潰時的寄存器值如下:

  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這一行匯編代碼中。

而查看寄存器對應的內存值,發現有點問題:

  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,這個和r3值(400fc1b8)不一樣,

同樣

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

這在平時的tombstone里是非常少見的!

乍一看非常不可思議,但仔細想想tombstone的生成過程,就能發現其中的問題。

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

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

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

  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 

 

可以確定確實有別的線程在修改這個值。

這里就存在兩個可能性了:

1、別的線程也持有functor指針,并修改內容

2、functor是野指針,對應的內存已經還回系統,其他模塊可任意使用。

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

為了查明是哪個線程在改,對functor指向的內存做了寫保護操作:

  1. static int** s_saved_vtbl = NULL
  2. static void* s_saved_functor = NULL
  3.  
  4. static void  mprotect_local(int** p) { 
  5.     // 一旦發現vtbl有變化就將對應內存設置為只讀 
  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到手機中復現問題,很容易抓到訪問權限引起的crash。

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

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

關于野指針:

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

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

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

但這么做有兩個問題:

1、log太量多,一秒內可能會有成千上萬的malloc/free函數被調用。

2、打印調用棧的函數本身會調用free函數,這樣會陷入死循環。

為了解決上面兩個問題,需要用到hook技術。

關于hook技術:

要了解hook技術,得先了解外部函數的調用過程。

所謂外部函數就是外部模塊中定義的函數。比如,libhwui.so中的某個源文件中調用了malloc函數,而這個malloc函數是libc.so中定義的。

當編譯libhwui.so的這個源文件時,對應調用malloc的地方會生成如下的匯編代碼:

 

  1. blx addr 

這里blx是arm的跳轉指令,addr是目標地址,也就是malloc函數的地址,那這個malloc函數的地址如何確定?

這個編譯的階段是無法確定的,只有當運行時進程加載完libc.so以后,malloc函數的地址才能被確定。

所以編譯器在編譯的時候會在libbinder.so中留出一部分空間作為地址表,專門用于存放外部函數的地址,這個區域叫got表。

每一個本模塊調用到的外部函數都對應got表中的一項。

當然got表里面的內容是在進程啟動階段,加載動態庫時被連接器linker填充的。

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

1、從got表對應位置獲取外部函數地址

2、跳轉到這個外部函數的地址

這個動作需要由若干的指令來完成,所以跳轉指令blx addr中的addr其實指向本模塊的一組指令:

  1. blx cb74 <malloc@plt> 

這組指令所在的區域就是elf文件結構里的plt表,plt表中每一個外部函數都對應一個表項,如:

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]! ;

每一個plt表項都是做相同操作:

1、先獲取got表中外目標函數對應的地址(前兩行);

2、從got表中獲取地址目標函數的地址,并賦給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技術就是通過修改so的got表來截獲so中的某些外部函數調用。

so的代碼段是多個進程共享的,但它的數據段私有的,而got表就是數據段。

所以我們只修改music應用進程的libhwui.so的got表中free函數對應的項,影響范圍將大大減少。

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

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

 

為了不影響原來的邏輯,打印完debug信息,還是要調用原來被hook的函數。

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

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

 

責任編輯:龐桂玉 來源: 小米開放平臺
相關推薦

2016-11-24 15:39:03

JavaNATIVE野指針

2021-08-06 13:48:53

C語言野指針內存

2023-12-26 12:13:31

野指針C++編程

2023-05-29 18:33:30

得物H5容器

2021-07-29 20:28:24

靜態代碼Hdfs

2017-05-03 16:26:24

MySQL并發死鎖

2010-05-19 10:00:17

2022-08-05 11:55:13

FlutteriOS

2011-07-12 17:33:09

PHP

2025-02-14 08:59:09

2016-12-12 12:37:45

結構C代碼賦值

2014-06-04 09:34:36

2018-04-10 13:02:51

HBase寫入流程數據

2021-09-02 07:56:46

HDFSHIVE元數據

2025-01-08 08:47:44

Node.js內存泄露定時器

2024-01-03 16:39:07

2022-11-03 16:10:29

groovyfullGC

2024-04-25 10:06:03

內存泄漏

2010-09-14 10:41:59

無線網絡配置

2022-07-10 07:51:46

元宇宙3DWeb
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 神马久久久久久久久久 | 国产亚洲欧美另类一区二区三区 | 国产一区二区三区久久久久久久久 | 久在线视频播放免费视频 | 国产视频三级 | 色综合一区二区三区 | 亚洲品质自拍视频 | 久久99精品久久久久婷婷 | 久久影音先锋 | 色婷婷久久久亚洲一区二区三区 | 国产精品一区二区在线 | 久久国产欧美日韩精品 | 亚洲国产精品一区二区第一页 | 久久久久久免费毛片精品 | 国产精品久久777777 | 国产午夜亚洲精品不卡 | 水蜜桃久久夜色精品一区 | 欧美在线一区二区三区 | 国产精品乱码一区二区三区 | 欧美福利一区 | 国产一区二区精华 | 99久久婷婷国产亚洲终合精品 | 日韩日韩日韩日韩日韩日韩日韩 | 欧美日一区二区 | a在线v| 不卡在线一区 | 久久久久国产精品www | 免费在线观看成人 | 亚洲欧洲在线视频 | 国产精品久久久久久久久久久久久久 | 91精品国产欧美一区二区 | 成人在线视频看看 | 天天天堂 | 欧美一级做a爰片免费视频 国产美女特级嫩嫩嫩bbb片 | 91麻豆精品一区二区三区 | 成人一区二区三区在线观看 | 国产成人在线视频免费观看 | 欧美一区二区三 | 秋霞影院一区二区 | 成人在线观看免费观看 | 亚洲欧美日韩网站 |