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 bản trình bày ABI từ các tệp nhị phân hạt nhân hiện có (mô-đun vmlinux+ GKI). Các nội dung đại diện ABI này là tệp .stg và danh sách biểu tượng. Giao diện mà nội dung trình bày cung cấp một chế độ xem đượ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 bản trình bày.

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

Quy trình

Việc phân tích ABI của hạt nhân cần thực hiện nhiều bước, hầu hết các bước đều có thể được tự động hoá:

  1. Tạo nhân và phần trình bày ABI của nhân.
  2. Phân tích sự khác biệt về ABI giữa bản dựng và tệp đối chiếu.
  3. Cập nhật nội dung trình bày ABI (nếu cần).
  4. Xử lý danh sách ký hiệu.

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

Danh sách biểu tượng

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

Chỉ những ký hiệu được liệt kê trong danh sách ký hiệu và các cấu trúc và định nghĩa liên quan của chúng mới được coi là một phần của KMI. Bạn có thể đăng các thay đổi lên danh sách ký hiệu nếu không có ký hiệu bạn cần. Sau khi giao diện mới nằm trong danh sách ký hiệu và là một phần của nội dung mô tả KMI, các giao diện này sẽ được duy trì ở trạng thái ổn định và không được xoá khỏi danh sách ký hiệu 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 nhóm danh sách biểu tượng riêng. Không có nỗ lực nào nhằm đảm bảo tính ổn định của ABI giữa các nhánh hạt nhân 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 ký hiệu KMI để giới hạn những giao diện cần được theo dõi để đảm bảo tính ổn định. Nhà cung cấp dự kiến sẽ gửi và cập nhật danh sách biểu tượng của riêng họ để đảm bảo rằng các giao diện mà họ dựa vào duy trì khả năng tương thích với ABI. Ví dụ: để xem danh sách danh sách biểu tượng cho hạt 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 ký hiệu chứa các ký hiệu được báo cáo là cần thiết cho một 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à tập hợp của tất cả các tệp danh sách ký hiệu 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 giao diện KMI hiện có; giao diện này sẽ ổn định. Tuy nhiên, nhà cung cấp có thể tự do thêm các ký hiệu vào KMI bất cứ lúc nào miễn là các nội dung bổ sung không ảnh hưởng đến độ ổn định của ABI hiện có. Các ký hiệu mới thêm sẽ được duy trì ở trạng thái ổn định ngay khi được trích dẫn theo danh sách ký hiệu KMI. Bạn không nên xoá các ký hiệu khỏi danh sách cho một hạt nhân trừ phi có thể xác nhận rằng không có thiết bị nào từng được vận chuyển với phần phụ thuộc trên ký hiệu đó.

Bạn có thể tạo danh sách ký hiệu KMI cho một thiết bị bằng cách làm theo hướng dẫn trong phần Cách xử lý danh sách ký hiệu. 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 việc này giúp ích cho việc bảo trì, bạn có thể gửi nhiều danh sách biểu tượng.

Mở rộng KMI

Mặc dù các ký hiệu KMI và các 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 các thay đổi làm hỏng giao diện ổn định trong một hạt nhân có KMI bị đóng băng), nhưng hạt nhân GKI vẫn mở cho các tiện ích để các thiết bị xuất xưởng vào cuối năm không cần xác định tất cả các phần phụ thuộc trước khi KMI bị đóng băng. Để mở rộng KMI, bạn có thể thêm các ký hiệu mới vào KMI cho các hàm nhân mới hoặc hiện có được xuất, ngay cả khi KMI bị đóng băng. Các bản vá hạt nhân mới cũng có thể được chấp nhận nếu không làm hỏng KMI.

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

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

Xử lý lỗi ABI

Ví dụ: bản vá sau đây gây ra lỗi 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 bằng bản vá này, công cụ sẽ thoát bằng một 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 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 ký hiệu mới từ hạt nhân không có trong danh sách ký hiệu 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à nội dung biểu thị ABI. Ví dụ: các 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à bản trình bày ABI.

  • Ví dụ về thay đổ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 trình bày ABI có trong aosp/1349377.

