7194 字
36 分钟
汇编语言零基础入门教程(x86架构·全实操版)
2026-04-10

汇编语言零基础入门教程(x86架构·全实操版)#

文档说明#

本教程专为零基础编程爱好者设计,聚焦最常用的x86架构汇编语言,兼顾理论讲解与实操落地,避免复杂晦涩的专业术语堆砌,每一个知识点都配套对应代码示例和实操步骤,看完就能动手编写运行汇编程序。

适用环境:Windows系统(主力讲解MASM编译器)、Linux系统(补充NASM编译器操作)

适用场景:计算机底层原理学习、逆向工程入门、嵌入式开发基础、计算机专业课程辅助

学习建议:按章节顺序学习,每节内容先理解理论,再动手实操代码,遇到报错先对照“常见问题”排查,避免跳过基础直接进阶。

目录#

  1. 汇编语言基础认知(搞懂“为什么学”“是什么”)

  2. 学习前准备(工具安装与环境配置)

  3. 汇编核心基础:寄存器、内存与栈

  4. 第一个汇编程序(Hello World·手把手实操)

  5. 基础指令详解(数据传输、运算、跳转)

  6. 流程控制(条件判断、循环结构)

  7. 函数调用与栈的实际应用

  8. Linux环境下NASM汇编补充

  9. 常见报错与解决方案

  10. 学习进阶建议与资源推荐

第一章 汇编语言基础认知#

1.1 什么是汇编语言?#

汇编语言(Assembly Language)是机器语言的符号化表示,是连接人类可读语言与计算机底层硬件的“桥梁”,也是最接近计算机硬件的编程语言。

我们可以通过三层对比,快速理解汇编的定位:

  • 机器语言:由0和1组成的二进制指令(如10110000),计算机可直接执行,但人类难以记忆和编写;

  • 汇编语言:用英文单词(称为“助记符”,如MOV、ADD)代替二进制指令,用标识符代替内存地址,人类可读写,需通过编译器转为机器语言才能执行;

  • 高级语言(C/Java/Python):语法接近人类自然语言,屏蔽了底层硬件细节,需通过编译/解释器先转为汇编语言,再转为机器语言执行。

简单来说:汇编语言 = 机器语言 + 人类可读的符号,学会汇编,就能直接“指挥”计算机硬件工作。

1.2 为什么要学汇编语言?#

很多初学者会疑惑“有高级语言,为什么还要学汇编?”,核心原因有3点:

  1. 理解计算机底层原理:汇编直接操作CPU、内存、寄存器,学完能彻底搞懂“高级语言的代码最终是怎么在计算机上运行的”,比如变量存储、函数调用的底层逻辑;

  2. 特定场景必备:逆向工程(破解、漏洞分析)、嵌入式开发(单片机、物联网设备)、操作系统内核开发,都需要汇编基础;

  3. 提升编程思维:汇编语法简单但逻辑严谨,能培养“底层思维”,帮助你写出更高效、更严谨的高级语言代码(比如理解内存溢出、性能优化的本质)。

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个工具,全程傻瓜式操作:

  1. 汇编编译器:MASM32(集成了MASM编译器、链接器、编辑器,一站式搞定);

  2. 文本编辑器:Notepad++(推荐,支持汇编语法高亮,方便编写代码;也可使用记事本、VS Code);

  3. 命令行工具:Windows自带的CMD(用于执行编译、链接、运行命令)。

2.2 MASM32安装步骤(手把手)#

  1. 下载MASM32:百度搜索“MASM32官方下载”,选择最新版本(如MASM32 SDK v11),下载后得到压缩包;

  2. 解压安装:将压缩包解压到任意路径(建议路径无中文、无空格,如D:\MASM32,避免后续报错);

  3. 验证安装:打开CMD,输入“masm”,按回车,若出现“Microsoft (R) Macro Assembler Version 6.14.8444”,说明安装成功;若提示“不是内部或外部命令”,需配置环境变量。

2.3 环境变量配置(解决“masm命令不可用”)#

  1. 右键“此电脑”→“属性”→“高级系统设置”→“环境变量”;

  2. 在“系统变量”中找到“Path”,双击打开;

  3. 点击“新建”,输入MASM32的“bin”文件夹路径(如D:\MASM32\bin),点击“确定”保存;

  4. 关闭所有CMD窗口,重新打开,输入“masm”,验证是否能正常显示版本信息。

