Giám sát ABI hạt nhân Android

Bạn có thể sử dụng công cụ giám sát giao diện nhị phân của ứng dụng (ABI) (có trong Android 11 trở lên) để ổn định ABI trong nhân của nhân Android. Công cụ này thu thập và so sánh các biểu thị ABI từ các tệp nhị phân hiện có của nhân (vmlinux + các mô-đun GKI). Các biểu thị ABI này là tệp .stg và danh sách biểu tượng. Giao diện mà bản trình bày cung cấp một khung hiển thị được gọi là Giao diện mô-đun hạt nhân (KMI). Bạn có thể sử dụng công cụ này để theo dõi và giảm thiểu các thay đổi đối với KMI.

Công cụ giám sát ABI được phát triển trong AOSP và sử dụng STG (hoặc libabigail trong Android 13 trở xuống) để tạo và so sánh các biểu thị.

Trang này mô tả công cụ, quy trình thu thập và phân tích các biểu thị ABI, cũng như cách sử dụng các biểu thị đó để mang lại sự ổn định cho ABI trong nhân. Trang này cũng cung cấp thông tin về việc đóng góp các thay đổi cho nhân Android.

Quy trình

Việc phân tích ABI của nhân bao gồm nhiều bước, hầu hết trong số đó có thể được tự động hoá:

  1. Tạo nhân và biểu diễn ABI của nhân.
  2. Phân tích sự khác biệt về ABI giữa bản dựng và một bản dựng tham chiếu.
  3. Cập nhật biểu thị ABI (nếu cần).
  4. Làm việc với danh sách ký hiệu.

Các hướng dẫn sau đây áp dụng cho mọi nhân mà bạn có thể tạo bằng một chuỗi công cụ được hỗ trợ (chẳng hạn như chuỗi công cụ Clang được tạo sẵn). repo manifests có sẵn cho tất cả các nhánh hạt nhân chung của Android và cho một số hạt nhân dành riêng cho thiết bị. Các nhánh này xác minh rằng bạn sử dụng đúng chuỗi công cụ khi tạo bản phân phối hạt nhân để phân tích.

Danh sách biểu tượng