Nếu biểu tượng được xuất (bởi bạn hoặc đã đượ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 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 ký hiệu KMI trong cả hạt nhân và ACK (xem phần Cập nhật nội dung trình bày ABI). Để biết ví dụ về cách cập nhật danh sách biểu tượng và cách biểu thị ABI trong ACK, hãy tham khảo aosp/1367601.

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

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

Sơ đồ quy trình xử lý lỗi ABI

Hình 1. Giải pháp khắc phục lỗi ABI

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

Hãy cố gắng 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á các 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 một 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ệp tham chiếu nguồn) và đảm bảo CONFIG dùng cho #ifdef bị tắt cho defconfig sản xuất và gki_defconfig. Để biết ví dụ về cách thêm cấu hình gỡ lỗi vào một cấu trúc mà không làm hỏng ABI, hãy tham khảo nhóm bản vá này.

  • Tái cấu trúc các tính năng để không thay đổi hạt 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 cố gắng tái cấu trúc phần ABI của thay đổi để tránh sửa đổi ABI hạt 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ố ý phá vỡ ABI hạt nhân, 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 dẫn đến lỗi là do thay đổi cấu trúc dữ liệu và các thay đổi CRC biểu tượng liên quan, hoặc do thay đổi tuỳ chọn cấu hình dẫn đến bất kỳ nguyên nhân nào nêu trên. Hãy bắt đầu bằng cách giải quyết các vấn đề mà công cụ này phát hiện được.

Bạn có thể tái tạo các phát hiện về ABI trên máy, hãy xem phần Tạo hạt nhân và nội dung trình bày ABI của hạt 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 chứa KMI đã bị đóng băng hoặc hoàn tất, thì các thay đổi đó phải vượt qua AbiAnalyzer để đảm bảo các thay đổi không ảnh hưởng đến ABI ổn định theo cách không tương thích. Trong quá trình này, AbiAnalyzer sẽ tìm báo cáo ABI được tạo trong quá trình xây dựng (một bản dựng mở rộng thực hiện bản dựng thông thường, sau đó là một số bước trích xuất và so sánh ABI.

Nếu AbiAnalyzer tìm thấy một báo cáo không trống, thì báo cáo đó sẽ đặt nhãn Lint-1 và thay đổi sẽ bị chặn gửi cho đến khi được giải quyết; cho đến khi nhóm bản vá nhận được nhãn Lint+1.

Cập nhật ABI hạt 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ã, nội dung trình bày ABI và danh sách biểu tượng cho ACK. Để yêu cầu Tìm lỗi mã nguồn xoá -1 mà không làm hỏng khả năng tương thích với 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 điểm Code-Review +2 cho nhóm bản vá.

  3. Cập nhật nội dung trình bày ABI tham chiếu.

  4. Hợp nhất các thay đổi về mã và thay đổi về nội dung 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 đang được thực hiện.

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

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

  • Nếu một thay đổi ABI giới thiệu một tính năng mới không cần đưa vào ACK, bạn có thể giới thiệu các ký hiệu vào ACK bằng cách sử dụng một mã giả như mô tả trong phần sau.

Sử dụng mã giả lập cho ACK

Bạn chỉ cần sử dụng các mô-đun giả lập cho các thay đổi về nhân cốt lõi không mang lại lợi ích cho ACK, chẳng hạn như 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ề mã giả và một số lựa chọn trong ACK cho GKI.

  • Phần mô phỏng tính năng Core-isolate (aosp/1284493). Các chức năng trong ACK không cần thiết, nhưng các biểu tượng cần có trong ACK để các mô-đun của bạn có thể 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ỉ ABI mới có thể chọn 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 để ACK và sau đó được cắt bớt để chỉ bao gồm những thay đổi cần thiết để 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.

  • Một số thay đổi ABI cấu trúc nhiệt cần nhiều hơn việc chỉ thêm các trường ABI mới.

    • Bản vá aosp/1255544 đã giải quyết các 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 phát hiện được 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 chạy 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.

  • Thay đổi ABI CONFIG_NL80211_TESTMODE (aosp/1344321). Bản vá này đã thêm các thay đổi cấu trúc cần thiết 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ọ và vẫn duy trì khả năng tuân thủ GKI.

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

Hạt nhân GKI sử dụng các tuỳ 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 đã xuất bằng EXPORT_SYMBOL_GPL()) ở những biểu tượng được liệt kê trên 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. Quy định 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 hạt nhân GKI không bao gồm tính năng cắt bớt ký hiệu (nghĩa là tất cả các ký hiệu thường được xuất đều có thể được sử dụng). Để tìm các bản dựng này, hãy tìm các 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 tạo phiên bản mô-đun

