汇编proto、proc、invoke伪指令与函数声明、函数定义、函数调用;与实现

本文详细介绍了汇编语言中使用proto、proc和invoke伪指令进行函数声明、定义及调用的方法。通过实例展示了如何在32位汇编中声明函数、定义函数体以及调用函数,并解释了参数传递和调用约定的细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

伪指令调用函数

一、proto伪指令–函数声明

功能和高级语言中的函数声明一样,在代码最前面写函数声明,在后面写函数定义

proto伪指令的格式
函数名 proto [距离] [语言] [参数1]:数据类型,[参数2]:数据类型,……

代码示例:

Asm_Function_1 proto stdcall arg1:dword,arg2:dword

注意事项:

32位汇编不需要指定距离
可以指定语言也可也不指定,直接写参数,这里的语言就是和调用约定对等的。写“C”就是cdecl调用约定,写“stdcall”就是stdcall调用约定
proto指令、距离、语言和参数之间用空格隔开
在参数与参数之间用“,”隔开
在参数与参数对应的数据类型之间用“:”隔开
可以不用写参数名称,但必须要写上数据类型和“:”

二、proc伪指令–函数定义

使用proto指令用来函数声明,使用proc函数用来函数定义。使用规则和proto指令一样

代码示例:

proc stdcall arg1:dword,arg2:dword
  函数体代码块...
Asm_Function_1 endp
注意事项:

函数定义语句和proto一样,写完函数体代码之后需要以[函数名] endp结束函数定义
但proc和proto搭配使用时,proto可以省略参数名,但proc不能省略。(和C中的规则一模一样)

三、invoke伪指令–函数调用

使用invoke伪指令会帮你完成参数校检和压参操作,也就是说不用写压参的push指令。直接和高级语言一样直接调用函数即可

invoke伪指令的格式
invoke 函数名[,参数1][,参数2]……
代码示例:
invoke Asm_Function_1,100,0x100

注意事项:

函数名称和参数,参数和参数之间都用 空格 隔开

四、测试代码

.386
.MODEL FLAT, C       
.DATA

.CODE
;------------------------------------------------------
;函数声明
Asm_Function_1 proto stdcall :dword,:dword

;------------------------------------------------------
;主函数
main_proc PROC
    ;函数调用
    invoke    Asm_Function_1,100,100h
    mov        edx,eax
main_proc ENDP

;------------------------------------------------------
;函数定义,实现简单功能 返回 arg1 + arg2 的值
Asm_Function_1 PROC  stdcall arg1:dword,arg2:dword
    mov        eax,arg1
    add        eax,arg2
    ret    8
Asm_Function_1 endp

END

参考文章:
原文:https://blog.csdn.net/u011770174/article/details/77914375

不使用伪指令的函数实现

; 定义使用 32 位指令集
BITS 32

; 数据段定义
SECTION .data
    ; 这里可以定义数据,当前示例没有实际数据需求,可根据实际情况添加
    ; 例如定义一个字符串:
    ; message db 'Hello, World!', 0

; 代码段定义
SECTION .text
    ; 声明全局入口点
    GLOBAL _start

; 主程序部分
_start:
    ; 手动将参数压入栈
    push 100h
    push 100
    ; 调用函数
    call Asm_Function_1
    ; 清理栈上的参数
    add esp, 8
    ; 将函数返回值从 eax 移动到 edx
    mov edx, eax

    ; 程序结束,返回操作系统
    mov eax, 1      ; 系统调用号 1 表示退出程序
    xor ebx, ebx    ; 返回状态码 0
    int 0x80        ; 执行系统调用

; 函数实现部分
Asm_Function_1:
    ; 将第一个参数(arg1)移动到 eax
    mov eax, [esp + 4]
    ; 将第二个参数(arg2)加到 eax 上
    add eax, [esp + 8]
    ; 返回
    ret

不使用call的实现

按照正常的函数调用流程,应该先压入参数,再压入返回地址,这样才能保证在子程序里正确地获取参数。下面是修正后的代码:

; 定义使用 32 位指令集
BITS 32

; 数据段定义
SECTION .data
    ; 此数据段当前未使用,可根据实际需求添加数据
    ; 例如定义一个提示信息:
    ; prompt db 'The result is: ', 0

; 代码段定义
SECTION .text
    ; 声明全局入口点
    GLOBAL _start

_start:
    ; 手动压入参数
    push 100h
    push 100
    ; 手动保存返回地址
    lea eax, [after_Asm_Function_1]
    push eax
    ; 跳转到子程序
    jmp Asm_Function_1_code

after_Asm_Function_1:
    ; 清理栈上的参数
    add esp, 8
    ; 将函数返回值从 eax 移动到 edx
    mov edx, eax
    ; 程序结束,返回操作系统
    mov eax, 1      ; 系统调用号 1 表示退出程序
    xor ebx, ebx    ; 返回状态码 0
    int 0x80        ; 执行系统调用

; 函数实现部分
Asm_Function_1_code:
    ; 从栈中获取参数
    mov eax, [esp + 4] ; 第一个参数 arg1
    add eax, [esp + 8] ; 加上第二个参数 arg2
    ; 手动恢复返回地址并跳转
    pop eax
    jmp eax

代码解释

主程序部分 (_start)
  1. 传递参数:运用 push 指令依次把参数 100100h 压入栈,模拟函数调用时的参数传递。
  2. 保存返回地址:使用 lea 指令获取 after_Asm_Function_1 标签的地址,接着将其压入栈,该地址就是子程序执行完毕后要返回的位置。
  3. 跳转到子程序:通过 jmp 指令跳转到 Asm_Function_1_code 标签处开始执行子程序。
子程序部分 (Asm_Function_1_code)
  1. 获取参数:借助栈指针 esp 的偏移量来获取传入的参数,[esp + 4] 是第一个参数 arg1[esp + 8] 是第二个参数 arg2
  2. 计算结果:把 arg1 移到 eax 寄存器,再加上 arg2,结果存于 eax 中。
  3. 返回主程序:使用 pop 指令从栈中弹出之前保存的返回地址到 eax 寄存器,再用 jmp 指令跳转到该地址,从而回到主程序继续执行。
主程序后续部分 (after_Asm_Function_1)
  1. 清理栈上的参数:使用 add esp, 8 清理栈上的两个参数(每个参数 4 字节)。
  2. 处理返回值:将子程序的返回值(存于 eax 中)移到 edx 寄存器。
  3. 程序结束:通过系统调用 int 0x80 退出程序,返回状态码为 0。

编译和运行

在 Linux 系统中,你可以用以下命令来编译和运行这个汇编程序:

# 汇编代码
nasm -f elf32 your_file.asm -o your_file.o
# 链接生成可执行文件
ld -m elf_i386 your_file.o -o your_file
# 运行可执行文件
./your_file

请把 your_file 替换成你实际保存代码的文件名。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值