Hello and welcome.
In this lecture, I want to talk a little bit about the call stack, what exactly it is and how you
can read it to further help debug your program.
A stack trace, which is essentially what the call stack is, is generated whenever your app crashes
because of a fatal error.
The reason the term stack is used is because it's referring to a data structure that's common in programming.
Stack is a data structure that's known as a LIFO, last in, first out, and that's how the data is
stored.
The last data element that's added is the first one that you retrieve back out.
You can kind of think of it if you're eating a stack of pancakes, the first pancake is at the bottom
that you add.
The last pancake is at the top.
When you go to eat them, you eat the last one that was added.
And so a stack trace is very useful when your program crashes because it contains information that helps
you debug it.
So a stack trace shows a list of the function calls that lead to the error.
And what will happen is in a program when you call your main function, that's the entry point.
The main function is going to call other functions.
Those functions are going to call other functions and all those functions that get get implemented or
get called will be put on the stack.
And so you can do is when a program crashes, the call stack will tell you that the last function that
was invoked and the specific line of code that caused the error.
So the Callstack contains a lot of good information.
It will include the file name and the line numbers of the code that causes the exception or the fatal
error to occur.
The top of the stack contains the last call that caused the error and there could be nested calls.
The bottom of the stack contains the first call that started the chain of calls to call the error.
So if you usually in C that will be at the main routine, you invoke the main, the main invokes another
function, which invokes another function and so forth and so forth.
And then that last line is detailed.
It's very, very helpful when app for app crashes.
And so when a C program crashes, it may generate a segmentation fault and that doesn't tell you a lot
of information.
It just means something bad happened.
That will also generate a core file.
You can analyze the core file which contains the call stack, which will tell you the exact line of
code that caused the problem for your crash.
So you need to find the call in your application that's causing the crash.
And the call stack is probably the best way to do this.
And you don't always need to look at that core file.
You can also analyze the call stack in the using GDB with a debugger.
And I want to show you what this looks like.
A programmer is also able to dump the stack trace at any time.
So if you do catch an error or you recognize that a fatal error might happen, you can dump the call
stack to see what's going on.
If you don't actually know when the error is going to happen, you can still dump the call stack to
see the current traces that led up to a particular call.
When using GDB, the call stack is going to look something like this.
You can access the call stack via the menu when you're running the debugger in GDB and I'm going to
show you how to do this, but this is what it looks like and what you'll see here is it has a number,
an address and then the function.
This particular call stack here has a lot of different calls, so it must have been doing a lot of different
nesting and a lot of different function calls.
But at the very bottom of the stack in line 22, it says GTK main.
That means the.
That means it probably it executed the main function.
And then on lines 21, it looks like it executed.
Maybe a statement says main loop run.
And then it looks like called a function called G main context iterate.
And inside that function, it's there was some whenever there was another call to an IAG main context
dispatch function.
So there was an iterate function, then a dispatch function, and then the question marks in line 18
mean that there's no debugging information.
It doesn't know what happened there.
And that's why when you build your program, you can either build it for release or you build it with
debugging information.
When you build it with debugging information, it contains extra information when you compile so that
when you run, you can look at call stacks.
When you release it, build something for release.
The executable is a lot smaller and may not contain this information.
It may contain more question marks.
But this will also tell you if you invoked any libraries, any additional programs, it will call those
and will tell you the call stack from there.
But what this is showing, again, is the different nested events for the call at the very top is what's
important, because that will signify what caused the crash.
So it looks like in this particular call stack, the inner refresh was causing a problem.
And you can see that from line 22 to line zero, that there are many different invocations of different
code.
But the eventual program crash in line zero call stacks.
You can also look at call stacks while the program is running to just see information so you can trace
it back.
So if you put a breakpoint inside of a function, you debug, you run the debugger on that.
The debugger is going to stop at that breakpoint.
You can then bring up the call stack and it will tell you how you got to that line of code.
This this function was invoked before this function.
And again, it usually will start with the main function.
You can also switch to different frames if you're familiar with the stack and the concept and how it's
in memory, you can look at different frames, but this is a very useful tool to find mainly crashes
in C, and it's very easy to read.
If you just look at the top and you don't understand a lot about the call stack, you can look at the
top and say, Oh, that's a source, that's a line of code that caused the crash.
Here's a nice example I found on the web.
Excma.
Edu.
That illustrates function call stack examples.
So if we look at an example in code like we'll see we just have some simple code.
The calculations don't really matter.
We're basically just calling some functions.
So we have this mogrify function and this truly half function and the main is going to call a mogrify
function and then it's going to call truly half and then it's going to mogrify so we can illustrate
the call stack and what it looks like on these calls by basically just setting a simple breakpoint in
main and then looking at the call stack as we progress and make calls.
So again, we're going to get more familiar with breakpoints and information like that.
But I just want to demonstrate the call stack real quick and we'll actually give a more detailed example
of the call stack later.
So here I set a breakpoint.
I can go ahead and build and compile this and I can start the debugger and we'll hit that breakpoint.
And what you'll notice now you can see all the debugger menu over here on the right hand side in the
bottom, and you'll see the call stack.
Let's make this a little bit bigger.
But right when it stops at line 14, here it just has some assignment statements and you'll see that
the call stack shows main.
If we step over that and go to the next line, we can actually see that it just goes to line 15 before
it calls Mogrify, right?
And so you see the call stack stays with Main.
If we step into, you'll now see that the call stack shows Mogrify.
So we're actually inside this function.
We can step over that.
So it executes the first line and then it's going to return temp.
It still stays in that function and then it returns where it does the print F on line 16, you see how
the call stack is changing.
We then go to line 18, call stack stills in main and we step into the truly half function.
And now you see the call stack change where it shows truly half the function at the top is always the
function that was last called.
Again, we step over and you see we're still in truly half.
We go back to Main, we print something out on line 19, we go to line 21.
We again jump into the modify function, which again you see on the call stack, We jump over, we jump
over and eventually we print out the final result in line 23 and returned from the program.
But the call stack again shows us the order of the functions that are being invoked from one function
to the next.
So if you have nested functions, it can be more and more complicated.
The advantage of the call stack is if you have a line of code that crashes, say on line four, you'll
have a call stack that contains at the very top where the line crashed.
And so you can actually see by going through the call stack, what line of code was the last one that
was invoked and that will tell you what line of code was crashed.
But you will want to use this tool when your program crashes.
Thank you.