Hạt nhân Hình ảnh hạt nhân chung (GKI) sử dụng tính năng phân cấp 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 dư thừa tuần hoàn (CRC) tại thời điểm tải mô-đun nếu KMI dự kiến của mô-đun không khớp với KMI vmlinux. Ví dụ: sau đây là lỗi điển hình xảy ra tại thời điểm tải mô-đun do CRC không khớp với 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 tạo phiên bản mô-đun

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

  • Việc tạo phiên bản mô-đun sẽ phát hiện 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 mờ (opaque data structure), tức là các cấu trúc dữ liệu không thuộc KMI, thì các mô-đun đó sẽ bị lỗi sau khi cấu trúc thay đổi trong tương lai.

    Ví dụ: hãy xem xét trường fwnode trong struct device. Trường này PHẢI mờ đố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 đó. Sau đó, mô-đun có thể thực hiện các thay đổi đối với device->fwnode->dev hoặc device->fwnode->ops. Tình huống này có vấn đề vì một số lý do, 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ộ.

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

  • Mô-đun hiện tại được coi là không tương thích với KMI khi được tải vào một ngày sau đó bằng một nhân mới không tương thích. Việc tạo phiên bản mô-đun sẽ thêm một bước kiểm tra 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 với hạt nhân. Bước kiểm tra này giúp ngăn chặn các sự cố thời gian chạy và sự cố hạt nhân khó gỡ lỗi 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 tạo phiên bản mô-đun sẽ ngăn chặn tất cả các vấn đề này.

Kiểm tra để tìm những trường hợp không khớp CRC 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 hạt nhân cùng với các điểm khác biệt khác về ABI.

Ngoài ra, bản dựng hạt nhân đầy đủ với CONFIG_MODVERSIONS được bật sẽ tạo một tệp Module.symvers trong quy trình xây dựng thông thường. Tệp này có một dòng cho mỗi ký hiệu do hạt 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 đang 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 mình để 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 ra hay 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 biểu tượng đó được một trong các mô-đun bạn tải trong thiết bị sử dụng, thì mô-đun đó sẽ không tải.

Nếu không có tất cả cấu phần phần mềm xây dựng, nhưng có các tệp vmlinux của hạt nhân GKI và hạt nhân của bạn, bạn có thể so sánh các giá trị CRC cho một ký hiệu cụ thể bằng cách chạy lệnh sau trên cả hai hạt 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 ký hiệu 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ô-đun:

  1. Tạo hạt nhân GKI và hạt 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 hạt nhân GKI và hạt nhân thiết bị bằng cách thêm KBUILD_SYMTYPES=1 vào đầu lệnh bạn sử dụng để tạo hạt nhân, như 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 chứa biểu tượng có CRC không khớp được xuất bằ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ó một tệp .symtypes tương ứng trong GKI và các cấu phần phần mềm bản dựng 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ả loại và ký hiệu:

    • 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 ký hiệu. 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 thay đổi gây ra vấn đề, sau đó là tại thời điểm 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, bạn chỉ cần 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ể lấy định nghĩa loại mới (ví dụ: struct foo) vào tệp nguồn. Trong những trường hợp này, nội dung mô tả của #include trong tệp .symtypes tương ứng sẽ thay đổi từ structure_type foo { } trống thành định nghĩa đầy đủ.

Điều này sẽ ảnh hưởng đến tất cả CRC của tất cả các ký hiệu 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 hạt nhân sẽ gây ra sự không khớp CRC, một trong số đó là module_layout():

 #include <linux/fwnode.h>

