c语言截尾的方式,关于c ++:C中的函数序言和结尾

本文介绍了C语言中函数调用时堆栈的行为,特别是关于函数序言(Prologue)和尾声(Epilogue)的过程。序言用于设置函数的堆栈帧,保存旧的EBP并设置新的EBP,然后调整ESP以分配空间给局部变量。尾声则负责恢复堆栈到调用函数的状态,通过调整ESP和恢复EBP,然后使用RET指令返回。内容详细解释了这些过程为何如此设计,并讨论了调用约定的角色,特别是x86的cdecl约定。
摘要由CSDN通过智能技术生成

我知道嵌套函数调用中的数据会进入堆栈。堆栈本身实现了一种逐步方法,用于在函数被调用或返回时从堆栈中存储和检索数据。这些方法的名称最著名的是Prologue和 结语。

我尝试搜索有关此主题的材料没有成功。 你们是否了解有关函数序言和尾声在C语言中通常如何工作的任何资源(网站,视频,文章)? 或者,如果您能解释会更好。

附注:我只想提供一些一般性的看法,而不是太详细。

您可以搜索有关编译器和代码生成的材料。

有很多资源可以解释这一点:

功能序言(Wikipedia)

x86拆卸/调用约定(WikiBooks)

编写Prolog / Epilog代码(MSDN)的注意事项

仅举几例。

基本上,正如您所描述的那样,"堆栈"在程序执行中有几个用途:

调用函数时跟踪返回的位置

在函数调用的上下文中存储局部变量

将参数从调用函数传递给被调用者。

问题是在函数开始时发生的事情。它的责任是建立被调用函数的堆栈框架。结语正好相反:它是函数中最后发生的事情,其目的是恢复调用(父)函数的堆栈框架。

在IA-32(x86)cdecl中,该语言使用ebp寄存器来跟踪函数的堆栈帧。处理器使用esp寄存器指向堆栈上的最新加法(最高值)。

call指令有两件事:首先,它将返回地址压入堆栈,然后跳转到被调用的函数。 call之后,esp指向堆栈上的返回地址。

然后执行序言:

push  ebp         ; Save the stack-frame base pointer (of the calling function).

mov   ebp, esp    ; Set the stack-frame base pointer to be the current

; location on the stack.

sub   esp, N      ; Grow the stack by N bytes to reserve space for local variables

在这一点上,我们有:

...

ebp + 4:    Return address

ebp + 0:    Calling function's old ebp value

ebp - 4:    (local variables)

...

结语:

mov   esp, ebp    ; Put the stack pointer back where it was when this function

; was called.

pop   ebp         ; Restore the calling function's stack frame.

ret               ; Return to the calling function.

只是为了完整起见,值得一提的是ret指令执行与call指令相反的操作,即ret指令还必须做两件事-使用esp将返回地址弹出堆栈,然后跳转到该地址 从那里恢复执行。

这就引出了一个问题,即谁清理何时传递的参数? 我的猜测是,根据x86 cdecl调用约定,调用者必须是在调用调用指令之前推送args的那个,因此,必须是同一调用者,在调用之后需要从堆栈中清除args。 ret指令。

C函数调用约定和堆栈很好地解释了调用堆栈的概念

功能序言简要解释了汇编代码以及操作方式和原因。

功能外生基因

我参加聚会已经很晚了,我敢肯定,自从提出问题以来的最近7年中,您对事情有了更清晰的了解,当然,如果您选择进一步探讨这个问题。但是,我认为我仍然会特别关注序言和尾声部分的原因。

同样,被接受的答案优雅而简洁地解释了结尾词和序言的方式,并提供了很好的参考。我只打算用"为什么"(至少是逻辑上的"为什么")部分补充这个答案。

我将从接受的答案中引用以下内容,并尝试扩展其解释。

In IA-32 (x86) cdecl, the ebp register is used by the language to keep

track of the function's stack frame. The esp register is used by the

processor to point to the most recent addition (the top value) on the

stack.

Ok.

The call instruction does two things: First it pushes the return

address onto the stack, then it jumps to the function being called.

