Aby ustabilizować interfejs ABI wewnątrz jądra w jądrach Androida, możesz użyć narzędzia do monitorowania interfejsu binarnego aplikacji (ABI), dostępnego w Androidzie 11 i nowszych. Narzędzie zbiera i porównuje reprezentacje ABI z dotychczasowych plików binarnych jądra (vmlinux
+ moduły GKI). Te reprezentacje ABI to pliki .stg
i listy symboli. Interfejs, w którym reprezentacja udostępnia widok, nazywa się interfejsem modułu jądra (KMI). Za pomocą tych narzędzi możesz śledzić i ograniczać zmiany w KMI.
Narzędzia do monitorowania ABI są opracowywane w AOSP i korzystają z STG (lub libabigail
w Androidzie 13 i starszych), aby generować i porównywać reprezentacje.
Na tej stronie opisaliśmy narzędzia, proces zbierania i analizowania reprezentacji ABI oraz wykorzystanie 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 ABI jądra wymaga wykonania kilku kroków, z których większość można zautomatyzować:
- Utwórz jądro i jego reprezentacja ABI.
- Analizować różnice w ABI między kompilacją a plikiem referencyjnym.
- Zaktualizuj reprezentację ABI (jeśli to konieczne).
- Praca z listami symboli.
Podane niżej instrukcje dotyczą dowolnego jądra, które możesz skompilować za pomocą obsługiwanego zestawu narzędzi (np. wstępnie skompilowanego zestawu narzędzi Clang). repo manifests
są dostępne dla wszystkich gałęzi jądra Androida i dla kilku jąder przeznaczonych do konkretnych urządzeń. Umożliwiają one używanie prawidłowej łańcucha narzędzi podczas kompilowania dystrybucji jądra na potrzeby analizy.
Listy symboli
KMI nie obejmuje wszystkich symboli w jądrze ani nawet wszystkich 30 tys. wyeksportowanych symboli. Zamiast tego symbole, których mogą używać moduły dostawców, są wyraźnie wymienione w zbiorze plików listy symboli, który jest publicznie dostępny w drzewie jądra (gki/{ARCH}/symbols/*
lub android/abi_gki_{ARCH}_*
w Androidzie 15 i starszych). Zbiór wszystkich symboli we wszystkich plikach listy symboli definiuje zbiór symboli KMI, które są utrzymywane jako stabilne. Przykładowy plik z listą symboli to gki/aarch64/symbols/db845c
, który zawiera symbole wymagane w przypadku DragonBoard 845c.
Do KMI zalicza się tylko symbole wymienione na liście symboli oraz powiązane z nimi struktury i definicje. Jeśli brakuje Ci potrzebnych symboli, możesz wprowadzić zmiany w listach symboli. Gdy nowe interfejsy zostaną dodane do listy symboli i staną się częścią opisu KMI, będą traktowane jako stabilne. Nie można ich usuwać z listy symboli ani modyfikować po zamrożeniu gałęzi.
Każda gałąź jądra KMI w ramach wspólnego jądra Androida (ACK) ma własny zestaw list symboli. Nie podejmuje się prób zapewnienia stabilności 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ć liczbę interfejsów, które należy monitorować pod kątem stabilności. Dostawcy powinni przesyłać i aktualizować własne listy symboli, aby zapewnić zgodność interfejsów, których używają, z interfejsami ABI. Aby na przykład wyświetlić listę list symboli dla jądra android16-6.12
, przejdź do https://android.googlesource.com/kernel/common/+/refs/heads/android16-6.12/gki/aarch64/symbols
.
Lista symboli zawiera symbole, które są potrzebne w przypadku konkretnego dostawcy lub urządzenia. Pełna lista używana przez narzędzia jest zbiorem 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ć zmian w dotychczasowych interfejsach KMI, ponieważ są one stabilne. Dostawcy mogą jednak dodawać symbole do KMI w dowolnym momencie, o ile nie wpłynie to na stabilność istniejącego ABI. Nowo dodane symbole są utrzymywane w stanie stabilnym od momentu, gdy są one wymienione na liście symboli KMI. Symboli nie należy usuwać z listy jądra, chyba że można potwierdzić, że żadne urządzenie nigdy nie było dostarczane z zależnością od tego symbolu.
Listę symboli KMI dla urządzenia możesz wygenerować, postępując zgodnie z instrukcjami podanymi w artykule Praca z listami symboli. Wielu partnerów przesyła jedną listę symboli na ACK, ale nie jest to rygorystyczne wymaganie. Jeśli ułatwi to konserwację, możesz przesłać kilka list symboli.
Rozszerzenie KMI
Chociaż symbole KMI i powiązane z nimi struktury są utrzymywane jako stabilne (co oznacza, że zmiany, które powodują niestabilność interfejsów w jądrze z zamrożonym KMI, nie mogą być akceptowane), jądro GKI pozostaje otwarte na rozszerzenia, aby urządzenia wysyłane w późniejszej części roku nie musiały definiować wszystkich swoich 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ą być również akceptowane, jeśli nie naruszają KMI.
Informacje o awariach KMI
Kernel ma źródła, na podstawie których tworzone są pliki binarne.
Gałęzie jądra monitorowane pod kątem ABI zawierają reprezentację ABI bieżącego ABI GKI (w postaci pliku .stg
). Po skompilowaniu plików binarnych (vmlinux
, Image
i wszystkich modułów GKI) można wyodrębnić z nich reprezentację ABI. Każda zmiana wprowadzona w pliku źródłowym jądra może wpłynąć na pliki binarne, a w efekcie także na wyodrębnione .stg
. Analizator AbiAnalyzer
porównuje zaakceptowany plik .stg
z plikiem wyodrębnionym z elementów kompilacji i jeśli znajdzie różnicę semakntyczną, ustawi etykietę Lint-1 dla zmiany w Gerrecie.
Rozwiązywanie problemów z interfejsem ABI
Na przykład ten pakiet poprawek 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 zaimplementowaną łatką, 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 ABI wykryte w momencie kompilacji,
Najczęstszą przyczyną błędów jest użycie przez sterownik nowego symbolu z jądra, którego nie ma na żadnej z list symboli.
Jeśli symbol nie znajduje się na liście symboli, musisz najpierw sprawdzić, czy został wyeksportowany za pomocą funkcji EXPORT_SYMBOL_GPL(symbol_name)
, a następnie zaktualizować listę symboli i reprezentację ABI. Na przykład poniższe zmiany wprowadzają do gałęzi android-12-5.10
nową funkcję przyrostowego FS, która obejmuje aktualizację listy symboli i reprezentacji ABI.
- Przykład zmiany funkcji znajduje się w aosp/1345659.
- Przykład listy symboli znajduje się w pliku aosp/1346742.
- Przykład zmiany reprezentacji ABI znajduje się 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.
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 zarówno w jądrze, jak i w ACK (patrz Aktualizowanie reprezentacji ABI). Przykład aktualizacji listy symboli i reprezentacji ABI w pliku ACK znajdziesz w artykule aosp/1367601.
Rozwiązywanie problemów z interfejsem ABI jądra
Możesz rozwiązać problem z naruszeniem ABI jądra, przekształcając kod, aby nie zmieniać ABI lub zaktualizować reprezentację ABI. Aby określić najlepsze podejście w danej sytuacji, użyj poniższej tabeli.
Rysunek 1. Rozwiązanie problemu z naruszeniem ABI
Refaktoryzuj kod, aby uniknąć zmian ABI
Dokładaj wszelkich starań, aby nie modyfikować istniejącego ABI. W wielu przypadkach możesz przerobić kod, aby usunąć zmiany, które wpływają na ABI.
Refaktoryzacja zmian pól struktury. Jeśli zmiana modyfikuje ABI funkcji debugowania, dodaj
#ifdef
wokół pól (w strukturach i odniesieniach do źródła) i upewnij się, żeCONFIG
używany w#ifdef
jest wyłączony w defconfigie produkcyjnym igki_defconfig
. Przykład dodawania konfiguracji debugowania do struktury bez naruszania ABI znajdziesz w tym zestawie poprawek.Refaktoryzacja funkcji, aby nie zmieniać jądra jądra. Jeśli do ACK trzeba dodać nowe funkcje, aby obsługiwać moduły partnera, spróbuj przerobić ABI w ramach zmiany, aby uniknąć modyfikowania ABI jądra. Przykład użycia istniejącego interfejsu ABI jądra w celu dodania dodatkowych funkcji bez zmiany interfejsu ABI jądra: aosp/1312213.
Naprawianie nieprawidłowego ABI w Gerrite na Androida
Jeśli nie doszło do celowego naruszenia ABI jądra, musisz zbadać problem, korzystając z wskazówek podanych przez narzędzie do monitorowania ABI. Najczęstsze przyczyny przerw w działaniu to zmiany struktur danych i powiązane zmiany CRC symboli lub zmiany opcji konfiguracji, które prowadzą do któregokolwiek z wymienionych wyżej. Zacznij od rozwiązania problemów znalezionych przez narzędzie.
Możesz odtworzyć wyniki dotyczące ABI lokalnie. W tym celu zobacz artykuł Tworzenie jądra i jego reprezentacja ABI.
Etykiety Lint-1
Jeśli prześlesz zmiany do gałęzi zawierającej zamrożoną lub sfinalizowaną KMI, zmiany muszą przejść weryfikację AbiAnalyzer
, aby upewnić się, że nie wpłyną one na stabilną ABI w niezgodny sposób. Podczas tego procesu AbiAnalyzer
szuka raportu ABI utworzonego podczas kompilacji (rozszerzona kompilacja, która wykonuje normalną kompilację, a następnie niektóre kroki ekstrakcji i porównywania ABI).
Jeśli AbiAnalyzer
znajdzie niepusty raport, ustawi etykietę Lint-1, a zmianę zablokuje do momentu jej rozwiązania, czyli dopóki zestaw poprawek nie otrzyma etykiety Lint+1.
Aktualizacja ABI jądra
Jeśli modyfikacja ABI jest nieunikniona, musisz zastosować zmiany kodu, reprezentację ABI i listę symboli w ACK. Aby Lint usuwał -1, a nie łamał zgodności z GKI, wykonaj te czynności:
Poczekaj na otrzymanie oceny Code-Review +2 dla zestawu poprawek.
Połącz zmiany kodu z aktualizacją ABI.
Prześlij zmiany kodu ABI do ACK
Aktualizacja ABI ACK zależy od typu wprowadzanej zmiany.
Jeśli zmiana ABI jest związana z funkcją, która wpływa na testy CTS lub VTS, można ją zwykle zastosować w zbieraniu opinii w niezmienionej formie. Przykłady:
- Aby dźwięk działał, musisz mieć zainstalowaną wersję aosp/1289677.
- aosp/1295945 jest wymagany do działania USB.
Jeśli zmiana ABI dotyczy funkcji, którą można udostępnić w wersji ACK, można ją zastosować w wersji ACK w postaci domyślnej. Na przykład te zmiany nie są wymagane do testu CTS ani VTS, ale można je udostępnić w wersji ACK:
- aosp/1250412to zmiana funkcji związanych z cieplnem.
- aosp/1288857
to zmiana
EXPORT_SYMBOL_GPL
.
Jeśli zmiana ABI wprowadza nową funkcję, która nie musi być uwzględniona w ACK, możesz wprowadzić symbole do ACK za pomocą zastępnika zgodnie z opisem w następującej sekcji.
Używanie zaczepów do potwierdzenia odbioru
Stuby muszą być niezbędne tylko w przypadku zmian w rdzeniu jądra, które nie przynoszą korzyści ACK, takich jak zmiany wydajności i poboru mocy. Poniżej znajdziesz listę przykładów szkiców i częściowych elementów w ACK dla GKI.
Element szablonu funkcji izolowania funkcji podstawowych (aosp/1284493). Funkcje w ACK nie są wymagane, ale symbole muszą być obecne w ACK, aby moduły mogły ich używać.
Symbol zastępczy dla modułu dostawcy (aosp/1288860).
Funkcja śledzenia zdarzeń
mm
(aosp/1288454) wybierana tylko dla ABI. Oryginalny pakiet poprawek został wybrany do zaakceptowania, a następnie przycięty tak, aby zawierał tylko zmiany niezbędne do rozwiązania różnic w ABI w przypadkutask_struct
imm_event_count
. Ta poprawka aktualizuje też enumeracjęmm_event_type
, aby zawierała ostateczne elementy.Wybrane zmiany ABI struktury termicznej, które wymagały czegoś więcej niż tylko dodania nowych pól ABI.
Poprawka aosp/1255544 rozwiązała różnice w ABI między jądrem partnera a ACK.
Aktualizacja aosp/1291018rozwiązała problemy z funkcjonalnością, które zostały wykryte podczas testów GKI poprzedniej aktualizacji. Naprawa obejmowała zainicjowanie struktury parametrów czujnika w celu zarejestrowania wielu stref termicznych dla jednego czujnika.
CONFIG_NL80211_TESTMODE
zmiany ABI (aosp/1344321). Ta poprawka wprowadziła niezbędne zmiany struktury dla ABI i zadbała o to, aby dodatkowe pola nie powodowały różnic funkcjonalnych, co umożliwiło partnerom uwzględnienieCONFIG_NL80211_TESTMODE
w jądrach produkcyjnych przy zachowaniu zgodności z GKI.
Wymuszanie KMI w czasie wykonywania
Kernele GKI używają opcji konfiguracji TRIM_UNUSED_KSYMS=y
i UNUSED_KSYMS_WHITELIST=<union
of all symbol lists>
, które ograniczają eksportowane symbole (takie jak 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 niewyeksportowanego symbolu jest odrzucane. To ograniczenie jest egzekwowane w momencie kompilacji, a brakujące wpisy są oznaczane.
Na potrzeby programowania możesz użyć wersji jądra GKI, która nie obejmuje przycinania symboli (co oznacza, że można używać wszystkich symboli wyeksportowanych w standardowy sposób). Aby znaleźć te wersje, poszukaj wersji kernel_debug_aarch64
na stronie ci.android.com.
Egzekwowanie KMI za pomocą wersji modułu
Kernele z obrazu Generic Kernel Image (GKI) korzystają z wersji modułów (CONFIG_MODVERSIONS
) jako dodatkowego sposobu zapewnienia zgodności z KMI w czasie wykonywania. Wersja modułu może powodować błędy niezgodności CRC podczas wczytywania modułu, jeśli oczekiwany identyfikator KMI modułu nie pasuje do identyfikatora vmlinux
KMI. Poniżej znajdziesz przykład typowego błędu, który występuje w czasie 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 ''
Zastosowanie obsługi wersji modułów
Wersje modułów są przydatne z tych powodów:
Wersje modułów rejestrują zmiany w widoczności struktury danych. Jeśli moduły zmieniają nieprzezroczyste struktury danych, czyli struktury danych, które nie są częścią KMI, przestają działać po wprowadzeniu zmian w strukturze.
Weź pod uwagę na przykład pole
fwnode
w plikustruct device
. To pole MUSI być nieprzezroczyste dla modułów, aby nie mogły one wprowadzać zmian w polachdevice->fw_node
ani zakładać, jaki jest ich rozmiar.Jeśli jednak moduł zawiera element
<linux/fwnode.h>
(bezpośrednio lub pośrednio), polefwnode
w elementziestruct device
nie jest już dla niego nieprzezroczyste. Następnie moduł może wprowadzić zmiany w lubdevice->fwnode->ops
.device->fwnode->dev
Ten scenariusz jest problematyczny z kilku powodów:Może to zakłócić założenia, które kod jądra przyjmuje na temat swoich 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. Ponadtostgdiff
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 uchwycić, tylko przeglądając reprezentacje binarne.
Aktualny moduł jest uznawany za niezgodny z KMI, gdy zostanie załadowany w późniejszym terminie przez nowe, niezgodne jądro. Wersje modułów dodają kontrolę w czasie działania, aby uniknąć przypadkowego załadowania modułu, który nie jest zgodny z jądrowym interfejsem modułów. Ten test zapobiega trudnym do debugowania problemom w czasie działania i awariom jądra, które mogą być spowodowane niewykrytą niezgodnością w KMI.
Włączenie wersji modułów zapobiega wszystkim tym problemom.
Sprawdzanie niezgodności CRC bez uruchamiania urządzenia
Pole stgdiff
porównuje i zgłasza rozbieżności CRC między rdzeniami oraz inne różnice w ABI.
Ponadto podczas pełnego kompilowania jądra z włączoną opcją CONFIG_MODVERSIONS
generowany jest plik Module.symvers
w ramach normalnego procesu kompilacji. Ten plik zawiera po jednym wierszu na każdy symbol wyeksportowany przez jądro (vmlinux
) i moduły. Każdy wiersz składa się z wartości CRC, nazwy symbolu, przestrzeni nazw symbolu, nazwy vmlinux
lub modułu, który eksportuje symbol, oraz typu eksportu (np. EXPORT_SYMBOL
lub EXPORT_SYMBOL_GPL
).
Możesz porównać pliki Module.symvers
z wersji GKI i swojej wersji, aby sprawdzić, czy w symbolach wyeksportowanych przez vmlinux
występują różnice CRC. Jeśli wartość CRC dowolnego symbolu wyeksportowanego przez vmlinux
i jest inna, a symbol 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
z jądrem GKI i swojego jądra, możesz porównać wartości CRC dla konkretnego symbolu, wykonują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 dla 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, wykonaj te czynności:
Utwórz jądro GKI i jądro urządzenia, używając 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 wKBUILD_SYMTYPES
w Kleaf.W przypadku Androida 13 i starszych kompiluj jądro GKI oraz jądro urządzenia, dodając do polecenia kompilacji
KBUILD_SYMTYPES=1
, jak w tym przykładzie:KBUILD_SYMTYPES=1 BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh
Gdy używasz parametru
build_abi.sh,
, flagaKBUILD_SYMTYPES=1
jest już domyślnie ustawiona.Znajdź plik
.c
, w którym wyeksportowano symbol z niezgodem CRC, za pomocą tego polecenia:git -C common grep EXPORT_SYMBOL.*module_layout kernel/module/version.c:EXPORT_SYMBOL(module_layout);
Plik
.c
ma odpowiadający mu plik.symtypes
w GKI oraz 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, przy użyciu 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 odnosić się do innych kluczy w tym samym pliku.Klucze takie jak
[s|u|e|t]#foo
odnoszą się do[struct|union|enum|typedef] foo
. Przykład:t#bool typedef _Bool bool
Klucze bez prefiksu
x#
to tylko nazwy symboli. Przykład:find_module s#module * find_module ( const char * )
Porównaj oba pliki i usuń wszystkie różnice.
Najlepiej wygenerować symtypes
z wersją tuż przed problematyczną zmianą, a potem w miejscu problematycznej zmiany. Zapisanie wszystkich plików oznacza, że można je porównać zbiorczo.
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 wybrane pliki.
Przypadek 1. Różnice wynikające z widoczności typu danych
Nowy element #include
może pobrać nową definicję typu (np. struct foo
) do pliku źródłowego. W takich przypadkach jego opis w odpowiednim pliku .symtypes
zmieni się z pustego structure_type foo { }
na pełną definicję.
Spowoduje to zmianę wszystkich 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 wiersza kodu, który powoduje niezgodności CRC, powoduje niezgodności CRC, z których jedna dotyczy module_layout()
:
#include <linux/fwnode.h>
Porównanie module/version.symtypes
dla tego symbolu pokazuje te 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 zawiera pełną definicję typu, a w Twoim jądrze jej brakuje (co jest bardzo mało prawdopodobne), scal z jądrem najnowsze jądro Android Common Kernel, aby używać najnowszej wersji jądra GKI.
W większości przypadków w jądrze GKI brakuje pełnej definicji typu w .symtypes
, ale Twoje jądro ją ma dzięki dodatkowym dyrektywom #include
.
Rozdzielczość na Androidzie 16 lub nowszym
Upewnij się, że dotknięty plik źródłowy 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 odpowiedniego pliku źródłowego.
Jeśli na przykład różnica symtypes
miała postać:
--- 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)
Problem polega na tym, że struct ubuf_info
ma teraz pełną definicję w symtypes
. Rozwiązanie polega na dodaniu wiersza do pliku drivers/android/vendor_hooks.c
:
ANDROID_KABI_DECLONLY(ubuf_info);
To polecenie instruuje gendwarfksyms
, aby traktować typ o nazwie jako niezdefiniowany w pliku.
Bardziej złożona możliwość polega na tym, że nowa #include
znajduje się w pliku nagłówka. W takim przypadku może być konieczne rozproszenie 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ą już zawierać niektóre definicje typów.
Ze względu na czytelność umieszczaj takie wywołania makr na początku pliku źródłowego.
Rozwiązanie dla Androida 15 i starszych
Często wystarczy ukryć nowy element #include
z elementu genksyms
.
#ifndef __GENKSYMS__
#include <linux/fwnode.h>
#endif
Aby zidentyfikować #include
, który powoduje różnicę, wykonaj te czynności:
Otwórz plik nagłówka, który definiuje symbol lub typ danych z tą różnicą. Zmodyfikuj na przykład kolumnę
include/linux/fwnode.h
w wierszustruct fwnode_handle
.U góry pliku nagłówka dodaj ten kod:
#ifdef CRC_CATCH #error "Included from here" #endif
W pliku
.c
modułu, który ma niezgodność CRC, dodaj następujący wiersz jako pierwszy przed dowolnym wierszem#include
.#define CRC_CATCH 1
Zkompiluj moduł. Wynikający z tego błąd w czasie kompilacji pokazuje łańcuch plików nagłówka
#include
, który doprowadził do niezgodności CRC. 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ą w jądrze, która nie występuje w jądrze GKI.
Przypadek 2. Różnice spowodowane zmianami typu danych
Jeśli rozbieżność CRC symbolu lub typu danych nie jest spowodowana różnicą w widoczności, to wynika to z rzeczywistych zmian (dodanych, usuniętych lub zmienionych) w samym typie danych.
Na przykład wprowadzenie tej zmiany w jądrze 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 niespójny 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 znaleźć różnice w symbolach między Twoim jądrem a jądrem GKI, znajdź zatwierdzanie, uruchamiając to polecenie:
git blame
- W przypadku usuniętych symboli (gdy symbol został usunięty z drzewa i chcesz go usunąć z drugiego drzewa) musisz znaleźć zmianę, która spowodowała usunięcie linii. Użyj tego polecenia w drzewie, w którym usunięto linię:
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. Pierwszy commit to prawdopodobnie ten, którego szukasz. Jeśli nie, przejrzyj listę, aż znajdziesz odpowiednią wersję.
Po zidentyfikowaniu zatwierdzenia możesz je cofnąć w jądrze lub zaktualizować, aby pominąć zmianę CRC i przesłać do ACK, a następnie scalić. Każda przerwa w ramach residual ABI musi zostać sprawdzona pod kątem bezpieczeństwa i w razie potrzeby można zarejestrować dozwoloną przerwę.
wolą używać istniejącego wypełnienia;
Niektóre struktury w GKI są wypełnione, aby umożliwić ich rozszerzenie bez zakłócania dotychczasowych modułów dostawców. Jeśli commit na wyższym poziomie (np.) doda element do takiej struktury, można zmienić tę strukturę, aby wykorzystać część wypełnienia. Ta zmiana jest wtedy ukryta przed obliczaniem CRC.
Standardowe makro z automatycznym dokumentowaniem ANDROID_KABI_RESERVE
rezerwuje u64
(wyrównany)spacji. Jest używany zamiast deklaracji członkostwa.
Przykład:
struct data {
u64 handle;
ANDROID_KABI_RESERVE(1);
ANDROID_KABI_RESERVE(2);
};
Wypełnienie może być używane bez wpływu na CRC symboli za pomocą ANDROID_KABI_USE
(lub ANDROID_KABI_USE2
lub innych zdefiniowanych wariantów).
Element sekret
jest dostępny tak, jakby został zadeklarowany bezpośrednio, ale makro rozszerza się do anonimowego elementu typu union zawierającego sekret
oraz elementy używane przez gendwarfksyms
w celu zachowania stabilności symtype.
struct data {
u64 handle;
ANDROID_KABI_USE(1, void *sekret);
ANDROID_KABI_RESERVE(2);
};
Rozdzielczość na Androidzie 16 lub nowszym
Wartości CRC są obliczane przez gendwarfksyms
, który używa informacji debugowania DWARF, a tym samym obsługuje typy C i Rust. Rozwiązanie zależy od rodzaju zmiany. Oto kilka przykładów.
nowe lub zmodyfikowane liczniki,
Czasami dodawane są nowe liczniki, a czasem dotyczy to również wartości licznika MAX
lub podobnej. Te zmiany są bezpieczne, jeśli nie „wydostaną się” poza GKI lub jeśli możemy mieć pewność, że moduły dostawców nie mogą się interesować ich wartościami.
Przykład:
enum outcome {
SUCCESS,
FAILURE,
RETRY,
+ TRY_HARDER,
OUTCOME_LIMIT
};
Dodanie TRY_HARDER
i zmiana na OUTCOME_LIMIT
mogą być ukryte przed obliczeniem CRC przy wywołaniu makr w zakresie globalnym:
ANDROID_KABI_ENUMERATOR_IGNORE(outcome, TRY_HARDER);
ANDROID_KABI_ENUMERATOR_VALUE(outcome, OUTCOME_LIMIT, 3);
Ze względu na czytelność umieść je tuż po definicji enum
.
Nowy element struktury zajmujący istniejący otwór
Ze względu na wyrównanie między urgent
a scratch
będą występować niewykorzystane bajty.
void *data;
bool urgent;
+ bool retry;
void *scratch;
Dodanie retry
nie ma wpływu na przesunięcie ani rozmiar żadnego z dotychczasowych elementów. Może to jednak mieć wpływ na CRC symboli lub reprezentację ABI lub na oba te elementy.
Spowoduje to ukrycie go z obliczenia CRC:
void *data;
bool urgent;
+ ANDROID_KABI_IGNORE(1, bool retry);
void *scratch_space;
Element retry
jest dostępny tak, jakby został zadeklarowany bezpośrednio, ale makro rozszerza się do anonimowego elementu typu union zawierającego retry
oraz elementy używane przez gendwarfksyms
w celu zachowania stabilności symtype.
Rozszerzenie struktury o nowych członków
Czasami elementy są dodawane na końcu struktury. Nie ma to wpływu na przesunięcia dotychczasowych elementów ani na użytkowników struktury, którzy mają do niej dostęp tylko za pomocą wskaźnika. Wielkość struktury wpływa na jej CRC. Zmiany w tym zakresie można pominąć, wywołując dodatkowy makropolecenie w zakresie globalnym w ten sposób:
struct data {
u64 handle;
u64 counter;
ANDROID_KABI_IGNORE(1, void *sekret);
};
ANDROID_KABI_BYTE_SIZE(data, 16);
Ze względu na czytelność umieść tę definicję tuż za definicją struct
.
wszystkie inne zmiany typu lub typu symbolu;
Bardzo rzadko zdarza się, że występują zmiany, które nie pasują do żadnej z poprzednich kategorii, co powoduje zmiany CRC, których nie można stłumić za pomocą poprzednich makr.
W takich przypadkach pierwotny opis symtypes
typu lub symbolu może być podany z wywołaniem ANDROID_KABI_TYPE_STRING
w zakresie globalnym.
struct data {
/* extensive changes */
};
ANDROID_KABI_TYPE_STRING("s#data", "original s#data symtypes definition");
Ze względu na czytelność umieść go tuż za definicją typu lub symbolu.
Rozwiązanie dla Androida 15 i starszych
Zmiany typu i typu symbolu muszą być ukryte przed genksyms
. Można to zrobić, kontrolując wstępną obróbkę za pomocą funkcji __GENKSYMS__
.
W ten sposób można wyrazić dowolne przekształcenia kodu.
Aby na przykład ukryć nowy element, który zajmuje miejsce w istniejącej strukturze:
struct parcel {
void *data;
bool urgent;
#ifndef __GENKSYMS__
bool retry;
#endif
void *scratch_space;
};