02 Procedure Calls and Returns


[MUSIC] Now that we've seen how a stack is set up in memory and the operations that we can use on a stack namely the push and pop instructions. Let's see how we can keep track of procedure calls using the stack. And also how we can, remember the return address where we need to return at the end of our procedure call, and the return value we need to get from that procedure. How do we keep track of those, using a stack? Okay. Let's take a look, okay, let's take a look at, an overview of the procedure call, and what what happens, in that one step at a time. So, we're going to start off with two procedures, a caller and a callee. the caller is going to set up some arguments for calling a second procedure, the callee. And then execute a call instruction, which will change the flow of control to the first instruction in the assembly code corresponding to the callee procedure. What's going to be in that assembly code is probably some stuff about creating some local variables that this procedure will need. then some computation in here. then we'll probably get a return value created and that will be what this procedure will want to give back to the caller. then they'll might be some stuff to cleanup the local variables that were created reclaim the space. And finally a, a, return instruction to to tell the CPU that we're going to, we want to give control back to the caller. And go back to the very next instruction after the original call. that next instruction will probably start the process of cleaning up the space that was used to set up the arguments in the first place. And then go find that return value that was created here in the callee. Okay. And that, that's the process of calling a procedure, set up the arguments, get the procedure executed, clean up the arguments and find the return value. Okay. So, the callee has to know, though, where to find those arguments. where did the caller put them? We have to have some agreement about that. the callee must also know where to find the return address. Where's this address, after the call instruction, that I have to go back to when I'm done? with the the called and the callee. Alright, the caller must know where to find a return value that the callee generated. where is, where is he going to find that and since the caller and callee are running on the same CPU of course. They're using the same registers in that CPU. They have to make sure that they don't step on each other. So if the callee is going to use some registers that the callee also wants to use, it better save them away before gives them to the a callee. before gives control over to the callee. And similary the callee might want to save somethings as well, which one is going to to do what ? Are we going to give all the responsibility to the caller or all the responsibility to the callee or some combination of the two. Okay, so we now embellish our diagram a little bit. You'll notice that now we've added maybe we need to save some registers before we get this call set up. the callee might want to save some other registers. again we have to decide how we're going to distribuate that work. and if it saves some registers it needs to make sure to restore them before returns. So that the caller will find the same contents there that it, that it started things with. And similarly the caller may want to restore some registers after the callee is done. Okay. So this convention of where to find these things where to save them how to coordinate all of this activity, is called a procedure called linkage. And we're going to look at the detail for the IA32 Linux way of doing this procedure called linkage. There are many others every operating language and program has slight variations. but this what we're going to be using for C on Linux. Okay. Now the last question at the bottom is asking of course if our program didn't follow these conventions we'd be quite confused about where. How to do this and we'd have to think individual about every procedure and what it might have done or where it might have put things. what it might need to save and so on. And that just too much of a burden on the programmer. That's why we like to have a particular convention and then we can assume it's always done the same way, okay. So let's take a look at this in a little bit more detail and see how this relates to the use of the stack and memory. Okay. How you going to use to support the procedure call and return. So we, we seem we execute a procedure call using the call instruction, CALL. And we give it a label as argument, namely as address of the callee function. What function are we going to what procedure are we going to go and execute next. So what we're going to do, is before we do that, as part of that call instruction. We're going to push the return address onto the stack and save it there. what address will that be, that's going to be the address of this, of the instruction immediately following the call. The one that we have to go to when we're done. Executing the procedure and we would execute next after the call. Okay, and then we'll jump to the label or namely the address of the call, the instruction. Just is if it were a jump instruction. So as I said the return address that we've saved on the stack is the address of the instruction immediatly after the call. So let's take a look at a little fragment of code here. Here's a call instruction. that is going to go to this particular address to execute the next instruction in a different procedure. so the address immediately after it is going to be this address of the very next instruction that happens to be a push instruction. But that's totally coincidental, that could be any instruction. the important thing is that this is the address that we want to return to, so our return address will be that value. What we're going to do is push that value onto the stack, so that when we execute the return instruction in the callee procedure. We can go to the stack and pop that value off and then jump to that value to that address. Okay, so the return instruction is going to be a pop followed by a jump, while the call instruction was a push followed by a jump. And you can see the two are complimentary. Alright let's take a look at this procedure call in more detail. Here I have that same little code fragment again, a call instruction followed by the very next instruction after the call. And here I am showing the current contents of the stack. You'll notice that there is a value at that 123, that may be as an argument to this procedure. and you'll notice that the stack pointer of course is always pointing to the top element of that stack. And the instruction pointer where we are in the program is pointing to the address of the call instruction. Okay, and that's we going to execute, right now. So heres our call instruction to execute its as called the procedure at this address. Okay. So what's going to happen? Well we will start off with just add contents of memory. the very first thing that we'll be doing is since we just read the call instruction, we'll advance our instruction pointer to point to the next instruction. Alright? But we're not done executing the call yet. But our instruction pointer will have advanced and you notice that now says 553. Okay. The very next thing that happens is that we're going to take that value, that address of the next instruction after the call, and push it onto the stack. So you notice that the stack grew a little bit we now have something at location in 104. And the stack pointer was adjusted to point to that location, we subtracted 4 from the stack pointer, that's the push instruction. Okay. The next thing that happens is that we have to, of course, go to this address and the way we're going to generate that address, you'll notice is by doing an addition to the current address of our instruction pointer. This is, a method called Relative Addressing. you'll notice that in the instruction, we have the constant, 06 3d of course, in Little Endian notation. Here's that 06 3d, and we're adding that to the current value of the instruction pointer to generate the address of the actual place in memory where are the call tells us to go? Okay, now why don't we just put this address the rectly in the instruction. There was room there to fit that. we could made this be 80 or rather 08 04 8b 90. But we didn't do that. We used this relative addressing instead, and in fact, both kinds of instructions exist in, in the x86 architecture. it doesn't really matter though because a compiler is doing this for us. And it decided to do it, that, with Relative Addressing. So the shorter value, 06 3d, and that just gets added to the address. Okay? So this is not something you need to worry about, generating. Of course, the compiler takes care of all of that when it generates the assembly code. Am just explaining how we actually get the address we really care about going to. Okay. So that address then replaces the instruction pointer because that's the next instruction we are going to go to. Okay. So we are now executing that next instruction in that callee procedure. we are going through that entire set of instructions for that procedure and finally will reach a returning structure. Thats at the end of the callee procedure. Okay, when we hit that the stack will be let hopefully have returned to the same position. So the stack pointer is pointing to that return address we had saved away. And now what we're going to do is pop that stack and get that value back so that we can go to that location next. So what the return is going to do, is it's going to pop this value from the stack. And push it into the instruction pointer pop it and store it into the instruction pointer. So that's what we see happening here, that value moved there, and of course we have to adjust the stack, that's the other part of the pop instruction. So the stack now changes to 108, pointing back to where it pointed to before we called this procedure in the first place. And, our next instruction to execute is going to be at that address, which is remember that push instruction following the call. The last thing to talk about in this, portion is what do we do with return values from a function? And by convention, values returned by procedures are placed in the EAX register. the choice of EAX is pretty arbitrary. It could have easily been a different register like ECX or EDX, but we've chosen EAX. That's part of that procedure calling convention. the caller has to make sure to save that register before calling a callee that might return a value. because that callee is going to over right the contents of that eax register. So this is part of the register, register saving convention we'll see later on. so at the end of the procedure, the call, the callee procedure, the return values place in the eax and of course EAX is only 4 bytes. So we could only put certain types there, things like integeres, floats or pointers. Of course if we want to return anything that's bigger than 4 bytes, prolly the best thing to do is to just return a pointer to that object. Rather than the object itself. Okay, so the thing to remember for now that is upon return, a caller procedure finds the return value of the callee in the EAX register.

Wyszukiwarka

Podobne podstrony:
02 References and Methods
02 Hearts And Bones
02 Modeling and Design of a Micromechanical Phase Shifting Gate Optical ModulatorW42 03
a spicer film studies and return to history
2006 02 Menus and Choices Creating a Multimedia Center with Mpeg Menu System V2
06 x86 64 Procedures and Stacks
M99 Sub Program Call and Return
02 Performance and Fragmentation
06 x86 64 Procedures and Stacks
duties and procedures
2005 02 All on Board Kontact with Imap Based Calendar and Address Management
SHSpec 025 6107C05 Q and A Period Procedures in Auditing
Installing and Repairing Windows NTServer 02
Electronics 4 Systems and procedures S

więcej podobnych podstron