AddressSanitizer

AddressSanitizer (ASan) เป็นเครื่องมือที่ใช้คอมไพเลอร์ที่รวดเร็วสำหรับตรวจหา ข้อบกพร่องด้านหน่วยความจำในโค้ดเนทีฟ

ASan ตรวจพบ

  • บัฟเฟอร์ล้น/ขาดในสแต็กและฮีป
  • การใช้ฮีปหลังจากปล่อย
  • การใช้สแต็กนอกขอบเขต
  • ดับเบิลฟรี/ไวลด์ฟรี

ASan ทำงานได้ทั้งใน ARM 32 บิตและ 64 บิต รวมถึง x86 และ x86-64 ค่าใช้จ่ายของ CPU ของ ASan จะอยู่ที่ประมาณ 2 เท่า ค่าใช้จ่ายของขนาดโค้ดจะอยู่ระหว่าง 50% ถึง 2 เท่า และค่าใช้จ่ายของหน่วยความจำจำนวนมาก (ขึ้นอยู่กับรูปแบบการจัดสรรของคุณ แต่จะอยู่ที่ประมาณ 2 เท่า)

Android 10 และสาขาการเผยแพร่ล่าสุดของ AOSP บน AArch64 รองรับ Hardware-assisted AddressSanitizer (HWASan) ซึ่งเป็นเครื่องมือที่คล้ายกันที่มีค่าใช้จ่าย RAM ต่ำกว่าและมี ช่วงของข้อบกพร่องที่ตรวจพบกว้างกว่า HWASan จะตรวจหาการใช้สแต็กหลังจากกลับมาทำงาน นอกเหนือจากข้อบกพร่องที่ ASan ตรวจพบ

HWASan มีค่าใช้จ่ายด้าน CPU และขนาดโค้ดที่คล้ายกัน แต่มีค่าใช้จ่ายด้าน RAM น้อยกว่ามาก (15%) HWASan ไม่แน่นอน ค่าแท็กที่เป็นไปได้มีเพียง 256 ค่า ดังนั้นจึงมีโอกาส 0.4% ที่จะพลาดข้อบกพร่อง HWASan ไม่มีโซนสีแดงขนาดจำกัดของ ASan สำหรับ การตรวจหาการล้น และไม่มีการกักเก็บที่มีความจุจำกัดสำหรับการตรวจหาการใช้หน่วยความจำที่เลิกจัดสรรไปแล้ว ดังนั้น HWASan จึงไม่สนใจว่าการล้นจะมีขนาดใหญ่เพียงใดหรือหน่วยความจำ ถูกเลิกจัดสรรไปนานแค่ไหนแล้ว ซึ่งทำให้ HWASan ดีกว่า ASan คุณอ่านเพิ่มเติมเกี่ยวกับการออกแบบ HWASan หรือเกี่ยวกับการใช้ HWASan ใน Android ได้

ASan ตรวจหาการล้นสแต็ก/การล้นทั่วโลก นอกเหนือจากการล้นฮีป และทำงานได้อย่างรวดเร็วโดยมีโอเวอร์เฮดหน่วยความจำน้อยที่สุด

เอกสารนี้อธิบายวิธีสร้างและเรียกใช้บางส่วนหรือทั้งหมดของ Android ด้วย ASan หากคุณสร้างแอป SDK/NDK ด้วย ASan โปรดดู Address Sanitizer แทน

ล้างข้อมูลที่เรียกใช้งานได้แต่ละรายการด้วย ASan

เพิ่ม LOCAL_SANITIZE:=address หรือ sanitize: { address: true } ลงใน กฎการสร้างสำหรับไฟล์ที่เรียกใช้งานได้ คุณสามารถค้นหาโค้ดเพื่อดูตัวอย่างที่มีอยู่หรือค้นหา ตัวกรองอื่นๆ ที่พร้อมใช้งาน

เมื่อตรวจพบข้อบกพร่อง ASan จะพิมพ์รายงานแบบละเอียดทั้งในเอาต์พุตมาตรฐานและใน logcat จากนั้นจะทำให้กระบวนการขัดข้อง

ล้างข้อมูลไลบรารีที่ใช้ร่วมกันด้วย ASan

เนื่องจากลักษณะการทำงานของ ASan ไลบรารีที่สร้างด้วย ASan จึงใช้ได้เฉพาะกับ ไฟล์ที่เรียกใช้งานได้ซึ่งสร้างด้วย ASan

