Debugginginformatie voor inlined functies

BPF-programma's maken gebruik van BPF type format (BTF) debugginginformatie om te bepalen hoe ze moeten communiceren met functies in de kernel. Specifiek houdt het tracen van een kernelfunctie in dat het adres van deze functie in de BTF-sectie van de kernel wordt gezocht. Dit werkt echter niet voor functies die zijn inlined, aangezien deze geen enkel, specifiek adres hebben. Alan Maguire wil informatie over inlined functies toevoegen aan BTF om ze traceerbaar te maken; hij leidde hierover een sessie tijdens de 2026 Linux Storage, Filesystem, Memory-Management, and BPF Summit.

Volgens Maguire bevinden zich meer dan 100.000 inlined functies in de kernel, verspreid over vijf keer zoveel locaties. Bovendien zijn sommige functies gedeeltelijk inlined: ze worden op sommige plaatsen normaal aangeroepen en op andere plekken inlined. Dit kan er momenteel toe leiden dat het lijkt alsof een functie succesvol is getraceerd, terwijl sommige aanroepen niet zijn geregistreerd.

Het goede nieuws is dat de rest van de infrastructuur voor het tracen van inlined functies al aanwezig is om kprobes mogelijk te maken, die kunnen worden gekoppeld aan willekeurige locaties. Het is volgens Maguire enkel een kwestie van de gegevens over waar functies zijn inlined in een bruikbaar formaat krijgen. "Het verhaal is eigenlijk al vrij compleet," aldus Maguire.

Opslag van informatie in BTF

Wat is er nodig om deze informatie in BTF op te slaan? Het DWARF-debuggingformaat heeft al een manier om inlining-informatie aan te geven, maar DWARF is complex om mee te werken en biedt geen eenvoudige manier om veelvoorkomende scenario's weer te geven. Een oplossing voor BTF moet compact zijn en deduplicatie toestaan om de geheugenoverhead laag te houden. Ideaal gezien zou de inlining-informatie kunnen worden opgeslagen in een aparte sectie van het kernel-binairbestand, of zelfs kunnen worden gedistribueerd als een aparte kernelmodule, zodat deze pas wordt geladen wanneer dat nodig is.

Concreet stelt Maguire voor om drie nieuwe soorten informatie aan BTF toe te voegen:

  1. De "location section": Site-specifieke informatie over welke functie op elke aanroepsite (call site) is inlined en hoe deze zou zijn aangeroepen als deze niet was inlined. Deze data kan niet eenvoudig worden gededupliceerd omdat het specifiek is voor een bepaalde aanroepsite; daarom moet de informatie zoveel mogelijk bestaan uit pointers naar data die wél gededupliceerd kan worden.
  2. Het "location prototype": Dit specificeert hoe de argumenten van de inlined functie worden weergegeven op de aanroepsite, als een lijst met pointers naar location parameters.
  3. De "location parameter": Deze slaat op hoe er toegang wordt verkregen tot een enkel functieargument.

In theorie zou de compiler een specifiek functieparameter op een andere locatie kunnen opslaan voor elke inlined aanroepsite (waar er 538.090 van zijn). In de praktijk zijn er echter maar een beperkt aantal manieren waarop de compiler parameters transformeert, en veel functies hebben compatibele signatures waardoor de compiler dezelfde keuzes maakt. In de huidige kernel resulteert deduplicatie van location prototypes in slechts 57.141 unieke vermeldingen die in totaal naar slechts 17.535 location parameter-vermeldingen verwijzen.

Dit betekent dat de location sections het grootste deel van de toegevoegde data beslaan. In totaal zouden de voorgestelde toevoegingen aan BTF ongeveer 11 MB extra data vereisen, wat neerkomt op circa 21 bytes per inlined aanroepsite. Wanneer dit wordt uitgeplaatst in een aparte kernelmodule en gecomprimeerd, daalt de totale hoeveelheid data naar 3,5 MB.

Voorbeeld van dataopslag

Maguire illustreerde met een voorbeeld hoe informatie over een specifieke functie zou worden opgeslagen. Neem deze functie:

int foo(int a, void *b, bool c);

Stel dat de compiler ervoor kiest om foo() zodanig in te linen dat a als ongebruikt wordt geëlimineerd, b wordt gepromoveerd om via een register te worden doorgegeven, en wordt vastgesteld dat c een constante is. De BTF-representatie zou dan bestaan uit:

  • Een enkele vermelding in de location section met het BTF type ID van foo(), de offset van de aanroepsite ten opzichte van het basisadres van de kernel in het geheugen, en een pointer naar de location-prototype vermelding.
  • De location prototype entry, die een array is van referenties naar location-parameter vermeldingen:
  • De eerste vermelding is null, wat aangeeft dat a niet kan worden hersteld.
  • De tweede wijst naar een location parameter entry met een vlag die aangeeft dat de waarde zich in een register bevindt, gevolgd door het specifieke registernummer van b.
  • De laatste location-parameter vermelding heeft een andere vlag die aangeeft dat het een constante is, gevolgd door de waarde van die constante.

Implementatiedetails en uitdagingen

Alexei Starovoitov vroeg naar de volgorde waarin de location-section vermeldingen worden opgeslagen, aangezien de kernel hierdoor moet zoeken om de juiste vermelding te vinden bij het instellen van tracing. Aanvankelijk dacht Maguire dat sorteren op het adres van de aanroepsite het beste zou zijn, maar na analyse van het gebruik besloot hij de vermeldingen in plaats daarvan op functienaam te sorteren. Hierdoor kunnen tracers die zoeken naar informatie over een specifieke functie deze snel vinden via een binaire zoekopdracht.

Maguire benoemde daarnaast twee randvoorwaarden voor het toevoegen van inlining-informatie aan BTF:

  • Veldgrootte: Het aantal location-section vermeldingen vereiste dat de grootte van het lengteveld van de bevatttende structuur werd verhoogd naar 24 bits.
  • Tooling en compatibiliteit: Sommige bestaande tooling voor het verwerken van BTF kon niet goed omgaan met nieuwe tags die het niet herkende. Dit is een terugkerend probleem bij elke uitbreiding van BTF en is mede de reden waarom DWARF zo fragiel is geworden. Om dit op te lossen, heeft Maguire informatie over hoe BTF geparsed moet worden, toegevoegd aan BTF zelf. Elke tool die deze meta-informatie kan lezen, kan nu correct nieuwe tags overslaan die hij niet begrijpt.

Daarnaast moest de poke-a-hole (pahole) utility worden bijgewerkt om de nieuwe soorten BTF-tags correct te sorteren. Dit bleek complex, maar zou moeten werken voor normale kernelbuilds. Op de vraag van Andrii Nakryiko over hoe dit omgaat met out-of-tree modules, legde Maguire uit dat deze modules veerkrachtige module BTF nodig hebben die expliciet gerelocated kunnen worden bij het laden in een draaiende kernel. Zijn ontwerp staat dit toe, maar het maakt het buildproces complexer. Er was enige discussie over de benodigde wijzigingen om out-of-tree modules eleganter te ondersteunen, maar uiteindelijk is er geen overeenstemming bereikt over aanpassingen.

Op het moment van schrijven is de patchset van Maguire nog niet samengevoegd (merged). Het is duidelijk nuttig om inlined en gedeeltelijk inlined functies te kunnen tracen; de vraag is of dit opweegt tegen de complexiteit en de geheugenoverhead. De tijd zal het uitwijzen.