IA-32汇编语言笔记——堆栈的作用
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了IA-32汇编语言笔记——堆栈的作用相关的知识,希望对你有一定的参考价值。
- 记录汇编语言课笔记,可能有不正确的地方,欢迎指出
- 教材《新概念汇编语言》—— 杨季文
- 这篇文章对应书第二章 IA32处理器基本功能 3.1部分
文章目录
- 一、过程调用和返回指令
- (1)过程
- (2)过程调用指令
- (3)过程返回指令
- 二、参数传递
- (1)参数传递
- (2)参数传递方法
- 三、局部变量
一、过程调用和返回指令
(1)过程
-
过程
:汇编语言中的子程序称为过程(procedure),对应C语言中的函数。 - 调用子程序(过程、函数)在本质上是控制转移,它与无条件转移的区别是调用子程序要考虑返回
(2)过程调用指令
名称 | CALL(过程调用指令) |
格式 | |
动作 | 先把返回地址偏移(EIP内容)压入堆栈,然后给EIP赋值为目标LABEL的地址偏移,实现转移 |
注意 | |
| |
与无条件转移指令相比,过程调用指令CALL只是多了第一步(保存返回地址) |
(3)过程返回指令
名称 | RET(过程返回指令) |
格式 | |
动作 | 从堆栈弹出地址偏移,并送到指令指针寄存器EIP |
注意 | 通常,这个返回地址就是在执行对应的调用指令时所压入堆栈的返回地址 |
过程返回指令的使用应该与过程调用指令相对应 |
- 示例:
/*
dp32
·演示子程序的调用和返回,说明call和RET指令的作用
·调试时关注ESP的值,进出子程序时有压栈和出栈
·这里都是用寄存器传递参数的,影响堆栈的只有进/出子程序时保存/弹出原EIP位置
·特别注意,这里把子程序的汇编安排在return语句之后,这样能避免不经调用直接进入子程序。如果有编译器不支持这样写,需要用goto跳过
*/
#include <stdio.h>
char string[] = "abcde";
int main()
_asm
LEA ESI,string //指针寄存器ESI指向string首
MOV AX,[ESI] //从ESI取两个字节放入AX (ab)
CALL TUPPER //把这两个字节的字符小写转大写
MOV [ESI],AX
MOV AX,[ESI+2]
CALL TUPPER //把这两个字节的字符小写转大写(这里进栈了返回地址偏移,ret时自动平衡)
MOV [ESI+2],AX
MOV AL, [ESI+4]
CALL UPPER //把最后一个字节的字符小写转大写
MOV [ESI+4],AL
printf("%s\\n", string);
system("pause");
return 0;
_asm
//UPPER函数用于把AL中的字符小写转大写
UPPER:
CMP AL, a
JB UPPER2
CMP AL, z
JA UPPER2
SUB AL, 20H //确认是小写字符后,转为大写
UPPER2 :
RET
//TUPPER函数用于把AX中的两个字符小写转大写
TUPPER :
CALL UPPER
XCHG AH, AL
CALL UPPER
XCHG AH, AL
RET
二、参数传递
(1)参数传递
-
参数传递
:主程序在调用子程序时,往往要向子程序传递一些参数;同样,子程序运行后也经常要把一些结果返回给主程序。主程序与子程序之间的这种信息传递被称为参数传递 -
入口参数
:由主程序传给子程序的参数称为子程序的入口参数
出口参数
:由子程序传给主程序的参数称为子程序的出口参数 - 一般而言,子程序既有入口参数,又有出口参数。但有的子程序只有入口参数,而没有出口参数;少数子程序只有出口参数,而没有入口参数。
(2)参数传递方法
- 有多种传递参数的方法∶寄存器传递法、堆栈传递法、约定内存单元传递法和CALL后续区传递法等。有时可能同时采用多种方法,根据具体情况而事先约定好
- 常用的两种方法:
方法 | 说明 | 特点 |
寄存器传递参数 | 把参数放在约定的寄存器中,在主程序中将参数放入寄存器,在子程序中取出 | 实现简单和调用方便,但只适用于传递参数较少的情形 |
堆栈传递参数 | 主程序在调用子程序之前,把需要传递的参数依次压入堆栈,然后子程序从堆栈中取入口参数 | 不占用寄存器,也无需额外的存储单元。但较为复杂 |
- 示例:
/*
dp33
·演示利用堆栈传递子程序参数,子程序参数x,y,返回2x+5y+100
·注意平衡堆栈:esp和ebp的值在子程序调用前后是一致的
·如果反汇编C语言形式的这个函数,不要加_fastcall前缀,否则会用寄存器传参,不会生成这样的代码
*/
#include <stdio.h>
int main()
int sum = 0;
_asm
push 456 //参数y进栈,esp+=4
push 23 //参数x进栈,esp+=4
call cf34 //这里进栈了返回地址偏移(ret自动平衡)
add esp,8 //手动平衡堆栈(x,y)
mov sum,eax
printf("sum=%d\\n", sum);
system("pause");
return 0;
_asm
cf34:
push ebp //子程序返回时ebp要复原,这里先保存一下,esp+=4
mov ebp,esp //ebp指向栈顶(esp)——建立堆栈框架
mov eax,DWORD PTR[ebp+12] //之前入栈了3个值,这里地址往回找4*3=12,取出第一个参数y
mov ecx,DWORD PTR[ebp+8] //取第二个参数x
//计算2x+5y+100,存入eax
lea eax,DWORD PTR[eax+eax*4+100]
lea eax, DWORD PTR[eax+ecx*2]
pop ebp //回复ebp的值,平衡堆栈——撤销堆栈框架
ret
- 堆栈传递参数的堆栈变化示意
三、局部变量
- 局部变量是高级语言中的概念。所谓局部变量指对其的访问仅限于某个局部范围。在C语言中,局部的范围可能是函数,或者是复合语句。局部变量还有动态和静态之分。
- 堆栈可以用于安排动态局部变量
- 示例:
/*
dp36
·演示利用堆栈安排动态局部变量
·等价的C程序:
int cf36(int x,int y) //返回xy中较大的
int z;
z=x;
if(x<y)
z=y;
return z;
*/
#include <stdio.h>
int main()
int max = 0;
int x, y;
printf("输入两个整数:");
scanf("%d %d", &x, &y);
_asm
mov eax,y
push eax
mov eax,x
push eax
call cf36 //这里进栈了返回地址偏移
add esp,8 //平衡堆栈
mov max,eax
printf("max = %d\\n", max);
system("pause");
return 0;
_asm
cf36:
push ebp
mov ebp,esp //建立堆栈框架
push ecx //在堆栈安排局部变量z
mov eax,DWORD PTR[ebp+8] //eax取得参数x
mov DWORD PTR[ebp-4],eax //z=x
mov ecx, DWORD PTR[ebp+8] //ecx取得参数x
cmp ecx, DWORD PTR[ebp+12] //比较x和y
jge SHORT LN1cf36 //x>y则跳转,SHORT表示转移目的地就在附近
mov edx, DWORD PTR[ebp+12] //x<=y,则edx取得y,赋值给z
mov DWORD PTR[ebp-4],edx
LN1cf36:
mov eax,DWORD PTR[ebp-4] //eax取得z的值
mov esp,ebp //撤销局部变量z
pop ebp //撤销堆栈框架
ret
- 堆栈示意,安排局部变量并且由堆栈传递参数
以上是关于IA-32汇编语言笔记——堆栈的作用的主要内容,如果未能解决你的问题,请参考以下文章