หากต้องการล้างข้อมูลไลบรารีที่ใช้ร่วมกันซึ่งใช้ในไฟล์ที่เรียกใช้งานได้หลายไฟล์ แต่ไม่ได้สร้างทั้งหมดด้วย ASan คุณจะต้องมีไลบรารี 2 ชุด วิธีที่แนะนำในการดำเนินการนี้คือการเพิ่มข้อมูลต่อไปนี้ลงใน Android.mk สำหรับโมดูลที่เป็นปัญหา

LOCAL_SANITIZE:=address
LOCAL_MODULE_RELATIVE_PATH := asan

ซึ่งจะทำให้คลังอยู่ใน /system/lib/asan แทนที่จะอยู่ใน /system/lib จากนั้นเรียกใช้ไฟล์ที่เรียกใช้งานได้ด้วยคำสั่งต่อไปนี้

LD_LIBRARY_PATH=/system/lib/asan

สำหรับ Daemon ของระบบ ให้เพิ่มข้อมูลต่อไปนี้ลงในส่วนที่เหมาะสมของ /init.rc หรือ /init.$device$.rc

setenv LD_LIBRARY_PATH /system/lib/asan

ตรวจสอบว่ากระบวนการใช้ไลบรารีจาก /system/lib/asan เมื่อมีอยู่โดยอ่าน /proc/$PID/maps หากไม่เป็นเช่นนั้น คุณอาจต้อง ปิดใช้ SELinux โดยทำดังนี้

adb root
adb shell setenforce 0
# restart the process with adb shell kill $PID
# if it is a system service, or may be adb shell stop; adb shell start.

สแต็กเทรซที่ดีขึ้น

ASan ใช้ตัวยกเลิกการเรียกใช้ที่รวดเร็วซึ่งอิงตามตัวชี้เฟรมเพื่อบันทึก Stack Trace สำหรับเหตุการณ์การจัดสรรและการยกเลิกการจัดสรรหน่วยความจำทุกรายการในโปรแกรม Android ส่วนใหญ่สร้างขึ้นโดยไม่มีตัวชี้เฟรม ด้วยเหตุนี้ คุณจึงมักจะได้รับ เฟรมที่มีความหมายเพียง 1 หรือ 2 เฟรม หากต้องการแก้ไขปัญหานี้ ให้สร้างไลบรารีใหม่ด้วย ASan (แนะนำ) หรือด้วย

LOCAL_CFLAGS:=-fno-omit-frame-pointer
LOCAL_ARM_MODE:=arm

หรือตั้งค่า ASAN_OPTIONS=fast_unwind_on_malloc=0 ในสภาพแวดล้อมของกระบวนการ ซึ่งอาจใช้ CPU มาก ขึ้นอยู่กับภาระงาน

การแทนที่ด้วยสัญลักษณ์

ในระยะแรก รายงาน ASan จะมีการอ้างอิงถึงออฟเซ็ตในไบนารีและไลบรารีที่ใช้ร่วมกัน คุณรับข้อมูลไฟล์ต้นฉบับและบรรทัดได้ 2 วิธีดังนี้

  • ตรวจสอบว่ามีไบนารี llvm-symbolizer อยู่ใน /system/bin llvm-symbolizer สร้างจากแหล่งข้อมูลใน third_party/llvm/tools/llvm-symbolizer
  • กรองรายงานผ่านexternal/compiler-rt/lib/asan/scripts/symbolize.py สคริปต์

แนวทางที่ 2 จะให้ข้อมูลได้มากกว่า (เช่น file:lineตำแหน่ง) เนื่องจาก ความพร้อมใช้งานของไลบรารีที่สร้างสัญลักษณ์ในโฮสต์

ASan ในแอป

ASan ไม่สามารถดูโค้ด Java ได้ แต่ตรวจหาข้อบกพร่องในไลบรารี JNI ได้ คุณต้องสร้างไฟล์ที่เรียกใช้งานได้ด้วย ASan ซึ่งในกรณีนี้คือ /system/bin/app_process(32|64) การดำเนินการนี้จะ เปิดใช้ ASan ในแอปทั้งหมดในอุปกรณ์พร้อมกัน ซึ่งเป็น ภาระงานหนัก แต่อุปกรณ์ที่มี RAM 2 GB ควรจะรองรับได้

เพิ่ม LOCAL_SANITIZE:=address ไปยัง app_process กฎการสร้างใน frameworks/base/cmds/app_process

แก้ไขส่วน service zygote ของไฟล์ system/core/rootdir/init.zygote(32|64).rc ที่เหมาะสมเพื่อเพิ่มบรรทัดต่อไปนี้ลงในบล็อกของบรรทัดที่เยื้องซึ่งมี class main โดยเยื้องในจำนวนเดียวกัน

    setenv LD_LIBRARY_PATH /system/lib/asan:/system/lib
    setenv ASAN_OPTIONS allow_user_segv_handler=true