2.4 Linux系统准备(补充)#

若使用Linux系统(如Ubuntu),无需手动安装复杂工具,打开终端,输入以下命令即可安装NASM编译器:

Terminal window
sudo apt update
sudo 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)的原则,就像一个叠盘子的容器,只能从最上面取盘子,也只能往最上面放盘子——先放入的内容会被压在最下面,后放入的内容在最上面,取出时只能先取最上面的内容。

栈的核心用途(零基础重点记,后续函数调用会高频用到):

栈的核心用途(零基础重点记):

  1. 保存函数调用的返回地址:当我们调用一个函数时,CPU需要记住“调用完函数后,回到原来代码的哪个位置继续执行”,这个返回地址会被自动压入栈中,函数执行完毕后,再从栈中取出地址,跳回原位置。

  2. 保存函数调用时的返回地址(比如调用完函数后,能回到原来的代码位置继续执行);

  3. 保存函数的参数和局部变量:函数运行时需要的参数(比如加法函数的两个加数)、临时使用的局部变量,会被压入栈中,避免占用寄存器导致数据冲突,函数执行结束后,这些数据会从栈中弹出,释放空间。

  4. 保存函数的参数、局部变量(避免局部变量占用寄存器,导致数据冲突);

  5. 临时存储数据(比如运算过程中,暂时用不到的数据,先放到栈中,后续再取出)。

补充:栈指针寄存器ESP始终指向栈顶,每次往栈中存放数据(压栈,指令PUSH),ESP的值会减小;每次从栈中取出数据(出栈,指令POP),ESP的值会增大。

第四章 第一个汇编程序(Hello World·手把手实操)#

掌握了核心基础后,我们动手编写第一个汇编程序——打印“Hello World”,全程实操,每一步都有详细说明,确保零基础也能运行成功。

本章节使用:Windows + MASM32 + Notepad++

4.1 编写汇编代码#

  1. 打开Notepad++,新建一个文本文件,保存为“hello.asm”(注意:保存路径无中文、无空格,如D:\ASM\hello.asm,后缀必须是.asm,代表汇编文件);

  2. 复制以下代码到文件中,每一行代码后面都有注释,看不懂没关系,先复制运行,后续再讲解含义:

; 第一个汇编程序:打印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 ; 程序结束,指定入口为MAIN

4.2 编译与链接(将汇编代码转为可执行文件)#

汇编代码无法直接运行,需要通过“编译”(转为目标文件.obj)和“链接”(转为可执行文件.exe)两个步骤,操作如下:

  1. 打开CMD,切换到代码保存的路径(比如代码保存在D:\ASM,输入“cd D:\ASM”,按回车);

  2. 执行编译命令:输入“masm hello.asm”,按回车,此时会提示输入目标文件名、列表文件名、交叉引用文件名,直接按3次回车(默认即可),编译成功后,会生成“hello.obj”目标文件;

  3. 执行链接命令:输入“link hello.obj”,按回车,同样直接按3次回车(默认),链接成功后,会生成“hello.exe”可执行文件;

  4. 验证:此时在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点:

  1. 报错“不是内部或外部命令”:masm或link命令不可用,检查环境变量是否配置正确,或重新打开CMD;

  2. 编译报错“unexpected end of file”:代码缺少结束标志,检查是否有“END MAIN”,或代码中有语法错误(比如括号不匹配);

  3. 运行无输出:字符串MSG结尾忘记加“”,导致DOS中断无法识别字符串结束位置,加上“”,导致DOS中断无法识别字符串结束位置,加上“”即可。

第五章 基础指令详解(数据传输、运算、跳转)#

汇编程序的核心是“指令”,就像高级语言的“语句”,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=10
ADD EAX, 20 ; EAX = 10 + 20 = 30
ADD EBX, EAX ; EBX = EBX + 30

5.2.2 SUB指令(Subtract,减法)#

格式:SUB 目的操作数, 源操作数

功能:目的操作数 = 目的操作数 - 源操作数

示例:

MOV EAX, 50 ; EAX=50
SUB EAX, 15 ; EAX = 50 - 15 = 35
SUB ECX, 10 ; ECX = ECX - 10

5.2.3 MUL指令(Multiply,乘法)#

格式:MUL 源操作数(寄存器、内存地址)

功能:无符号乘法,EAX = EAX × 源操作数(若结果超过32位,高位存放在EDX中)

示例:

MOV EAX, 12 ; EAX=12
MUL EBX ; EAX = 12 × EBX,结果高位存EDX,低位存EAX

5.2.4 DIV指令(Divide,除法)#

格式:DIV 源操作数(寄存器、内存地址)

功能:无符号除法,EAX ÷ 源操作数,商存放在EAX中,余数存放在EDX中

示例:

MOV EAX, 100 ; EAX=100
MOV EBX, 20 ; EBX=20
DIV EBX ; EAX=5(商),EDX=0(余数)

5.3 跳转指令(改变程序执行顺序)#

跳转指令用于改变程序的执行流程,比如跳过某段代码、循环执行某段代码,核心指令有JMP(无条件跳转)、JE/JNE(条件跳转)。

5.3.1 JMP指令(Jump,无条件跳转)#

格式:JMP 标签名

功能:无条件跳转到指定的标签位置,执行标签后的代码

示例:

MOV EAX, 10
JMP LABEL1 ; 无条件跳转到LABEL1标签
MOV EBX, 20 ; 这行代码不会执行,因为被跳转跳过了
LABEL1: ; 标签,用于标记跳转目标
ADD EAX, 30 ; EAX=10+30=40

5.3.2 条件跳转指令(JE/JNE)#

条件跳转指令需要结合“标志寄存器”(FLAGS)使用,标志寄存器用于存储运算结果的状态(如是否相等、是否为负),零基础先掌握最常用的两个:

  • JE(Jump Equal):相等则跳转,当运算结果为0时(即两个数相等),执行跳转;

  • JNE(Jump Not Equal):不相等则跳转,当运算结果不为0时(即两个数不相等),执行跳转。

示例(判断两个数是否相等):

MOV EAX, 10
MOV EBX, 10
SUB EAX, EBX ; EAX=0,标志寄存器的ZF位(零标志位)置1
JE EQUAL ; 因为ZF=1(相等),跳转到EQUAL标签
JMP NOTEQUAL ; 若不相等,跳转到NOTEQUAL标签
EQUAL:
MOV ECX, 1 ; 相等时执行,ECX=1
JMP END ; 跳转到结束标签
NOTEQUAL:
MOV ECX, 0 ; 不相等时执行,ECX=0
END:
INT 21H ; 程序结束

第六章 流程控制(条件判断、循环结构)#

汇编语言的流程控制和高级语言类似,主要包括“条件判断”和“循环”,核心是结合跳转指令、计数寄存器(ECX)实现,本章通过实例讲解,直接套用即可。

6.1 条件判断结构(if-else)#

汇编中没有专门的if-else关键字,通过“运算 + 条件跳转”实现,核心逻辑:先通过运算改变标志寄存器状态,再用条件跳转指令跳转到对应代码块。

实例:判断一个数(存放在EAX中)是正数、负数还是零

.MODEL SMALL
.STACK 100H
.DATA
POS DB 'Positive$' ; 正数提示
NEG DB 'Negative$' ; 负数提示
ZERO DB 'Zero$' ; 零提示
.CODE
MAIN 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 EXIT
ZERO_LABEL:
MOV AH,09H
LEA DX,ZERO
INT 21H
EXIT:
MOV AH,4CH
INT 21H
MAIN ENDP
END 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,'$'
.CODE
MAIN 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 21H
MAIN ENDP
END MAIN

6.2.2 示例2:计算1到10的和(循环累加)#

.MODEL SMALL
.STACK 100H
.DATA
SUM DB 0 ; 存储累加和,初始值为0
RESULT DB 'Sum: $' ; 结果提示
.CODE
MAIN 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 21H
MAIN ENDP
END 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 21H
MAIN ENDP
END 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 21H
MAIN ENDP
END 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)#

  1. 打开Linux终端,新建文件“hello.asm”,输入以下代码:
; Linux下NASM汇编:打印Hello World
section .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终端)#

  1. 编译命令:nasm -f elf32 hello.asm -o hello.o(-f elf32表示生成32位目标文件);

  2. 链接命令:ld -m elf_i386 hello.o -o hello(-m elf_i386表示链接32位程序);

  3. 运行命令:./hello,即可看到屏幕上打印“Hello World!”。

(注:文档部分内容可能由 AI 生成)

汇编语言零基础入门教程(x86架构·全实操版)
https://021028.xyz/posts/default/18/
作者
021028
发布于
2026-04-10
许可协议
CC BY-NC-SA 4.0