请教一下大家对桢指针的理解
各位,本人看的是虎书,请教一下大家对桢指针的理解。
首先,本人说说自己的理解。
1、书上说 FP=SP+“frame大小”(151页)。那也就是说 FP在高址,而SP在低址。那也就是说,对于一个桢来说,FP是它的栈底,SP是它的栈顶。
2、书上说 当新开一个桢的时候,新的FP变成老的SP(91页)。那也就是说,在新的一桢里,前一桢的栈顶(SP)变成新的一桢的栈底(FP)。
3、由1和2,我画了个示意图如下(暂时只包括函数参数):
=============================================
Frame
---------------------------------------------------------------------------------
地址 名称 大小 指针
---------------------------------------------------------------------------------
1036 参数2 4 (第一桢)FP1 (FP1 = esp1+桢大小)
1032 参数1 4
1028 静态链 4
1024 EIP 4
1020 ECS 4 栈指针esp1
---------------------------------------------------------------------------------
1016 参数2 4 (第二桢)FP2(FP2 = esp2+桢大小)
1012 参数1 4
1008 静态链 4
1004 EIP 4
1000 ECS 4 栈指针esp2
=============================================
注:这是个示意图,其实esp1和FP2是一样的,都是1016,只是为了方便表示而已
现在就有一个问题,我们需要桢指针来干什么?为了获取上一桢的栈顶(SP)?如果是这样,OK,可以理解。但是,在处理当前桢的时候,我们怎么根据偏移从栈中获取相应的数据?很显然,如果要正确获取当前桢的数据,那必须知道当前桢的栈顶(SP),而不是FP。因为FP指的是前一桢的SP!
关于对FP的获取,书上列了一个函数,F_FP()。如果我上面的理解没错的话,我们应该更需要 F_SP(),因为那是获取当前桢的栈顶,直接对应栈中的各个变量在 frame结构 中的偏移量设置。
以上是我目前对桢指针的一些理解,请个位不吝指教,谢谢。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
你就不能自已画一画 stack 的结构出来
画一画什么都清楚了。
再改一改 c 代码:
void caller()
{
callee(1,2);
}
void callee(int a, int b)
{
int c = a+b;
... ...
}
对应的汇编代码:
caller:
push ebp
mov ebp, esp
push 2 /* arg2 */
push 1 /* arg1 */
call callee /* ----> callee() */
pop ebp
ret
callee:
push ebp
mov ebp, esp
sub esp, 4
mov eax, [ebp+8] /* arg1 */
add eax, [ebp+0xc] /* arg2 */
mov [ebp-4], eax /* c = a + b */
add esp, 4
pop ebp
ret
stack:
/ ----------
| 2 ---------> arg2 ========> [ebp+0xc]
| -----------
caller() | 1 ---------> arg1 ========> [ebp+8]
| -----------
\ eip ========================> [ebp+4]
----------- \
[ebp] (esp) --> ebp | ===> push ebp =======> [ebp] 也就是 esp
----------- |
[ebp-4] ---> c | =======> [ebp-4]
----------- | ----> callee
XXX XXX |
----------- |
XXX XXX /
-----------
当:
push ebp ========> [esp] = ebp
mov ebp, esp ========> [ebp] = ebp
先顶一下
桢指针就是以 ebp 为基址的局部 local stack 区
bp (ebp、rbp) --- stack frame base-pointer。
sp (esp、rsp) --- stack pointer ( stack top-pointer)
----------------------------------------------------------------------------------
以 ebp 为桢指针的函数局部栈区的形成:
push ebp \
mov ebp,esp / 桢指针的形成
... ....
pop ebp
ret
mik,
你所说的:
桢指针就是以 ebp 为基址的局部 local stack 区
local stack 区: 以 ebp 为桢指针的函数局部栈区的形成
以 ebp 为桢指针的函数局部栈区的形成:
push ebp \
mov ebp,esp / 桢指针的形成
... ....
pop ebp
ret
-----------------------------------------------------------------
我的看法:
1、按照你的说法在栈中任何一桢都是一个“局部栈区”,是这样吗?
2、对于“push ebp mov ebp,esp”。这个没错,但是要看它在什么时候用。
1)如果在创建一个新桢之初(也就是假设未来的新桢它处于地址为:1000~1100这样一个局部栈区),未来新桢的局部栈区是空的,在这个时候我们用“push ebp mov ebp,esp”,然后把 ebp->FP,那FP还是上一桢的栈顶啊。
2)如果在刚创建完一个新桢的时候(也就是说新桢的栈中已push进去要调用函数的局部变量,要保存的寄存器,返回值,参数等,且栈顶SP已指向要调用函数的CS和IP),在这个时候我们用“push ebp mov ebp,esp”,然后把 ebp->FP,那FP是新桢的栈顶。
mik,
对于第2点中的1)和2),你是指哪一种?
1、没错
2、push ebp
mov ebp,esp
-------------------
绝大多数编译器都会产生这个 stack frame 结构,当然,也可以使用参数来取消这个 stack frmae 结构。这个时候,函数要以 esp 作为绝对针指来参考局部变量。
以下面为例 ( caller() ---> callee() )
调用方:
void caller()
{
... ...
callee(1); /* 调用函数 */
... ...
}
被调用方:
void callee(int i)
{
... ...
return;
}
-------------------------------------------------------------------------------
通常编译器会为 callee() 产生以下的 stack frame 结构
callee:
push ebp
mov ebp, esp
sub esp, 0xc
... ...
mov eax, [ebp+8] /* 参数1 */
... ...
add esp, 0x0c
pop ebp
ret
但是,若 disable 掉 stack frame 结构后
callee:
mov eax, [esp+4] /* 参数 */
... ...
ret
取消掉 stack frame 将以 esp 作为基址。
3、实际上调用一个函数就等于开辟一个 stack 区
caller() ---> callee()
--------
eip -----> return address
--------
XXX \
-------- |
XXX |
------- |
\
... ... / local stack
-------
|mik,
抱歉,我还是没看懂你的回复......
你上面提到的(我在里面加了序号):
通常编译器会为 callee() 产生以下的 stack frame 结构
callee:
1、push ebp
2、mov ebp, esp
3、sub esp, 0xc
... ...
4、mov eax, [ebp+8] /* 参数1 */
... ...
5、add esp, 0x0c
6、pop ebp
7、ret
-----------------------------------------------------------------
我的理解是:
1、从第1、2句来看,你是倾向于我之前回复提到的1),即在新桢创建之初(新桢的局部栈区是空的),将 ebp->FP,而这时的 FP 是上一桢的栈顶,当前桢的栈底。
2、从第4句来看(即:mov eax, [ebp+8]) ,如果上面第1点成立,即FP是上一桢的栈顶,则第4句的意思是:取”上一桢“的第一个参数?
都不知道怎样跟你说了,
你还是看表格吧
stack:
/ -----------
| XXX XXX
caller | -----------
\ eip callee() /* 调用函数 */
----------- \
esp ----> ebp | ===> push ebp
----------- |
XXX XXX |
----------- | ----> callee
XXX XXX |
----------- |
XXX XXX /
-----------
红色部分是 caller() 的 stack
蓝色部分是 callee() 的 stack
蓝色部分的 ebp 是由 push ebp 指令产生的入栈结果,esp 就指令这个地方
另外 call 指令产生的压栈动作,在现在 OS 来说,大多数情况是没有 CS 寄存器的
在跨段情况下才要压入 CS,现在的 OS 都是平坦的内存模式,所以一般的 call 是不需要 CS 入栈的
在产生 int /trap 时才需要压入 CS
mik,
你提到的:
stack:
/ -----------
| XXX XXX
caller | -----------
\ eip callee() /* 调用函数 */
----------- \
esp ----> ebp | ===> push ebp
----------- |
XXX XXX |
----------- | ----> callee
XXX XXX |
----------- |
XXX XXX /
-----------
红色部分是 caller() 的 stack
蓝色部分是 callee() 的 stack
---------------------------------------------------------------------
我的理解:
1、这跟我最初发帖时的示意图是类似的,即 caller()的”局部栈区“是”第一桢“, callee()的”局部栈区“是 第2桢。是这样吗?
2、你提到的”蓝色部分的 ebp 是由 push ebp 指令产生的入栈结果,esp 就指令这个地方“,不也就是说 ebp->FP , FP 是上一桢的栈顶。 不是吗?
3、关于之前的疑问。
以下2点,mik 你认为哪一点是对桢指针的正确理解?
对于“push ebp mov ebp,esp”,它的使用时机:
1)如果在创建一个新桢之初(也就是假设未来的新桢它处于地址为:1000~1100这样一个局部栈区),未来新桢的局部栈区是空的,在这个时候我们用“push ebp mov ebp,esp”,然后把 ebp->FP,那FP还是上一桢的栈顶啊。
2)如果在刚创建完一个新桢的时候(也就是说新桢的栈中已push进去要调用函数的局部变量,要保存的寄存器,返回值,参数等,且栈顶SP已指向要调用函数的CS和IP),在这个时候我们用“push ebp mov ebp,esp”,然后把 ebp->FP,那FP是新桢的栈顶。
4、有点提外的问题。
在看你对CS的分析之后,有关你之前提到的一个例子,里面有一句
”mov eax, [ebp+8] /* 参数1 */“
里面的数字8应该是 CS的偏移 + EIP的偏移, 是这样吗?
其实我之前对虎书的例子就是这样分析的,不知道是不是这样?如果CALL段内跳转,那CS是不压栈,那第一个参数的提取是不是要改成: mov eax, [ebp+4] /* 参数1 */, 是这样吗?
mik,谢谢你的回复,希望你能理解我的穷根究底(我感觉如果不把桢指针弄清楚,虎书里面的东西就肯定学不会)。