汇编学习-堆栈与子程序

任何程序在运行过程中都需要使用堆栈,操作系统为每一个程序(进程及线程)设置一个堆栈。在使用高级语言编程时,源程序中使用的函数调用、局部变量都要用到堆栈,由编译器来负责生成有关的机器指令。我的理解,堆栈就是维护当前线程中运行状态的一个数据结构,这种状态包括:需要传递的变量,函数的返回地址,局部变量等等。

与堆栈相关的 3 个寄存器是:SS, ESP, EBP。

ESP 寄存器中的内容作为堆栈的当前指针。PUSH, POP, CALL, RET 等指令都与堆栈有关,使用 SS:ESP 指向堆栈单元。

EBP 寄存器中的内容作为堆栈的“基准”指针。SS:EBP 指向的地址作为基准地址。在函数(子程序)内部,可以使用 [EBP+立即数] 的形式来取得主程序传递的参数,使用 [EBP-立即数] 的形式来访问局部变量。

从上述的描述可以看出,整个堆栈的是一个经典的先入后出的栈结构,在栈中还存在着“栈帧”这样的结构,用来表示当前函数运行的环境,里面存放着当前函数的局部变量,所需的返回地址。栈帧由 EBP 所指出,EBP 和 ESP 之间即为当前函数帧,调用函数和被调用函数的栈帧是相邻存放,这样即可通过 EBP加减固定的数,来获取主调函数所传递的函数参数。

上述的堆栈结构如下:

堆栈是经过精心设计的结构,使得编程人员能够方便的设计函数来实现结构化设计,高级语言也得益于这种结构,c 语言基本上就是汇编的一种简单的翻译。这里要强调的是,在用汇编写程序的时候,完全可以不按这种结构来设计,比如参数我们可以放到指定的内存区,被调用的函数到指定的内存区去取出传递给它的参数;也可以让函数参数全部通过寄存器来传送,尤其是在 ARM 这种寄存器特别多,而且每个寄存器的地位都相同的处理器之中。之所以要设计出堆栈,就是为了能够以统一的方式来编写程序。

对于 c 语言,函数有好几种调用规则。最常见的是两种,cdecl 方式和 stdcall 方式。

Cdecl 方式

(1)使用堆栈传递参数

(2)主程序按从右向左的顺序将参数逐个压栈,最后一个参数先入栈。每一个参数压栈一次。

(3)在子程序中,使用 [EBP+X] 的方式来访问参数。X=8 代表第 1 个参数;X=12 代表第二个参数,依次类推

(4)子程序用 RET 指令返回。

(5)由主程序执行“ADD ESP, n”指令调整 ESP,达到堆栈平衡。

(6)一般返回值放在 EAX 中

Stdcall 方式

与Cdecl的不同是,堆栈的平衡不是由主程序完成,而是由子程序通过调用“RET n”指令主动平衡。

这些调用规则,都是针对 c 语言来说的,在 c 语言和汇编需要互相调用的情况;c 语言编写的不通的二进制函数库之间,都要注意函数的调用规则。

下面举一个普通的例子

AddProc1    proc   ; cdecl方式
    push    ebp    ; 记下上一个栈帧地址
    mov     ebp, esp
    mov     eax, dword ptr [ptr+8]  ; 取第一个参数
    add     eax, dword ptr [ptr+12] ; 取第二个参数
    pop     ebp    ; 恢复栈帧为调用者,但这时候堆栈还未平衡
    ret
AddProc1    endp
AddProc2    proc   ; stdcall
    push    ebp
    mov     ebp, esp
    mov     eax, dword ptr [ptr+8]
    add     eax, dword ptr [ptr+12]
    pop     ebp
    ret     8    ; 和前面一样,唯一不同是,直接由子程序恢复堆栈,8 正好是两个参数的长度
AddProc2    endp
start:
    push    20
    push    10
    call    AddProc1
    add     esp, 8   ; 由主调函数恢复堆栈
    push    50
    push    60
    call    AddProc2
    ret
end start 

时间: 2024-10-03 06:05:17

汇编学习-堆栈与子程序的相关文章

AT&T汇编学习笔记

