AddressSanitizer

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

ASan จะตรวจหาสิ่งต่อไปนี้

  • การล้น/การขาดบัฟเฟอร์สแต็กและฮีป
  • การใช้งานฮีปหลังจากมีการปลดปล่อย
  • การใช้สแต็กนอกขอบเขต
  • ฟรีคู่/ฟรีแบบดุเดือด

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

Android 10 และสาขาหลักของ AOSP ใน AArch64 รองรับ 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

สําหรับเดรัมของระบบ ให้เพิ่มข้อมูลต่อไปนี้ลงในส่วนที่เหมาะสมของ /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 ใช้โปรแกรมยกเลิกการจัดเฟรมที่รวดเร็วเพื่อบันทึกการติดตามกองซ้อนสําหรับเหตุการณ์การจัดสรรและการยกเลิกการจัดสรรหน่วยความจําทั้งหมดในโปรแกรม 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 ละเว้นเป้าหมาย app_process__asan ในไฟล์เดียวกันในตอนนี้ (หากยังมีอยู่ในขณะที่คุณอ่านข้อความนี้)

แก้ไขส่วน 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

บิลด์, ซิงค์ adb, บูตแฟลช Fastboot และรีบูต

ใช้พร็อพเพอร์ตี้ 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 จะเขียนทับไฟล์ปฏิบัติการจากบิลด์ที่ 1 ไฟล์ปฏิบัติการที่เครื่องมือ 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 บางส่วนจากไลบรารีแบบคงที่ที่ใช้

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