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

คุณสามารถใช้เครื่องมือการตรวจสอบ 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 ของเคอร์เนลนั้นมีหลายขั้นตอน ซึ่งส่วนใหญ่สามารถทำให้เป็นแบบอัตโนมัติได้:

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

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

แผนภูมิการไหลการแตกหักของ 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 ให้ทำตามขั้นตอนเหล่านี้:

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

  2. รอรับ Code-Review +2 สำหรับชุดแพทช์

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

  4. รวมการเปลี่ยนแปลงโค้ดของคุณและการเปลี่ยนแปลงการอัปเดต 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 ที่ไม่ตรงกันเมื่อโหลดโมดูล:

  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 คือหนึ่งบรรทัด (อาจยาวมาก) ต่อสัญลักษณ์

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

      t#bool typedef _Bool bool
      
    • คำนำหน้า # ที่หายไปในตอนต้นของบรรทัดบ่งชี้ว่าสัญลักษณ์นั้นเป็นฟังก์ชัน ตัวอย่างเช่น:

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

กรณีที่ 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 ที่ทำให้เกิดความแตกต่าง ให้ทำตามขั้นตอนเหล่านี้:

  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 ที่ไม่ตรงกันหนึ่งรายการสำหรับ 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 และรวมเข้าด้วยกัน