成人免费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

2025-02-14 08:59:09

2011-07-12 17:33:09

PHP

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技術棧公眾號

主站蜘蛛池模板: 久久久久亚洲精品 | 中文字幕在线视频免费观看 | 久久av在线播放 | 成人不卡 | 久久综合一区 | a级黄色毛片免费播放视频 国产精品视频在线观看 | 色av一区二区 | 久久一二三区 | 中文字幕免费观看 | 成人在线免费观看视频 | 日韩免费视频一区二区 | 国产精品一区在线观看 | 无吗视频 | 日本高清视频在线播放 | 天天影视亚洲综合网 | 国产精品日韩欧美一区二区三区 | 成人性视频在线播放 | 成人av大全 | 91麻豆精品国产91久久久久久久久 | 久久久这里都是精品 | av高清毛片 | av在线电影网 | 国产精品免费一区二区三区四区 | 一级毛片在线播放 | 在线一区观看 | 成人在线观看欧美 | 艹逼网| 亚洲第一天堂 | 欧美精品在线免费 | 嫩呦国产一区二区三区av | 久久午夜剧场 | 成人欧美一区二区三区1314 | 狠狠色综合久久丁香婷婷 | 麻豆视频国产在线观看 | 自拍偷拍亚洲视频 | 国产精品久久久久久久久久久久久 | 91亚洲视频在线 | 色网站在线免费观看 | 免费黄色日本 | 国产精品美女久久久久久久久久久 | 一区二区三区在线播放 |