As we always know the procedure of executing task by a microprocessor is just executing binary instructions from memory one by one and there is a program counter which holds the address of the next instruction. So this is how processor executes it's tasks if I am not wrong. But there is also another pointer named Stack Pointer which does almost same thing like the program counter. My question is why we need a Stack Pointer to point address of memory(Stack)? Can somebody tell me about the main difference between Stack Pointer and program counter?
void show ( unsigned int );
unsigned int fun ( unsigned int x )
{
if(x&1) show(x+1);
return(x|1);
}
0000200c <fun>:
200c: e3100001 tst r0, #1
2010: e92d4010 push {r4, lr}
2014: e1a04000 mov r4, r0
2018: 1a000002 bne 2028 <fun+0x1c>
201c: e3840001 orr r0, r4, #1
2020: e8bd4010 pop {r4, lr}
2024: e12fff1e bx lr
2028: e2800001 add r0, r0, #1
202c: ebfffff5 bl 2008 <show>
2030: e3840001 orr r0, r4, #1
2034: e8bd4010 pop {r4, lr}
2038: e12fff1e bx lr
take a simple function, compile and disassemble using one of the arm instruction sets as you tagged arm on this question.
Lets assume a simple serial non-pipe old school type execution.
In order to get here a call (bl in this instruction set, branch and link) happened which modified the program counter to be 0x200C. The program counter is used to fetch that instruction 0xe3100001 then after fetch before execution the program counter is set to point at the next instruction 0x2010. As this program counter is described for this particular instruction set it fetches and stages the next instruction 0xe92d4010 and before execution of the 0x200C instruction the pc contains the value 0x2014, two instructions ahead. For demonstration purposes lets think old school we fetched 0xe3100001 from 0x200C the pc is now set to 0x2010 waiting for execution to complete and for the next fetch cycle.
This first instruction tests the lsbit of r0, the passed in parameter (x), the program counter is not modified so the next fetch reads 0xe92d4010 from 0x2010
The program counter now contains 0x2014, the 0x2010 instruction executes. This instruction is a push it uses the stack pointer. On entry into this function as a programmer we dont care what the exact value of the stack pointer is it could be 0x2468 it could be 0x4010, we dont care. So we will just say it contains the value/address sp_start. This push instruction is using the stack to save two things one is the link register lr, r14, the return address, when this function finishes we want to return to the calling function. And r4 which per the rules of the calling convention used by this compiler for this instruction set says that r4 must be preserved in that if you modify it you must return it to the value it was when called. So we are going to save that on the stack, instead of putting x on the stack and referring to x a number of times in this function, this compiler chooses to save whatever was in r4 (we dont care we just have to save it) and use r4 to hold x for the duration of this function as compiled. any function we call and they call, etc will preserve r4 so when anyone we call returns back to us r4 is whatever it was when we called. So the stack pointer itself changes to sp_start-8 and at sp_start-8 lives the saved copy of r4 and at sp_start-4 the saved copy of lr or r14, we can now modify r4 or lr as we wish we have a scratch pad (the stack) with a saved copy and a pointer for which we can do relative addressing to get at those values, and any calling functions that want to use the stack will grow down from sp_start-8 and not stomp on our scratch pad.
Now we fetch 0x2014 change the pc to 0x2018, this makes a copy of x (passed in in r0) in r4 so we can use it later in the function.
we fetch 0x2018 change the pc to 0x201C. This is a conditional branch so depending on the condition the pc will remain 0x201C or it will change to 0x2028. The flag in question was set during execution of tst r0,#1 the other instructions didnt touch that flag. So we have two paths to follow now, if the condition is not true then we use 0x201C to fetch
fetch from 0x201c change pc to 0x2020, this performs the x=x|1, r0 is the register that contains the return value for the function. This instruction does not modify the program counter
fetch from 0x2020 change the pc to 0x2024, execute the pop. we have not modified the stack pointer (another register that is preserved, you have to put it back where you found it) so sp is equal to sp_start-8 (which is sp+0) right now we read from sp_start-8 and put that value in r4, read from sp_start-4 (which is sp+4) and put that value in lr and add 8 to the stack pointer so it is now set to sp_start, the value it was when we started, put it back the way you found it.
fetch from 0x2024 change the pc to 0x2028. bx lr is a branch to r14 basically it is a return from the function, this modifies the program counter to point at the calling function, the instruction after that calling function called fun(). pc is modified execution continues from that function.
If the bne at 0x2018 did happen then the pc during the execution of bne changes to 0x2028 we fetch from 0x2028 and change the pc to 0x202c before execution. 0x2028 is an add instruction, does not modify the program counter.
we fetch from 0x202c and change the pc to 0x2030 before executing. the bl instruction does modify the program counter and the link register it sets the link register to 0x2030 in this case and the program counter to 0x2008.
the show function executes and returns with a fetch of 0x2030 changing the pc to 0x2034 the orr instruction at 0x2030 happens does not modify the program counter
fetch 0x2034 set pc to 0x2038 execute 0x2034, like 0x2020 this takes the value at address sp+0 and puts it in r4 takes sp+4 and puts it in the lr and then adds 8 to the stack pointer.
fetch 0x2038 set the pc to 0x203c. this does a return puts the callers return address in the program counter causing the next fetch to be from that address.
The program counter is used to fetch the current instruction and to point at the next instruction.
The stack pointer in this case does both jobs it shows where the top of stack is, where the free to use space starts as well as provides a relative address to access items in this function so for the duration of this function after the push the saved r4 register is at sp+0 as this code is designed and the return address at sp+8. if we had several other things on the stack then the stack pointer would have been moved further into the then free space and the items on the stack would be at sp+0, sp+4, sp+8, etc or other values for 8, 16, 32 or 64, bit items.
Some instruction sets and some compiler settings can also setup a frame pointer which is a second stack pointer. One job is to keep track of the boundary between used stack space and free stack space. The other job is to provide a pointer from which to do relative addressing. In this example the stack pointer itself r13 was used for both jobs. But we could tell the compiler and in other instruction sets you dont have a choice, we could have the frame pointer get saved to the stack then frame pointer = stack pointer. then we move the stack pointer in this case 8 bytes and the frame pointer would be used as fp-4 and fp-8 lets say to address the two items on the stack and sp would be used for callee functions to know where the free space starts. A frame pointer is generally a waste of a register, but some implementations use it by default and there are some instruction sets that you dont have a choice, to reach twice as far they will require stack accesses to be hardcoded using a specific register and the offset to be in only one direction add a positive offset or a negative. In arm in this case the push is actually a pseudo instruction for a generic store multiple in which the register r13 is encoded.
Some instruction sets you cant see the program counter it isnt visible to you in any way. Likewise some instruction sets you cannot see the stack pointer it is not visible to you in any way.
Well, they're fundamentally different concepts. They both contain memory addresses, but remember that both instructions and data are held in (effectively) the same memory space.
The program counter contains the address of the instruction that's currently executing. In fact the CPU uses the value in the program counter to fetch the instructions before it executes them. As instructions are executed, its value is incremented, and if the code branches it will have its value forcibly overwritten.
The stack pointer contains the address of the top of the hardware stack, which is a region of memory that the running code uses as a scratchpad. Values are stored there temporarily, arguments to functions are sometimes put there, and code addresses can be stored there too (for example when one function calls another).
void show ( unsigned int );
unsigned int fun ( unsigned int x )
{
if(x&1) show(x+1);
return(x|1);
}
0000200c <fun>:
200c: e3100001 tst r0, #1
2010: e92d4010 push {r4, lr}
2014: e1a04000 mov r4, r0
2018: 1a000002 bne 2028 <fun+0x1c>
201c: e3840001 orr r0, r4, #1
2020: e8bd4010 pop {r4, lr}
2024: e12fff1e bx lr
2028: e2800001 add r0, r0, #1
202c: ebfffff5 bl 2008 <show>
2030: e3840001 orr r0, r4, #1
2034: e8bd4010 pop {r4, lr}
2038: e12fff1e bx lr
take a simple function, compile and disassemble using one of the arm instruction sets as you tagged arm on this question.
Lets assume a simple serial non-pipe old school type execution.
In order to get here a call (bl in this instruction set, branch and link) happened which modified the program counter to be 0x200C. The program counter is used to fetch that instruction 0xe3100001 then after fetch before execution the program counter is set to point at the next instruction 0x2010. As this program counter is described for this particular instruction set it fetches and stages the next instruction 0xe92d4010 and before execution of the 0x200C instruction the pc contains the value 0x2014, two instructions ahead. For demonstration purposes lets think old school we fetched 0xe3100001 from 0x200C the pc is now set to 0x2010 waiting for execution to complete and for the next fetch cycle.
This first instruction tests the lsbit of r0, the passed in parameter (x), the program counter is not modified so the next fetch reads 0xe92d4010 from 0x2010
The program counter now contains 0x2014, the 0x2010 instruction executes. This instruction is a push it uses the stack pointer. On entry into this function as a programmer we dont care what the exact value of the stack pointer is it could be 0x2468 it could be 0x4010, we dont care. So we will just say it contains the value/address sp_start. This push instruction is using the stack to save two things one is the link register lr, r14, the return address, when this function finishes we want to return to the calling function. And r4 which per the rules of the calling convention used by this compiler for this instruction set says that r4 must be preserved in that if you modify it you must return it to the value it was when called. So we are going to save that on the stack, instead of putting x on the stack and referring to x a number of times in this function, this compiler chooses to save whatever was in r4 (we dont care we just have to save it) and use r4 to hold x for the duration of this function as compiled. any function we call and they call, etc will preserve r4 so when anyone we call returns back to us r4 is whatever it was when we called. So the stack pointer itself changes to sp_start-8 and at sp_start-8 lives the saved copy of r4 and at sp_start-4 the saved copy of lr or r14, we can now modify r4 or lr as we wish we have a scratch pad (the stack) with a saved copy and a pointer for which we can do relative addressing to get at those values, and any calling functions that want to use the stack will grow down from sp_start-8 and not stomp on our scratch pad.
Now we fetch 0x2014 change the pc to 0x2018, this makes a copy of x (passed in in r0) in r4 so we can use it later in the function.
we fetch 0x2018 change the pc to 0x201C. This is a conditional branch so depending on the condition the pc will remain 0x201C or it will change to 0x2028. The flag in question was set during execution of tst r0,#1 the other instructions didnt touch that flag. So we have two paths to follow now, if the condition is not true then we use 0x201C to fetch
fetch from 0x201c change pc to 0x2020, this performs the x=x|1, r0 is the register that contains the return value for the function. This instruction does not modify the program counter
fetch from 0x2020 change the pc to 0x2024, execute the pop. we have not modified the stack pointer (another register that is preserved, you have to put it back where you found it) so sp is equal to sp_start-8 (which is sp+0) right now we read from sp_start-8 and put that value in r4, read from sp_start-4 (which is sp+4) and put that value in lr and add 8 to the stack pointer so it is now set to sp_start, the value it was when we started, put it back the way you found it.
fetch from 0x2024 change the pc to 0x2028. bx lr is a branch to r14 basically it is a return from the function, this modifies the program counter to point at the calling function, the instruction after that calling function called fun(). pc is modified execution continues from that function.
If the bne at 0x2018 did happen then the pc during the execution of bne changes to 0x2028 we fetch from 0x2028 and change the pc to 0x202c before execution. 0x2028 is an add instruction, does not modify the program counter.
we fetch from 0x202c and change the pc to 0x2030 before executing. the bl instruction does modify the program counter and the link register it sets the link register to 0x2030 in this case and the program counter to 0x2008.
the show function executes and returns with a fetch of 0x2030 changing the pc to 0x2034 the orr instruction at 0x2030 happens does not modify the program counter
fetch 0x2034 set pc to 0x2038 execute 0x2034, like 0x2020 this takes the value at address sp+0 and puts it in r4 takes sp+4 and puts it in the lr and then adds 8 to the stack pointer.
fetch 0x2038 set the pc to 0x203c. this does a return puts the callers return address in the program counter causing the next fetch to be from that address.
The program counter is used to fetch the current instruction and to point at the next instruction.
The stack pointer in this case does both jobs it shows where the top of stack is, where the free to use space starts as well as provides a relative address to access items in this function so for the duration of this function after the push the saved r4 register is at sp+0 as this code is designed and the return address at sp+8. if we had several other things on the stack then the stack pointer would have been moved further into the then free space and the items on the stack would be at sp+0, sp+4, sp+8, etc or other values for 8, 16, 32 or 64, bit items.
Some instruction sets and some compiler settings can also setup a frame pointer which is a second stack pointer. One job is to keep track of the boundary between used stack space and free stack space. The other job is to provide a pointer from which to do relative addressing. In this example the stack pointer itself r13 was used for both jobs. But we could tell the compiler and in other instruction sets you dont have a choice, we could have the frame pointer get saved to the stack then frame pointer = stack pointer. then we move the stack pointer in this case 8 bytes and the frame pointer would be used as fp-4 and fp-8 lets say to address the two items on the stack and sp would be used for callee functions to know where the free space starts. A frame pointer is generally a waste of a register, but some implementations use it by default and there are some instruction sets that you dont have a choice, to reach twice as far they will require stack accesses to be hardcoded using a specific register and the offset to be in only one direction add a positive offset or a negative. In arm in this case the push is actually a pseudo instruction for a generic store multiple in which the register r13 is encoded.
Some instruction sets you cant see the program counter it isnt visible to you in any way. Likewise some instruction sets you cannot see the stack pointer it is not visible to you in any way.
© 2022 - 2024 — McMap. All rights reserved.