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 บางส่วนจากไลบรารีแบบคงที่ที่ใช้