Build, adb sync, fastboot flash boot และ reboot

ใช้พร็อพเพอร์ตี้ Wrap

แนวทางในส่วนก่อนหน้าจะใส่ ASan ลงในทุกแอปในระบบ (จริงๆ แล้วคือทุกแอปที่สืบทอดมาจากกระบวนการ Zygote) คุณสามารถเรียกใช้แอปเพียงแอปเดียว (หรือหลายแอป) ด้วย ASan ได้ โดยแลกกับค่าใช้จ่ายด้านหน่วยความจำที่เพิ่มขึ้นเพื่อการเริ่มต้นแอปที่ช้าลง

โดยทำได้ด้วยการเริ่มต้นแอปด้วยพร็อพเพอร์ตี้ wrap. ตัวอย่างต่อไปนี้จะเรียกใช้แอป Gmail ภายใต้ ASan

adb root
adb shell setenforce 0  # disable SELinux
adb shell setprop wrap.com.google.android.gm "asanwrapper"

ในบริบทนี้ asanwrapper จะเขียน /system/bin/app_process ใหม่เป็น /system/bin/asan/app_process ซึ่งสร้างด้วย ASan นอกจากนี้ยังเพิ่ม /system/lib/asan ที่จุดเริ่มต้นของ เส้นทางการค้นหาไลบรารีแบบไดนามิกด้วย วิธีนี้จะทำให้ระบบเลือกใช้ไลบรารีที่ใช้ ASan จาก /system/lib/asan มากกว่าไลบรารีปกติใน /system/lib เมื่อเรียกใช้ด้วย asanwrapper

หากพบข้อบกพร่อง แอปจะขัดข้องและพิมพ์รายงานไปยัง บันทึก

SANITIZE_TARGET

Android 7.0 ขึ้นไปรองรับการสร้างแพลตฟอร์ม Android ทั้งหมดด้วย ASan ในครั้งเดียว (หากคุณกำลังสร้างรุ่นที่สูงกว่า Android 9 ขอแนะนำให้ใช้ HWASan)

เรียกใช้คำสั่งต่อไปนี้ในทรีการสร้างเดียวกัน

make -j42
SANITIZE_TARGET=address make -j42

ในโหมดนี้ userdata.img มีไลบรารีเพิ่มเติมและต้องแฟลชไปยังอุปกรณ์ด้วย ใช้บรรทัดคำสั่งต่อไปนี้

fastboot flash userdata && fastboot flashall

ซึ่งจะสร้างไลบรารีที่ใช้ร่วมกัน 2 ชุด ได้แก่ ชุดปกติใน /system/lib (การเรียกใช้ make ครั้งแรก) และชุดที่ใช้ ASan ใน /data/asan/lib (การเรียกใช้ make ครั้งที่สอง) ไฟล์ที่เรียกใช้งานได้จากบิลด์ที่ 2 จะเขียนทับไฟล์จากบิลด์แรก ไฟล์ที่ปฏิบัติการได้ซึ่งมีเครื่องมือ ASan จะมีเส้นทางการค้นหาไลบรารีที่แตกต่างกัน ซึ่งรวมถึง /data/asan/lib ก่อน /system/lib โดยใช้ /system/bin/linker_asan ใน PT_INTERP

ระบบบิลด์จะล้างไดเรกทอรีออบเจ็กต์กลางเมื่อค่า $SANITIZE_TARGET เปลี่ยนแปลง ซึ่งจะเป็นการบังคับให้สร้างเป้าหมายทั้งหมดใหม่ ขณะที่เก็บรักษาไบนารีที่ติดตั้งไว้ภายใต้ /system/lib

คุณสร้างเป้าหมายบางอย่างด้วย ASan ไม่ได้

  • ไฟล์ปฏิบัติการที่ลิงก์แบบคงที่
  • เป้าหมาย LOCAL_CLANG:=false
  • LOCAL_SANITIZE:=false ไม่ได้ ASan สำหรับ SANITIZE_TARGET=address

ระบบจะข้ามไฟล์ที่เรียกใช้งานได้เช่นนี้ในบิลด์ SANITIZE_TARGET และจะปล่อยให้เวอร์ชันจากการเรียกใช้ make ครั้งแรกอยู่ใน /system/bin

ไลบรารีเช่นนี้สร้างขึ้นโดยไม่มี ASan โดยอาจมีโค้ด ASan บางส่วนจากไลบรารีแบบคงที่ที่ขึ้นอยู่กับ

เอกสารประกอบ