เครื่องมือเพิ่มประสิทธิภาพที่อยู่

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

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

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

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

Android 10 และสาขาหลักของ AOSP ใน AArch64 รองรับ API ที่อยู่แบบฮาร์ดแวร์ช่วย (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 ใช้โปรแกรมคลายการอ่านแบบ Framepointer ที่รวดเร็วเพื่อบันทึกสแต็กเทรซสำหรับทุกเหตุการณ์การจัดสรรหน่วยความจำและดีลตำแหน่งต่างๆ ในโปรแกรม 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 Flash Boot และรีบูต

ใช้พร็อพเพอร์ตี้การรวม

วิธีการในส่วนก่อนหน้านี้จะใส่ 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 และเวอร์ชันจากการเรียกใช้ครั้งแรกจะยังคงอยู่ใน /system/bin

ไลบรารีลักษณะนี้สร้างขึ้นโดยไม่มี ASan ไฟล์เหล่านี้อาจมีโค้ด ASan บางส่วนจากไลบรารีแบบคงที่ที่ใช้

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