การตรวจสอบ ABI ของเคอร์เนล Android

คุณสามารถใช้เครื่องมือตรวจสอบ Application Binary Interface (ABI) ซึ่งมีให้ใน Android 11 ขึ้นไปเพื่อทำให้ ABI ในเคอร์เนลของ Android ทำงานได้อย่างเสถียร เครื่องมือจะรวบรวมและเปรียบเทียบการแสดง ABI จากไบนารีเคอร์เนลที่มีอยู่ (vmlinux+ โมดูล GKI) การนําเสนอ ABI เหล่านี้คือไฟล์ .stg และรายการสัญลักษณ์ อินเทอร์เฟซที่การแสดงผลแสดงมุมมองเรียกว่าอินเทอร์เฟซโมดูลเคอร์เนล (KMI) คุณสามารถใช้เครื่องมือเพื่อติดตามและบรรเทาการเปลี่ยนแปลงใน KMI ได้

เครื่องมือตรวจสอบ ABI พัฒนาใน AOSP และใช้ STG (หรือ libabigail ใน Android 13 และต่ำกว่า) เพื่อสร้างและเปรียบเทียบการแสดงผล

หน้านี้อธิบายเครื่องมือ กระบวนการรวบรวมและวิเคราะห์การนําเสนอ ABI และการใช้การนําเสนอดังกล่าวเพื่อเพิ่มความเสถียรให้กับ ABI ในเคอร์เนล หน้านี้ยังมีข้อมูลเกี่ยวกับการมีส่วนร่วมในการเปลี่ยนแปลงเคอร์เนล Android ด้วย

กระบวนการ

การวิเคราะห์ ABI ของเคอร์เนลต้องดำเนินการหลายขั้นตอน ซึ่งส่วนใหญ่สามารถทําแบบอัตโนมัติได้ ดังนี้

  1. สร้างเคอร์เนลและการแสดง ABI ของเคิร์นเนล
  2. วิเคราะห์ความแตกต่างของ ABI ระหว่างบิลด์กับข้อมูลอ้างอิง
  3. อัปเดตการแสดง ABI (หากจำเป็น)
  4. ทำงานกับรายการสัญลักษณ์

วิธีการต่อไปนี้ใช้ได้กับเคอร์เนลที่คุณสร้างได้โดยใช้ชุดเครื่องมือที่รองรับ (เช่น ชุดเครื่องมือ Clang ที่คอมไพล์ไว้ล่วงหน้า) repo manifests พร้อมใช้งานสำหรับสาขาเคอร์เนลทั่วไปทั้งหมดของ Android และสำหรับเคอร์เนลเฉพาะอุปกรณ์หลายรายการ ซึ่งจะช่วยให้มั่นใจได้ว่าจะใช้เครื่องมือทางเทคนิคที่ถูกต้องเมื่อคุณสร้างการแจกจ่ายเคอร์เนลเพื่อการวิเคราะห์

รายการสัญลักษณ์

