คุณสามารถใช้เครื่องมือการตรวจสอบ Binary Interface (ABI) ของแอปพลิเคชันซึ่งมีใน Android 11 ขึ้นไป เพื่อรักษาเสถียรภาพ ABI ในเคอร์เนลของเคอร์เนล Android เครื่องมือจะรวบรวมและเปรียบเทียบการแสดง ABI จากไบนารีเคอร์เนลที่มีอยู่ (โมดูล vmlinux
+ GKI) การแสดง ABI เหล่านี้คือไฟล์ .stg
และรายการสัญลักษณ์ อินเทอร์เฟซที่การแสดงแสดงมุมมองเรียกว่า Kernel Module Interface (KMI) คุณสามารถใช้เครื่องมือเพื่อติดตามและลดการเปลี่ยนแปลง KMI
เครื่องมือตรวจสอบ ABI ได้ รับการพัฒนาใน AOSP และใช้ STG (หรือ libabigail
ใน Android 13 และต่ำกว่า) เพื่อสร้างและเปรียบเทียบการเป็นตัวแทน
หน้านี้อธิบายเครื่องมือ กระบวนการรวบรวมและวิเคราะห์การนำเสนอ ABI และการใช้งานการนำเสนอดังกล่าวเพื่อให้เสถียรภาพแก่ ABI ในเคอร์เนล หน้านี้ยังให้ข้อมูลสำหรับการสนับสนุนการเปลี่ยนแปลงเคอร์เนล Android
กระบวนการ
การวิเคราะห์ ABI ของเคอร์เนลนั้นมีหลายขั้นตอน ซึ่งส่วนใหญ่สามารถทำให้เป็นแบบอัตโนมัติได้:
- สร้างเคอร์เนลและการเป็นตัวแทน ABI
- วิเคราะห์ความแตกต่าง ABI ระหว่างบิลด์และการอ้างอิง
- อัปเดตการแสดง ABI (หากจำเป็น)
- ทำงานกับรายการสัญลักษณ์
คำแนะนำต่อไปนี้ใช้ได้กับ เคอร์เนลใดๆ ที่คุณสามารถสร้างได้ โดยใช้ toolchain ที่รองรับ (เช่น Clang toolchain ที่สร้างไว้ล่วงหน้า) repo manifests
พร้อมใช้งานสำหรับสาขาเคอร์เนลทั่วไปของ Android ทั้งหมด และสำหรับเคอร์เนลเฉพาะอุปกรณ์หลายรายการ ช่วยให้มั่นใจได้ว่ามีการใช้ toolchain ที่ถูกต้องเมื่อคุณสร้างการกระจายเคอร์เนลเพื่อการวิเคราะห์
รายการสัญลักษณ์
KMI ไม่ได้รวมสัญลักษณ์ทั้งหมดในเคอร์เนล หรือแม้แต่สัญลักษณ์ที่ส่งออกมากกว่า 30,000 รายการทั้งหมด แต่สัญลักษณ์ที่โมดูลผู้จำหน่ายสามารถใช้ได้นั้นจะแสดงรายการไว้อย่างชัดเจนในชุดของไฟล์รายการสัญลักษณ์ที่เก็บรักษาไว้แบบสาธารณะในรากของแผนผังเคอร์เนล การรวมสัญลักษณ์ทั้งหมดในไฟล์รายการสัญลักษณ์ทั้งหมดจะกำหนดชุดของสัญลักษณ์ KMI ที่คงความเสถียร ไฟล์รายการสัญลักษณ์ตัวอย่างคือ abi_gki_aarch64_db845c ซึ่งประกาศสัญลักษณ์ที่จำเป็นสำหรับ DragonBoard 845c
เฉพาะสัญลักษณ์ที่อยู่ในรายการสัญลักษณ์ ตลอดจนโครงสร้างและคำจำกัดความที่เกี่ยวข้องเท่านั้นที่ถือว่าเป็นส่วนหนึ่งของ KMI คุณสามารถโพสต์การเปลี่ยนแปลงในรายการสัญลักษณ์ของคุณได้ หากไม่มีสัญลักษณ์ที่คุณต้องการ หลังจากที่อินเทอร์เฟซใหม่อยู่ในรายการสัญลักษณ์ และเป็นส่วนหนึ่งของคำอธิบาย KMI อินเทอร์เฟซเหล่านั้นจะถูกรักษาให้มีความเสถียร และจะต้องไม่ถูกลบออกจากรายการสัญลักษณ์หรือแก้ไขหลังจากที่สาขาถูกแช่แข็ง
สาขาเคอร์เนล KMI ทั่วไปของ Android Common Kernel (ACK) แต่ละสาขามีชุดรายการสัญลักษณ์ของตัวเอง ไม่มีการพยายามสร้างความเสถียรของ ABI ระหว่างสาขาเคอร์เนล KMI ที่แตกต่างกัน ตัวอย่างเช่น KMI สำหรับ android12-5.10
จะไม่ขึ้นอยู่กับ KMI สำหรับ android13-5.10
โดยสิ้นเชิง
เครื่องมือ ABI ใช้รายการสัญลักษณ์ KMI เพื่อจำกัดอินเทอร์เฟซที่ต้องตรวจสอบเพื่อความเสถียร รายการสัญลักษณ์หลัก ประกอบด้วยสัญลักษณ์ที่โมดูลเคอร์เนล GKI กำหนด ผู้ขายได้รับการคาดหวังให้ส่งและอัปเดตรายการสัญลักษณ์เพิ่มเติมเพื่อให้แน่ใจว่าอินเทอร์เฟซที่ใช้จะรักษาความเข้ากันได้ของ ABI ตัวอย่างเช่น หากต้องการดูรายการสัญลักษณ์สำหรับ android13-5.15
โปรดดูที่ https://android.googlesource.com/kernel/common/+/refs/heads/android13-5.15/android
รายการสัญลักษณ์ประกอบด้วยสัญลักษณ์ที่รายงานว่าจำเป็นสำหรับผู้จำหน่ายหรืออุปกรณ์เฉพาะ รายการทั้งหมดที่ใช้โดยเครื่องมือคือการรวมไฟล์รายการสัญลักษณ์ KMI ทั้งหมด เครื่องมือ ABI จะกำหนดรายละเอียดของแต่ละสัญลักษณ์ รวมถึงลายเซ็นของฟังก์ชันและโครงสร้างข้อมูลที่ซ้อนกัน
เมื่อ KMI ถูกแช่แข็ง จะไม่อนุญาตให้ทำการเปลี่ยนแปลงกับอินเทอร์เฟซ KMI ที่มีอยู่ พวกมันมั่นคง อย่างไรก็ตาม ผู้จำหน่ายสามารถเพิ่มสัญลักษณ์ให้กับ KMI ได้ตลอดเวลา ตราบใดที่การเพิ่มเติมไม่ส่งผลกระทบต่อความเสถียรของ ABI ที่มีอยู่ สัญลักษณ์ที่เพิ่มใหม่จะคงความเสถียรทันทีที่มีการอ้างอิงโดยรายการสัญลักษณ์ KMI ไม่ควรลบสัญลักษณ์ออกจากรายการเคอร์เนล เว้นแต่จะสามารถยืนยันได้ว่าไม่มีอุปกรณ์ใดจัดส่งมาพร้อมกับการพึ่งพาสัญลักษณ์นั้น
คุณสามารถสร้างรายการสัญลักษณ์ KMI สำหรับอุปกรณ์โดยใช้คำแนะนำจาก วิธีทำงานกับรายการสัญลักษณ์ พันธมิตรหลายรายส่งรายการสัญลักษณ์หนึ่งรายการต่อ ACK แต่นี่ไม่ใช่ข้อกำหนดที่ยาก หากช่วยในการบำรุงรักษา คุณสามารถส่งรายการสัญลักษณ์ได้หลายรายการ
ขยาย KMI
แม้ว่าสัญลักษณ์ KMI และโครงสร้างที่เกี่ยวข้องจะคงไว้ซึ่งความเสถียร (หมายถึงการเปลี่ยนแปลงที่ทำลายอินเทอร์เฟซที่เสถียรในเคอร์เนลที่มี KMI ที่ถูกตรึงไว้นั้นไม่สามารถยอมรับได้) เคอร์เนล GKI ยังคงเปิดรับส่วนขยายเพื่อให้อุปกรณ์ที่จัดส่งในช่วงปลายปีไม่จำเป็นต้องกำหนดทั้งหมด การพึ่งพาก่อนที่ KMI จะถูกแช่แข็ง หากต้องการขยาย KMI คุณสามารถเพิ่มสัญลักษณ์ใหม่ให้กับ KMI สำหรับฟังก์ชันเคอร์เนลที่ส่งออกใหม่หรือที่มีอยู่ได้ แม้ว่า KMI จะถูกแช่แข็งก็ตาม อาจยอมรับเคอร์เนลแพตช์ใหม่ได้หากไม่ทำลาย KMI
เกี่ยวกับการแตกหักของ KMI
เคอร์เนลมี แหล่งที่มา และไบนารีถูกสร้างขึ้นจากแหล่งเหล่านั้น สาขาเคอร์เนลที่ตรวจสอบโดย ABI ประกอบด้วยการแสดง ABI ของ GKI ABI ปัจจุบัน (ในรูปแบบของไฟล์ .stg
) หลังจากสร้างไบนารี ( vmlinux
, Image
และโมดูล GKI ใด ๆ ) แล้ว การเป็นตัวแทน ABI ก็สามารถแยกออกจากไบนารีได้ การเปลี่ยนแปลงใด ๆ ที่ทำกับไฟล์ต้นฉบับเคอร์เนลอาจส่งผลต่อไบนารีและยังส่งผลต่อ .stg
ที่แตกออกมาด้วย เครื่องวิเคราะห์ AbiAnalyzer
จะเปรียบเทียบไฟล์ .stg
ที่คอมมิตกับไฟล์ที่แยกมาจาก build artefacts และตั้งค่าป้ายกำกับ Lint-1 บนการเปลี่ยนแปลงใน Gerrit หากพบความแตกต่างทางความหมาย
จัดการกับการแตกหักของ ABI
ตามตัวอย่าง แพตช์ต่อไปนี้ทำให้เกิดความเสียหายของ ABI ที่ชัดเจนมาก:
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index 42786e6364ef..e15f1d0f137b 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -657,6 +657,7 @@ struct mm_struct {
ANDROID_KABI_RESERVE(1);
} __randomize_layout;
+ int tickle_count;
/*
* The mm_cpumask needs to be at the end of mm_struct, because it
* is dynamically sized based on nr_cpu_ids.
เมื่อคุณรันบิลด์ ABI โดยใช้แพตช์นี้ เครื่องมือจะออกพร้อมกับรหัสข้อผิดพลาดที่ไม่ใช่ศูนย์ และรายงานความแตกต่างของ ABI ที่คล้ายกับสิ่งนี้:
function symbol 'struct block_device* I_BDEV(struct inode*)' changed
CRC changed from 0x8d400dbd to 0xabfc92ad
function symbol 'void* PDE_DATA(const struct inode*)' changed
CRC changed from 0xc3c38b5c to 0x7ad96c0d
function symbol 'void __ClearPageMovable(struct page*)' changed
CRC changed from 0xf489e5e8 to 0x92bd005e
... 4492 omitted; 4495 symbols have only CRC changes
type 'struct mm_struct' changed
byte size changed from 992 to 1000
member 'int tickle_count' was added
member 'unsigned long cpu_bitmap[0]' changed
offset changed by 64
ตรวจพบความแตกต่างของ ABI ณ เวลาที่สร้าง
สาเหตุที่พบบ่อยที่สุดของข้อผิดพลาดคือเมื่อไดรเวอร์ใช้สัญลักษณ์ใหม่จากเคอร์เนลที่ไม่อยู่ในรายการสัญลักษณ์ใดๆ
หากสัญลักษณ์ไม่รวมอยู่ในรายการสัญลักษณ์ ( android/abi_gki_aarch64
) คุณต้องตรวจสอบก่อนว่าส่งออกด้วย EXPORT_SYMBOL_GPL( symbol_name )
จากนั้นอัปเดตการแสดง ABI XML และรายการสัญลักษณ์ ตัวอย่างเช่น การเปลี่ยนแปลงต่อไปนี้จะเพิ่มฟีเจอร์ FS แบบเพิ่มหน่วยใหม่ให้กับสาขา android-12-5.10
ซึ่งรวมถึงการอัปเดตรายการสัญลักษณ์และการแสดง ABI XML
- ตัวอย่างการเปลี่ยนแปลงคุณสมบัติอยู่ใน aosp/1345659
- ตัวอย่างรายการสัญลักษณ์อยู่ใน aosp/1346742
- ตัวอย่างการเปลี่ยนแปลง ABI XML อยู่ใน aosp/1349377
หากสัญลักษณ์ถูกส่งออก (โดยคุณหรือถูกส่งออกก่อนหน้านี้) แต่ไม่มีไดรเวอร์อื่นใช้งานอยู่ คุณอาจได้รับข้อผิดพลาดในการสร้างที่คล้ายกับต่อไปนี้
Comparing the KMI and the symbol lists:
+ build/abi/compare_to_symbol_list out/$BRANCH/common/Module.symvers out/$BRANCH/common/abi_symbollist.raw
ERROR: Differences between ksymtab and symbol list detected!
Symbols missing from ksymtab:
Symbols missing from symbol list:
- simple_strtoull
หากต้องการแก้ไข ให้อัปเดตรายการสัญลักษณ์ KMI ทั้งในเคอร์เนลและ ACK ของคุณ (ดู อัปเดตการแสดง ABI ) สำหรับตัวอย่างการอัปเดต ABI XML และรายการสัญลักษณ์ใน ACK โปรดดูที่ aosp/1367601
แก้ไขการแตกหักของเคอร์เนล ABI
คุณสามารถจัดการกับการเสียหายของเคอร์เนล ABI ได้โดย การปรับโครงสร้างโค้ดใหม่เพื่อไม่ให้เปลี่ยน ABI หรือ อัปเดตการแสดง ABI ใช้แผนภูมิต่อไปนี้เพื่อกำหนดแนวทางที่ดีที่สุดสำหรับสถานการณ์ของคุณ
รูปที่ 1 ความละเอียดการแตกหักของ ABI
โค้ด Refactor เพื่อหลีกเลี่ยงการเปลี่ยนแปลง ABI
พยายามทุกวิถีทางเพื่อหลีกเลี่ยงการแก้ไข ABI ที่มีอยู่ ในหลายกรณี คุณสามารถปรับโครงสร้างโค้ดของคุณใหม่เพื่อลบการเปลี่ยนแปลงที่ส่งผลต่อ ABI
การเปลี่ยนแปลงฟิลด์โครงสร้างการปรับโครงสร้างใหม่ หากการเปลี่ยนแปลงแก้ไข ABI สำหรับคุณลักษณะการแก้ไขข้อบกพร่อง ให้เพิ่ม
#ifdef
รอบฟิลด์ (ในโครงสร้างและการอ้างอิงแหล่งที่มา) และตรวจสอบให้แน่ใจว่าCONFIG
ที่ใช้สำหรับ#ifdef
ถูกปิดใช้งานสำหรับ defconfig การผลิตและgki_defconfig
สำหรับตัวอย่างวิธีที่สามารถเพิ่มการกำหนดค่าการดีบักลงในโครงสร้างโดยไม่ทำลาย ABI โปรดดูที่ ชุดแพทช์นี้คุณสมบัติการปรับโครงสร้างใหม่เพื่อไม่ให้เปลี่ยนเคอร์เนลหลัก หากจำเป็นต้องเพิ่มคุณลักษณะใหม่ลงใน ACK เพื่อรองรับโมดูลคู่ค้า ให้ลองปรับโครงสร้างส่วน ABI ของการเปลี่ยนแปลงใหม่เพื่อหลีกเลี่ยงการแก้ไข ABI เคอร์เนล สำหรับตัวอย่างการใช้เคอร์เนล ABI ที่มีอยู่เพื่อเพิ่มฟังก์ชันการทำงานเพิ่มเติมโดยไม่ต้องเปลี่ยนเคอร์เนล ABI ให้อ้างอิงถึง aosp/1312213
แก้ไข ABI ที่เสียหายบน Android Gerrit
หากคุณไม่ได้ตั้งใจทำลายเคอร์เนล ABI คุณจะต้องตรวจสอบโดยใช้คำแนะนำที่ได้รับจากเครื่องมือตรวจสอบ ABI สาเหตุที่พบบ่อยที่สุดของการแตกหักคือการเปลี่ยนแปลงโครงสร้างข้อมูลและการเปลี่ยนแปลงสัญลักษณ์ CRC ที่เกี่ยวข้อง หรือเนื่องจากการเปลี่ยนแปลงตัวเลือกการกำหนดค่าที่นำไปสู่สิ่งที่กล่าวมาข้างต้น เริ่มต้นด้วยการแก้ไขปัญหาที่พบในเครื่องมือ
คุณสามารถสร้างการค้นพบ ABI ได้ในเครื่อง โปรดดูที่ สร้างเคอร์เนลและการแสดง ABI
เกี่ยวกับฉลาก Lint-1
หากคุณอัปโหลดการเปลี่ยนแปลงไปยังสาขาที่มี KMI ที่ค้างหรือสรุปแล้ว การเปลี่ยนแปลงจะต้องผ่าน AbiAnalyzer
เพื่อให้แน่ใจว่าการเปลี่ยนแปลงจะไม่ส่งผลกระทบต่อ ABI ที่เสถียรในลักษณะที่เข้ากันไม่ได้ ในระหว่างกระบวนการนี้ AbiAnalyzer
จะค้นหารายงาน ABI ที่สร้างขึ้นในระหว่างการบิลด์ (บิลด์แบบขยายที่ดำเนินการบิลด์ปกติ จากนั้นจึงแยก ABI และขั้นตอนการเปรียบเทียบบางส่วน
หาก AbiAnalyzer
พบรายงานที่ไม่ว่างเปล่า ระบบจะตั้งค่าป้ายกำกับ Lint-1 และการเปลี่ยนแปลงจะถูกบล็อกไม่ให้ส่งจนกว่าจะได้รับการแก้ไข จนกว่าแพตช์เซ็ตจะได้รับป้ายกำกับ Lint+1
อัปเดตเคอร์เนล ABI
หากไม่สามารถหลีกเลี่ยงการแก้ไข ABI ได้ คุณจะต้องใช้การเปลี่ยนแปลงโค้ด การแสดง ABI และรายการสัญลักษณ์กับ ACK หากต้องการให้ Lint ลบ -1 และไม่ทำลายความเข้ากันได้ของ GKI ให้ทำตามขั้นตอนเหล่านี้:
รอรับ Code-Review +2 สำหรับชุดแพทช์
รวมการเปลี่ยนแปลงโค้ดของคุณและการเปลี่ยนแปลงการอัปเดต ABI
อัปโหลดการเปลี่ยนแปลงรหัส ABI ไปยัง ACK
การอัปเดต ACK ABI ขึ้นอยู่กับประเภทของการเปลี่ยนแปลงที่กำลังดำเนินการ
หากการเปลี่ยนแปลง ABI เกี่ยวข้องกับคุณลักษณะที่ส่งผลต่อการทดสอบ CTS หรือ VTS การเปลี่ยนแปลงมักจะสามารถเลือกเป็น ACK ได้ตามปกติ ตัวอย่างเช่น:
- ต้องใช้ aosp/1289677 เพื่อให้เสียงใช้งานได้
- aosp/1295945 จำเป็นสำหรับ USB ในการทำงาน
หากการเปลี่ยนแปลง ABI มีไว้สำหรับคุณลักษณะที่สามารถแชร์กับ ACK ได้ การเปลี่ยนแปลงนั้นจะสามารถเลือกเป็น ACK ได้เหมือนเดิม ตัวอย่างเช่น การเปลี่ยนแปลงต่อไปนี้ไม่จำเป็นสำหรับการทดสอบ CTS หรือ VTS แต่สามารถแชร์กับ ACK ได้:
- aosp/1250412 คือการเปลี่ยนแปลงคุณสมบัติการระบายความร้อน
- aosp/1288857 เป็นการเปลี่ยนแปลง
EXPORT_SYMBOL_GPL
หากการเปลี่ยนแปลง ABI ทำให้เกิดคุณลักษณะใหม่ที่ไม่จำเป็นต้องรวมอยู่ใน ACK คุณสามารถแนะนำสัญลักษณ์ให้กับ ACK โดยใช้ stub ตามที่อธิบายไว้ในส่วนต่อไปนี้
ใช้ต้นขั้วสำหรับ ACK
Stubs ต้องมีความจำเป็นเฉพาะสำหรับการเปลี่ยนแปลงเคอร์เนลหลักที่ไม่เป็นประโยชน์ต่อ ACK เช่น การเปลี่ยนแปลงประสิทธิภาพและกำลัง รายการต่อไปนี้มีรายละเอียดตัวอย่างต้นขั้วและต้นเชอร์รี่บางส่วนใน ACK สำหรับ GKI
ต้นขั้วฟีเจอร์แยกคอร์ ( aosp/1284493 ) ไม่จำเป็นต้องใช้ฟังก์ชันการทำงานใน ACK แต่ต้องมีสัญลักษณ์อยู่ใน ACK เพื่อให้โมดูลของคุณใช้สัญลักษณ์เหล่านี้
สัญลักษณ์ตัวยึดตำแหน่งสำหรับโมดูลผู้ขาย ( aosp/1288860 )
คุณลักษณะการติดตามเหตุการณ์ต่อกระบวนการ
mm
ที่คัดสรรเฉพาะ ABI เท่านั้น ( aosp/1288454 ) แพตช์ดั้งเดิมได้รับการคัดเลือกเป็น ACK จากนั้นตัดแต่งให้รวมเฉพาะการเปลี่ยนแปลงที่จำเป็นเพื่อแก้ไข ABI diff สำหรับtask_struct
และmm_event_count
โปรแกรมปรับปรุงนี้ยังอัปเดตmm_event_type
enum เพื่อให้มีสมาชิกสุดท้ายการเปลี่ยนแปลง ABI โครงสร้างทางความร้อนบางส่วนที่คัดเลือกมาอย่างดีซึ่งต้องการมากกว่าแค่การเพิ่มฟิลด์ ABI ใหม่
โปรแกรมแก้ไข aosp/1255544 แก้ไขความแตกต่าง ABI ระหว่างเคอร์เนลของคู่ค้าและ ACK
Patch aosp/1291018 แก้ไขปัญหาการทำงานที่พบในระหว่างการทดสอบ GKI ของแพตช์ก่อนหน้า การแก้ไขนี้รวมถึงการเริ่มต้นโครงสร้างพารามิเตอร์เซ็นเซอร์เพื่อบันทึกโซนความร้อนหลายโซนไว้ในเซ็นเซอร์ตัวเดียว
CONFIG_NL80211_TESTMODE
การเปลี่ยนแปลง ABI ( aosp/1344321 ) โปรแกรมปรับปรุงนี้เพิ่มการเปลี่ยนแปลงโครงสร้างที่จำเป็นสำหรับ ABI และตรวจสอบให้แน่ใจว่าฟิลด์เพิ่มเติมไม่ทำให้เกิดความแตกต่างในการทำงาน ช่วยให้พันธมิตรสามารถรวมCONFIG_NL80211_TESTMODE
ในเคอร์เนลการใช้งานจริงของตน และยังคงรักษาการปฏิบัติตามข้อกำหนดของ GKI
บังคับใช้ KMI ณ รันไทม์
เคอร์เนล GKI ใช้ตัวเลือกการกำหนดค่า TRIM_UNUSED_KSYMS=y
และ UNUSED_KSYMS_WHITELIST=<union of all symbol lists>
ซึ่งจำกัดสัญลักษณ์ที่ส่งออก (เช่น สัญลักษณ์ที่ส่งออกโดยใช้ EXPORT_SYMBOL_GPL()
) ไปยังรายการที่อยู่ในรายการสัญลักษณ์ สัญลักษณ์อื่นๆ ทั้งหมดจะไม่ถูกส่งออก และการโหลดโมดูลที่ต้องการสัญลักษณ์ที่ไม่ได้ส่งออกจะถูกปฏิเสธ ข้อจำกัดนี้บังคับใช้ในขณะสร้างและรายการที่ขาดหายไปจะถูกตั้งค่าสถานะ
เพื่อวัตถุประสงค์ในการพัฒนา คุณสามารถใช้โครงสร้างเคอร์เนล GKI ที่ไม่มีการตัดแต่งสัญลักษณ์ได้ (หมายถึงสามารถใช้สัญลักษณ์ที่ส่งออกโดยทั่วไปทั้งหมดได้) หากต้องการค้นหาบิลด์เหล่านี้ ให้มองหา kernel_debug_aarch64
บิลด์บน ci.android.com
บังคับใช้ KMI โดยใช้การกำหนดเวอร์ชันโมดูล
เคอร์เนล Generic Kernel Image (GKI) ใช้ การกำหนดเวอร์ชันโมดูล ( CONFIG_MODVERSIONS
) เป็นมาตรการเพิ่มเติมเพื่อบังคับใช้การปฏิบัติตาม KMI ณ รันไทม์ การกำหนดเวอร์ชันของโมดูลอาจทำให้เกิดความล้มเหลวในการตรวจสอบความซ้ำซ้อนแบบวนรอบ (CRC) ในเวลาโหลดโมดูล หาก KMI ที่คาดหวังของโมดูลไม่ตรงกับ vmlinux
KMI ตัวอย่างเช่น ต่อไปนี้เป็นความล้มเหลวทั่วไปที่เกิดขึ้นในเวลาโหลดโมดูล เนื่องจาก CRC ไม่ตรงกันสำหรับสัญลักษณ์ module_layout()
:
init: Loading module /lib/modules/kernel/.../XXX.ko with args ""
XXX: disagrees about version of symbol module_layout
init: Failed to insmod '/lib/modules/kernel/.../XXX.ko' with args ''
การใช้การกำหนดเวอร์ชันของโมดูล
การกำหนดเวอร์ชันของโมดูลมีประโยชน์ด้วยเหตุผลต่อไปนี้:
การกำหนดเวอร์ชันของโมดูลจะตรวจจับการเปลี่ยนแปลงในการมองเห็นโครงสร้างข้อมูล หากโมดูลเปลี่ยนโครงสร้างข้อมูลทึบแสง นั่นคือ โครงสร้างข้อมูลที่ไม่ได้เป็นส่วนหนึ่งของ KMI โมดูลจะพังหลังจากการเปลี่ยนแปลงโครงสร้างในอนาคต
เป็นตัวอย่าง ให้พิจารณาฟิลด์
fwnode
ในstruct device
ฟิลด์นี้จะต้องทึบแสงสำหรับโมดูล เพื่อไม่ให้มีการเปลี่ยนแปลงฟิลด์ของdevice->fw_node
หรือตั้งสมมติฐานเกี่ยวกับขนาดของมันอย่างไรก็ตาม หากโมดูลมี
<linux/fwnode.h>
(โดยตรงหรือโดยอ้อม) ฟิลด์fwnode
ในstruct device
จะไม่ทึบอีกต่อไป จากนั้นโมดูลสามารถทำการเปลี่ยนแปลงกับdevice->fwnode->dev
หรือdevice->fwnode->ops
สถานการณ์นี้มีปัญหาด้วยเหตุผลหลายประการ ดังระบุไว้ดังนี้:สามารถทำลายสมมติฐานที่โค้ดเคอร์เนลหลักสร้างเกี่ยวกับโครงสร้างข้อมูลภายในได้
หากการอัพเดตเคอร์เนลในอนาคตเปลี่ยน
struct fwnode_handle
(ประเภทข้อมูลของfwnode
) แสดงว่าโมดูลจะไม่ทำงานกับเคอร์เนลใหม่อีกต่อไป นอกจากนี้stgdiff
จะไม่แสดงความแตกต่างใดๆ เนื่องจากโมดูลทำลาย KMI โดยการจัดการโครงสร้างข้อมูลภายในโดยตรงในลักษณะที่ไม่สามารถบันทึกโดยการตรวจสอบการเป็นตัวแทนไบนารี่เท่านั้น
โมดูลปัจจุบันจะถือว่าเข้ากันไม่ได้กับ KMI เมื่อมีการโหลดในภายหลังโดยเคอร์เนลใหม่ที่เข้ากันไม่ได้ การกำหนดเวอร์ชันของโมดูลจะเพิ่มการตรวจสอบรันไทม์เพื่อหลีกเลี่ยงการโหลดโมดูลที่ไม่รองรับ KMI กับเคอร์เนลโดยไม่ตั้งใจ การตรวจสอบนี้ป้องกันปัญหารันไทม์ที่ยากต่อการแก้ไขและการหยุดทำงานของเคอร์เนลซึ่งอาจเป็นผลมาจากความเข้ากันไม่ได้ใน KMI ที่ตรวจไม่พบ
การเปิดใช้งานการกำหนดเวอร์ชันโมดูลจะป้องกันปัญหาเหล่านี้ทั้งหมด
ตรวจสอบ CRC ที่ไม่ตรงกันโดยไม่ต้องบูตอุปกรณ์
stgdiff
เปรียบเทียบและรายงาน CRC ไม่ตรงกันระหว่างเคอร์เนลพร้อมกับความแตกต่าง ABI อื่น ๆ
นอกจากนี้ การสร้างเคอร์เนลแบบเต็มโดยเปิดใช้งาน CONFIG_MODVERSIONS
จะสร้างไฟล์ Module.symvers
ซึ่งเป็นส่วนหนึ่งของกระบวนการสร้างปกติ ไฟล์นี้มีหนึ่งบรรทัดสำหรับทุกสัญลักษณ์ที่ส่งออกโดยเคอร์เนล ( vmlinux
) และโมดูล แต่ละบรรทัดประกอบด้วยค่า CRC ชื่อสัญลักษณ์ เนมสเปซสัญลักษณ์ vmlinux
หรือชื่อโมดูลที่ส่งออกสัญลักษณ์ และประเภทการส่งออก (เช่น EXPORT_SYMBOL
กับ EXPORT_SYMBOL_GPL
)
คุณสามารถเปรียบเทียบไฟล์ Module.symvers
ระหว่าง GKI บิลด์และบิลด์ของคุณ เพื่อตรวจสอบความแตกต่างของ CRC ในสัญลักษณ์ที่ส่งออกโดย vmlinux
หากมีความแตกต่างของค่า CRC ในสัญลักษณ์ใดๆ ที่ส่งออกโดย vmlinux
และ สัญลักษณ์นั้นถูกใช้โดยหนึ่งในโมดูลที่คุณโหลดในอุปกรณ์ของคุณ โมดูลนั้นจะไม่โหลด
หากคุณไม่มี build artifact ทั้งหมด แต่มีไฟล์ vmlinux
ของเคอร์เนล GKI และเคอร์เนลของคุณ คุณสามารถเปรียบเทียบค่า CRC สำหรับสัญลักษณ์เฉพาะได้โดยการรันคำสั่งต่อไปนี้บนทั้งเคอร์เนลและเปรียบเทียบเอาต์พุต:
nm <path to vmlinux>/vmlinux | grep __crc_<symbol name>
ตัวอย่างเช่น คำสั่งต่อไปนี้จะตรวจสอบค่า CRC สำหรับสัญลักษณ์ module_layout
:
nm vmlinux | grep __crc_module_layout
0000000008663742 A __crc_module_layout
แก้ไข CRC ที่ไม่ตรงกัน
ใช้ขั้นตอนต่อไปนี้เพื่อแก้ไข CRC ที่ไม่ตรงกันเมื่อโหลดโมดูล:
สร้างเคอร์เนล GKI และเคอร์เนลอุปกรณ์ของคุณโดยใช้ตัวเลือก
--kbuild_symtypes
ดังที่แสดงในคำสั่งต่อไปนี้:tools/bazel run --kbuild_symtypes //common:kernel_aarch64_dist
คำสั่งนี้สร้างไฟล์
.symtypes
สำหรับไฟล์.o
แต่ละไฟล์ ดูKBUILD_SYMTYPES
ใน Kleaf เพื่อดูรายละเอียดสำหรับ Android 13 และต่ำกว่า ให้สร้างเคอร์เนล GKI และเคอร์เนลอุปกรณ์ของคุณโดยเติม
KBUILD_SYMTYPES=1
ไว้หน้าคำสั่งที่คุณใช้สร้างเคอร์เนล ดังที่แสดงในคำสั่งต่อไปนี้:KBUILD_SYMTYPES=1 BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh
เมื่อใช้
build_abi.sh,
แฟล็กKBUILD_SYMTYPES=1
จะถูกตั้งค่าโดยปริยายแล้วค้นหาไฟล์
.c
ที่มีการส่งออกสัญลักษณ์ที่มี CRC ไม่ตรงกัน โดยใช้คำสั่งต่อไปนี้:cd common && git grep EXPORT_SYMBOL.*module_layout kernel/module.c:EXPORT_SYMBOL(module_layout);
ไฟล์
.c
มีไฟล์.symtypes
ที่เกี่ยวข้องใน GKI และส่วนการสร้างเคอร์เนลอุปกรณ์ของคุณ ค้นหาไฟล์.c
โดยใช้คำสั่งต่อไปนี้:cd out/$BRANCH/common && ls -1 kernel/module.* kernel/module.o kernel/module.o.symversions kernel/module.symtypes
ต่อไปนี้เป็นคุณลักษณะของไฟล์
.c
:รูปแบบของไฟล์
.c
คือหนึ่งบรรทัด (อาจยาวมาก) ต่อสัญลักษณ์[s|u|e|etc]#
ที่จุดเริ่มต้นของบรรทัดหมายความว่าสัญลักษณ์นั้นเป็นประเภทข้อมูล[struct|union|enum|etc]
ตัวอย่างเช่น:t#bool typedef _Bool bool
คำนำหน้า
#
ที่หายไปในตอนต้นของบรรทัดบ่งชี้ว่าสัญลักษณ์นั้นเป็นฟังก์ชัน ตัวอย่างเช่น:find_module s#module * find_module ( const char * )
เปรียบเทียบทั้งสองไฟล์และแก้ไขความแตกต่างทั้งหมด
กรณีที่ 1: ความแตกต่างเนื่องจากการมองเห็นชนิดข้อมูล
หากเคอร์เนลตัวหนึ่งเก็บสัญลักษณ์หรือประเภทข้อมูลไว้ไม่ชัดเจนสำหรับโมดูล แต่เคอร์เนลอีกเคอร์เนลไม่ทึบ ความแตกต่างนั้นจะปรากฏขึ้นระหว่างไฟล์ .symtypes
ของเคอร์เนลทั้งสอง ไฟล์ .symtypes
จากเคอร์เนลตัวใดตัวหนึ่งมี UNKNOWN
สำหรับสัญลักษณ์ และไฟล์ .symtypes
จากเคอร์เนลอื่นมีมุมมองแบบขยายของสัญลักษณ์หรือชนิดข้อมูล
ตัวอย่างเช่น การเพิ่มบรรทัดต่อไปนี้ลงในไฟล์ include/linux/device.h
ในเคอร์เนลของคุณทำให้ CRC ไม่ตรงกัน ซึ่งหนึ่งในนั้นคือสำหรับ module_layout()
:
#include <linux/fwnode.h>
เมื่อเปรียบเทียบ module.symtypes
สำหรับสัญลักษณ์นั้น จะเผยให้เห็นความแตกต่างดังต่อไปนี้:
$ diff -u <GKI>/kernel/module.symtypes <your kernel>/kernel/module.symtypes
--- <GKI>/kernel/module.symtypes
+++ <your kernel>/kernel/module.symtypes
@@ -334,12 +334,15 @@
...
-s#fwnode_handle struct fwnode_handle { UNKNOWN }
+s#fwnode_reference_args struct fwnode_reference_args { s#fwnode_handle * fwnode ; unsigned int nargs ; t#u64 args [ 8 ] ; }
...
หากเคอร์เนลของคุณมีค่า UNKNOWN
และเคอร์เนล GKI มีมุมมองที่ขยายของสัญลักษณ์ (ไม่น่าเป็นไปได้มาก) ให้รวมเคอร์เนลทั่วไปของ Android ล่าสุดเข้ากับเคอร์เนลของคุณเพื่อให้คุณใช้ฐานเคอร์เนล GKI ล่าสุด
ในกรณีส่วนใหญ่ เคอร์เนล GKI มีค่าเป็น UNKNOWN
แต่เคอร์เนลของคุณมีรายละเอียดภายในของสัญลักษณ์เนื่องจากมีการเปลี่ยนแปลงกับเคอร์เนลของคุณ เนื่องจากไฟล์หนึ่งในเคอร์เนลของคุณเพิ่ม #include
ที่ไม่มีอยู่ในเคอร์เนล GKI
บ่อยครั้ง การแก้ไขทำได้ง่ายเพียงแค่ซ่อน #include
ใหม่จาก genksyms
#ifndef __GENKSYMS__
#include <linux/fwnode.h>
#endif
มิฉะนั้น หากต้องการระบุ #include
ที่ทำให้เกิดความแตกต่าง ให้ทำตามขั้นตอนเหล่านี้:
เปิดไฟล์ส่วนหัวที่กำหนดสัญลักษณ์หรือประเภทข้อมูลที่มีความแตกต่างนี้ ตัวอย่างเช่น แก้ไข
include/linux/fwnode.h
สำหรับstruct fwnode_handle
เพิ่มรหัสต่อไปนี้ที่ด้านบนของไฟล์ส่วนหัว:
#ifdef CRC_CATCH #error "Included from here" #endif
ในไฟล์
.c
ของโมดูลที่มี CRC ไม่ตรงกัน ให้เพิ่มบรรทัดต่อไปนี้เป็นบรรทัดแรกก่อนบรรทัด#include
ใดๆ#define CRC_CATCH 1
รวบรวมโมดูลของคุณ ข้อผิดพลาดเวลาบิลด์ที่เกิดขึ้นจะแสดงห่วงโซ่ของไฟล์ส่วนหัว
#include
ที่นำไปสู่ความไม่ตรงกันของ CRC นี้ ตัวอย่างเช่น:In file included from .../drivers/clk/XXX.c:16:` In file included from .../include/linux/of_device.h:5: In file included from .../include/linux/cpu.h:17: In file included from .../include/linux/node.h:18: .../include/linux/device.h:16:2: error: "Included from here" #error "Included from here"
ลิงก์หนึ่งในห่วงโซ่ของ
#include
นี้เกิดจากการเปลี่ยนแปลงที่เกิดขึ้นในเคอร์เนลของคุณ ซึ่งหายไปในเคอร์เนล GKIระบุการเปลี่ยนแปลง เปลี่ยนกลับในเคอร์เนลของคุณ หรือ อัปโหลดไปที่ ACK และรวมเข้าด้วยกัน
กรณีที่ 2: ความแตกต่างเนื่องจากการเปลี่ยนแปลงชนิดข้อมูล
หาก CRC ไม่ตรงกันสำหรับสัญลักษณ์หรือประเภทข้อมูลไม่ได้เกิดจากความแตกต่างในการมองเห็น นั่นก็เนื่องมาจากการเปลี่ยนแปลงที่เกิดขึ้นจริง (เพิ่มเติม การลบออก หรือการเปลี่ยนแปลง) ในประเภทข้อมูลนั้นเอง
ตัวอย่างเช่น การเปลี่ยนแปลงเคอร์เนลต่อไปนี้ทำให้ CRC ไม่ตรงกันหลายรายการ เนื่องจากการเปลี่ยนแปลงประเภทนี้ได้รับผลกระทบทางอ้อมหลายสัญลักษณ์:
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -259,7 +259,7 @@ struct iommu_ops {
void (*iotlb_sync)(struct iommu_domain *domain);
phys_addr_t (*iova_to_phys)(struct iommu_domain *domain, dma_addr_t iova);
phys_addr_t (*iova_to_phys_hard)(struct iommu_domain *domain,
- dma_addr_t iova);
+ dma_addr_t iova, unsigned long trans_flag);
int (*add_device)(struct device *dev);
void (*remove_device)(struct device *dev);
struct iommu_group *(*device_group)(struct device *dev);
CRC ที่ไม่ตรงกันหนึ่งรายการสำหรับ devm_of_platform_populate()
หากคุณเปรียบเทียบไฟล์ .symtypes
สำหรับสัญลักษณ์นั้น อาจมีลักษณะดังนี้:
$ diff -u <GKI>/drivers/of/platform.symtypes <your kernel>/drivers/of/platform.symtypes
--- <GKI>/drivers/of/platform.symtypes
+++ <your kernel>/drivers/of/platform.symtypes
@@ -399,7 +399,7 @@
...
-s#iommu_ops struct iommu_ops { ... ; t#phy
s_addr_t ( * iova_to_phys_hard ) ( s#iommu_domain * , t#dma_addr_t ) ; int
( * add_device ) ( s#device * ) ; ...
+s#iommu_ops struct iommu_ops { ... ; t#phy
s_addr_t ( * iova_to_phys_hard ) ( s#iommu_domain * , t#dma_addr_t , unsigned long ) ; int ( * add_device ) ( s#device * ) ; ...
หากต้องการระบุประเภทที่เปลี่ยนแปลง ให้ทำตามขั้นตอนเหล่านี้:
ค้นหาคำจำกัดความของสัญลักษณ์ในซอร์สโค้ด (โดยปกติจะอยู่ในไฟล์
.h
)- สำหรับความแตกต่างสัญลักษณ์อย่างง่ายระหว่างเคอร์เนลและเคอร์เนล GKI ให้ค้นหาคอมมิตโดยการรันคำสั่งต่อไปนี้:
git blame
- สำหรับสัญลักษณ์ที่ถูกลบ (โดยที่สัญลักษณ์ถูกลบในแผนภูมิและคุณต้องการลบในแผนภูมิอื่นด้วย) คุณต้องค้นหาการเปลี่ยนแปลงที่ลบบรรทัด ใช้คำสั่งต่อไปนี้บนแผนผังที่บรรทัดถูกลบ:
git log -S "copy paste of deleted line/word" -- <file where it was deleted>
ตรวจสอบรายการคอมมิตที่ส่งคืนเพื่อค้นหาการเปลี่ยนแปลงหรือการลบ คอมมิตแรกอาจเป็นสิ่งที่คุณกำลังมองหา หากไม่เป็นเช่นนั้น ให้ดำเนินการตามรายการจนกว่าคุณจะพบการคอมมิต
หลังจากที่คุณระบุการเปลี่ยนแปลงแล้ว ให้เปลี่ยนกลับในเคอร์เนลของคุณหรือ อัปโหลดไปที่ ACK และรวมเข้าด้วยกัน