KMI không bao gồm tất cả các biểu tượng trong nhân hoặc thậm chí tất cả hơn 30.000 biểu tượng được xuất. Thay vào đó, các biểu tượng mà mô-đun của nhà cung cấp có thể sử dụng được liệt kê rõ ràng trong một bộ tệp danh sách biểu tượng được duy trì công khai trong cây nhân (gki/{ARCH}/symbols/* hoặc android/abi_gki_{ARCH}_* trong Android 15 trở xuống). Liên hợp của tất cả các ký hiệu trong tất cả các tệp danh sách ký hiệu xác định tập hợp các ký hiệu KMI được duy trì ở trạng thái ổn định. Một tệp danh sách biểu tượng mẫu là gki/aarch64/symbols/db845c, trong đó khai báo các biểu tượng cần thiết cho DragonBoard 845c.

Chỉ những ký hiệu có trong danh sách ký hiệu, cũng như các cấu trúc và định nghĩa liên quan mới được coi là một phần của KMI. Bạn có thể đăng các thay đổi đối với danh sách biểu tượng nếu không có biểu tượng bạn cần. Sau khi các giao diện mới nằm trong danh sách biểu tượng và là một phần của nội dung mô tả KMI, chúng sẽ được duy trì ở trạng thái ổn định và không được xoá khỏi danh sách biểu tượng hoặc sửa đổi sau khi nhánh bị đóng băng.

Mỗi nhánh hạt nhân KMI của Hạt nhân chung Android (ACK) đều có một tập hợp danh sách biểu tượng riêng. Không có nỗ lực nào để cung cấp tính ổn định của ABI giữa các nhánh kernel KMI khác nhau. Ví dụ: KMI cho android12-5.10 hoàn toàn độc lập với KMI cho android13-5.10.

Các công cụ ABI sử dụng danh sách biểu tượng KMI để giới hạn những giao diện phải được giám sát về độ ổn định. Các nhà cung cấp phải gửi và cập nhật danh sách biểu tượng của riêng họ để xác minh rằng các giao diện mà họ dựa vào vẫn duy trì khả năng tương thích ABI. Ví dụ: để xem danh sách các danh sách biểu tượng cho nhân android16-6.12, hãy tham khảo https://android.googlesource.com/kernel/common/+/refs/heads/android16-6.12/gki/aarch64/symbols

Danh sách biểu tượng chứa các biểu tượng được báo cáo là cần thiết cho nhà cung cấp hoặc thiết bị cụ thể. Danh sách đầy đủ mà các công cụ sử dụng là hợp của tất cả các tệp danh sách biểu tượng KMI. Các công cụ ABI xác định thông tin chi tiết của từng biểu tượng, bao gồm cả chữ ký hàm và cấu trúc dữ liệu lồng nhau.

Khi KMI bị đóng băng, bạn không được phép thay đổi các giao diện KMI hiện có; chúng ổn định. Tuy nhiên, các nhà cung cấp có thể thêm các ký hiệu vào KMI bất cứ lúc nào, miễn là việc bổ sung không ảnh hưởng đến tính ổn định của ABI hiện có. Các biểu tượng mới được thêm sẽ được duy trì ở trạng thái ổn định ngay khi được một danh sách biểu tượng KMI trích dẫn. Bạn không nên xoá các biểu tượng khỏi danh sách cho một nhân trừ phi bạn có thể xác nhận rằng chưa có thiết bị nào được xuất xưởng có phụ thuộc vào biểu tượng đó.

Bạn có thể tạo danh sách biểu tượng KMI cho một thiết bị theo hướng dẫn trong bài viết Cách sử dụng danh sách biểu tượng. Nhiều đối tác gửi một danh sách ký hiệu cho mỗi ACK, nhưng đây không phải là yêu cầu bắt buộc. Nếu giúp ích cho việc bảo trì, bạn có thể gửi nhiều danh sách ký hiệu.

Mở rộng KMI

Mặc dù các biểu tượng KMI và cấu trúc liên quan được duy trì ở trạng thái ổn định (nghĩa là không thể chấp nhận những thay đổi làm gián đoạn các giao diện ổn định trong một nhân có KMI cố định), nhưng nhân GKI vẫn có thể mở rộng để các thiết bị được xuất xưởng sau đó trong năm không cần xác định tất cả các phần phụ thuộc của chúng trước khi KMI được cố định. Để mở rộng KMI, bạn có thể thêm các biểu tượng mới vào KMI cho các hàm kernel đã xuất mới hoặc hiện có, ngay cả khi KMI bị đóng băng. Các bản vá kernel mới cũng có thể được chấp nhận nếu chúng không làm hỏng KMI.

Giới thiệu về các lỗi KMI

Nhân có các nguồn và các tệp nhị phân được tạo từ những nguồn đó. Các nhánh hạt nhân được ABI giám sát bao gồm một bản trình bày ABI của ABI GKI hiện tại (dưới dạng tệp .stg). Sau khi các tệp nhị phân (vmlinux, Image và mọi mô-đun GKI) được tạo, bạn có thể trích xuất một bản trình bày ABI từ các tệp nhị phân. Mọi thay đổi đối với tệp nguồn của nhân đều có thể ảnh hưởng đến các tệp nhị phân và do đó cũng ảnh hưởng đến .stg đã trích xuất. Phân tích mức độ tuân thủ ABI sẽ so sánh tệp .stg đã cam kết với tệp được trích xuất từ các cấu phần phần mềm bản dựng và đặt nhãn Lint-1 cho thay đổi trong Gerrit nếu tìm thấy sự khác biệt về ngữ nghĩa.

Xử lý các lỗi ABI

Ví dụ: bản vá sau đây gây ra một sự cố ABI rất rõ ràng:

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.

Khi bạn chạy ABI bản dựng với bản vá này, công cụ sẽ thoát với mã lỗi khác 0 và báo cáo sự khác biệt về ABI tương tự như sau:

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

Phát hiện thấy sự khác biệt về ABI tại thời điểm tạo bản dựng

Lý do phổ biến nhất gây ra lỗi là khi trình điều khiển sử dụng một biểu tượng mới từ nhân mà không có trong bất kỳ danh sách biểu tượng nào.

Nếu biểu tượng không có trong danh sách biểu tượng, trước tiên, bạn cần xác minh rằng biểu tượng đó được xuất bằng EXPORT_SYMBOL_GPL(symbol_name), sau đó cập nhật danh sách biểu tượng và biểu thị ABI. Ví dụ: những thay đổi sau đây sẽ thêm tính năng FS gia tăng mới vào nhánh android-12-5.10, bao gồm cả việc cập nhật danh sách biểu tượng và biểu diễn ABI.

  • Ví dụ về thay đổi đối với tính năng có trong aosp/1345659.
  • Ví dụ về danh sách biểu tượng có trong aosp/1346742.
  • Ví dụ về thay đổi cách biểu thị ABI có trong aosp/1349377.

Nếu biểu tượng được xuất (do bạn xuất hoặc biểu tượng đã được xuất trước đó) nhưng không có trình điều khiển nào khác đang sử dụng biểu tượng đó, thì bạn có thể gặp phải lỗi bản dựng tương tự như sau.

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

Để giải quyết, hãy cập nhật danh sách biểu tượng KMI trong cả nhân và ACK (xem phần Cập nhật biểu diễn ABI). Để biết ví dụ về cách cập nhật danh sách biểu tượng và biểu thị ABI trong ACK, hãy tham khảo aosp/1367601.

Giải quyết các lỗi ABI của nhân

Bạn có thể xử lý các điểm ngắt ABI của nhân bằng cách tái cấu trúc mã để không thay đổi ABI hoặc cập nhật biểu diễn ABI. Hãy sử dụng biểu đồ sau đây để xác định phương pháp phù hợp nhất cho tình huống của bạn.

Sơ đồ quy trình thay đổi ABI

Hình 1. Cách giải quyết vấn đề ABI bị hỏng

Tái cấu trúc mã để tránh các thay đổi về ABI

Hãy cố gắng hết sức để tránh sửa đổi ABI hiện có. Trong nhiều trường hợp, bạn có thể tái cấu trúc mã để xoá những thay đổi ảnh hưởng đến ABI.

  • Tái cấu trúc các thay đổi về trường cấu trúc. Nếu một thay đổi sửa đổi ABI cho tính năng gỡ lỗi, hãy thêm #ifdef xung quanh các trường (trong cấu trúc và tài liệu tham khảo nguồn) và đảm bảo rằng CONFIG được dùng cho #ifdef bị vô hiệu hoá đối với defconfig sản xuất và gki_defconfig. Để biết ví dụ về cách thêm một debugconfig vào một cấu trúc mà không làm hỏng ABI, hãy tham khảo tập hợp bản vá này.

  • Tái cấu trúc các tính năng để không thay đổi nhân cốt lõi. Nếu cần thêm các tính năng mới vào ACK để hỗ trợ các mô-đun đối tác, hãy thử tái cấu trúc phần ABI của thay đổi để tránh sửa đổi ABI của nhân. Để biết ví dụ về cách sử dụng ABI hạt nhân hiện có để thêm các chức năng bổ sung mà không thay đổi ABI hạt nhân, hãy tham khảo aosp/1312213.

Khắc phục ABI bị hỏng trên Android Gerrit

Nếu không cố ý làm hỏng ABI của nhân, thì bạn cần điều tra bằng cách sử dụng hướng dẫn do công cụ giám sát ABI cung cấp. Nguyên nhân phổ biến nhất gây ra lỗi là do cấu trúc dữ liệu thay đổi và các thay đổi về CRC biểu tượng liên kết, hoặc do các thay đổi về lựa chọn cấu hình dẫn đến bất kỳ nguyên nhân nào nêu trên. Bắt đầu bằng cách giải quyết những vấn đề mà công cụ này phát hiện được.

Bạn có thể tái tạo các kết quả ABI cục bộ, hãy xem phần Tạo nhân và biểu diễn ABI của nhân.

Giới thiệu về nhãn Lint-1

Nếu bạn tải các thay đổi lên một nhánh có chứa KMI đã được cố định hoặc hoàn tất, thì các thay đổi đó phải vượt qua quy trình phân tích Khả năng tuân thủ và Khả năng tương thích của ABI để đảm bảo các thay đổi đối với biểu thị ABI phản ánh ABI thực tế và không chứa bất kỳ điểm không tương thích nào (xoá biểu tượng hoặc thay đổi loại).

Mỗi phân tích ABI này có thể đặt nhãn Lint-1 và chặn việc gửi thay đổi cho đến khi tất cả các vấn đề được giải quyết hoặc nhãn bị ghi đè.

Cập nhật ABI của nhân

Nếu không thể tránh việc sửa đổi ABI, thì bạn phải áp dụng các thay đổi về mã, biểu thị ABI và danh sách biểu tượng cho ACK. Để Lint xoá -1 và không làm gián đoạn khả năng tương thích GKI, hãy làm theo các bước sau:

  1. Tải các thay đổi về mã lên ACK.

  2. Chờ nhận được Code-Review +2 cho nhóm bản vá.

  3. Cập nhật biểu diễn ABI tham chiếu.

  4. Hợp nhất các thay đổi về mã và thay đổi về bản cập nhật ABI.

Tải các thay đổi về mã ABI lên ACK

Việc cập nhật ABI ACK phụ thuộc vào loại thay đổi được thực hiện.

  • Nếu thay đổi ABI có liên quan đến một tính năng ảnh hưởng đến các kiểm thử CTS hoặc VTS, thì bạn thường có thể chọn thay đổi đó để ACK như hiện tại. Ví dụ:

  • Nếu thay đổi ABI là cho một tính năng có thể được chia sẻ với ACK, thì thay đổi đó có thể được chọn lọc cho ACK. Ví dụ: các thay đổi sau đây không cần thiết cho kiểm thử CTS hoặc VTS nhưng vẫn có thể được chia sẻ với ACK:

  • Nếu thay đổi ABI giới thiệu một tính năng mới không cần có trong ACK, bạn có thể giới thiệu các biểu tượng cho ACK bằng cách sử dụng một phần giữ chỗ như mô tả trong phần sau.

Sử dụng các phần giữ chỗ cho ACK

Các phần giữ chỗ chỉ cần thiết cho những thay đổi cốt lõi của nhân không mang lại lợi ích cho ACK, chẳng hạn như các thay đổi về hiệu suất và nguồn điện. Danh sách sau đây trình bày chi tiết các ví dụ về các phần giữ chỗ và các bản vá chọn lọc một phần trong ACK cho GKI.

  • Phần giữ chỗ tính năng cốt lõi-cô lập (aosp/1284493). Các chức năng trong ACK là không cần thiết, nhưng các biểu tượng cần phải có trong ACK để các mô-đun của bạn sử dụng các biểu tượng này.

  • Ký hiệu phần giữ chỗ cho mô-đun nhà cung cấp (aosp/1288860).

  • Chỉ chọn lọc ABI của tính năng theo dõi sự kiện mm theo quy trình (aosp/1288454). Bản vá ban đầu được chọn lọc để ACK và sau đó được cắt bớt để chỉ bao gồm những thay đổi cần thiết nhằm giải quyết sự khác biệt về ABI cho task_structmm_event_count. Bản vá này cũng cập nhật enum mm_event_type để chứa các thành phần cuối cùng.

  • Chọn lọc một phần các thay đổi về ABI cấu trúc nhiệt, những thay đổi này không chỉ yêu cầu thêm các trường ABI mới.

    • Bản vá aosp/1255544 đã giải quyết sự khác biệt về ABI giữa nhân đối tác và ACK.

    • Bản vá aosp/1291018 đã khắc phục các vấn đề về chức năng được phát hiện trong quá trình kiểm thử GKI của bản vá trước đó. Bản sửa lỗi bao gồm việc khởi tạo cấu trúc tham số cảm biến để đăng ký nhiều vùng nhiệt cho một cảm biến.

  • CONFIG_NL80211_TESTMODE Thay đổi ABI (aosp/1344321). Bản vá này đã thêm các thay đổi cần thiết về cấu trúc cho ABI và đảm bảo rằng các trường bổ sung không gây ra sự khác biệt về chức năng, cho phép các đối tác đưa CONFIG_NL80211_TESTMODE vào nhân sản xuất của họ mà vẫn duy trì sự tuân thủ GKI.

Thực thi KMI trong thời gian chạy

Các nhân GKI sử dụng các lựa chọn cấu hình TRIM_UNUSED_KSYMS=yUNUSED_KSYMS_WHITELIST=<union of all symbol lists>, giúp giới hạn các biểu tượng đã xuất (chẳng hạn như các biểu tượng được xuất bằng EXPORT_SYMBOL_GPL()) thành những biểu tượng có trong danh sách biểu tượng. Tất cả các biểu tượng khác đều không được xuất và việc tải một mô-đun yêu cầu biểu tượng không được xuất sẽ bị từ chối. Hạn chế này được thực thi tại thời điểm tạo bản dựng và các mục bị thiếu sẽ được gắn cờ.

Đối với mục đích phát triển, bạn có thể sử dụng bản dựng kernel GKI không bao gồm việc cắt bớt biểu tượng (nghĩa là bạn có thể sử dụng tất cả các biểu tượng thường được xuất). Để tìm các bản dựng này, hãy tìm bản dựng kernel_debug_aarch64 trên ci.android.com.

Thực thi KMI bằng cách sử dụng tính năng lập phiên bản mô-đun

Các nhân Hình ảnh hạt nhân chung (GKI) sử dụng phiên bản mô-đun (CONFIG_MODVERSIONS) làm biện pháp bổ sung để thực thi việc tuân thủ KMI trong thời gian chạy. Việc tạo phiên bản mô-đun có thể gây ra lỗi không khớp kiểm tra tính dư thừa theo chu kỳ (CRC) tại thời điểm tải mô-đun nếu KMI dự kiến của một mô-đun không khớp với KMI vmlinux. Ví dụ: sau đây là một lỗi điển hình xảy ra tại thời điểm tải mô-đun do không khớp CRC cho biểu tượng 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 ''

Các trường hợp sử dụng tính năng quản lý phiên bản mô-đun

Việc tạo phiên bản mô-đun rất hữu ích vì những lý do sau:

  • Tính năng lập phiên bản mô-đun nắm bắt các thay đổi về chế độ hiển thị cấu trúc dữ liệu. Nếu các mô-đun thay đổi cấu trúc dữ liệu không rõ ràng (tức là cấu trúc dữ liệu không thuộc KMI), thì chúng sẽ bị hỏng sau những thay đổi trong tương lai đối với cấu trúc.

    Ví dụ: hãy xem xét trường fwnode trong struct device. Trường này PHẢI ở trạng thái không trong suốt đối với các mô-đun để chúng không thể thay đổi các trường của device->fw_node hoặc đưa ra giả định về kích thước của trường này.

    Tuy nhiên, nếu một mô-đun bao gồm <linux/fwnode.h> (trực tiếp hoặc gián tiếp), thì trường fwnode trong struct device sẽ không còn mờ đối với mô-đun đó nữa. Sau đó, mô-đun có thể thay đổi device->fwnode->dev hoặc device->fwnode->ops. Trường hợp này gây ra một số vấn đề, cụ thể như sau:

    • Điều này có thể phá vỡ các giả định mà mã nhân cốt lõi đang đưa ra về cấu trúc dữ liệu nội bộ của mã.

    • Nếu bản cập nhật kernel trong tương lai thay đổi struct fwnode_handle (kiểu dữ liệu của fwnode), thì mô-đun sẽ không còn hoạt động với kernel mới nữa. Hơn nữa, stgdiff sẽ không cho thấy bất kỳ sự khác biệt nào vì mô-đun này đang phá vỡ KMI bằng cách thao tác trực tiếp với các cấu trúc dữ liệu nội bộ theo những cách mà chỉ kiểm tra biểu diễn nhị phân không thể nắm bắt được.

  • Một mô-đun hiện tại được coi là không tương thích với KMI khi mô-đun đó được tải vào một ngày sau đó bằng một nhân mới không tương thích. Tính năng quản lý phiên bản mô-đun bổ sung một quy trình kiểm tra trong thời gian chạy để tránh vô tình tải một mô-đun không tương thích với KMI bằng hạt nhân. Bước kiểm tra này giúp ngăn chặn các vấn đề về thời gian chạy khó gỡ lỗi và các sự cố về nhân có thể xảy ra do sự không tương thích chưa được phát hiện trong KMI.

Việc bật tính năng quản lý phiên bản mô-đun sẽ ngăn chặn tất cả những vấn đề này.

Kiểm tra xem có sự không khớp CRC hay không mà không cần khởi động thiết bị

stgdiff so sánh và báo cáo các trường hợp không khớp CRC giữa các nhân cùng với những khác biệt khác về ABI.

Ngoài ra, bản dựng kernel đầy đủ có CONFIG_MODVERSIONS được bật sẽ tạo tệp Module.symvers trong quy trình tạo thông thường. Tệp này có một dòng cho mỗi biểu tượng do nhân (vmlinux) và các mô-đun xuất. Mỗi dòng bao gồm giá trị CRC, tên biểu tượng, không gian tên biểu tượng, vmlinux hoặc tên mô-đun xuất biểu tượng và loại xuất (ví dụ: EXPORT_SYMBOL so với EXPORT_SYMBOL_GPL).

Bạn có thể so sánh các tệp Module.symvers giữa bản dựng GKI và bản dựng của bạn để kiểm tra xem có sự khác biệt nào về CRC trong các biểu tượng do vmlinux xuất không. Nếu có sự khác biệt về giá trị CRC trong bất kỳ biểu tượng nào do vmlinux xuất, thì biểu tượng đó sẽ được một trong các mô-đun mà bạn tải trên thiết bị sử dụng, mô-đun sẽ không tải.

Nếu không có tất cả các cấu phần phần mềm, nhưng có các tệp vmlinux của nhân GKI và nhân của bạn, thì bạn có thể so sánh các giá trị CRC cho một biểu tượng cụ thể bằng cách chạy lệnh sau trên cả hai nhân và so sánh đầu ra:

nm <path to vmlinux>/vmlinux | grep __crc_<symbol name>

Ví dụ: lệnh sau đây kiểm tra giá trị CRC cho biểu tượng module_layout:

nm vmlinux | grep __crc_module_layout
0000000008663742 A __crc_module_layout

Giải quyết trường hợp không khớp CRC

Hãy làm theo các bước sau để giải quyết lỗi không khớp CRC khi tải một mô-đun:

  1. Tạo nhân GKI và nhân thiết bị bằng tuỳ chọn --kbuild_symtypes như trong lệnh sau:

    tools/bazel run --kbuild_symtypes //common:kernel_aarch64_dist

    Lệnh này tạo một tệp .symtypes cho mỗi tệp .o. Hãy xem KBUILD_SYMTYPES trong Kleaf để biết thông tin chi tiết.

    Đối với Android 13 trở xuống, hãy tạo nhân GKI và nhân thiết bị bằng cách thêm KBUILD_SYMTYPES=1 vào lệnh mà bạn dùng để tạo nhân, như minh hoạ trong lệnh sau:

    KBUILD_SYMTYPES=1 BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh

    Khi sử dụng build_abi.sh,, cờ KBUILD_SYMTYPES=1 đã được đặt ngầm.

  2. Tìm tệp .c mà biểu tượng có CRC không khớp được xuất, bằng cách dùng lệnh sau:

    git -C common grep EXPORT_SYMBOL.*module_layout
    kernel/module/version.c:EXPORT_SYMBOL(module_layout);
  3. Tệp .c có tệp .symtypes tương ứng trong GKI và các cấu phần phần mềm bản dựng của nhân thiết bị. Tìm tệp .symtypes bằng các lệnh sau:

    cd bazel-bin/common/kernel_aarch64/symtypes
    ls -1 kernel/module/version.symtypes

    Trong Android 13 trở xuống, khi sử dụng tập lệnh bản dựng cũ, vị trí có thể là out/$BRANCH/common hoặc out_abi/$BRANCH/common.

    Mỗi tệp .symtypes là một tệp văn bản thuần tuý bao gồm nội dung mô tả về loại và biểu tượng:

    • Mỗi dòng có dạng key description, trong đó nội dung mô tả có thể tham chiếu đến các khoá khác trong cùng một tệp.

    • Các khoá như [s|u|e|t]#foo tham chiếu đến [struct|union|enum|typedef] foo. Ví dụ:

      t#bool typedef _Bool bool
      
    • Các khoá không có tiền tố x# chỉ là tên biểu tượng. Ví dụ:

      find_module s#module * find_module ( const char * )
      
  4. So sánh hai tệp và khắc phục mọi điểm khác biệt.

Tốt nhất là bạn nên tạo symtypes bằng một bản dựng ngay trước khi có thay đổi gây ra vấn đề và sau đó là tại thay đổi gây ra vấn đề. Việc lưu tất cả các tệp có nghĩa là bạn có thể so sánh hàng loạt các tệp.

Ví dụ:

for f in $(find good bad -name '*.symtypes' | sed -r 's;^(good|bad)/;;' | LANG=C sort -u); do
  diff -N -U0 --label good/"$f" --label bad/"$f" <(LANG=C sort good/"$f") <(LANG=C sort bad/"$f")
done

Nếu không, hãy chỉ so sánh các tệp cụ thể mà bạn quan tâm.

Trường hợp 1: Sự khác biệt do chế độ hiển thị loại dữ liệu

#include mới có thể kéo một định nghĩa kiểu mới (ví dụ: struct foo) vào một tệp nguồn. Trong những trường hợp này, nội dung mô tả của định nghĩa đó trong tệp .symtypes tương ứng sẽ thay đổi từ structure_type foo { } trống thành một định nghĩa đầy đủ.

Việc này sẽ ảnh hưởng đến tất cả các CRC của mọi biểu tượng trong tệp .symtypes có nội dung mô tả phụ thuộc trực tiếp hoặc gián tiếp vào định nghĩa loại.

Ví dụ: việc thêm dòng sau vào tệp include/linux/device.h trong nhân sẽ gây ra lỗi không khớp CRC, một trong số đó là cho module_layout():

 #include <linux/fwnode.h>

Khi so sánh module/version.symtypes cho biểu tượng đó, bạn sẽ thấy những điểm khác biệt sau:

 $ diff -u <GKI>/kernel/module/version.symtypes <your kernel>/kernel/module/version.symtypes
  --- <GKI>/kernel/module/version.symtypes
  +++ <your kernel>/kernel/module/version.symtypes
  @@ -334,12 +334,15 @@
  ...
  -s#fwnode_handle structure_type fwnode_handle { }
  +s#fwnode_reference_args structure_type fwnode_reference_args { s#fwnode_handle * fwnode ; unsigned int nargs ; t#u64 args [ 8 ] ; }
  ...

Nếu nhân GKI có định nghĩa đầy đủ về loại, nhưng nhân của bạn lại thiếu định nghĩa này (rất khó xảy ra), thì hãy hợp nhất Nhân chung Android mới nhất vào nhân của bạn để bạn đang sử dụng cơ sở nhân GKI mới nhất.

Trong hầu hết các trường hợp, hạt nhân GKI thiếu định nghĩa đầy đủ về loại trong .symtypes, nhưng hạt nhân của bạn có định nghĩa này do các chỉ thị #include bổ sung.

Độ phân giải cho Android 16 trở lên

Đảm bảo rằng tệp nguồn bị ảnh hưởng có chứa tiêu đề ổn định KABI của Android:

#include <linux/android_kabi.h>

Đối với mỗi loại bị ảnh hưởng, hãy thêm ANDROID_KABI_DECLONLY(name); ở phạm vi toàn cầu vào tệp nguồn bị ảnh hưởng.

Ví dụ: nếu symtypes diff là:

--- good/drivers/android/vendor_hooks.symtypes
+++ bad/drivers/android/vendor_hooks.symtypes
@@ -1051 +1051,2 @@
-s#ubuf_info structure_type ubuf_info { }
+s#ubuf_info structure_type ubuf_info { member pointer_type { const_type { s#ubuf_info_ops } } ops data_member_location(0) , member t#refcount_t refcnt data_member_location(8) , member t#u8 flags data_member_location(12) } byte_size(16)
+s#ubuf_info_ops structure_type ubuf_info_ops { member pointer_type { subroutine_type ( formal_parameter pointer_type { s#sk_buff } , formal_parameter pointer_type { s#ubuf_info } , formal_parameter t#bool ) -> base_type void } complete data_member_location(0) , member pointer_type { subroutine_type ( formal_parameter pointer_type { s#sk_buff } , formal_parameter pointer_type { s#ubuf_info } ) -> base_type int byte_size(4) encoding(5) } link_skb data_member_location(8) } byte_size(16)

Vấn đề là struct ubuf_info hiện có định nghĩa đầy đủ trong symtypes. Giải pháp là thêm một dòng vào drivers/android/vendor_hooks.c:

ANDROID_KABI_DECLONLY(ubuf_info);

Thao tác này hướng dẫn gendwarfksyms coi loại được đặt tên là không xác định trong tệp.

Một khả năng phức tạp hơn là #include mới nằm trong chính tệp tiêu đề. Trong trường hợp này, bạn có thể cần phân phối các nhóm lệnh gọi macro ANDROID_KABI_DECLONLY khác nhau trên các tệp nguồn gián tiếp kéo theo các định nghĩa kiểu bổ sung vì một số tệp có thể đã có một số định nghĩa kiểu.

Để dễ đọc, hãy đặt các lệnh gọi macro như vậy ở gần đầu tệp nguồn.

Độ phân giải cho Android 15 trở xuống

Thông thường, cách khắc phục chỉ là ẩn #include mới khỏi genksyms.

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

Nếu không, để xác định #include gây ra sự khác biệt, hãy làm theo các bước sau:

  1. Mở tệp tiêu đề xác định biểu tượng hoặc kiểu dữ liệu có sự khác biệt này. Ví dụ: chỉnh sửa include/linux/fwnode.h cho struct fwnode_handle.

  2. Thêm đoạn mã sau vào đầu tệp tiêu đề:

    #ifdef CRC_CATCH
    #error "Included from here"
    #endif
    
  3. Trong tệp .c của mô-đun có CRC không khớp, hãy thêm đoạn mã sau làm dòng đầu tiên trước bất kỳ dòng #include nào.

    #define CRC_CATCH 1
    
  4. Biên dịch mô-đun của bạn. Lỗi thời gian tạo kết quả cho thấy chuỗi tệp tiêu đề #include dẫn đến sự không khớp CRC này. Ví dụ:

    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"
    

    Một trong các đường liên kết trong chuỗi #include này là do một thay đổi được thực hiện trong nhân của bạn, nhưng lại bị thiếu trong nhân GKI.

Trường hợp 2: Sự khác biệt do thay đổi về kiểu dữ liệu

Nếu sự không khớp CRC cho một biểu tượng hoặc kiểu dữ liệu không phải do sự khác biệt về khả năng hiển thị, thì đó là do những thay đổi thực tế (thêm, xoá hoặc thay đổi) trong chính kiểu dữ liệu.

Ví dụ: việc thực hiện thay đổi sau đây trong nhân sẽ gây ra một số lỗi không khớp CRC vì nhiều biểu tượng chịu ảnh hưởng gián tiếp của loại thay đổi này:

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);

Một lỗi CRC không khớp là cho devm_of_platform_populate().

Nếu bạn so sánh các tệp .symtypes cho biểu tượng đó, thì có thể tệp sẽ trông như sau:

 $ 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 structure_type 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 structure_type 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 * ) ; ...

Để xác định loại đã thay đổi, hãy làm theo các bước sau:

  1. Tìm định nghĩa của biểu tượng trong mã nguồn (thường là trong các tệp .h).

    • Để biết sự khác biệt về biểu tượng giữa nhân của bạn và nhân GKI, hãy tìm cam kết bằng cách chạy lệnh sau:
    git blame
    • Đối với các biểu tượng đã xoá (khi một biểu tượng bị xoá trong một cây và bạn cũng muốn xoá biểu tượng đó trong cây kia), bạn cần tìm thay đổi đã xoá dòng đó. Sử dụng lệnh sau trên cây nơi dòng đã bị xoá:
    git log -S "copy paste of deleted line/word" -- <file where it was deleted>
  2. Xem lại danh sách các cam kết được trả về để xác định vị trí của nội dung thay đổi hoặc nội dung bị xoá. Cam kết đầu tiên có thể là cam kết bạn đang tìm kiếm. Nếu không, hãy xem qua danh sách cho đến khi bạn tìm thấy cam kết đó.

  3. Sau khi xác định được cam kết, hãy hoàn nguyên cam kết đó trong nhân hoặc cập nhật cam kết đó để ngăn chặn thay đổi CRC và tải cam kết đó lên ACK rồi hợp nhất. Bạn cần xem xét từng điểm ngắt ABI còn lại để đảm bảo an toàn và nếu cần, bạn có thể ghi lại một điểm ngắt được phép.

Ưu tiên sử dụng khoảng đệm hiện có

Một số cấu trúc trong GKI được thêm khoảng đệm để cho phép mở rộng mà không làm hỏng các mô-đun hiện có của nhà cung cấp. Nếu một cam kết ở luồng trên (ví dụ: cam kết này thêm một thành viên vào cấu trúc như vậy), thì bạn có thể thay đổi cam kết đó để sử dụng một số khoảng đệm thay thế. Sau đó, thay đổi này sẽ không xuất hiện trong tính toán CRC.

Macro ANDROID_KABI_RESERVE tự ghi lại và được chuẩn hoá sẽ dành ra một khoảng trống u64 có giá trị (được căn chỉnh). Thông tin này được dùng thay cho một tuyên bố của thành viên.

Ví dụ:

struct data {
        u64 handle;
        ANDROID_KABI_RESERVE(1);
        ANDROID_KABI_RESERVE(2);
};

Bạn có thể sử dụng phần đệm mà không ảnh hưởng đến CRC của biểu tượng bằng ANDROID_KABI_USE (hoặc ANDROID_KABI_USE2 hoặc các biến thể khác có thể được xác định).

Thành viên sekret có sẵn như thể được khai báo trực tiếp, nhưng macro thực sự mở rộng thành một thành viên hợp nhất ẩn danh chứa sekret cũng như những thứ mà gendwarfksyms dùng để duy trì tính ổn định của symtype.

struct data {
        u64 handle;
        ANDROID_KABI_USE(1, void *sekret);
        ANDROID_KABI_RESERVE(2);
};
Độ phân giải cho Android 16 trở lên

CRC được tính bằng gendwarfksyms, sử dụng thông tin gỡ lỗi DWARF, do đó hỗ trợ cả các loại C và Rust. Độ phân giải sẽ khác nhau tuỳ theo loại thay đổi. Dưới đây là một số ví dụ.

Trình liệt kê mới hoặc đã sửa đổi

Đôi khi, các trình liệt kê mới được thêm vào và thỉnh thoảng, giá trị trình liệt kê MAX hoặc giá trị tương tự cũng bị ảnh hưởng. Những thay đổi này sẽ an toàn nếu chúng không "thoát" GKI hoặc nếu chúng ta có thể chắc chắn rằng các mô-đun của nhà cung cấp không thể quan tâm đến các giá trị của chúng.

Ví dụ:

 enum outcome {
       SUCCESS,
       FAILURE,
       RETRY,
+      TRY_HARDER,
       OUTCOME_LIMIT
 };

Việc thêm TRY_HARDER và thay đổi thành OUTCOME_LIMIT có thể bị ẩn khỏi quá trình tính toán CRC bằng các lệnh gọi macro ở phạm vi toàn cục:

ANDROID_KABI_ENUMERATOR_IGNORE(outcome, TRY_HARDER);
ANDROID_KABI_ENUMERATOR_VALUE(outcome, OUTCOME_LIMIT, 3);

Để dễ đọc, hãy đặt các định nghĩa này ngay sau định nghĩa enum.

Một thành phần cấu trúc mới chiếm một lỗ hiện có

Do việc căn chỉnh, sẽ có các byte không dùng đến giữa urgentscratch.

        void *data;
        bool urgent;
+       bool retry;
        void *scratch;

Không có thành phần bù trừ nào hiện có hoặc kích thước của cấu trúc bị ảnh hưởng bởi việc thêm retry. Tuy nhiên, điều này có thể ảnh hưởng đến CRC biểu tượng hoặc biểu thị ABI hoặc cả hai.

Thao tác này sẽ ẩn tệp đó khỏi quá trình tính toán CRC:

        void *data;
        bool urgent;
+       ANDROID_KABI_IGNORE(1, bool retry);
        void *scratch_space;

Thành viên retry có sẵn như thể được khai báo trực tiếp, nhưng macro thực sự mở rộng thành một thành viên hợp nhất ẩn danh chứa retry cũng như những thứ mà gendwarfksyms dùng để duy trì tính ổn định của symtype.

Mở rộng một cấu trúc bằng các thành viên mới

Đôi khi, các thành viên được thêm vào cuối một cấu trúc. Điều này không ảnh hưởng đến các độ lệch của thành viên hiện có hoặc ảnh hưởng đến người dùng hiện tại của cấu trúc chỉ truy cập vào cấu trúc đó bằng con trỏ. Kích thước của cấu trúc ảnh hưởng đến CRC của cấu trúc và các thay đổi đối với cấu trúc này có thể bị chặn bằng một lệnh gọi macro bổ sung ở phạm vi toàn cục, như sau:

struct data {
        u64 handle;
        u64 counter;
        ANDROID_KABI_IGNORE(1, void *sekret);
};

ANDROID_KABI_BYTE_SIZE(data, 16);

Để dễ đọc, hãy đặt đoạn mã này ngay sau định nghĩa struct.

Tất cả các thay đổi khác đối với một loại hoặc loại biểu tượng

Chỉ thỉnh thoảng, có thể có những thay đổi không thuộc một trong các danh mục trước đó, dẫn đến những thay đổi về CRC mà bạn không thể ngăn chặn bằng các macro trước đó.

Trong những trường hợp này, nội dung mô tả symtypes ban đầu của một loại hoặc biểu tượng có thể được cung cấp bằng một lệnh gọi ANDROID_KABI_TYPE_STRING ở phạm vi chung.

struct data {
        /* extensive changes */
};

ANDROID_KABI_TYPE_STRING("s#data", "original s#data symtypes definition");

Để dễ đọc, hãy đặt phần này ngay sau định nghĩa về loại hoặc biểu tượng.

Độ phân giải cho Android 15 trở xuống

Bạn cần ẩn các thay đổi về loại và loại biểu tượng khỏi genksyms. Bạn có thể thực hiện việc này bằng cách kiểm soát quá trình tiền xử lý bằng __GENKSYMS__.

Bạn có thể biểu thị các phép biến đổi mã tuỳ ý theo cách này.

Ví dụ: để ẩn một thành phần mới chiếm một lỗ trong cấu trúc hiện có:

struct parcel {
        void *data;
        bool urgent;
#ifndef __GENKSYMS__
        bool retry;
#endif
        void *scratch_space;
};