Hallo en welkom.
In deze lezing wil ik iets vertellen over de call stack, wat die precies is en hoe je die kunt
lezen om je programma verder te helpen debuggen.
Een stack trace, wat in wezen de call stack is, wordt gegenereerd wanneer je app crasht
door een fatale fout.
De term stack wordt gebruikt omdat het verwijst naar een gegevensstructuur die gebruikelijk is bij het programmeren.
Stack is een gegevensstructuur die bekend staat als LIFO, last in, first out, en zo worden de gegevens opgeslagen.
Het laatste gegevenselement dat wordt toegevoegd is het eerste dat je terughaalt.
Je kunt het zien als een stapel pannenkoeken, de eerste pannenkoek ligt onderaan die
je toevoegt.
De laatste pannenkoek ligt bovenaan.
Als je ze gaat opeten, eet je de laatste die erbij kwam.
En dus is een stack trace zeer nuttig wanneer je programma crasht, omdat het informatie bevat die je helpt bij
het debuggen.
Een stack trace toont dus een lijst van de functie-aanroepen die tot de fout hebben geleid.
En wat er gebeurt in een programma is dat wanneer je je hoofdfunctie aanroept, dat is het beginpunt.
De hoofdfunctie gaat andere functies aanroepen.
Die functies gaan andere functies aanroepen en al die functies die worden uitgevoerd of aangeroepen
komen op de stack.
En zo kun je doen, wanneer een programma crasht, zal de aanroep-stapel je vertellen dat de laatste functie die
werd aangeroepen en de specifieke regel code die de fout veroorzaakte.
De Callstack bevat dus veel goede informatie.
Het bevat de bestandsnaam en de regelnummers van de code die de uitzondering of de fatale fout
veroorzaakt.
De top van de stack bevat de laatste aanroep die de fout veroorzaakte en er kunnen geneste aanroepen zijn.
De onderkant van de stack bevat de eerste oproep die de keten van oproepen voor de fout begon.
Dus als je in C meestal bij de main routine bent, roep je de main aan, de main roept een andere functie aan, die weer een andere
functie aanroept, enzovoort, enzovoort.
En dan is die laatste regel gedetailleerd.
Het is zeer, zeer nuttig wanneer app voor app crasht.
En dus wanneer een C-programma crasht, kan het een segmentatiefout genereren en dat vertelt je niet veel
informatie.
Het betekent gewoon dat er iets ergs is gebeurd.
Dat zal ook een kernbestand genereren.
U kunt het core-bestand analyseren dat de call stack bevat, die u de exacte regel code zal vertellen
die het probleem voor uw crash veroorzaakte.
Je moet dus de aanroep in je applicatie vinden die de crash veroorzaakt.
En de call stack is waarschijnlijk de beste manier om dit te doen.
En je hoeft niet altijd naar dat kernbestand te kijken.
U kunt de call stack ook analyseren met behulp van GDB met een debugger.
En ik wil je laten zien hoe dit eruit ziet.
Een programmeur kan ook op elk moment de stack trace dumpen.
Dus als je een foutmelding krijgt of herkent dat er een fatale fout kan optreden, kun je de aanroepstapel
dumpen om te zien wat er aan de hand is.
Als u niet echt weet wanneer de fout gaat optreden, kunt u nog steeds de aanroepstapel
dumpen om de huidige sporen te zien die tot een bepaalde aanroep hebben geleid.
Bij gebruik van GDB ziet de aanroepstapel er ongeveer zo uit.
U kunt de aanroepstapel openen via het menu wanneer u de debugger in GDB draait en
ik zal u laten zien hoe u dat doet, maar zo ziet het eruit en wat u hier ziet is een nummer,
een adres en dan de functie.
Deze specifieke aanroepstapel hier heeft veel verschillende aanroepen, dus hij moet veel verschillende nesten en veel verschillende
functie-aanroepen hebben gedaan.
Maar helemaal onderaan de stack op regel 22 staat GTK main.
Dat betekent de.
Dat betekent dat het waarschijnlijk de hoofdfunctie heeft uitgevoerd.
En dan op regel 21, lijkt het erop dat het is uitgevoerd.
Misschien zegt een verklaring dat de hoofdlus wordt uitgevoerd.
En dan lijkt het op een functie genaamd G main context iterate.
En binnen die functie was er telkens een oproep naar een IAG main context
dispatch functie.
Dus er was een iterate functie, dan een dispatch functie, en de vraagtekens in regel 18 betekenen
dat er geen debug informatie is.
Het weet niet wat daar gebeurd is.
En daarom bouw je je programma, of je bouwt het voor release of je bouwt het
met debug informatie.
Wanneer u het bouwt met debug-informatie, bevat het extra informatie bij het compileren, zodat u bij
het uitvoeren naar call stacks kunt kijken.
Als je het vrijgeeft, bouw dan iets voor de vrijlating.
De executable is veel kleiner en bevat deze informatie mogelijk niet.
Het kan meer vraagtekens bevatten.
Maar dit zal je ook vertellen of je bibliotheken hebt aangeroepen, extra programma's, het zal die aanroepen en je de aanroepstapel
vanaf daar vertellen.
Maar wat hieruit blijkt, is dat de verschillende geneste gebeurtenissen voor de oproep helemaal bovenaan
belangrijk zijn, omdat dat aangeeft wat de oorzaak van de crash was.
Dus het lijkt erop dat in deze specifieke oproepstapel, de innerlijke verversing een probleem veroorzaakte.
En je ziet dat vanaf regel 22 tot regel nul, dat er veel verschillende aanroepen zijn van verschillende
code.
Maar de uiteindelijke programma crash in lijn nul roept stacks op.
U kunt ook kijken naar call stacks terwijl het programma draait om gewoon informatie te zien, zodat u het kunt
herleiden.
Dus als je een breekpunt in een functie zet, dan debug je, je voert de debugger daarop uit.
De debugger stopt op dat breekpunt.
Je kunt dan de call stack oproepen en die vertelt je hoe je bij die regel code bent gekomen.
Deze functie werd voor deze functie aangeroepen.
En nogmaals, het begint meestal met de hoofdfunctie.
Als je bekend bent met de stack en het concept en hoe het in het geheugen zit, kun je ook verschillende
frames bekijken, maar dit is een heel handig hulpmiddel om voornamelijk crashes in C te vinden, en
het is heel gemakkelijk te lezen.
Als je alleen naar de bovenkant kijkt en je begrijpt niet veel van de aanroepstapel,
kun je naar de bovenkant kijken en zeggen: Oh, dat is een bron, dat is een regel code die de crash veroorzaakte.
Hier is een mooi voorbeeld dat ik op het web vond.
Excma.
Edu.
Dat illustreert functie-aanroep-stapel voorbeelden.
Dus als we kijken naar een voorbeeld in code zoals we zullen zien hebben we gewoon wat eenvoudige code.
De berekeningen doen er niet echt toe.
We roepen gewoon wat functies op.
Dus we hebben deze mogrify-functie en deze echt halve functie en de hoofdfunctie gaat een mogrify-functie aanroepen en
dan gaat hij echt de helft aanroepen en dan gaat hij mogrify aanroepen, dus we kunnen de aanroepstapel
illustreren en hoe die eruit ziet bij deze aanroepen door in principe gewoon een eenvoudig breekpunt in te stellen in de hoofdfunctie
en dan te kijken naar de aanroepstapel terwijl we vorderen en aanroepen doen.
Dus nogmaals, we gaan meer vertrouwd raken met breekpunten en dat soort informatie.
Maar ik wil de aanroepstapel snel demonstreren en later zullen we een meer gedetailleerd voorbeeld van
de aanroepstapel geven.
Dus hier stel ik een breekpunt in.
Ik kan doorgaan en dit bouwen en compileren en ik kan de debugger starten en we raken dat breekpunt.
En wat je nu zult zien is dat je het debugger-menu aan de rechterkant onderaan ziet, en
je ziet de aanroepstapel.
Laten we dit een beetje groter maken.
Maar als het stopt bij regel 14, heeft het hier alleen wat toewijzingsverklaringen en je ziet dat de
aanroepstapel main laat zien.
Als we daar overheen stappen en naar de volgende regel gaan, kunnen we eigenlijk zien dat het gewoon naar regel 15 gaat
voordat het Mogrify aanroept, toch?
En zo zie je dat de oproepstapel bij Main blijft.
Als we erin stappen, zie je nu dat de aanroepstapel Mogrify toont.
Dus we zitten eigenlijk in deze functie.
Daar kunnen we overheen stappen.
Dus het voert de eerste regel uit en stuurt dan temp terug.
Hij blijft nog steeds in die functie en keert dan terug waar hij de afdruk F doet op regel 16, je ziet hoe de aanroepstapel
verandert.
We gaan dan naar regel 18, roepen stack stills op in main en stappen in de echte halve functie.
En nu zie je de aanroepstapel veranderen waar hij echt de helft van de functie bovenaan toont, altijd de functie
die het laatst werd aangeroepen.
We stappen weer over en je ziet dat we nog steeds in de echte helft zitten.
We gaan terug naar Main, we drukken iets af op lijn 19, we gaan naar lijn 21.
We springen weer in de modify functie, die je weer ziet op de call stack, We springen over, we springen over en uiteindelijk
printen we het eindresultaat in regel 23 en keren terug uit het programma.
Maar de call stack toont ons weer de volgorde van de functies die van de ene functie naar de andere worden aangeroepen.
Dus als je geneste functies hebt, kan het steeds ingewikkelder worden.
Het voordeel van de call stack is dat als je een regel code hebt die crasht, bijvoorbeeld op regel vier, je een call
stack hebt die helemaal bovenaan bevat waar de regel crashte.
En dus kun je zien door de aanroepstapel te doorlopen, welke code de laatste was die werd
aangeroepen en dat zal je vertellen welke code gecrasht is.
Maar je zult dit gereedschap willen gebruiken als je programma crasht.
Bedankt.