汇编语言零基础入门教程(x86架构·全实操版)
文档说明
本教程专为零基础编程爱好者设计,聚焦最常用的x86架构汇编语言,兼顾理论讲解与实操落地,避免复杂晦涩的专业术语堆砌,每一个知识点都配套对应代码示例和实操步骤,看完就能动手编写运行汇编程序。
适用环境:Windows系统(主力讲解MASM编译器)、Linux系统(补充NASM编译器操作)
适用场景:计算机底层原理学习、逆向工程入门、嵌入式开发基础、计算机专业课程辅助
学习建议:按章节顺序学习,每节内容先理解理论,再动手实操代码,遇到报错先对照“常见问题”排查,避免跳过基础直接进阶。
目录
-
汇编语言基础认知(搞懂“为什么学”“是什么”)
-
学习前准备(工具安装与环境配置)
-
汇编核心基础:寄存器、内存与栈
-
第一个汇编程序(Hello World·手把手实操)
-
基础指令详解(数据传输、运算、跳转)
-
流程控制(条件判断、循环结构)
-
函数调用与栈的实际应用
-
Linux环境下NASM汇编补充
-
常见报错与解决方案
-
学习进阶建议与资源推荐
第一章 汇编语言基础认知
1.1 什么是汇编语言?
汇编语言(Assembly Language)是机器语言的符号化表示,是连接人类可读语言与计算机底层硬件的“桥梁”,也是最接近计算机硬件的编程语言。
我们可以通过三层对比,快速理解汇编的定位:
-
机器语言:由0和1组成的二进制指令(如10110000),计算机可直接执行,但人类难以记忆和编写;
-
汇编语言:用英文单词(称为“助记符”,如MOV、ADD)代替二进制指令,用标识符代替内存地址,人类可读写,需通过编译器转为机器语言才能执行;
-
高级语言(C/Java/Python):语法接近人类自然语言,屏蔽了底层硬件细节,需通过编译/解释器先转为汇编语言,再转为机器语言执行。
简单来说:汇编语言 = 机器语言 + 人类可读的符号,学会汇编,就能直接“指挥”计算机硬件工作。
1.2 为什么要学汇编语言?
很多初学者会疑惑“有高级语言,为什么还要学汇编?”,核心原因有3点:
-
理解计算机底层原理:汇编直接操作CPU、内存、寄存器,学完能彻底搞懂“高级语言的代码最终是怎么在计算机上运行的”,比如变量存储、函数调用的底层逻辑;
-
特定场景必备:逆向工程(破解、漏洞分析)、嵌入式开发(单片机、物联网设备)、操作系统内核开发,都需要汇编基础;
-
提升编程思维:汇编语法简单但逻辑严谨,能培养“底层思维”,帮助你写出更高效、更严谨的高级语言代码(比如理解内存溢出、性能优化的本质)。
1.3 汇编语言的分类与架构
汇编语言与CPU架构强绑定,不同架构的CPU(如x86、ARM),汇编指令和语法完全不同,本教程聚焦最常用的x86架构(适用于Windows、Linux桌面端、服务器,是学习汇编的首选)。
常见汇编编译器(将汇编代码转为机器语言的工具):
-
MASM:微软开发的x86汇编编译器,适配Windows系统,语法简洁,适合零基础入门;
-
NASM:跨平台(Windows、Linux、Mac)x86汇编编译器,语法更灵活,在Linux环境中更常用;
-
TASM:Borland公司开发,与MASM语法兼容,早年常用,目前逐渐被MASM、NASM替代。
本教程以“Windows + MASM”为主线,最后补充Linux + NASM的操作,兼顾不同环境需求。
第二章 学习前准备(工具安装与环境配置)
2.1 所需工具(Windows系统)
零基础无需复杂配置,只需安装3个工具,全程傻瓜式操作:
-
汇编编译器:MASM32(集成了MASM编译器、链接器、编辑器,一站式搞定);
-
文本编辑器:Notepad++(推荐,支持汇编语法高亮,方便编写代码;也可使用记事本、VS Code);
-
命令行工具:Windows自带的CMD(用于执行编译、链接、运行命令)。
2.2 MASM32安装步骤(手把手)
-
下载MASM32:百度搜索“MASM32官方下载”,选择最新版本(如MASM32 SDK v11),下载后得到压缩包;
-
解压安装:将压缩包解压到任意路径(建议路径无中文、无空格,如D:\MASM32,避免后续报错);
-
验证安装:打开CMD,输入“masm”,按回车,若出现“Microsoft (R) Macro Assembler Version 6.14.8444”,说明安装成功;若提示“不是内部或外部命令”,需配置环境变量。
2.3 环境变量配置(解决“masm命令不可用”)
-
右键“此电脑”→“属性”→“高级系统设置”→“环境变量”;
-
在“系统变量”中找到“Path”,双击打开;
-
点击“新建”,输入MASM32的“bin”文件夹路径(如D:\MASM32\bin),点击“确定”保存;
-
关闭所有CMD窗口,重新打开,输入“masm”,验证是否能正常显示版本信息。
2.4 Linux系统准备(补充)
若使用Linux系统(如Ubuntu),无需手动安装复杂工具,打开终端,输入以下命令即可安装NASM编译器:
sudo apt updatesudo apt install nasm # 安装NASM编译器sudo apt install gcc # 用于链接生成可执行文件(可选)安装完成后,输入“nasm -v”,显示版本信息即安装成功。
第三章 汇编核心基础:寄存器、内存与栈
汇编语言的核心是“直接操作硬件”,而CPU的寄存器、内存、栈,是我们操作的核心对象——就像做饭时的“锅碗瓢盆”,必须先熟悉它们的用途,才能写出正确的汇编代码。
3.1 寄存器(CPU的“临时仓库”)
寄存器是CPU内部的高速存储单元,速度远快于内存,用于临时存储数据、地址、指令,x86架构中,常用寄存器分为3类,我们先掌握最基础的通用寄存器:
3.1.1 通用寄存器(8个,32位模式下)
| 寄存器名称 | 英文缩写 | 核心用途(零基础重点记) |
|---|---|---|
| 累加器 | EAX | 用于算术运算、数据传输,是最常用的寄存器(比如存储运算结果) |
| 基址寄存器 | EBX | 用于存储内存地址、基址数据(辅助访问内存) |
| 计数寄存器 | ECX | 用于循环计数、字符串操作(比如循环执行的次数) |
| 数据寄存器 | EDX | 辅助EAX进行运算,存储额外数据(比如乘法运算的高位结果) |
| 栈指针寄存器 | ESP | 指向栈顶地址,管理栈的操作(后续讲栈会详细说) |
| 基址指针寄存器 | EBP | 指向栈底地址,用于函数调用时保存栈帧 |
| 源变址寄存器 | ESI | 字符串操作时,指向源数据地址 |
| 目的变址寄存器 | EDI | 字符串操作时,指向目的数据地址 |
补充:x86架构支持16位、32位、64位模式,32位模式下寄存器前缀为“E”(如EAX),16位模式下前缀为“AX”(如AX),8位模式下为“AL”(低8位)或“AH”(高8位),零基础先聚焦32位模式。
3.2 内存(程序的“长期仓库”)
内存是用于存储程序代码、变量、数据的存储空间,速度比寄存器慢,但容量大。汇编语言中,我们通过“内存地址”来访问内存中的数据,就像通过“门牌号”找到对应的房间。
关键注意点:
-
内存地址是十六进制数(如0x00400000),x86架构中,32位系统的内存地址范围是0x00000000 ~ 0xFFFFFFFF;
-
汇编中,变量本质上就是一段内存地址的“别名”,比如定义一个变量var,其实就是给某段内存地址起了个名字,方便我们访问;
-
内存的访问速度远慢于寄存器,因此汇编编程中,会尽量将常用数据放到寄存器中,提升程序效率。
3.3 栈(程序的“临时中转站”)
栈是内存中的一块特殊区域,遵循“先进后出”(FILO)的原则,就像一个叠盘子的容器,只能从最上面取盘子,也只能往最上面放盘子。栈是内存中的一块特殊区域,遵循“先进后出”(FILO)的原则,就像一个叠盘子的容器,只能从最上面取盘子,也只能往最上面放盘子——先放入的内容会被压在最下面,后放入的内容在最上面,取出时只能先取最上面的内容。
栈的核心用途(零基础重点记,后续函数调用会高频用到):
栈的核心用途(零基础重点记):
-
保存函数调用的返回地址:当我们调用一个函数时,CPU需要记住“调用完函数后,回到原来代码的哪个位置继续执行”,这个返回地址会被自动压入栈中,函数执行完毕后,再从栈中取出地址,跳回原位置。
-
保存函数调用时的返回地址(比如调用完函数后,能回到原来的代码位置继续执行);
-
保存函数的参数和局部变量:函数运行时需要的参数(比如加法函数的两个加数)、临时使用的局部变量,会被压入栈中,避免占用寄存器导致数据冲突,函数执行结束后,这些数据会从栈中弹出,释放空间。
-
保存函数的参数、局部变量(避免局部变量占用寄存器,导致数据冲突);
-
临时存储数据(比如运算过程中,暂时用不到的数据,先放到栈中,后续再取出)。
补充:栈指针寄存器ESP始终指向栈顶,每次往栈中存放数据(压栈,指令PUSH),ESP的值会减小;每次从栈中取出数据(出栈,指令POP),ESP的值会增大。
第四章 第一个汇编程序(Hello World·手把手实操)
掌握了核心基础后,我们动手编写第一个汇编程序——打印“Hello World”,全程实操,每一步都有详细说明,确保零基础也能运行成功。
本章节使用:Windows + MASM32 + Notepad++
4.1 编写汇编代码
-
打开Notepad++,新建一个文本文件,保存为“hello.asm”(注意:保存路径无中文、无空格,如D:\ASM\hello.asm,后缀必须是.asm,代表汇编文件);
-
复制以下代码到文件中,每一行代码后面都有注释,看不懂没关系,先复制运行,后续再讲解含义:
; 第一个汇编程序:打印Hello World; 注释:汇编中,用分号(;)开头的内容是注释,不会被编译器执行.MODEL SMALL ; 定义内存模型为SMALL(小模型,适合小程序).STACK 100H ; 定义栈的大小为100H(256字节,足够本程序使用).DATA ; 数据段:用于定义变量、常量 MSG DB 'Hello World!',0DH,0AH,'$' ; 定义字符串MSG,0DH=回车,0AH=换行,$是字符串结束标志.CODE ; 代码段:用于编写汇编指令MAIN PROC ; 定义主函数(程序入口) MOV AX,@DATA ; 将数据段地址送入AX寄存器 MOV DS,AX ; 将AX中的数据段地址送入DS寄存器(DS是数据段寄存器,指向数据段)
; 打印字符串MSG(调用DOS中断功能) MOV AH,09H ; AH=09H,表示调用DOS中断的“打印字符串”功能 LEA DX,MSG ; 将字符串MSG的地址送入DX寄存器(DX存储字符串地址) INT 21H ; 触发DOS中断,执行打印操作
; 程序退出(调用DOS中断,正常退出) MOV AH,4CH ; AH=4CH,表示调用DOS中断的“程序退出”功能 INT 21H ; 触发DOS中断,退出程序MAIN ENDP ; 主函数结束END MAIN ; 程序结束,指定入口为MAIN4.2 编译与链接(将汇编代码转为可执行文件)
汇编代码无法直接运行,需要通过“编译”(转为目标文件.obj)和“链接”(转为可执行文件.exe)两个步骤,操作如下:
-
打开CMD,切换到代码保存的路径(比如代码保存在D:\ASM,输入“cd D:\ASM”,按回车);
-
执行编译命令:输入“masm hello.asm”,按回车,此时会提示输入目标文件名、列表文件名、交叉引用文件名,直接按3次回车(默认即可),编译成功后,会生成“hello.obj”目标文件;
-
执行链接命令:输入“link hello.obj”,按回车,同样直接按3次回车(默认),链接成功后,会生成“hello.exe”可执行文件;
-
验证:此时在D:\ASM文件夹中,会有hello.asm(源文件)、hello.obj(目标文件)、hello.exe(可执行文件)三个文件,说明编译链接成功。
4.3 运行程序
在CMD中,输入“hello.exe”,按回车,即可看到屏幕上打印出“Hello World!”,恭喜你,第一个汇编程序运行成功!
4.4 代码含义讲解(零基础必看)
结合代码注释,我们拆解核心部分,不用死记硬背,理解逻辑即可:
-
.MODEL SMALL:定义内存模型,SMALL模型表示数据段和代码段分开,适合小程序,零基础固定写这一句即可;
-
.STACK 100H:定义栈的大小,100H(256字节)足够大部分简单程序使用;
-
.DATA:数据段,用于定义变量,这里的MSG是一个字符串变量,结尾的“$”是DOS中断打印字符串的结束标志,必须加;
-
.CODE:代码段,程序的核心指令都在这里;
-
MAIN PROC / MAIN ENDP:定义主函数,程序从MAIN开始执行;
-
MOV AX,@DATA / MOV DS,AX:将数据段地址送入DS寄存器,因为汇编中访问数据段的变量,必须先让DS指向数据段(固定写法);
-
MOV AH,09H / LEA DX,MSG / INT 21H:调用DOS中断的打印功能,固定组合,记住“AH=09H是打印,DX存字符串地址”即可;
-
MOV AH,4CH / INT 21H:调用DOS中断退出程序,固定写法,避免程序异常结束。
4.5 常见报错排查
零基础第一次操作,可能会遇到报错,重点排查以下3点:
-
报错“不是内部或外部命令”:masm或link命令不可用,检查环境变量是否配置正确,或重新打开CMD;
-
编译报错“unexpected end of file”:代码缺少结束标志,检查是否有“END MAIN”,或代码中有语法错误(比如括号不匹配);
-
运行无输出:字符串MSG结尾忘记加“”即可。
第五章 基础指令详解(数据传输、运算、跳转)
汇编程序的核心是“指令”,就像高级语言的“语句”,x86汇编有上百条指令,但零基础只需掌握最常用的10+条,就能编写简单程序,本章重点讲解3类核心指令。
指令格式:指令助记符 源操作数, 目的操作数(注意:源操作数和目的操作数的类型要一致,比如寄存器对寄存器、立即数对寄存器)
5.1 数据传输指令(最常用,核心!)
数据传输指令用于将数据从一个位置(源)传到另一个位置(目的),最常用的是MOV指令,还有PUSH(压栈)、POP(出栈)。
5.1.1 MOV指令(Move,移动数据)
格式:MOV 目的操作数, 源操作数
功能:将源操作数的值,复制到目的操作数中(源操作数的值不变)
常见用法(重点记):
MOV EAX, 100 ; 将立即数100送入EAX寄存器(EAX=100)MOV EBX, EAX ; 将EAX的值复制到EBX(EBX=100,EAX不变)MOV [0x00400000], EAX ; 将EAX的值送入内存地址0x00400000中MOV ECX, [VAR] ; 将变量VAR(本质是内存地址)的值送入ECX注意事项(易错点):
-
不能直接将内存地址的值传到另一个内存地址,比如“MOV [ADDR1], [ADDR2]”是错误的,必须通过寄存器中转;
-
立即数不能直接送入段寄存器(如DS、CS),比如“MOV DS, 1000H”是错误的,必须通过通用寄存器中转(如MOV AX, 1000H; MOV DS, AX)。
5.1.2 PUSH指令(压栈)
格式:PUSH 操作数(寄存器、立即数、内存地址)
功能:将操作数的值压入栈中,ESP指针减小(栈顶向下生长)
示例:
PUSH EAX ; 将EAX的值压入栈中PUSH 0x1234 ; 将立即数0x1234压入栈中PUSH [VAR] ; 将变量VAR的值压入栈中5.1.3 POP指令(出栈)
格式:POP 操作数(寄存器、内存地址)
功能:将栈顶的值取出,送入操作数中,ESP指针增大
示例:
POP EBX ; 将栈顶的值取出,送入EBX中POP [VAR] ; 将栈顶的值取出,送入变量VAR中注意:PUSH和POP必须成对使用,压栈多少次,就要出栈多少次,否则会导致栈溢出,程序报错。
5.2 算术运算指令(基础运算)
算术运算指令用于执行加减乘除等运算,核心指令有ADD(加)、SUB(减)、MUL(乘)、DIV(除),运算结果默认存放在EAX寄存器中。
5.2.1 ADD指令(Add,加法)
格式:ADD 目的操作数, 源操作数
功能:目的操作数 = 目的操作数 + 源操作数
示例:
MOV EAX, 10 ; EAX=10ADD EAX, 20 ; EAX = 10 + 20 = 30ADD EBX, EAX ; EBX = EBX + 305.2.2 SUB指令(Subtract,减法)
格式:SUB 目的操作数, 源操作数
功能:目的操作数 = 目的操作数 - 源操作数
示例:
MOV EAX, 50 ; EAX=50SUB EAX, 15 ; EAX = 50 - 15 = 35SUB ECX, 10 ; ECX = ECX - 105.2.3 MUL指令(Multiply,乘法)
格式:MUL 源操作数(寄存器、内存地址)
功能:无符号乘法,EAX = EAX × 源操作数(若结果超过32位,高位存放在EDX中)
示例:
MOV EAX, 12 ; EAX=12MUL EBX ; EAX = 12 × EBX,结果高位存EDX,低位存EAX5.2.4 DIV指令(Divide,除法)
格式:DIV 源操作数(寄存器、内存地址)
功能:无符号除法,EAX ÷ 源操作数,商存放在EAX中,余数存放在EDX中
示例:
MOV EAX, 100 ; EAX=100MOV EBX, 20 ; EBX=20DIV EBX ; EAX=5(商),EDX=0(余数)5.3 跳转指令(改变程序执行顺序)
跳转指令用于改变程序的执行流程,比如跳过某段代码、循环执行某段代码,核心指令有JMP(无条件跳转)、JE/JNE(条件跳转)。
5.3.1 JMP指令(Jump,无条件跳转)
格式:JMP 标签名
功能:无条件跳转到指定的标签位置,执行标签后的代码
示例:
MOV EAX, 10JMP LABEL1 ; 无条件跳转到LABEL1标签MOV EBX, 20 ; 这行代码不会执行,因为被跳转跳过了LABEL1: ; 标签,用于标记跳转目标ADD EAX, 30 ; EAX=10+30=405.3.2 条件跳转指令(JE/JNE)
条件跳转指令需要结合“标志寄存器”(FLAGS)使用,标志寄存器用于存储运算结果的状态(如是否相等、是否为负),零基础先掌握最常用的两个:
-
JE(Jump Equal):相等则跳转,当运算结果为0时(即两个数相等),执行跳转;
-
JNE(Jump Not Equal):不相等则跳转,当运算结果不为0时(即两个数不相等),执行跳转。
示例(判断两个数是否相等):
MOV EAX, 10MOV EBX, 10SUB EAX, EBX ; EAX=0,标志寄存器的ZF位(零标志位)置1JE EQUAL ; 因为ZF=1(相等),跳转到EQUAL标签JMP NOTEQUAL ; 若不相等,跳转到NOTEQUAL标签EQUAL:MOV ECX, 1 ; 相等时执行,ECX=1JMP END ; 跳转到结束标签NOTEQUAL:MOV ECX, 0 ; 不相等时执行,ECX=0END:INT 21H ; 程序结束第六章 流程控制(条件判断、循环结构)
汇编语言的流程控制和高级语言类似,主要包括“条件判断”和“循环”,核心是结合跳转指令、计数寄存器(ECX)实现,本章通过实例讲解,直接套用即可。
6.1 条件判断结构(if-else)
汇编中没有专门的if-else关键字,通过“运算 + 条件跳转”实现,核心逻辑:先通过运算改变标志寄存器状态,再用条件跳转指令跳转到对应代码块。
实例:判断一个数(存放在EAX中)是正数、负数还是零
.MODEL SMALL.STACK 100H.DATA POS DB 'Positive$' ; 正数提示 NEG DB 'Negative$' ; 负数提示 ZERO DB 'Zero$' ; 零提示.CODEMAIN PROC MOV AX,@DATA MOV DS,AX
MOV EAX, 0 ; 可修改EAX的值(如10、-5、0),测试不同情况 CMP EAX, 0 ; 比较EAX和0(本质是EAX - 0,不改变EAX的值,只修改标志寄存器) JE ZERO_LABEL ; 若相等(EAX=0),跳转到ZERO_LABEL JG POS_LABEL ; 若大于0(JG=Jump Greater),跳转到POS_LABEL JL NEG_LABEL ; 若小于0(JL=Jump Less),跳转到NEG_LABEL
POS_LABEL: MOV AH,09H LEA DX,POS INT 21H JMP EXIT ; 跳转到退出,避免执行后续代码NEG_LABEL: MOV AH,09H LEA DX,NEG INT 21H JMP EXITZERO_LABEL: MOV AH,09H LEA DX,ZERO INT 21HEXIT: MOV AH,4CH INT 21HMAIN ENDPEND MAIN关键说明:CMP指令(Compare,比较)是条件判断的核心,格式为“CMP 操作数1, 操作数2”,本质是执行“操作数1 - 操作数2”,不改变操作数的值,只修改标志寄存器的状态,供后续条件跳转使用。
6.2 循环结构(for/while)
汇编中的循环,常用“计数寄存器(ECX) + 循环标签 + 条件跳转”实现,核心逻辑:设置循环次数,每次循环后计数器减1,直到计数器为0,退出循环。
6.2.1 示例1:循环打印5次“Hello World”
.MODEL SMALL.STACK 100H.DATA MSG DB 'Hello World!',0DH,0AH,'$'.CODEMAIN PROC MOV AX,@DATA MOV DS,AX
MOV ECX, 5 ; 设置循环次数为5(ECX=5,每次循环减1)LOOP_LABEL: ; 循环标签 ; 打印字符串 MOV AH,09H LEA DX,MSG INT 21H
DEC ECX ; ECX = ECX - 1(计数器减1) JNZ LOOP_LABEL ; JNZ=Jump Not Zero,若ECX≠0,跳回LOOP_LABEL继续循环
; 循环结束,退出程序 MOV AH,4CH INT 21HMAIN ENDPEND MAIN6.2.2 示例2:计算1到10的和(循环累加)
.MODEL SMALL.STACK 100H.DATA SUM DB 0 ; 存储累加和,初始值为0 RESULT DB 'Sum: $' ; 结果提示.CODEMAIN PROC MOV AX,@DATA MOV DS,AX
MOV ECX, 10 ; 循环次数10次(1到10) MOV AL, 1 ; AL存储当前数字(从1开始)LOOP_SUM: ADD SUM, AL ; SUM = SUM + AL INC AL ; AL = AL + 1(当前数字加1) DEC ECX ; 计数器减1 JNZ LOOP_SUM ; 计数器不为0,继续循环
; 打印结果(简化版,后续会讲解更完整的数字打印方法) MOV AH,09H LEA DX,RESULT INT 21H MOV AH,02H MOV DL,SUM ADD DL,30H ; 将数字转为ASCII码(0的ASCII码是30H) INT 21H
MOV AH,4CH INT 21HMAIN ENDPEND MAIN关键说明:INC指令(Increment,自增)用于将操作数加1,DEC指令(Decrement,自减)用于将操作数减1,常和循环计数器配合使用。
第七章 函数调用与栈的使用
当程序变得复杂时,我们会将重复的代码封装成“函数”(汇编中称为“子程序”),通过调用函数,实现代码复用,函数调用的核心是“栈的使用”——保存返回地址、传递参数。
7.1 函数的定义与调用格式
汇编中,函数(子程序)的定义的格式:
函数名 PROC ; 函数开始 ; 函数体代码 RET ; 函数返回(将栈中保存的返回地址取出,跳回调用处)函数名 ENDP ; 函数结束函数调用的格式:CALL 函数名
核心原理:调用CALL指令时,会自动将“下一条指令的地址”(返回地址)压入栈中;执行RET指令时,会将栈中的返回地址取出,跳回该地址,继续执行后续代码。
7.2 实例:封装打印字符串的函数
将第四章的“打印字符串”代码封装成函数,实现重复调用:
.MODEL SMALL.STACK 100H.DATA MSG1 DB 'Hello World!',0DH,0AH,'$' MSG2 DB 'Assembly is easy!',0DH,0AH,'$'.CODE; 封装打印字符串函数:参数是字符串地址(存在DX中)PRINT PROC MOV AH,09H INT 21H ; 打印DX指向的字符串 RET ; 函数返回PRINT ENDP
MAIN PROC MOV AX,@DATA MOV DS,AX
; 第一次调用PRINT函数,打印MSG1 LEA DX,MSG1 CALL PRINT
; 第二次调用PRINT函数,打印MSG2 LEA DX,MSG2 CALL PRINT
; 退出程序 MOV AH,4CH INT 21HMAIN ENDPEND MAIN运行结果:会依次打印“Hello World!”和“Assembly is easy!”,实现了代码复用,后续需要打印字符串,直接调用PRINT函数即可。
7.3 函数参数传递(栈传递)
上面的例子中,函数参数(字符串地址)是通过DX寄存器传递的,但寄存器数量有限,当参数较多时,需要通过“栈”传递参数,核心逻辑:调用函数前,将参数依次压入栈中;函数内部,通过EBP寄存器访问栈中的参数。
实例:封装一个加法函数,通过栈传递两个参数,返回和值
.MODEL SMALL.STACK 100H.DATA RESULT DB 'Sum: $'.CODE; 加法函数:参数1(栈中第1个值) + 参数2(栈中第2个值),返回值存在EAX中ADD_FUNC PROC PUSH EBP ; 保存旧的EBP值,压入栈中 MOV EBP, ESP ; 将ESP的值送入EBP,EBP作为栈帧基址 MOV EAX, [EBP+8] ; 访问栈中第1个参数(EBP+8:EBP本身4字节,返回地址4字节,参数从EBP+8开始) ADD EAX, [EBP+12]; 访问栈中第2个参数,与EAX相加 POP EBP ; 恢复旧的EBP值 RET 8 ; RET 8:返回并清理栈中的8字节(两个32位参数,每个4字节)ADD_FUNC ENDP
MAIN PROC MOV AX,@DATA MOV DS,AX
; 传递参数:将10和20依次压入栈中(注意:栈是先进后出,参数顺序相反) PUSH 20 ; 第2个参数 PUSH 10 ; 第1个参数 CALL ADD_FUNC ; 调用加法函数,返回值存在EAX中
; 打印结果 MOV AH,09H LEA DX,RESULT INT 21H MOV AH,02H MOV DL,AL ADD DL,30H INT 21H
MOV AH,4CH INT 21HMAIN ENDPEND MAIN关键说明:RET 8中的“8”表示“清理栈中的8字节参数”,因为我们传递了两个32位参数(每个4字节,共8字节),如果不清理,会导致栈溢出,程序报错。
第八章 Linux环境下NASM汇编补充
前面讲解的是Windows + MASM,Linux环境下常用NASM编译器,语法和MASM有细微差异,核心逻辑一致,本章讲解Linux下的Hello World程序,快速上手。
8.1 NASM语法差异(重点)
-
NASM中,变量定义不需要“.DATA”,直接用“section .data”定义数据段;
-
代码段用“section .text”,程序入口用“global _start”(指定入口为_start);
-
Linux中,打印字符串、退出程序,调用的是“系统调用”(int 80H),而非DOS中断(int 21H);
-
寄存器使用:系统调用号存放在EAX中,参数存放在EBX、ECX、EDX中。
8.2 Linux下Hello World程序(NASM)
- 打开Linux终端,新建文件“hello.asm”,输入以下代码:
; Linux下NASM汇编:打印Hello Worldsection .data ; 数据段 msg db 'Hello World!',0xA ; 0xA是换行符(相当于Windows的0AH) len equ $ - msg ; 计算字符串长度:$表示当前地址,减去msg的地址,得到长度
section .text ; 代码段 global _start ; 声明程序入口,供链接器识别
_start: ; 程序入口 ; 系统调用:write(打印字符串) mov eax, 4 ; 系统调用号4 = write mov ebx, 1 ; 文件描述符1 = 标准输出(屏幕) mov ecx, msg ; 字符串地址 mov edx, len ; 字符串长度 int 0x80 ; 触发系统调用,执行打印
; 系统调用:exit(退出程序) mov eax, 1 ; 系统调用号1 = exit mov ebx, 0 ; 退出状态码0(正常退出) int 0x80 ; 触发系统调用,退出程序8.3 编译与运行(Linux终端)
-
编译命令:nasm -f elf32 hello.asm -o hello.o(-f elf32表示生成32位目标文件);
-
链接命令:ld -m elf_i386 hello.o -o hello(-m elf_i386表示链接32位程序);
-
运行命令:./hello,即可看到屏幕上打印“Hello World!”。
(注:文档部分内容可能由 AI 生成)