# cat blog/abi-jadra-linuxa-narzedzia-i-diagnostyka.txt

title = ABI Linux Kernel — narzędzia i diagnostyka

date = 2026-06-01

ABI Linux Kernel — narzędzia i diagnostyka

ABI jądra Linuxa — narzędzia i diagnostyka

Poprzedni artykuł wyjaśnił, czym są trzy kontrakty ABI jądra Linuxa.
Ten wyjaśnia, jak je zobaczyć, zbadać i zdiagnozować — w praktyce, na działającym systemie.


Zanim zaczniesz

Każde z trzech ABI ma swój własny zestaw narzędzi. Artykuł jest podzielony według tej samej struktury co poprzedni:

ABI #1 — Userspace ABI  →  strace, ausyscall, /proc
ABI #2 — kABI           →  modinfo, nm, pahole
ABI #3 — Dystrybucyjne  →  dpkg, dkms, diff Modules.symvers

Dla każdego narzędzia: co robi, kluczowe flagi, przykład wyjścia, praktyczny scenariusz.


ABI #1 — Userspace ABI: co wywołuje twój program?

strace — podgląd syscalli w czasie rzeczywistym

strace przechwytuje wywołania systemowe procesu przez mechanizm jądra ptrace. Jest bezpośrednim oknem na Userspace ABI — widać dokładnie, jakich numerów syscalli używa program, z jakimi argumentami i co dostaje w odpowiedzi.

Instalacja:

# Debian/Ubuntu
apt install strace

# Arch/CachyOS
pacman -S strace

Kluczowe flagi:

Flaga Opis
-e trace=syscall1,syscall2 Filtruj tylko wybrane syscalle
-e trace=%file Wszystkie syscalle operujące na plikach
-e trace=%network Wszystkie syscalle sieciowe
-c Statystyki zbiorcze zamiast live output
-T Czas wykonania każdego syscalla
-f Śledź procesy potomne (fork/exec)
-p PID Dołącz do działającego procesu
-o plik Zapisz output do pliku

Przykład 1 — diagnoza Permission denied:

$ strace -e trace=%file ls /root 2>&1 | head -20
execve("/usr/bin/ls", ["ls", "/root"], 0x... /* 23 vars */) = 0
openat(AT_FDCWD, "/root", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = -1 EACCES (Permission denied)
write(2, "ls: cannot open directory '/root'", 33) = 33

Widoczna sekwencja: openatEACCES. Nie ma żadnych wątpliwości, który syscall zwrócił błąd i z jakim kodem. Bez strace wiadomo tylko, że coś się nie powiodło.

Przykład 2 — statystyki syscalli procesu:

$ strace -c -f curl -s https://example.com > /dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 38.21    0.021445          14      1500           read
 22.14    0.012431          10      1200           write
 18.05    0.010132          42       240        12 openat
  9.11    0.005118           8       620           mmap
  ...
------ ----------- ----------- --------- --------- ----------------
100.00    0.056126                  5120        18 total

Przydatne przy optymalizacji — widać, które syscalle dominują i ile czasu zajmują.

Przykład 3 — weryfikacja ABI przy migracji:

# Sprawdź, czy program używa przestarzałego syscalla 'open' (nr 2)
# zamiast nowszego 'openat' (nr 257) — istotne przy seccomp whitelisting
$ strace -e trace=open,openat ./moj_program 2>&1 | grep -c '^open('
0
$ strace -e trace=open,openat ./moj_program 2>&1 | grep -c '^openat('
47

ausyscall — mapa syscalli dla danej architektury

ausyscall tłumaczy nazwy syscalli na numery i odwrotnie, z pełną świadomością architektury procesora. To narzędzie z pakietu audit, niezastąpione przy pisaniu reguł seccomp, auditd oraz przy analizie logów z systemów heterogenicznych.

Instalacja:

# Debian/Ubuntu
apt install auditd

# Arch/CachyOS
pacman -S audit

Kluczowe użycia:

# Numer syscalla po nazwie (bieżąca architektura)
$ ausyscall openat
257

# Nazwa po numerze
$ ausyscall 1
write

# Wyraźna architektura — kluczowe na systemach biarch (x86_64 z 32-bit compat)
$ ausyscall x86_64 openat
257
$ ausyscall i386 openat
295

# Zrzut całej tabeli syscalli dla architektury
$ ausyscall x86_64 --dump | head -10
0   read
1   write
2   open
3   close
4   stat
5   fstat
6   lstat
7   poll
8   lseek
9   mmap

# Wyszukiwanie substring (domyślne)
$ ausyscall chown
chown       92
lchown      94
fchown      93
fchownat    260

# Szukanie dokładne
$ ausyscall --exact chown
chown       92

Praktyczny scenariusz — reguły auditd na systemie biarch:

Na x86_64 z obsługą 32-bitowych binariów ten sam syscall ma dwa różne numery. Bez weryfikacji reguła auditd może pominąć połowę zdarzeń:

$ ausyscall i386 open
5
$ ausyscall x86_64 open
2

# Prawidłowe reguły auditd — muszą pokryć obie architektury
-a always,exit -F arch=b32 -S open -F exit=-EPERM -k fail-open
-a always,exit -F arch=b64 -S open -F exit=-EPERM -k fail-open

/proc/PID/syscall — co robi proces w tej chwili?

Plik /proc/PID/syscall zawiera numer aktualnie wykonanego syscalla oraz wartości rejestrów. Dostępny bez żadnych dodatkowych narzędzi.

# Który syscall blokuje proces sshd?
$ cat /proc/$(pidof sshd)/syscall
202 0x7f3a1c002840 0x0 0x0 0x0 0x0 0x0 0x7ffd2b3e4290 0x7f3a1c44c270

# 202 to epoll_wait — sshd czeka na zdarzenia sieciowe
$ ausyscall 202
epoll_wait

Kolumny: numer_syscalla arg0 arg1 arg2 arg3 arg4 arg5 stack_pointer program_counter

# Szybki przegląd wszystkich procesów i ich aktualnych syscalli
$ for pid in /proc/[0-9]*/syscall; do
    p=${pid%/syscall}; p=${p#/proc/}
    sc=$(cat "$pid" 2>/dev/null | awk '{print $1}')
    [ -n "$sc" ] && [ "$sc" != "running" ] && \
        printf "PID %-6s %-20s %s\n" "$p" "$(ps -p $p -o comm= 2>/dev/null)" \
        "$(ausyscall $sc 2>/dev/null || echo $sc)"
  done

ABI #2 — kABI: czy moduł jest zgodny z jądrem?

modinfo — paszport modułu jądra

modinfo odczytuje metadane z pliku .ko bez jego ładowania. Kluczowe pole dla kABI to vermagic — ciąg znaków, który musi dokładnie zgadzać się z wartością w działającym jądrze.

# Pełne informacje o module
$ modinfo nvidia
filename:       /lib/modules/6.12.0-1-cachyos/kernel/drivers/video/nvidia.ko
version:        560.35.03
license:        NVIDIA
description:    NVIDIA Linux x64 Display Driver Kernel Module
author:         NVIDIA Corporation
srcversion:     A1B2C3D4E5F6A7B8C9D0E1F
depends:        drm
vermagic:       6.12.0-1-cachyos SMP preempt mod_unload

Pole vermagic — anatomia:

6.12.0-1-cachyos   SMP   preempt   mod_unload
     ^               ^       ^          ^
   wersja          tryb    scheduler  możliwość
   jądra           SMP    (opcja)    odładowania

Jeśli którakolwiek część nie pasuje — insmod odmówi załadowania:

$ insmod nvidia.ko
insmod: ERROR: could not insert 'nvidia': Invalid module format

$ dmesg | tail -3
[12345.678] nvidia: disagrees about version of symbol module_layout
[12345.679] nvidia: version magic '6.11.0-1-cachyos SMP preempt mod_unload'
[12345.680] nvidia: should be    '6.12.0-1-cachyos SMP preempt mod_unload'

Inne przydatne pola modinfo:

# Tylko vermagic
$ modinfo -F vermagic nvidia
6.12.0-1-cachyos SMP preempt mod_unload

# Tylko zależności
$ modinfo -F depends zfs
spl

# Informacje dla konkretnego jądra (nie bieżącego)
$ modinfo -k 6.11.0-1-cachyos -F vermagic nvidia
6.11.0-1-cachyos SMP preempt mod_unload

# Porównanie vermagic modułu z bieżącym jądrem
$ diff \
    <(modinfo -F vermagic nvidia) \
    <(uname -r | sed 's/$/ SMP preempt mod_unload/')

Skrypt diagnostyczny — sprawdź wszystkie załadowane moduły:

#!/bin/bash
# Wykryj moduły, których vermagic nie pasuje do bieżącego jądra
KERNEL=$(uname -r)
echo "Bieżące jądro: $KERNEL"
echo "---"
lsmod | awk 'NR>1 {print $1}' | while read mod; do
    vm=$(modinfo -F vermagic "$mod" 2>/dev/null | awk '{print $1}')
    if [ -n "$vm" ] && [ "$vm" != "$KERNEL" ]; then
        echo "NIEZGODNY: $mod (vermagic: $vm)"
    fi
done

nm -u i modprobe --dump-modversions — symbole i ich CRC

Każdy eksportowany symbol jądra ma CRC — hash opisujący jego sygnaturę. Moduł przechowuje listę oczekiwanych CRC. Jeśli jądro ma inny CRC dla danego symbolu, ładowanie jest blokowane (gdy CONFIG_MODVERSIONS=y).

nm -u — symbole wymagane od jądra:

# Jakich symboli jądra oczekuje moduł?
$ nm -u zfs.ko | head -15
                 U __alloc_pages
                 U alloc_workqueue
                 U blkdev_get_by_dev
                 U dentry_open
                 U filp_close
                 U kmalloc_caches
                 U printk
                 U schedule

U oznacza symbol niezdefiniowany w module — musi być dostarczony przez jądro. Jeśli jądro nie eksportuje któregoś z tych symboli (lub eksportuje z innym CRC), moduł się nie załaduje.

modprobe --dump-modversions — CRC symboli w module:

$ modprobe --dump-modversions /lib/modules/$(uname -r)/kernel/net/core/netfilter.ko | head -5
0x3b4e6f2a      nf_register_net_hooks
0x1c8d4a91      nf_unregister_net_hooks
0x7f2e9b3c      nf_hook_slow
0x4a1d8e05      netfilter_net_init

Porównanie CRC między dwoma wersjami jądra — praktyczny sposób oceny, czy kABI zostało złamane:

# Wyodrębnij symbole z Module.symvers obu wersji
$ diff \
    <(awk '{print $2, $1}' /usr/src/linux-headers-6.11.0-1-cachyos/Module.symvers | sort) \
    <(awk '{print $2, $1}' /usr/src/linux-headers-6.12.0-1-cachyos/Module.symvers | sort) \
  | grep '^[<>]' | head -20

< kmalloc 0x1a2b3c4d
> kmalloc 0x9f8e7d6c    ← CRC zmienione — kABI złamane dla tego symbolu
< sk_buff_head_init 0x5e6f7a8b
> sk_buff_head_init 0x5e6f7a8b   (brak w diff = CRC bez zmian)

Jeśli diff zwraca wiersze z różnymi CRC dla tego samego symbolu — moduły skompilowane pod starszą wersję nie załadują się na nową.


pahole — układ struktur danych w jądrze

pahole (część pakietu dwarves) odczytuje informacje debugowe DWARF i BTF, pokazując rzeczywisty układ struktur danych w pamięci. To najgłębsze narzędzie diagnostyczne kABI — pozwala zobaczyć, dlaczego CRC symbolu się zmieniło.

Instalacja:

# Debian/Ubuntu
apt install dwarves

# Arch/CachyOS
pacman -S dwarves

Wymaganie: jądro skompilowane z CONFIG_DEBUG_INFO_BTF=y. Sprawdzenie:

$ ls /sys/kernel/btf/vmlinux && echo "BTF dostępne" || echo "BTF niedostępne"
BTF dostępne

Przykład 1 — układ struktury sk_buff (bufor sieciowy):

$ pahole -C sk_buff /sys/kernel/btf/vmlinux | head -40
struct sk_buff {
        union {
                struct {
                        struct sk_buff *    next;                 /*     0     8 */
                        struct sk_buff *    prev;                 /*     8     8 */
                        union {
                                struct net_device * dev;          /*    16     8 */
                                long unsigned int   dev_scratch;  /*    16     8 */
                        };                                        /*    16     8 */
                };                                                /*     0    24 */
                ...
        };
        /* --- cacheline 1 boundary (64 bytes) --- */
        __u32              len;                                   /*    64     4 */
        __u32              data_len;                              /*    68     4 */
        __u16              mac_len;                               /*    72     2 */

Widoczne: każde pole, jego offset w bajtach i rozmiar. Jeśli między wersjami jądra zmieni się offset len — moduł operujący na sk_buff z hardkodowanym offsetem przestanie działać poprawnie.

Przykład 2 — wykrycie zmiany ABI między wersjami:

# Na jądrze 6.11: pole 'mark' w sk_buff jest na offsecie 224
$ pahole -C sk_buff /sys/kernel/btf/vmlinux | grep ' mark'
        __u32              mark;           /*   224     4 */

# Na jądrze 6.12: nowe pole zostało wstawione wcześniej, 'mark' przesunął się
$ pahole -C sk_buff /path/to/vmlinux-6.12 | grep ' mark'
        __u32              mark;           /*   228     4 */

Przesunięcie o 4 bajty = zmiana kABI dla wszystkich modułów czytających to pole bezpośrednio. To właśnie wykrywa zmiana CRC w Module.symvers.

Przykład 3 — lista wszystkich struktur zawierających dane pole:

# Które struktury mają pole 'pid_t pid'?
$ pahole -C '' /sys/kernel/btf/vmlinux | grep -B5 'pid_t.*pid;' | grep '^struct'
struct task_struct {
struct signal_struct {
struct perf_event {

Przykład 4 — padding i wyrównanie (optymalizacja):

# Ile miejsca marnuje dana struktura na padding?
$ pahole --show_holes /sys/kernel/btf/vmlinux -C inode | tail -5
        /* XXX 4 bytes hole, try to pack */
        ...
        /* size: 608, cachelines: 10, members: 47 */
        /* sum members: 584, holes: 1, sum holes: 4 */
        /* padding: 20 */

ABI #3 — Dystrybucyjne ABI: pakiety, numery, DKMS

dpkg i apt-cache — odczyt numeru ABI z nazwy pakietu

Numer ABI dystrybucji jest zakodowany w nazwie pakietu jądra. Nie ma osobnego polecenia do jego odczytu — widać go bezpośrednio.

# Lista zainstalowanych pakietów jądra
$ dpkg -l 'linux-image-*' | grep '^ii'
ii  linux-image-6.1.0-21-amd64   6.1.99-1    amd64   Linux 6.1 for 64-bit PCs
ii  linux-image-6.1.0-22-amd64   6.1.106-3   amd64   Linux 6.1 for 64-bit PCs
#                           ^^
#                           numer ABI dystrybucji

Dwa pakiety obok siebie to nie błąd — to wynik inkrementacji ABI. Pakiet linux-image-6.1.0-21-amd64 i linux-image-6.1.0-22-amd64 to dla apt dwa różne pakiety, nie wersje tego samego.

# Sprawdź dostępne wersje w repozytorium
$ apt-cache policy linux-image-amd64
linux-image-amd64:
  Installed: 6.1.99-1
  Candidate: 6.1.106-3
  Version table:
     6.1.106-3 500
        500 http://deb.debian.org/debian bookworm/main amd64 Packages
 *** 6.1.99-1 100
        100 /var/lib/dpkg/status

# Która wersja pakietu odpowiada któremu numerowi ABI?
$ apt-cache show linux-image-6.1.0-22-amd64 | grep -E 'Version|Provides'
Version: 6.1.106-3
Provides: linux-image, linux-image-6.1.0-22-amd64

dkms status — stan modułów dynamicznych

DKMS (Dynamic Kernel Module Support) automatycznie przebudowuje moduły zewnętrzne po każdej zmianie ABI. dkms status pokazuje, które moduły są gotowe dla którego jądra.

# Stan wszystkich modułów DKMS
$ dkms status
nvidia/560.35.03, 6.12.0-1-cachyos, x86_64: installed
zfs/2.2.6, 6.12.0-1-cachyos, x86_64: installed
nvidia/560.35.03, 6.11.0-1-cachyos, x86_64: installed

# Stan po aktualizacji jądra — nowe jądro, moduł jeszcze nieprzebudowany
$ dkms status
nvidia/560.35.03, 6.12.1-2-cachyos, x86_64: added   ← dodany, ale nie zbudowany
nvidia/560.35.03, 6.12.0-1-cachyos, x86_64: installed

Ręczna przebudowa dla konkretnego jądra:

# Przebuduj wszystkie moduły DKMS dla bieżącego jądra
$ dkms autoinstall -k $(uname -r)

# Przebuduj konkretny moduł dla konkretnego jądra
$ dkms build nvidia/560.35.03 -k 6.12.1-2-cachyos
$ dkms install nvidia/560.35.03 -k 6.12.1-2-cachyos

# Diagnoza gdy build się nie udał
$ dkms build nvidia/560.35.03 -k 6.12.1-2-cachyos 2>&1 | tail -20
# Szczegółowy log budowania
$ cat /var/lib/dkms/nvidia/560.35.03/build/make.log | tail -30

Skrypt weryfikacyjny — które moduły DKMS nie są zbudowane dla bieżącego jądra:

#!/bin/bash
KERNEL=$(uname -r)
echo "Jądro: $KERNEL"
echo "Moduły DKMS bez instalacji dla tego jądra:"
dkms status | grep -v ", ${KERNEL}," | grep -v "^$" | while read line; do
    echo "  → $line"
done

diff Modules.symvers — które symbole zmieniły CRC?

Module.symvers to plik generowany podczas każdego buildu jądra. Zawiera listę wszystkich eksportowanych symboli z ich CRC. Dystrybucje porównują ten plik między buildami — zmiana CRC oznacza złamanie kABI i inkrementację numeru ABI.

Lokalizacja pliku:

# Dla zainstalowanych nagłówków jądra
ls /usr/src/linux-headers-$(uname -r)/Module.symvers

# Format pliku
$ head -5 /usr/src/linux-headers-$(uname -r)/Module.symvers
0x1a2b3c4d  kmalloc vmlinux EXPORT_SYMBOL   GPL and additional rights
0x5e6f7a8b  schedule    vmlinux EXPORT_SYMBOL_GPL   GPL
0x9f1e2d3c  printk  vmlinux EXPORT_SYMBOL   GPL and additional rights

Kolumny: CRC nazwa_symbolu skąd_pochodzi typ_eksportu licencja

Porównanie dwóch wersji jądra:

# Które symbole zmieniły CRC? (złamanie kABI)
diff \
    <(awk '{print $2, $1}' /usr/src/linux-headers-6.11.0-1-cachyos/Module.symvers | sort) \
    <(awk '{print $2, $1}' /usr/src/linux-headers-6.12.0-1-cachyos/Module.symvers | sort) \
| grep '^[<>]' \
| awk '{print $2}' \
| sort | uniq -d
# Wyświetla nazwy symboli, których CRC zmieniło się między wersjami

Które symbole zostały dodane lub usunięte:

# Symbole obecne w 6.11, nieobecne w 6.12 (usunięte lub przeniesione)
comm -23 \
    <(awk '{print $2}' /usr/src/linux-headers-6.11.0-1-cachyos/Module.symvers | sort) \
    <(awk '{print $2}' /usr/src/linux-headers-6.12.0-1-cachyos/Module.symvers | sort)

Bonus — bpftool btf dump

Nowsze narzędzie, coraz częściej używane przy diagnostyce kABI i modułów BPF. Wymaga jądra z CONFIG_DEBUG_INFO_BTF=y.

# Instalacja
apt install linux-tools-$(uname -r)   # Debian/Ubuntu
pacman -S bpf                          # Arch/CachyOS

# Wyeksportuj pełny nagłówek C z typami jądra
$ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
# Wynikowy plik ma ~300 000 linii — zawiera WSZYSTKIE typy danych jądra

# Pokaż konkretną strukturę
$ bpftool btf dump file /sys/kernel/btf/vmlinux format raw | grep -A5 'sk_buff'

# Lista wszystkich modułów z BTF
$ ls /sys/kernel/btf/
vmlinux  nvidia  zfs  drm  ...

bpftool btf dump i pahole operują na tych samych danych BTF, ale z różnych perspektyw — pahole skupia się na układzie struktury (offsety, padding), bpftool na całości systemu typów.


Podsumowanie — tabela narzędzi

Narzędzie ABI Co bada Pakiet
strace Userspace Syscalle live, argumenty, kody błędów strace
ausyscall Userspace Mapowanie nazw/numerów syscalli per architektura audit
/proc/PID/syscall Userspace Aktywny syscall procesu w tej chwili wbudowane
modinfo kABI vermagic, zależności, srcversion kmod
nm -u kABI Symbole wymagane od jądra przez moduł binutils
modprobe --dump-modversions kABI CRC symboli zakodowane w pliku .ko kmod
pahole kABI Układ struktur DWARF/BTF, offsety pól dwarves
dpkg -l / apt-cache Dystrybucyjne Numer ABI w nazwie pakietu dpkg / apt
dkms status Dystrybucyjne Stan modułów DKMS per wersja jądra dkms
diff Module.symvers Dystrybucyjne Zmiany CRC symboli między buildami ręczne
bpftool btf dump kABI / BTF Pełny system typów jądra, nagłówki C bpf-tools

Gdzie szukać więcej

← back

[ulther@blog ~]$ cat comments/abi-jadra-linuxa-narzedzia-i-diagnostyka.log

// brak komentarzy

[ulther@blog ~]$ comment --post abi-jadra-linuxa-narzedzia-i-diagnostyka