Khi so sánh module/version.symtypes cho biểu tượng đó, bạn sẽ thấy các đ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 hạt nhân GKI có định nghĩa loại đầy đủ nhưng hạt nhân của bạn lại thiếu định nghĩa đó (rất khó xảy ra), hãy hợp nhất Hạt nhân Android phổ biến mới nhất vào hạt nhân của bạn để bạn đang sử dụng cơ sở hạt 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 loại đầy đủ trong .symtypes, nhưng hạt nhân của bạn có định nghĩa đó do các lệnh #include bổ sung.

Giải pháp cho Android 16 trở lên

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

#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ục vào tệp nguồn bị ảnh hưởng.

Ví dụ: nếu sự khác biệt symtypes 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)

Sau đó, 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 sẽ hướng dẫn gendwarfksyms coi loại đã đặ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 tệp tiêu đề. Trong trường hợp này, bạn có thể cần phân phối nhiều nhóm lệnh gọi macro ANDROID_KABI_DECLONLY trên các tệp nguồn gián tiếp lấy các định nghĩa loại bổ sung vì một số tệp có thể đã có một số định nghĩa loại.

Để 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 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 nội dung 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. 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 hạt nhân của bạn, nhưng lại bị thiếu trong hạt nhân GKI.

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

Nếu CRC không khớp cho một ký hiệu hoặc kiểu dữ liệu không phải do sự khác biệt về chế độ hiển thị, thì đó là do các 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 hạt nhân sẽ gây ra một số trường hợp không khớp CRC vì nhiều biểu tượng bị ảnh hưởng gián tiếp bởi 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 CRC không khớp là đối với devm_of_platform_populate().

Nếu so sánh các tệp .symtypes cho biểu tượng đó, bạn có thể thấy 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 tệp .h).

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

  3. Sau khi bạn xác định được thay đổi, hãy huỷ bỏ thay đổi đó trong hạt nhân hoặc cập nhật thay đổi đó để ngăn chặn thay đổi CRC và tải thay đổi đó lên ACK để 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 vào để cho phép mở rộng mà không làm hỏng các mô-đun nhà cung cấp hiện có. Nếu một thay đổi thượng nguồn (ví dụ:) thêm một thành viên vào một cấu trúc như vậy, thì bạn có thể thay đổi thành phần đó để sử dụng một số khoảng đệm. Sau đó, thay đổi này sẽ bị ẩn khỏi quá trình tính toán CRC.

Macro ANDROID_KABI_RESERVE tự ghi lại, được chuẩn hoá sẽ dành một không gian (được căn chỉnh) có giá trị u64. Phương thức này được dùng thay cho phần khai báo thành phần.

Ví dụ:

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

Bạn có thể sử dụng khoảng đệm mà không ảnh hưởng đến CRC ký hiệu 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 phầ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 phần liên kết ẩn chứa sekret cũng như những thành phần mà gendwarfksyms sử dụng để duy trì tính ổn định của loại ký hiệu.

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

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

Bộ đếm mới hoặc đã sửa đổi

Đôi khi, các bộ đếm mới được thêm vào và đôi khi MAX hoặc giá trị bộ đếm tương tự cũng bị ảnh hưởng. Những thay đổi này là an toàn nếu không "thoát" khỏi 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 tính toán CRC bằng 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 thuộc tính 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 căn chỉnh, sẽ có các byte không sử dụng giữa urgentscratch.

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

Việc thêm retry không ảnh hưởng đến độ dời thành phần hiện có hoặc kích thước của cấu trúc. Tuy nhiên, việc này có thể ảnh hưởng đến CRC biểu tượng hoặc cách trình bày ABI hoặc cả hai.

Thao tác này sẽ ẩn tệp khỏi tính năng tính CRC:

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

Thành phầ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 phần liên kết ẩn chứa retry cũng như những thành phần mà gendwarfksyms sử dụng để duy trì tính ổn định của loại ký hiệu.

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

Đôi khi, các thành phần được thêm vào cuối cấu trúc. Điều này không ảnh hưởng đến độ dời của các thành phầ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 và các thay đổi đối với kích thướ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 phần 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 của một biểu tượng

Đôi khi, có thể có những thay đổi không thuộc một trong các danh mục trước đó, dẫn đến việc không thể ngăn chặn các thay đổi CRC bằng các macro trước đó.

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

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 ký hiệu 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 xử lý trước 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;
};