[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 Methods02 Hearts And Bones02 Modeling and Design of a Micromechanical Phase Shifting Gate Optical ModulatorW42 03a spicer film studies and return to history2006 02 Menus and Choices Creating a Multimedia Center with Mpeg Menu System V206 x86 64 Procedures and StacksM99 Sub Program Call and Return02 Performance and Fragmentation06 x86 64 Procedures and Stacksduties and procedures2005 02 All on Board Kontact with Imap Based Calendar and Address ManagementSHSpec 025 6107C05 Q and A Period Procedures in AuditingInstalling and Repairing Windows NTServer 02Electronics 4 Systems and procedures Swięcej podobnych podstron