AT&T汇编和intel汇编的区别 (1)在Intel格式中大多使用大写字母,而在AT&T格式中都是用小写字母. (2)在AT&T格式中,寄存器名要加上"%"作为前缀,而在intel格式中则不带前缀. (3)在AT&T的386汇编语言中,指令的源操作数与目标操作数的顺序与在intel的386汇编语言中正好相反.在intel格式中是目标在前,源在后:而在AT&T格式中则是源在前,目标在后.例如,将寄存器eax的内容送入ebx,在intel格式中为&

Arm汇编学习总结

 Arm汇编学习总结: 1. LDR/STR架构 1).ARM采用RISC架构,CPU本身不能直接读取内存,而需要先将内存中内容加载入CPU中通用寄存器中才能被CPU处理. 2).ldr(load register)指令将内存内容加载入通用寄存器. 3).str(store register)指令将寄存器内容存入内存空间中. 4).ldr/str组合用来实现 ARM CPU和内存数据交换. 3级流水线如图所示(PC为程序计数器),流水线使用3个阶段,因此指令分3个阶段执行. ⑴ 取指从存储器装载

基于8086CPU微处理器的汇编学习之MOV指令

汇编指令:MOV的作用是往某个寄存器中存入数值. 格式:mov  寄存器名,数值                数值-->寄存器 mov  寄存器A,存器寄B          B-->A PS:必须前后位数匹配,如: mov   ah,bx     ;error   ah is 8 bit,bx is 16 bit mov   ah, bh    ;right    ah and bh all is 8  bit mov   cx,dx     ;right     cx and dx al

基于8086CPU微处理器的汇编学习之ADD指令

ADD指令: 把两个数值相加,将结果放到第一个寄存器里面. 格式: ADD  寄存器名,数值 ADD  寄存器名,寄存器名 add ax,11 add bx,22 PS:清空数据: mov ax,0000 两者数值的存储容量位数要匹配 ----------------------------------------------------------------------------------------------- 用汇编语言编写一个小程序:1122H + 9000H +   AC02H

8086汇编学习小结———实时更新

初学IBM-PC 8086,对INT指令不是很理解.现从网上总计如下: 表:DOS系统功能调INT 21H AH 功能 调用参数 返回参数 00 程序终止(同INT 20H) CS=程序段前缀 01 键盘输入并回显 AL=输入字符 02 显示输出 DL=输出字符 03 异步通迅输入 AL=输入数据 04 异步通迅输出 DL=输出数据 05 打印机输出 DL=输出字符 06 直接控制台I/O DL=FF(输入)DL=字符(输出) AL=输入字符 07 键盘输入(无回显) AL=输入字符 08 键盘

汇编学习笔记-序章

最近突然对汇编语言开始感兴趣,于是说干就干了. 之前也自学过一点汇编,是跟着王爽老师的<汇编语言(第3版) >这本书学习的,已经是有5 6前年的样子了.当时觉得这本书写的非常通俗易懂是一本非常好的启蒙书籍,但是最近在翻阅的时候却觉得这本书知识点介绍的非常杂乱没有个章程,感觉像是没组织过一样想到哪里写到哪里.哈哈,个人愚见,王爽老师的粉丝不要喷我. 于是乎我去各种百度汇编学习的书籍推荐,在知乎.csdn找到了好几本推荐的书,然后就一股脑买了4本.所以我简单的根据书名排了个阅读顺序: 80x86汇

AT&T汇编学习笔记(一)

file命令使用介绍 file最常用的场景就是用来查看可执行文件的运行环境,是arm呢,还是x86呢,还是mips呢?一看便知 $ file a.out a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0xa240b1958136fc294a6ee5833de2a0fc8c9e

汇编学习环境搭建

一 系统环境 win10 pro x86 masm 5.0 下载地址:http://download.pchome.net/development/linetools/detail-9028.html 二 编译环境搭建 1 将masm 5.0 下载下来,这个是免安装版的, 解压缩, 将MASM.EXE所在的目录添加到环境变量path里面. 2 使用方法 <1> 在cmd窗口(注意这个cmd跟下面的command不是一个东西)输入"masm 汇编源码文件;", 回车, 如果编

Android ARM 汇编学习(一)

给自己挖了个坑,一切都得从"Hello World"开始. hello.S .data msg:     .ascii      "Hello, World!\n" len = . - msg .text .globl _start _start:     /* syscall write(int fd, const void *buf, size_t count) */     mov     %r0, $1     /* fd -> stdout */