Immediately after the call, esp points to the return address on the

stack.

Ok.

上面引号的最后一行是immediately after the call, esp points to the return address on the stack.

为什么?

因此,假设我们当前正在执行的代码具有以下情况,如下图(非常糟糕地绘制)所示:

6287713bda5249a7a420d09629c03fa4.png

因此,我们要执行的下一条指令位于地址2。这就是EIP指向的位置。当前指令具有一个函数调用(它将在内部转换为汇编call指令)。

现在,理想情况下,由于EIP指向下一条指令,因此实际上就是要执行的下一条指令。但是,由于当前执行流路径有所偏离,(现在由于call而可以预料)EIP的值将发生变化。为什么?因为现在可能需要执行另一条指令,该指令可能位于其他地方,例如地址1234(或其他位置)。但是,为了按照程序员的意图完成程序的执行流程,在完成转移活动之后,控件必须返回到地址2,因为如果没有发生转移,则下一步应该执行该地址。让我们将此地址2称为正在生成的call的return address。

问题1

因此,在实际发生转移之前,必须将返回地址2临时存储在某个地方。

将其存储在任何可用寄存器或某个内存位置等中的选择可能很多,但是出于(我相信有充分的理由)决定将返回地址存储在堆栈中。

因此,现在需要做的是增加ESP(堆栈指针),以使堆栈顶部现在指向堆栈上的下一个地址。因此,指向地址(例如292)的TOS(递增之前的TOS)现在递增并开始指向地址293。这就是我们放置return address 2的位置。所以像这样:

608334eba4591986055237ae70b524c2.png

这样看来,现在我们已经实现了将返回地址临时存储在某个地方的目标。现在我们应该使转移call。我们可以。但是有一个小问题。在执行被调用函数期间,可以多次操作堆栈指针以及其他寄存器值。

问题2

因此,尽管我们的返回地址仍然存储在堆栈中的位置293中,在被调用函数完成执行之后,执行流程如何知道它现在应该转到293并在其中找到返回地址?

因此(再次相信,有充分的理由)解决上述问题的一种方法是将堆栈地址293(返回地址在其中)存储在称为EBP的(指定的)寄存器中。但是,EBP的内容又如何呢?那不会被覆盖吗?当然,这是有道理的。因此,让我们将EBP的当前内容存储到堆栈上,然后将此堆栈地址存储到EBP中。像这样:

df20ffaf3efd832e5f8d1a56c4900f99.png

堆栈指针增加。 EBP的当前值(表示为EBP'),即xxx,存储在堆栈的顶部,即地址294。现在,我们已备份了EBP的当前内容,可以放心地放EBP上的任何其他值。因此,我们将当前堆栈顶部的地址(即地址294)放在EBP中。

有了上述策略,我们就可以解决上述问题2。怎么样?因此,现在当执行流想要知道应该从哪里获取返回地址时,它将:

首先从EBP中获取值,然后将ESP指向该值。在我们的情况下,这将使TOS(堆栈顶部)指向地址294(因为这是存储在EBP中的内容)。

然后它将恢复先前的EBP值。为此,只需将值294(TOS)取为xxx(实际上是EBP的旧值),然后将其放回EBP。

然后它将使堆栈指针递减,以转到堆栈中的下一个较低地址,在本例中为293。从而最终达到293(请参阅第2个问题)。在那里可以找到寄信人地址,即2。

最终它将把这2弹出到EIP中,记住,如果没有发生转移,则应该执行该指令。

我们刚才看到的所有杂物都将临时执行返回地址,然后取回地址,这正是通过prolog函数(在函数call之前)和Epilog(在函数< x1>)。 方法已经被回答,我们也只是回答了原因。

只是个结尾说明:为简洁起见,我没有考虑堆栈地址可能会反过来增长这一事实。

好。

每个函数都有相同的序言(函数代码的开头)和结尾(函数的结尾)。

序言:序言的结构如下:

推息

mov esp,ebp

结语:序言的结构如下:

离开

退回

更详细:什么是序言和结尾

取决于调用约定

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值