KMI ไม่ได้รวมสัญลักษณ์ทั้งหมดในเคอร์เนลหรือแม้แต่สัญลักษณ์ทั้งหมดที่ส่งออกกว่า 30,000 รายการ แต่ระบบจะแสดงสัญลักษณ์ที่โมดูลของผู้ให้บริการใช้ได้อย่างชัดเจนในชุดไฟล์รายการสัญลักษณ์ที่ดูแลรักษาแบบสาธารณะในต้นไม้เคอร์เนล (gki/{ARCH}/symbols/* หรือ android/abi_gki_{ARCH}_* ใน Android 15 ลงไป) การรวมสัญลักษณ์ทั้งหมดในไฟล์รายการสัญลักษณ์ทั้งหมดจะกำหนดชุดสัญลักษณ์ KMI ที่คงที่ ตัวอย่างไฟล์รายการสัญลักษณ์คือ gki/aarch64/symbols/db845c ซึ่งประกาศสัญลักษณ์ที่จําเป็นสําหรับ DragonBoard 845c

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

แต่ละสาขาเคอร์เนล KMI ของ Android Common Kernel (ACK) จะมีชุดรายการสัญลักษณ์ของตัวเอง ไม่มีการพยายามทำให้ ABI คงที่ระหว่างสาขาเคอร์เนล KMI ต่างๆ เช่น KMI ของ android12-5.10 จะไม่เกี่ยวข้องกับ KMI ของ android13-5.10 แต่อย่างใด

เครื่องมือ ABI ใช้รายการสัญลักษณ์ KMI เพื่อจำกัดอินเทอร์เฟซที่ต้องตรวจสอบความเสถียร ผู้ให้บริการควรส่งและอัปเดตรายการสัญลักษณ์ของตนเองเพื่อให้อินเทอร์เฟซที่ใช้เข้ากันได้กับ ABI ตัวอย่างเช่น หากต้องการดูรายการสัญลักษณ์สำหรับเคอร์เนล android16-6.12 ให้ดูที่ https://android.googlesource.com/kernel/common/+/refs/heads/android16-6.12/gki/aarch64/symbols

รายการสัญลักษณ์ประกอบด้วยสัญลักษณ์ที่รายงานว่าจําเป็นสําหรับผู้ให้บริการหรืออุปกรณ์หนึ่งๆ รายการทั้งหมดที่เครื่องมือใช้คือชุดรวมของไฟล์รายการสัญลักษณ์ KMI ทั้งหมด เครื่องมือ ABI จะกำหนดรายละเอียดของสัญลักษณ์แต่ละรายการ ซึ่งรวมถึงลายเซ็นฟังก์ชันและโครงสร้างข้อมูลที่ฝังอยู่

เมื่อ KMI หยุดทำงาน ระบบจะไม่อนุญาตให้เปลี่ยนแปลงอินเทอร์เฟซ KMI ที่มีอยู่ เนื่องจากอินเทอร์เฟซเหล่านี้มีความเสถียร อย่างไรก็ตาม ผู้ให้บริการสามารถเพิ่มสัญลักษณ์ลงใน KMI ได้ทุกเมื่อ ตราบใดที่การเพิ่มดังกล่าวไม่ส่งผลต่อความเสถียรของ ABI ที่มีอยู่ ระบบจะคงสัญลักษณ์ที่เพิ่มเข้ามาใหม่ให้เสถียรทันทีที่มีการใช้สัญลักษณ์ดังกล่าวในรายการสัญลักษณ์ KMI คุณไม่ควรนำสัญลักษณ์ออกจากรายการสำหรับเคอร์เนล เว้นแต่จะสามารถยืนยันได้ว่าไม่มีอุปกรณ์ใดที่เคยจัดส่งโดยอาศัยสัญลักษณ์นั้น

คุณสามารถสร้างรายการสัญลักษณ์ KMI สำหรับอุปกรณ์โดยใช้วิธีการจากวิธีใช้รายการสัญลักษณ์ พาร์ทเนอร์จำนวนมากส่งรายการสัญลักษณ์ 1 รายการต่อ ACK 1 รายการ แต่นี่ไม่ใช่ข้อกำหนดที่เข้มงวด คุณส่งรายการสัญลักษณ์หลายรายการได้หากช่วยในการบำรุงรักษา

ขยาย KMI

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

เกี่ยวกับการหยุดทำงานของ KMI

เคอร์เนลมีซอร์ส และไบนารีจะสร้างขึ้นจากซอร์สเหล่านั้น สาขาเคอร์เนลที่ตรวจสอบ ABI จะรวมการนําเสนอ ABI ของ GKI ABI ปัจจุบัน (ในรูปแบบไฟล์ .stg) หลังจากสร้างไบนารี (vmlinux, Image และข้อบังคับ GKI ทั้งหมด) แล้ว คุณจะดึงข้อมูลการนำเสนอ ABI ออกจากไบนารีได้ การเปลี่ยนแปลงใดๆ ในไฟล์ซอร์สเคอร์เนลอาจส่งผลต่อไบนารีและส่งผลต่อ .stg ที่ดึงออกมาด้วย เครื่องมือวิเคราะห์ AbiAnalyzer จะเปรียบเทียบไฟล์ .stg ที่คอมมิตกับไฟล์ที่ดึงมาจากอาร์ติแฟกต์การสร้าง และตั้งค่าป้ายกำกับ 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 ของบิลด์ที่มีการใช้แพตช์นี้ เครื่องมือจะออกมาพร้อมกับรหัสข้อผิดพลาดที่ไม่ใช่ 0 และรายงานความแตกต่างของ 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 ขณะสร้าง

สาเหตุที่พบบ่อยที่สุดของข้อผิดพลาดคือเมื่อไดรเวอร์ใช้สัญลักษณ์ใหม่จากเคอร์เนลซึ่งไม่ได้อยู่ในรายการสัญลักษณ์

หากสัญลักษณ์ไม่รวมอยู่ในรายการสัญลักษณ์ คุณจะต้องตรวจสอบก่อนว่ามีการส่งออกสัญลักษณ์ด้วย EXPORT_SYMBOL_GPL(symbol_name) จากนั้นอัปเดตรายการสัญลักษณ์และการนําเสนอ ABI ตัวอย่างเช่น การเปลี่ยนแปลงต่อไปนี้จะเพิ่มฟีเจอร์ FS ที่เพิ่มขึ้นใหม่ไปยังสาขา android-12-5.10 ซึ่งรวมถึงการอัปเดตรายการสัญลักษณ์และการนําเสนอ ABI

  • ตัวอย่างการเปลี่ยนแปลงฟีเจอร์อยู่ใน aosp/1345659
  • ตัวอย่างรายการสัญลักษณ์อยู่ใน aosp/1346742
  • ตัวอย่างการเปลี่ยนแปลงการแสดง ABI อยู่ใน 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 ใน ACK ได้ที่ aosp/1367601

แก้ไขข้อขัดข้องของ ABI ของเคอร์เนล

คุณสามารถจัดการการหยุดทำงานของ ABI ของเคอร์เนลได้โดยการจัดระเบียบโค้ดใหม่เพื่อไม่ให้ ABI เปลี่ยนแปลงหรืออัปเดตการแสดง ABI ใช้แผนภูมิต่อไปนี้เพื่อพิจารณาแนวทางที่ดีที่สุดสำหรับสถานการณ์ของคุณ

แผนภาพขั้นตอน ABI

รูปที่ 1 การแก้ปัญหา ABI

ปรับโค้ดใหม่เพื่อหลีกเลี่ยงการเปลี่ยนแปลง ABI

พยายามหลีกเลี่ยงการแก้ไข ABI ที่มีอยู่ ในหลายกรณี คุณสามารถรีแฟกทอริกโค้ดเพื่อนำการเปลี่ยนแปลงที่ส่งผลต่อ ABI ออก

  • การจัดระเบียบการเปลี่ยนแปลงช่อง Struct หากการเปลี่ยนแปลงแก้ไข 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 ใช้งานไม่ได้ ให้ทำตามขั้นตอนต่อไปนี้

  1. อัปโหลดการเปลี่ยนแปลงโค้ดไปยัง ACK

  2. รอรับสถานะ Code-Review +2 สำหรับชุดแพตช์

  3. อัปเดตการแสดง ABI อ้างอิง

  4. ผสานการเปลี่ยนแปลงโค้ดเข้ากับการเปลี่ยนแปลงการอัปเดต ABI

อัปโหลดการเปลี่ยนแปลงโค้ด ABI ไปยัง ACK

การอัปเดต ACK ABI จะขึ้นอยู่กับประเภทการเปลี่ยนแปลงที่ทำ

  • หากการเปลี่ยนแปลง ABI เกี่ยวข้องกับฟีเจอร์ที่ส่งผลต่อการทดสอบ CTS หรือ VTS โดยทั่วไปแล้ว คุณสามารถเลือกการเปลี่ยนแปลงเพื่อยอมรับได้ตามต้องการ เช่น

    • aosp/1289677 เป็นสิ่งจําเป็นเพื่อให้เสียงทํางานได้
    • aosp/1295945 ต้องใช้งานเพื่อให้ USB ทำงานได้
  • หากการเปลี่ยนแปลง ABI มีไว้สำหรับฟีเจอร์ที่แชร์กับ ACK ได้ คุณจะเลือกการเปลี่ยนแปลงดังกล่าวมาแชร์กับ ACK ได้ ตัวอย่างเช่น การเปลี่ยนแปลงต่อไปนี้ไม่จำเป็นสำหรับการทดสอบ CTS หรือ VTS แต่แชร์กับ ACK ได้

    • aosp/1250412 คือการเปลี่ยนแปลงฟีเจอร์ความร้อน
    • aosp/1288857 เป็นการเปลี่ยนแปลง EXPORT_SYMBOL_GPL
  • หากการเปลี่ยนแปลง ABI เปิดตัวฟีเจอร์ใหม่ที่ไม่จำเป็นต้องรวมไว้ใน ACK คุณสามารถเปิดตัวสัญลักษณ์ใน ACK โดยใช้สแต็บตามที่อธิบายไว้ในส่วนต่อไปนี้

ใช้สแต็บสำหรับ ACK

สตับจําเป็นต้องใช้กับการเปลี่ยนแปลงหลักของเคิร์กัลเท่านั้น ซึ่งไม่ได้ให้ประโยชน์กับ ACK เช่น การเปลี่ยนแปลงด้านประสิทธิภาพและพลังงาน รายการต่อไปนี้แสดงตัวอย่างตัวอย่างของ Stub และการเลือกบางส่วนใน ACK สำหรับ GKI

  • Core-isolate feature stub (aosp/1284493) ความสามารถใน ACK นั้นไม่จำเป็น แต่สัญลักษณ์ต้องอยู่ใน ACK เพื่อให้โมดูลของคุณใช้สัญลักษณ์เหล่านี้ได้

  • สัญลักษณ์ตัวยึดตําแหน่งสําหรับข้อบังคับของผู้ให้บริการ (aosp/1288860)

  • เลือกเฉพาะ ABI สำหรับฟีเจอร์การติดตามเหตุการณ์ mm ในแต่ละกระบวนการ (aosp/1288454) เราได้เลือกแพตช์ต้นฉบับเพื่อยอมรับแล้ว จากนั้นตัดให้เหลือเฉพาะการเปลี่ยนแปลงที่จำเป็นเพื่อแก้ไขความแตกต่างของ ABI สำหรับ task_struct และ mm_event_count แพตช์นี้ยังอัปเดต mm_event_type enum ให้รวมสมาชิกขั้นสุดท้ายด้วย

  • การเปลี่ยนแปลง ABI ของโครงสร้างความร้อนบางส่วนที่ต้องใช้มากกว่าแค่การเพิ่มช่อง ABI ใหม่

    • การแก้ไข aosp/1255544 แก้ไขความแตกต่างของ ABI ระหว่างเคอร์เนลของพาร์ทเนอร์กับ ACK

    • แพตช์ 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 ที่รันไทม์ การกำหนดเวอร์ชันของโมดูลอาจทําให้การตรวจสอบ redundancy แบบวนซ้ำ (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 เป็นส่วนหนึ่งของกระบวนการสร้างตามปกติ ไฟล์นี้มี 1 บรรทัดสำหรับสัญลักษณ์ทั้งหมดที่เคอร์เนล (vmlinux) และโมดูลส่งออก แต่ละบรรทัดประกอบด้วยค่า CRC, ชื่อสัญลักษณ์, เนมสเปซของสัญลักษณ์, vmlinux หรือชื่อโมดูลที่ส่งออกสัญลักษณ์ และประเภทการส่งออก (เช่น EXPORT_SYMBOL เทียบกับ EXPORT_SYMBOL_GPL)

คุณสามารถเปรียบเทียบไฟล์ Module.symvers ระหว่างบิลด์ GKI กับบิลด์ของคุณเพื่อตรวจหาความแตกต่างของ CRC ในสัญลักษณ์ที่ vmlinux ส่งออก หากมีความแตกต่างของค่า CRC ในสัญลักษณ์ที่ส่งออกโดย vmlinux และ สัญลักษณ์นั้นใช้โดยหนึ่งในโมดูลที่คุณโหลดในอุปกรณ์ โมดูลจะไม่โหลด

หากไม่มีอาร์ติแฟกต์การสร้างทั้งหมด แต่มีไฟล์ vmlinux ของเคิร์ก GKI และเคิร์กของคุณ คุณสามารถเปรียบเทียบค่า CRC ของสัญลักษณ์ที่เฉพาะเจาะจงได้โดยเรียกใช้คําสั่งต่อไปนี้ในทั้ง 2 เคอร์นและเปรียบเทียบเอาต์พุต

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 ไม่ตรงกันเมื่อโหลดโมดูล

  1. สร้างเคอร์เนล 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, ระบบจะตั้งค่า Flag KBUILD_SYMTYPES=1 โดยปริยาย

  2. ค้นหาไฟล์ .c ที่ส่งออกสัญลักษณ์ที่มี CRC ไม่ตรงกันโดยใช้คำสั่งต่อไปนี้

    cd common && git grep EXPORT_SYMBOL.*module_layout
    kernel/module.c:EXPORT_SYMBOL(module_layout);
  3. ไฟล์ .c มีไฟล์ .symtypes ที่เกี่ยวข้องใน GKI และอาร์ติแฟกต์การสร้างเคอร์เนลของอุปกรณ์ ค้นหาไฟล์ .c โดยใช้คำสั่งต่อไปนี้

    cd out/$BRANCH/common && ls -1 kernel/module.*
    kernel/module.o
    kernel/module.o.symversions
    kernel/module.symtypes

    ลักษณะของไฟล์ .c มีดังนี้

    • รูปแบบของไฟล์ .c คือ 1 บรรทัด (อาจมีความยาวมาก) ต่อสัญลักษณ์ 1 รายการ

    • [s|u|e|etc]# ที่จุดเริ่มต้นของบรรทัดหมายความว่าสัญลักษณ์นั้นเป็นประเภทข้อมูล [struct|union|enum|etc] เช่น

      t#bool typedef _Bool bool
      
    • หากไม่มีคำนำหน้า # ที่จุดเริ่มต้นของบรรทัด แสดงว่าสัญลักษณ์นั้นคือฟังก์ชัน เช่น

      find_module s#module * find_module ( const char * )
      
  4. เปรียบเทียบไฟล์ 2 ไฟล์และแก้ไขความแตกต่างทั้งหมด

กรณี 1: ความแตกต่างเนื่องจากระดับการมองเห็นประเภทข้อมูล

หากเคอร์เนลหนึ่งเก็บสัญลักษณ์หรือประเภทข้อมูลที่มองไม่เห็นสำหรับโมดูล แต่อีกเคอร์เนลหนึ่งไม่เก็บ ความแตกต่างนั้นจะปรากฏระหว่างไฟล์ .symtypes ของเคอร์เนล 2 รายการ ไฟล์ .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 Common เวอร์ชันล่าสุดเข้ากับเคอร์เนลของคุณเพื่อให้ใช้ฐานเคอร์เนล GKI เวอร์ชันล่าสุด

ในกรณีส่วนใหญ่ เคอร์เนล GKI จะมีค่าเป็น UNKNOWN แต่เคอร์เนลของคุณจะมีรายละเอียดภายในของสัญลักษณ์เนื่องจากมีการเปลี่ยนแปลงในเคอร์เนล เนื่องจากไฟล์หนึ่งในเคอร์เนลของคุณเพิ่ม #include ที่ไม่มีอยู่ในเคอร์เนล GKI

บ่อยครั้งที่การแก้ไขเป็นเพียงการซ่อน #include ใหม่จาก genksyms

#ifndef __GENKSYMS__
#include <linux/fwnode.h>
#endif

หรือหากต้องการระบุ #include ที่ทำให้เกิดความแตกต่าง ให้ทำตามขั้นตอนต่อไปนี้

  1. เปิดไฟล์ส่วนหัวที่กําหนดสัญลักษณ์หรือประเภทข้อมูลที่มีความแตกต่างนี้ เช่น แก้ไข include/linux/fwnode.h สำหรับ struct fwnode_handle

  2. เพิ่มโค้ดต่อไปนี้ที่ด้านบนของไฟล์ส่วนหัว

    #ifdef CRC_CATCH
    #error "Included from here"
    #endif
    
  3. ในไฟล์ .c ของโมดูลที่มี CRC ไม่ตรงกัน ให้เพิ่มบรรทัดต่อไปนี้เป็นบรรทัดแรกก่อนบรรทัด #include

    #define CRC_CATCH 1
    
  4. คอมไพล์โมดูล ข้อผิดพลาดที่เกิดขึ้นขณะสร้างจะแสดงลําดับไฟล์ส่วนหัว #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

  5. ระบุการเปลี่ยนแปลง เปลี่ยนกลับในเคอร์เนล หรืออัปโหลดไปยัง 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 ไม่ตรงกัน 1 รายการสำหรับ 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 * ) ; ...

หากต้องการระบุประเภทที่เปลี่ยนแปลง ให้ทําตามขั้นตอนต่อไปนี้

  1. ค้นหาคําจํากัดความของสัญลักษณ์ในซอร์สโค้ด (มักจะอยู่ในไฟล์ .h)

    • หากต้องการดูความแตกต่างของสัญลักษณ์ระหว่างเคอร์เนลของคุณกับเคอร์เนล GKI ให้ค้นหาการคอมมิตโดยเรียกใช้คำสั่งต่อไปนี้
    git blame
    • สําหรับสัญลักษณ์ที่ลบไปแล้ว (ซึ่งมีการลบสัญลักษณ์ในแผนภูมิ และคุณต้องการลบสัญลักษณ์นั้นในแผนภูมิอื่นด้วย) คุณต้องค้นหาการเปลี่ยนแปลงที่ลบบรรทัดนั้น ใช้คำสั่งต่อไปนี้ในต้นไม้ที่มีการลบบรรทัด
    git log -S "copy paste of deleted line/word" -- <file where it was deleted>
  2. ตรวจสอบรายการคอมมิตที่แสดงเพื่อหาการเปลี่ยนแปลงหรือการลบ คอมมิตแรกอาจเป็นคอมมิตที่คุณค้นหาอยู่ หากไม่อยู่ ให้เลื่อนดูรายการจนกว่าจะพบการคอมมิต

  3. หลังจากระบุการเปลี่ยนแปลงแล้ว ให้เปลี่ยนกลับในเคอร์เนลหรืออัปโหลดไปยัง ACK และผสาน