免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
123下一页
最近访问板块 发新帖
查看: 6669 | 回复: 20
打印 上一主题 下一主题

请教一下大家对桢指针的理解 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-05-22 21:12 |只看该作者 |倒序浏览
20可用积分
各位,本人看的是虎书,请教一下大家对桢指针的理解。

首先,本人说说自己的理解。

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结构 中的偏移量设置。

以上是我目前对桢指针的一些理解,请个位不吝指教,谢谢。

最佳答案

查看完整内容

你就不能自已画一画 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 eb ...

论坛徽章:
0
2 [报告]
发表于 2009-05-22 21:12 |只看该作者
你就不能自已画一画 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

论坛徽章:
0
3 [报告]
发表于 2009-05-22 21:14 |只看该作者

回复 #1 sherf 的帖子

先顶一下

论坛徽章:
0
4 [报告]
发表于 2009-05-22 21:57 |只看该作者
桢指针就是以 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

论坛徽章:
0
5 [报告]
发表于 2009-05-22 22:24 |只看该作者

回复 #3 mik 的帖子

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),你是指哪一种?

论坛徽章:
0
6 [报告]
发表于 2009-05-22 22:47 |只看该作者

回复 #4 sherf 的帖子

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
-------                   |

论坛徽章:
0
7 [报告]
发表于 2009-05-22 23:02 |只看该作者

回复 #5 mik 的帖子

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句的意思是:取”上一桢“的第一个参数?

论坛徽章:
0
8 [报告]
发表于 2009-05-22 23:15 |只看该作者
都不知道怎样跟你说了,
你还是看表格吧

                        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 就指令这个地方

论坛徽章:
0
9 [报告]
发表于 2009-05-22 23:20 |只看该作者
另外 call 指令产生的压栈动作,在现在 OS 来说,大多数情况是没有 CS 寄存器的

在跨段情况下才要压入 CS,现在的 OS 都是平坦的内存模式,所以一般的 call 是不需要 CS 入栈的

在产生 int /trap 时才需要压入 CS

论坛徽章:
0
10 [报告]
发表于 2009-05-22 23:41 |只看该作者

回复 #7 mik 的帖子

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,谢谢你的回复,希望你能理解我的穷根究底(我感觉如果不把桢指针弄清楚,虎书里面的东西就肯定学不会)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP