java主程序怎样调用子程序_x86汇编之栈与子程序调用

本文介绍了x86架构中栈的原理和操作,包括ESP和EBP寄存器的作用。栈在函数调用时用于参数传递和局部变量存储,通过标准的入口和退出序列来管理栈空间。文中通过示例详细解释了如何使用汇编语言进行子程序调用,并阐述了保存EBP的原因。
摘要由CSDN通过智能技术生成

什么是栈

栈与普通数据结构所说的栈的概念是相似的,遵循后进先出原则。不同的是汇编中所说的栈是一个在内存中连续的保存数据的区域,也即是实际存在的内存区域,进栈和出栈遵循后进先出原则。

在x86架构中,栈是向下生长的,即栈顶指针小于栈底指针。

ESP

ESP是x86架构中用于保存当前栈顶位置的寄存器。更多详细内容请参阅参考资料[1]

下面的两对代码是相互等价的

入栈操作:

push eax

;修改栈顶指针

sub esp, 4 ; 由于是向下生长,所以esp - 4, 减去4是因为eax占4个字节

mov DWORD PTR SS:[ESP], eax ;放入esp指定的内存区域

出栈操作

pop eax

mov eax, dword ptr ss:[esp]

add esp, 4 ;理解同入栈,注意这两行代码顺序与入栈不同

清除栈顶数据

假如我们要清除栈顶的四个双字的数据,只需要修改ESP即可

add esp, 4 * 4 ; 一个双字占4个字节,共4个双字

EBP

栈的一个典型应用就是函数调用时的参数传递。ESP保存的是当前栈的栈顶指针,EBP保存的是当前stack frame的基址[2].

如[3]所述,在可执行环境中函数经常以stack frame的形式来进行参数传递和函数局部变量的访问。stack frame的概念使得每一个子程序(在汇编中函数通常称为子程序)都能够拥有独立的栈空间。当函数被调用时,以当前esp所在位置为基址创建了stack frame,当前的esp就是stack frame的栈帧基址,在执行其他命令之前需要把栈基址保存在ebp当中。

值得注意的是栈帧的概念是逻辑上的概念,实际上并不存在。一个进程仍然只是拥有一个栈,只是为了方便子程序内部的使用而引入了栈帧的概念。

standard entry sequence

有关更多在子程序调用中如何使用栈帧概念进行子程序调用请参阅[3:1].

一般而言,在子程序中首先要执行下面一段代码:

push ebp ;保存主调函数的栈帧基址

mov ebp, esp ;当前函数的栈帧基址

sub esp, X ;X表示函数中要用到的变量大小,用于分配空间

例如一个C程序的函数:

void MyFunction()

{

int a, b, c;

...

}

则对应汇编程序的进入代码为:

_MyFunction:

push ebp

mov ebp, esp

sub esp, 12 ;4 * 3, int 类型是dword

若对上面的代码有:

a = 10;

b = 5;

c = 2;

则对应的汇编为:

mov [ebp - 4], 10

mov [ebp - 8], 5

mov [ebp - 12],2

为什么保存ebp

为了更好的理解ebp,我们考虑下面带有参数的函数

vod MyFunction2(int x, int y, int z)

{

...

}

汇编代码如下:

_MyFunction2:

push ebp

mov ebp, esp

sub esp, 0; no local variables, most compilers will omit this line

当调用函数时MyFunction2(10,5, 2),在汇编中调用格式如下:

;通过栈进行参数传递

; 参数从右向左压入栈,这样第一个pop出来的数据即是第一个参数

push 2

push 5

push 10

call _MyFunction2

其中,call _MyFunction2等价于下列指令:

push eip + 2 ;return address is current address + size of two instructions

jmp _MyFunction2

进入到子程序之后就要执行entry sequence代码:

push ebp

mov ebp, esp

sub esp, X; X为局部变量需要的字节数目

此时在栈中的内容如下:

: :

| 2 | [ebp + 16] (3rd function argument)

| 5 | [ebp + 12] (2nd argument)

| 10 | [ebp + 8] (1st argument)

| RA | [ebp + 4] (return address)

| FP | [ebp] (old ebp value)

| | [ebp - 4] (1st local variable)

: :

: :

| | [ebp - X] (esp - the current stack pointer. The use of push / pop is valid now)

就目前看来似乎并没有必要使用ebp,因为单单使用esp也能够解决问题,但是利用esp访问变量是不可靠的,因此需要ebp去访问变量,因此需要保存旧的ebp的值。

Standard Exit Sequence

standard exit sequence是用于撤销standard entry sequence的。

void MyFunction3(int x, int y, int z)

{

int a, b, c;

...

return;

}

_MyFunction3:

push ebp

mov ebp, esp

sub esp, 12 ; sizeof(a) + sizeof(b) + sizeof(c)

;x = [ebp + 8]

;y = [ebp + 12]

;z = [ebp + 16]

;a = [ebp - 4] = [esp + 8]

;b = [ebp - 8] = [esp + 4]

;c = [ebp - 12] = [esp]

mov esp, ebp ; 这一步是直接把栈顶指针指向保存返回地址的地方

; 直接消除了局部变量的影响

pop ebp

ret 12 ; sizeof(x) + sizeof(y) + sizeof(z)

参考资料

x86 Disassembly/The Stack ↩︎

What is between ESP and EBP? ↩︎

x86 Disassembly/Functions and Stack Frames ↩︎ ↩︎

b739ec46bb5c46d9c0aa4ce35ba1ea56.png

关于找一找教程网

本站文章仅代表作者观点,不代表本站立场,所有文章非营利性免费分享。

本站提供了软件编程、网站开发技术、服务器运维、人工智能等等IT技术文章,希望广大程序员努力学习,让我们用科技改变世界。

[x86汇编之栈与子程序调用]http://www.zyiz.net/tech/detail-128870.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值