การตรวจสอบ 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 รายการ แต่สัญลักษณ์ที่โมดูลผู้ให้บริการจะใช้ได้จะแสดงอยู่ในชุดไฟล์รายการสัญลักษณ์ที่เปิดเผยต่อสาธารณะในรูทของเคอร์เนล การรวมสัญลักษณ์ทั้งหมดในไฟล์รายการสัญลักษณ์ทั้งหมดจะกำหนดชุดสัญลักษณ์ 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 สำหรับอุปกรณ์โดยใช้วิธีการจากวิธีใช้รายการสัญลักษณ์ พาร์ทเนอร์จํานวนมากส่งรายการสัญลักษณ์ 1 รายการต่อ ACK แต่นี่ไม่ใช่ข้อกําหนด คุณส่งรายการสัญลักษณ์หลายรายการได้หากจะช่วยเรื่องการบำรุงรักษาได้

ขยาย 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 ขณะสร้าง

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

หากสัญลักษณ์ไม่ได้อยู่ในรายการสัญลักษณ์ (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 ใช้แผนภูมิต่อไปนี้เพื่อกำหนดวิธีที่ดีที่สุดสำหรับสถานการณ์ของคุณ

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

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

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

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

  • การจัดระเบียบการเปลี่ยนแปลงช่อง Struct หากการเปลี่ยนแปลงแก้ไข ABI สําหรับฟีเจอร์การแก้ไขข้อบกพร่อง ให้เพิ่ม #ifdef รอบๆ ช่อง (ในโครงสร้างและข้อมูลอ้างอิงแหล่งที่มา) และตรวจสอบว่า CONFIG ที่ใช้สําหรับ #ifdef ปิดใช้สําหรับ defconfig และ gki_defconfig เวอร์ชันที่ใช้งานจริง หากต้องการดูตัวอย่างวิธีเพิ่มการกำหนดค่าการแก้ไขข้อบกพร่องไปยัง Struct โดยไม่ทำให้ 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

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

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

    • [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 และผสานรวม