请教一下大家对桢指针的理解

发布于 2022-09-18 17:42:26 字数 3554 浏览 12 评论 0

各位,本人看的是虎书,请教一下大家对桢指针的理解。

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

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

划一舟意中人 2022-09-25 17:42:26

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

盗心人 2022-09-25 17:42:26

先顶一下

给我一枪 2022-09-25 17:42:26

桢指针就是以 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

毅然前行 2022-09-25 17:42:26

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

戈亓 2022-09-25 17:42:26

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

手心的温暖 2022-09-25 17:42:26

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

倾城泪 2022-09-25 17:42:26

都不知道怎样跟你说了,
你还是看表格吧

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

究竟谁懂我的在乎 2022-09-25 17:42:26

另外 call 指令产生的压栈动作,在现在 OS 来说,大多数情况是没有 CS 寄存器的

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

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

鹿港小镇 2022-09-25 17:42:26

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文