Aby ustabilizować interfejs ABI w jądrze systemu Android, możesz użyć narzędzi do monitorowania interfejsu ABI aplikacji, które są dostępne w Androidzie 11 i nowszych wersjach. Narzędzie zbiera i porównuje reprezentacje interfejsu ABI z istniejących plików binarnych jądra (vmlinux
+ moduły GKI). Te reprezentacje ABI to pliki .stg
i listy symboli. Interfejs, w którym reprezentacja zapewnia widok, nazywa się interfejsem modułu jądra (KMI). Za pomocą narzędzi możesz śledzić i ograniczać zmiany w KMI.
Narzędzia do monitorowania interfejsu ABI są opracowywane w AOSP i używają STG (lub libabigail
w Androidzie 13 i starszych wersjach) do generowania i porównywania reprezentacji.
Na tej stronie opisujemy narzędzia, proces zbierania i analizowania reprezentacji interfejsu ABI oraz wykorzystywanie tych reprezentacji do zapewnienia stabilności interfejsu ABI w jądrze. Na tej stronie znajdziesz też informacje o wprowadzaniu zmian w jądrach Androida.
Przetwarzaj
Analiza interfejsu ABI jądra wymaga wykonania kilku czynności, z których większość można zautomatyzować:
- Skompiluj jądro i jego reprezentację ABI.
- Analizowanie różnic w interfejsie ABI między kompilacją a plikiem referencyjnym
- W razie potrzeby zaktualizuj reprezentację interfejsu ABI.
- Praca z listami symboli
Poniższe instrukcje działają w przypadku każdego jądra, które można skompilować za pomocą obsługiwanego łańcucha narzędzi (np. wstępnie skompilowanego łańcucha narzędzi Clang). repo manifests
są dostępne dla wszystkich wspólnych gałęzi jądra Androida i kilku jąder specyficznych dla urządzenia. Sprawdzają, czy podczas tworzenia dystrybucji jądra na potrzeby analizy używany jest prawidłowy łańcuch narzędzi.
Listy symboli
Interfejs KMI nie obejmuje wszystkich symboli w jądrze,a nawet wszystkich ponad 30 tys. wyeksportowanych symboli. Zamiast tego symbole, które mogą być używane przez moduły dostawców, są wyraźnie wymienione w zestawie plików z listą symboli utrzymywanych publicznie w drzewie jądra (gki/{ARCH}/symbols/*
lub android/abi_gki_{ARCH}_*
w Androidzie 15 i starszych wersjach). Suma wszystkich symboli we wszystkich plikach listy symboli określa zbiór symboli KMI, które są utrzymywane jako stabilne. Przykładowy plik listy symboli to gki/aarch64/symbols/db845c
, który deklaruje symbole wymagane w przypadku DragonBoard 845c.
Tylko symbole wymienione na liście symboli oraz powiązane z nimi struktury i definicje są uznawane za część KMI. Możesz publikować zmiany na listach symboli, jeśli potrzebne symbole nie są dostępne. Gdy nowe interfejsy znajdą się na liście symboli i będą częścią opisu KMI, będą traktowane jako stabilne i nie można ich usuwać z listy symboli ani modyfikować po zamrożeniu gałęzi.
Każda gałąź jądra KMI w ACK (Android Common Kernel) ma własny zestaw list symboli. Nie podejmujemy prób zapewnienia stabilności interfejsu ABI między różnymi gałęziami jądra KMI. Na przykład KMI dla android12-5.10
jest całkowicie niezależny od KMI dla android13-5.10
.
Narzędzia ABI korzystają z list symboli KMI, aby ograniczyć interfejsy, które muszą być monitorowane pod kątem stabilności. Dostawcy powinni przesyłać i aktualizować własne listy symboli, aby potwierdzić, że interfejsy, z których korzystają, zachowują zgodność z interfejsem ABI. Na przykład, aby wyświetlić listę list symboli dla jądra android16-6.12
, zapoznaj się z https://android.googlesource.com/kernel/common/+/refs/heads/android16-6.12/gki/aarch64/symbols
.
Lista symboli zawiera symbole, które są potrzebne w przypadku danego dostawcy lub urządzenia. Pełna lista używana przez narzędzia to suma wszystkich plików listy symboli KMI. Narzędzia ABI określają szczegóły każdego symbolu, w tym sygnaturę funkcji i zagnieżdżone struktury danych.
Gdy interfejs KMI jest zamrożony, nie można wprowadzać w nim żadnych zmian. Jest on stabilny. Dostawcy mogą jednak w dowolnym momencie dodawać symbole do interfejsu KMI, o ile nie wpływa to na stabilność istniejącego interfejsu ABI. Nowo dodane symbole są stabilne, gdy tylko zostaną wymienione na liście symboli KMI. Symboli nie należy usuwać z listy jądra, chyba że można potwierdzić, że żadne urządzenie nie zostało nigdy wysłane z zależnością od tego symbolu.
Listę symboli KMI dla urządzenia możesz wygenerować, postępując zgodnie z instrukcjami w artykule Jak pracować z listami symboli. Wielu partnerów przesyła jedną listę symboli na potwierdzenie odbioru, ale nie jest to bezwzględnie wymagane. Jeśli ułatwi to konserwację, możesz przesłać kilka list symboli.
Przedłużanie KMI
Symbole KMI i powiązane z nimi struktury są utrzymywane jako stabilne (co oznacza, że nie można akceptować zmian, które powodują przerwanie stabilnych interfejsów w jądrze z zamrożonym KMI), ale jądro GKI pozostaje otwarte na rozszerzenia, dzięki czemu urządzenia dostarczane w późniejszym okresie roku nie muszą definiować wszystkich zależności przed zamrożeniem KMI. Aby rozszerzyć KMI, możesz dodać do niego nowe symbole dla nowych lub istniejących wyeksportowanych funkcji jądra, nawet jeśli KMI jest zamrożony. Nowe poprawki jądra mogą zostać zaakceptowane, jeśli nie naruszają interfejsu KMI.
Informacje o awariach KMI
Jądro ma źródła, a pliki binarne są tworzone na ich podstawie.
Gałęzie jądra monitorowane przez ABI zawierają reprezentację ABI bieżącego interfejsu GKI ABI (w postaci pliku .stg
). Po utworzeniu plików binarnych (vmlinux
, Image
i wszystkich modułów GKI) można z nich wyodrębnić reprezentację ABI. Każda zmiana wprowadzona w pliku źródłowym jądra może wpłynąć na pliki binarne, a w konsekwencji także na wyodrębnione .stg
. Analiza zgodności interfejsu ABI porównuje zatwierdzony plik .stg
z plikiem wyodrębnionym z artefaktów kompilacji i w przypadku wykrycia różnicy semantycznej ustawia w Gerrit etykietę Lint-1.
Radzenie sobie z awariami interfejsu ABI
Na przykład poniższa poprawka wprowadza bardzo oczywiste naruszenie 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.
Gdy uruchomisz kompilację ABI z zastosowaną tą poprawką, narzędzie zakończy działanie z niezerowym kodem błędu i zgłosi różnicę w ABI podobną do tej:
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
Różnice w interfejsie ABI wykryte podczas kompilacji
Najczęstszą przyczyną błędów jest użycie przez sterownik nowego symbolu z jądra, którego nie ma na żadnej liście symboli.
Jeśli symbol nie znajduje się na liście symboli, musisz najpierw sprawdzić, czy jest eksportowany z EXPORT_SYMBOL_GPL(symbol_name)
, a następnie zaktualizować listę symboli i reprezentację ABI. Na przykład poniższe zmiany dodają nową funkcję przyrostowego FS do gałęzi android-12-5.10
, co obejmuje aktualizację listy symboli i reprezentacji ABI.
- Przykład zmiany funkcji znajdziesz w aosp/1345659.
- Przykład listy symboli znajdziesz w aosp/1346742.
- Przykład zmiany reprezentacji ABI znajdziesz w aosp/1349377.
Jeśli symbol jest wyeksportowany (przez Ciebie lub wcześniej), ale nie jest używany przez żaden inny sterownik, może pojawić się błąd kompilacji podobny do tego poniżej.
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
Aby rozwiązać ten problem, zaktualizuj listę symboli KMI w jądrze i w ACK (patrz Aktualizowanie reprezentacji interfejsu ABI). Przykład aktualizacji listy symboli i reprezentacji ABI w ACK znajdziesz w aosp/1367601.
Rozwiązywanie problemów z niezgodnością interfejsu ABI jądra
Możesz sobie poradzić z przerwami w interfejsie ABI jądra, refaktoryzując kod, aby nie zmieniać interfejsu ABI, lub aktualizując reprezentację interfejsu ABI. Skorzystaj z poniższego wykresu, aby określić najlepsze podejście w Twojej sytuacji.
Rysunek 1. Rozwiązywanie problemów z ABI
Refaktoryzacja kodu w celu uniknięcia zmian w interfejsie ABI
Dołóż wszelkich starań, aby nie modyfikować istniejącego interfejsu ABI. W wielu przypadkach możesz refaktoryzować kod, aby usunąć zmiany wpływające na ABI.
Zmiany w polach struktury. Jeśli zmiana modyfikuje interfejs ABI funkcji debugowania, dodaj znak
#ifdef
wokół pól (w strukturach i odwołaniach do kodu źródłowego) i upewnij się, żeCONFIG
używany w przypadku#ifdef
jest wyłączony w przypadku konfiguracji defconfig igki_defconfig
. Przykład dodawania konfiguracji debugowania do struktury bez naruszania ABI znajdziesz w tym zestawie zmian.Refaktoryzacja funkcji, aby nie zmieniać podstawowego jądra. Jeśli do ACK trzeba dodać nowe funkcje, aby obsługiwać moduły partnera, spróbuj zmodyfikować część ABI, aby uniknąć modyfikowania ABI jądra. Przykład użycia istniejącego interfejsu ABI jądra do dodania dodatkowych funkcji bez zmiany interfejsu ABI jądra znajdziesz w aosp/1312213.
Naprawianie uszkodzonego interfejsu ABI w Androidzie Gerrit
Jeśli nie doszło do tego celowo, musisz to sprawdzić, korzystając z wskazówek narzędzia do monitorowania interfejsu ABI. Najczęstsze przyczyny awarii to zmiany w strukturach danych i powiązane z nimi zmiany w sumie kontrolnej CRC symbolu lub zmiany opcji konfiguracji, które prowadzą do któregokolwiek z wyżej wymienionych problemów. Zacznij od rozwiązania problemów wykrytych przez narzędzie.
Wyniki ABI możesz odtworzyć lokalnie. Więcej informacji znajdziesz w artykule Kompilowanie jądra i jego reprezentacji ABI.
Informacje o etykietach Lint-1
Jeśli przesyłasz zmiany do gałęzi zawierającej zamrożony lub sfinalizowany interfejs KMI, zmiany muszą przejść analizy zgodności i kompatybilności interfejsu ABI, aby mieć pewność, że zmiany w reprezentacji interfejsu ABI odzwierciedlają rzeczywisty interfejs ABI i nie zawierają żadnych niezgodności (usunięcia symboli lub zmiany typu).
Każda z tych analiz interfejsu ABI może ustawić etykietę Lint-1 i zablokować przesłanie zmiany, dopóki wszystkie problemy nie zostaną rozwiązane lub etykieta nie zostanie zastąpiona.
Aktualizowanie interfejsu ABI jądra
Jeśli modyfikacja ABI jest nieunikniona, musisz zastosować zmiany w kodzie, reprezentację ABI i listę symboli w ACK. Aby narzędzie Lint usunęło wartość -1 i nie spowodowało braku zgodności z GKI, wykonaj te czynności:
Poczekaj na ocenę Code-Review +2 dla zestawu poprawek.
Połącz zmiany w kodzie ze zmianą aktualizacji ABI.
Przesyłanie zmian kodu ABI do ACK
Aktualizacja interfejsu ACK ABI zależy od rodzaju wprowadzanej zmiany.
Jeśli zmiana ABI jest związana z funkcją, która wpływa na testy CTS lub VTS, zwykle można ją wybrać do ACK w takiej postaci, w jakiej jest. Przykład:
- aosp/1289677 jest wymagany do działania dźwięku.
- aosp/1295945 jest wymagany do działania USB.
Jeśli zmiana interfejsu ABI dotyczy funkcji, którą można udostępnić ACK, można ją przenieść do ACK w niezmienionej postaci. Na przykład poniższe zmiany nie są potrzebne w przypadku testów CTS ani VTS, ale można je udostępnić ACK:
- aosp/1250412 to zmiana funkcji związanej z temperaturą.
- aosp/1288857
to
EXPORT_SYMBOL_GPL
zmiana.
Jeśli zmiana ABI wprowadza nową funkcję, której nie trzeba uwzględniać w ACK, możesz wprowadzić symbole do ACK za pomocą stuba, jak opisano w sekcji poniżej.
Używanie elementów zastępczych w przypadku potwierdzeń
Stubs muszą być konieczne tylko w przypadku podstawowych zmian w jądrze, które nie przynoszą korzyści ACK, takich jak zmiany wydajności i zużycia energii. Poniżej znajdziesz przykłady stubów i częściowych cherry-picków w ACK dla GKI.
Stub funkcji izolacji rdzenia (aosp/1284493). Funkcje w ACK nie są konieczne, ale symbole muszą być obecne w ACK, aby moduły mogły z nich korzystać.
Symbol zastępczy modułu dostawcy (aosp/1288860).
Wybrana tylko pod kątem ABI funkcja śledzenia zdarzeń
mm
w poszczególnych procesach (aosp/1288454). Pierwotna poprawka została wybrana do ACK, a następnie przycięta tak, aby zawierała tylko niezbędne zmiany w celu rozwiązania różnic w interfejsie ABI dlatask_struct
imm_event_count
. Ta poprawka aktualizuje też wyliczeniemm_event_type
, aby zawierało ostateczne elementy.Częściowe wybiórcze zastosowanie zmian w interfejsie ABI struktury termicznej, które wymagały czegoś więcej niż tylko dodania nowych pól interfejsu ABI.
Poprawka aosp/1255544 rozwiązuje różnice w interfejsie ABI między jądrem partnera a ACK.
Poprawka aosp/1291018 rozwiązuje problemy funkcjonalne wykryte podczas testowania poprzedniej poprawki GKI. Poprawka obejmowała zainicjowanie struktury parametru czujnika w celu zarejestrowania wielu stref termicznych na jednym czujniku.
CONFIG_NL80211_TESTMODE
Zmiany w interfejsie ABI (aosp/1344321). Ta poprawka wprowadziła niezbędne zmiany w strukturze interfejsu ABI i zapewniła, że dodatkowe pola nie powodują różnic w działaniu, co umożliwia partnerom uwzględnienieCONFIG_NL80211_TESTMODE
w jądrach produkcyjnych przy zachowaniu zgodności z GKI.
Wymuszanie KMI w środowisku wykonawczym
Jądra GKI korzystają z opcji konfiguracji TRIM_UNUSED_KSYMS=y
i UNUSED_KSYMS_WHITELIST=<union
of all symbol lists>
, które ograniczają eksportowane symbole (np. symbole eksportowane za pomocą EXPORT_SYMBOL_GPL()
) do tych, które znajdują się na liście symboli. Wszystkie inne symbole nie są eksportowane, a wczytywanie modułu wymagającego nieeksportowanego symbolu jest odrzucane. To ograniczenie jest egzekwowane w momencie kompilacji, a brakujące wpisy są oznaczane.
Na potrzeby programowania możesz użyć kompilacji jądra GKI, która nie obejmuje przycinania symboli (co oznacza, że można używać wszystkich zwykle eksportowanych symboli). Aby znaleźć te kompilacje, poszukaj kompilacji kernel_debug_aarch64
na stronie ci.android.com.
Wymuszanie KMI za pomocą wersji modułów
Jądra ogólnego obrazu jądra (GKI) używają wersjonowania modułów (CONFIG_MODVERSIONS
) jako dodatkowego środka do egzekwowania zgodności z KMI w czasie działania. Wersjonowanie modułów może powodować błędy niezgodności cyklicznej sumy kontrolnej (CRC) podczas wczytywania modułu, jeśli oczekiwany interfejs KMI modułu nie jest zgodny z interfejsem KMI vmlinux
. Na przykład poniżej znajdziesz typowy błąd, który występuje w momencie wczytywania modułu z powodu niezgodności CRC dla symbolu 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 ''
Zastosowania obsługi wersji modułów
Wersje modułów są przydatne z tych powodów:
Wersjonowanie modułów wykrywa zmiany w widoczności struktury danych. Jeśli moduły zmieniają nieprzezroczyste struktury danych, czyli struktury danych, które nie są częścią KMI, po przyszłych zmianach struktury przestaną działać.
Weźmy na przykład pole
fwnode
wstruct device
. To pole MUSI być nieprzezroczyste dla modułów, aby nie mogły one wprowadzać zmian w polachdevice->fw_node
ani zakładać jego rozmiaru.Jeśli jednak moduł zawiera
<linux/fwnode.h>
(bezpośrednio lub pośrednio), polefwnode
wstruct device
nie jest już dla niego nieprzezroczyste. Moduł może wtedy wprowadzać zmiany wdevice->fwnode->dev
lubdevice->fwnode->ops
. Taka sytuacja jest problematyczna z kilku powodów:Może to naruszyć założenia, które kod podstawowego jądra przyjmuje w odniesieniu do wewnętrznych struktur danych.
Jeśli przyszła aktualizacja jądra zmieni
struct fwnode_handle
(typ danychfwnode
), moduł przestanie działać z nowym jądrem. Co więcej,stgdiff
nie będzie wykazywać żadnych różnic, ponieważ moduł narusza KMI, bezpośrednio manipulując wewnętrznymi strukturami danych w sposób, którego nie można wykryć, sprawdzając tylko reprezentację binarną.
Bieżący moduł jest uznawany za niezgodny z KMI, gdy jest wczytywany w późniejszym terminie przez nowe, niezgodne jądro. Wersjonowanie modułów dodaje kontrolę w czasie działania, aby uniknąć przypadkowego wczytania modułu, który nie jest zgodny z KMI jądra. Zapobiega to trudnym do debugowania problemom z czasem działania i awariom jądra, które mogą wynikać z niewykrytej niezgodności w KMI.
Włączenie wersji modułu zapobiega wszystkim tym problemom.
Sprawdzanie niezgodności CRC bez uruchamiania urządzenia
stgdiff
porównuje i zgłasza niezgodności CRC między jądrami wraz z innymi różnicami w interfejsie ABI.
Dodatkowo pełna kompilacja jądra z włączoną opcją CONFIG_MODVERSIONS
generuje plik Module.symvers
w ramach normalnego procesu kompilacji. Ten plik zawiera po jednym wierszu dla każdego symbolu wyeksportowanego przez jądro (vmlinux
) i moduły. Każdy wiersz zawiera wartość CRC, nazwę symbolu, przestrzeń nazw symbolu, nazwę vmlinux
lub modułu, który eksportuje symbol, oraz typ eksportu (np. EXPORT_SYMBOL
lub EXPORT_SYMBOL_GPL
).
Możesz porównać pliki Module.symvers
między kompilacją GKI a Twoją kompilacją, aby sprawdzić, czy występują różnice w sumach kontrolnych CRC w symbolach eksportowanych przez vmlinux
. Jeśli w dowolnym symbolu wyeksportowanym przez vmlinux
i występuje różnica w wartości CRC, a symbol ten jest używany przez jeden z modułów wczytywanych na urządzeniu, moduł nie zostanie wczytany.
Jeśli nie masz wszystkich artefaktów kompilacji, ale masz pliki vmlinux
jądra GKI i własnego jądra, możesz porównać wartości CRC dla określonego symbolu, uruchamiając to polecenie w obu jądrach i porównując dane wyjściowe:
nm <path to vmlinux>/vmlinux | grep __crc_<symbol name>
Na przykład to polecenie sprawdza wartość CRC symbolu module_layout
:
nm vmlinux | grep __crc_module_layout
0000000008663742 A __crc_module_layout
Rozwiązywanie problemów z niezgodnością CRC
Aby rozwiązać problem z niezgodnością CRC podczas wczytywania modułu:
Skompiluj jądro GKI i jądro urządzenia za pomocą opcji
--kbuild_symtypes
, jak pokazano w tym poleceniu:tools/bazel run --kbuild_symtypes //common:kernel_aarch64_dist
To polecenie generuje plik
.symtypes
dla każdego pliku.o
. Więcej informacji znajdziesz w artykuleKBUILD_SYMTYPES
w Kleaf.W przypadku Androida 13 i starszych wersji skompiluj jądro GKI i jądro urządzenia, dodając przed poleceniem, którego używasz do kompilacji jądra, znak
KBUILD_SYMTYPES=1
, jak pokazano w tym poleceniu:KBUILD_SYMTYPES=1 BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh
Gdy używasz
build_abi.sh,
, flagaKBUILD_SYMTYPES=1
jest już ustawiona domyślnie.Znajdź plik
.c
, w którym wyeksportowano symbol z niezgodną sumą kontrolną CRC, za pomocą tego polecenia:git -C common grep EXPORT_SYMBOL.*module_layout kernel/module/version.c:EXPORT_SYMBOL(module_layout);
Plik
.c
ma odpowiedni plik.symtypes
w GKI i artefakty kompilacji jądra urządzenia. Znajdź plik.symtypes
, używając tych poleceń:cd bazel-bin/common/kernel_aarch64/symtypes ls -1 kernel/module/version.symtypes
W Androidzie 13 i starszych wersjach, w przypadku używania starszych skryptów kompilacji, lokalizacja będzie prawdopodobnie
out/$BRANCH/common
lubout_abi/$BRANCH/common
.Każdy plik
.symtypes
to zwykły plik tekstowy zawierający opisy typu i symbolu:Każdy wiersz ma postać
key description
, gdzie opis może odwoływać się do innych kluczy w tym samym pliku.Klucze takie jak
[s|u|e|t]#foo
odnoszą się do[struct|union|enum|typedef] foo
. Na przykład:t#bool typedef _Bool bool
Klucze bez prefiksu
x#
to tylko nazwy symboli. Na przykład:find_module s#module * find_module ( const char * )
Porównaj oba pliki i usuń wszystkie różnice.
Najlepiej wygenerować symtypes
w kompilacji tuż przed problematyczną zmianą, a potem w momencie tej zmiany. Zapisanie wszystkich plików umożliwia ich zbiorcze porównanie.
Na przykład
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
W przeciwnym razie porównaj tylko interesujące Cię pliki.
Przypadek 1. Różnice wynikające z widoczności typu danych
Nowy #include
może wstawić do pliku źródłowego nową definicję typu (np. struct foo
). W takich przypadkach opis w odpowiednim pliku .symtypes
zmieni się z pustego structure_type foo { }
na pełną definicję.
Będzie to miało wpływ na wszystkie sumy kontrolne CRC wszystkich symboli w pliku .symtypes
, których opisy zależą bezpośrednio lub pośrednio od definicji typu.
Na przykład dodanie do pliku include/linux/device.h
w jądrze tego wiersza powoduje niezgodność sumy kontrolnej CRC, z której jedna dotyczy module_layout()
:
#include <linux/fwnode.h>
Porównanie module/version.symtypes
tego symbolu ujawnia następujące różnice:
$ 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 ] ; }
...
Jeśli jądro GKI ma pełną definicję typu, ale Twoje jądro jej nie zawiera (jest to bardzo mało prawdopodobne), scal najnowsze jądro wspólne Androida z Twoim jądrem, aby używać najnowszej bazy jądra GKI.
W większości przypadków w jądrze GKI brakuje pełnej definicji typu w .symtypes
, ale Twoje jądro ma ją dzięki dodatkowym dyrektywom #include
.
Rozdzielczość na Androidzie 16 i nowszym
Upewnij się, że plik źródłowy, którego dotyczy problem, zawiera nagłówek stabilizacji KABI Androida:
#include <linux/android_kabi.h>
W przypadku każdego typu, którego dotyczy problem, dodaj ANDROID_KABI_DECLONLY(name);
w zakresie globalnym do pliku źródłowego, którego dotyczy problem.
Jeśli na przykład różnica symtypes
wyglądała tak:
--- 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)
W takim przypadku problem polega na tym, że struct ubuf_info
ma teraz pełną definicję w symtypes
. Rozwiązaniem jest dodanie wiersza do pliku drivers/android/vendor_hooks.c
:
ANDROID_KABI_DECLONLY(ubuf_info);
To polecenie nakazuje gendwarfksyms
traktować nazwany typ jako niezdefiniowany w pliku.
Bardziej złożona możliwość to sytuacja, w której nowy #include
znajduje się w pliku nagłówkowym. W takim przypadku może być konieczne rozprowadzenie różnych zestawów wywołań makr ANDROID_KABI_DECLONLY
w plikach źródłowych, które pośrednio pobierają dodatkowe definicje typów, ponieważ niektóre z nich mogły już zawierać niektóre definicje typów.
Aby zwiększyć czytelność, umieszczaj takie wywołania makr na początku pliku źródłowego.
Rozdzielczość na Androidzie 15 i starszych
Często wystarczy ukryć nowe #include
przed genksyms
.
#ifndef __GENKSYMS__
#include <linux/fwnode.h>
#endif
W przeciwnym razie, aby zidentyfikować #include
, które powoduje różnicę, wykonaj te czynności:
Otwórz plik nagłówkowy, który definiuje symbol lub typ danych, w którym występuje ta różnica. Na przykład edytuj kolumnę
include/linux/fwnode.h
w wierszustruct fwnode_handle
.U góry pliku nagłówkowego dodaj ten kod:
#ifdef CRC_CATCH #error "Included from here" #endif
W pliku
.c
modułu, w którym występuje niezgodność CRC, dodaj ten wiersz jako pierwszy przed wierszami#include
:#define CRC_CATCH 1
Skompiluj moduł. Wynikowy błąd czasu kompilacji pokazuje łańcuch plików nagłówkowych
#include
, który doprowadził do tej niezgodności CRC. Na przykład: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"
Jeden z linków w tym łańcuchu
#include
jest spowodowany zmianą wprowadzoną w jądrze, której brakuje w jądrze GKI.
Przypadek 2. Różnice wynikające ze zmian typu danych
Jeśli niezgodność CRC w przypadku symbolu lub typu danych nie wynika z różnicy w widoczności, oznacza to, że w samym typie danych zaszły rzeczywiste zmiany (dodania, usunięcia lub modyfikacje).
Na przykład wprowadzenie w jądrze poniższej zmiany powoduje kilka niezgodności CRC, ponieważ wiele symboli jest pośrednio dotkniętych tego typu zmianą:
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);
Jeden błąd CRC dotyczy pliku devm_of_platform_populate()
.
Jeśli porównasz pliki .symtypes
dla tego symbolu, mogą one wyglądać tak:
$ 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 * ) ; ...
Aby określić zmieniony typ, wykonaj te czynności:
Znajdź definicję symbolu w kodzie źródłowym (zwykle w plikach
.h
).- Aby sprawdzić różnice w symbolach między Twoim jądrem a jądrem GKI, znajdź zatwierdzenie, uruchamiając to polecenie:
git blame
- W przypadku usuniętych symboli (gdy symbol jest usuwany w jednym drzewie i chcesz go też usunąć w drugim) musisz znaleźć zmianę, która spowodowała usunięcie wiersza. W drzewie, z którego usunięto wiersz, użyj tego polecenia:
git log -S "copy paste of deleted line/word" -- <file where it was deleted>
Przejrzyj zwróconą listę zatwierdzeń, aby znaleźć zmianę lub usunięcie. Pierwsze zatwierdzenie jest prawdopodobnie tym, którego szukasz. Jeśli nie, przewiń listę, aż znajdziesz zatwierdzenie.
Po zidentyfikowaniu zatwierdzenia cofnij je w jądrze lub zaktualizuj, aby pominąć zmianę CRC, a następnie prześlij do ACK i połącz. Każda pozostała zmiana ABI będzie musiała zostać sprawdzona pod kątem bezpieczeństwa i w razie potrzeby można będzie zarejestrować dozwoloną zmianę.
Prefer to consume existing padding
Niektóre struktury w GKI są uzupełniane, aby można było je rozszerzać bez naruszania istniejących modułów dostawcy. Jeśli na przykład zatwierdzenie w repozytorium nadrzędnym dodaje element do takiej struktury, można zmienić ją tak, aby zamiast tego wykorzystywała część dopełnienia. Ta zmiana jest następnie ukrywana w obliczeniach CRC.
Ustandaryzowane, samodokumentujące się makro ANDROID_KABI_RESERVE
rezerwuje przestrzeń o wartości u64
(wyrównaną). Jest używany zamiast deklaracji członka.
Na przykład:
struct data {
u64 handle;
ANDROID_KABI_RESERVE(1);
ANDROID_KABI_RESERVE(2);
};
Wypełnienie można wykorzystać bez wpływu na sumy kontrolne symboli za pomocą znaku ANDROID_KABI_USE
(lub ANDROID_KABI_USE2
lub innych wariantów, które mogą być zdefiniowane).
Element sekret
jest dostępny tak, jakby był zadeklarowany bezpośrednio, ale makro rozwija się w anonimowy element unii zawierający sekret
oraz elementy używane przez gendwarfksyms
do zachowania stabilności typu symbolu.
struct data {
u64 handle;
ANDROID_KABI_USE(1, void *sekret);
ANDROID_KABI_RESERVE(2);
};
Rozdzielczość na Androidzie 16 i nowszym
Wartości CRC są obliczane przez gendwarfksyms
, który korzysta z informacji debugowania DWARF, dzięki czemu obsługuje typy C i Rust. Rozdzielczość zależy od rodzaju zmiany. Oto kilka przykładów.
Nowe lub zmodyfikowane wyliczenia
Czasami dodawane są nowe elementy wyliczeniowe, a czasem dotyczy to też wartości elementu wyliczeniowego MAX
lub podobnego. Te zmiany są bezpieczne, jeśli nie „wydostają się” z GKI lub jeśli mamy pewność, że moduły dostawcy nie mogą się nimi zajmować.
Na przykład:
enum outcome {
SUCCESS,
FAILURE,
RETRY,
+ TRY_HARDER,
OUTCOME_LIMIT
};
Dodanie znaku TRY_HARDER
i zmiana na OUTCOME_LIMIT
mogą być ukryte przed obliczeniami CRC za pomocą wywołań makr w zakresie globalnym:
ANDROID_KABI_ENUMERATOR_IGNORE(outcome, TRY_HARDER);
ANDROID_KABI_ENUMERATOR_VALUE(outcome, OUTCOME_LIMIT, 3);
Aby zwiększyć czytelność, umieść je tuż po definicji enum
.
Nowy element konstrukcyjny zajmujący istniejący otwór
Ze względu na wyrównanie między urgent
a scratch
będą występować nieużywane bajty.
void *data;
bool urgent;
+ bool retry;
void *scratch;
Dodanie znaku retry
nie ma wpływu na istniejące przesunięcie elementu ani na rozmiar struktury. Może to jednak wpłynąć na sumy kontrolne symboli lub reprezentację ABI albo na obie te wartości.
Spowoduje to ukrycie go w obliczeniach CRC:
void *data;
bool urgent;
+ ANDROID_KABI_IGNORE(1, bool retry);
void *scratch_space;
Element retry
jest dostępny tak, jakby był zadeklarowany bezpośrednio, ale makro rozwija się w anonimowy element unii zawierający retry
oraz elementy używane przez gendwarfksyms
do zachowania stabilności typu symbolu.
Rozszerzenie struktury o nowych członków
Elementy są czasami dodawane na końcu struktury. Nie ma to wpływu na przesunięcia istniejących elementów ani na obecnych użytkowników struktury, którzy mają do niej dostęp tylko za pomocą wskaźnika. Rozmiar struktury wpływa na jej CRC, a zmiany w tym zakresie można pominąć, dodając dodatkowe wywołanie makra w zakresie globalnym, jak poniżej:
struct data {
u64 handle;
u64 counter;
ANDROID_KABI_IGNORE(1, void *sekret);
};
ANDROID_KABI_BYTE_SIZE(data, 16);
Aby zwiększyć czytelność, umieść go tuż po definicji struct
.
Wszystkie inne zmiany typu lub typu symbolu
Bardzo rzadko zdarzają się zmiany, które nie należą do żadnej z wymienionych wcześniej kategorii. Powodują one zmiany CRC, których nie można pominąć za pomocą poprzednich makr.
W takich przypadkach pierwotny symtypes
opis typu lub symbolu można uzupełnić wywołaniem ANDROID_KABI_TYPE_STRING
w zakresie globalnym.
struct data {
/* extensive changes */
};
ANDROID_KABI_TYPE_STRING("s#data", "original s#data symtypes definition");
Aby zwiększyć czytelność, umieść go tuż za definicją typu lub symbolu.
Rozdzielczość na Androidzie 15 i starszych
Zmiany typu i symbolu muszą być ukryte przed genksyms
. Można to zrobić, kontrolując przetwarzanie wstępne za pomocą __GENKSYMS__
.
W ten sposób można wyrazić dowolne przekształcenia kodu.
Na przykład, aby ukryć nowy element zajmujący otwór w istniejącej konstrukcji:
struct parcel {
void *data;
bool urgent;
#ifndef __GENKSYMS__
bool retry;
#endif
void *scratch_space;
};