汇编语言轻松入门

article/2025/10/11 17:36:56

​通常说的学习编程其实就是学习高级语言编程,比如C语言、C++语言、Python语言、JAVA语言等等,即那些为人类设计的计算机语言。

但是,我们的计算机它并不理解什么是高级语言,计算机只是一个机器,它只有电气的特性,比如正负电荷、高低电平等,所以必须通过编译器将高级语言转换成二进制编码,这样才能运行。学会高级语言,并不等于理解计算机实际的运行步骤。更多关于计算机的知识,可以阅读下计算机组成原理方面的书籍。

在这里插入图片描述

计算机真正能够理解的编程语言是低级语言,它是被专门用来控制硬件的。我们的汇编语言就是一种低级语言,通过一些标识符直接描述/控制 CPU 的运行。如果你想了解 CPU 到底干了些什么,以及代码的运行步骤,就一定要学习汇编语言,因为汇编语言是最接近机器语言的,这里说的机器语言又是什么呢,简单可以把它看成是由0与1构成的一组组指令,这些0和1组成的指令存储在可编程的存储单元中,它最终会被计算机取出来转化成一个个对应的电气特性的信号,这样就华丽地实现了从软件到硬件的转变。

汇编语言是低级语言,所以不易阅读,也不易理解,因此它是一种不易学习的编程语言,但实际工作中所有的高级语言,最终编译执行,都少不了汇编作为中间桥梁,所以对它有一点了解,对于我们理解奇妙的计算机世界还是有帮助的。下面我尝试写一篇最好懂的汇编语言教程,解释 CPU 如何执行代码。
在这里插入图片描述

一、汇编语言是什么?

我们知道,CPU 只负责计算,本身不具备智能。你输入一条指令(instruction),它就运行一次,然后停下来,等待下一条指令。

这些指令都是由0和1组成的,它们是二进制的数据,称为操作码(opcode),我们日常用得比较多的进制是10进制数,即由0~9这10个数组成的数,但在计算机世界中,不仅有10进制,还有8进制、16进制、以及这里说的2进制,顾名思义,2进制数只有0和1这两个基本元素组成,比如加法指令就是00000011 。编译器的作用,就是将高级语言写好的程序,翻译成一条条操作码。

对于人类来说,二进制程序是不可读的,根本看不出来机器干了什么。为了解决可读性的问题,以及偶尔的编辑需求,就诞生了汇编语言。
在这里插入图片描述

汇编语言是二进制指令的文本形式,与指令是一一对应的关系。比如,加法指令00000011,把它写成汇编语言就是 ADDADD只是一个符号而已,具体到计算机它是00000011这样的指令码,所以只要还原成二进制,汇编语言就可以被 CPU 直接执行,所以它是最底层的低级语言。

二、汇率语言的来历

在计算机发展的早期,人们编写程序就是手写二进制指令,然后通过各种开关输入进计算机执行,比如要做加法运算,就按一下加法开关。随着时间的推移,科学家们发明了纸带打孔机,通过在纸带上打孔,将二进制指令自动输入计算机,这无形中提供了“生产率“。看到早期的计算机,回头再看看现在的计算机、手机等各种电子产品,是不是感到技术的伟大,发展的速度如此之快!
那时为了解决二进制指令的可读性问题,工程师将那些指令写成了八进制。二进制转八进制是轻而易举的,但是八进制的可读性也不行。很自然地,最后还是用文字表达,加法指令写成 ADD。内存地址也不再直接引用,而是用标签表示。
这样的话,就多出一个步骤,要把这些文字指令翻译成二进制,这个步骤就称为 assembling,完成这个步骤的程序就叫做 assembler。它处理的文本,自然就叫做 aseembly code。标准化以后,称为 assembly language,缩写为 asm,中文译为汇编语言。
在这里插入图片描述

每一种 CPU 的机器指令都是不一样的,因此对应的汇编语言也不一样。本文介绍的是目前最常见的 x86 汇编语言,即 Intel 公司的 CPU 使用的那一种。

三、计算机的寄存器

在我们学习汇编语言时,首先必须要了解两个重要的知识点:寄存器和内存模型。

先来说说寄存器吧。CPU 本身只负责运算,不负责储存数据。数据一般都储存在内存之中,**CPU **要用的时候就去内存读写数据。但是,CPU 的运算速度远高于内存的读写速度,为了避免被拖慢,CPU 都自带一级缓存和二级缓存。基本上,CPU 缓存可以看作是读写速度较快的内存。

但是,CPU 缓存还是不够快,另外数据在缓存里面的地址是不固定的,CPU 每次读写都要寻址也会拖慢速度。因此,除了缓存之外,CPU 还自带了寄存器(register),用来储存最常用的数据。也就是说,那些最频繁读写的数据(比如循环变量),都会放在寄存器里面,CPU 优先读写寄存器,再由寄存器跟内存交换数据。
在这里插入图片描述

寄存器不依靠地址区分数据,而依靠名称。每一个寄存器都有自己的名称,我们告诉 CPU 去具体的哪一个寄存器拿数据,这样的速度是最快的。有人比喻寄存器是 CPU 的零级缓存。

四、寄存器的种类

Intel早期的 x86 CPU 只有8个寄存器(现在我们用的是Intel i5、 i7、i9,有成百上千个寄存器吧,速度已经无法与当初的相比了!),而且每个都有不同的用途。现在的寄存器已经有100多个了,都变成通用寄存器,不特别指定用途了,但是早期寄存器的名字都被保存了下来。

EAX
EBX
ECX
EDX
EDI
ESI
EBP
ESP

上面列的这8个寄存器中,前面七个都是通用的寄存器。ESP 寄存器有特定用途,它有个名字叫栈寄存器,保存当前 Stack 的地址(详见下一节)。
在这里插入图片描述

我们常常看到 32位 CPU、64位 CPU 这样的名称,其实指的就是寄存器的大小。32 位 CPU 的寄存器大小就是4个字节。

五、计算机的内存模型:Heap

计算机的寄存器只能存放很少量的数据,在很多时候,CPU 要指挥寄存器工作,是直接跟内存交换数据。因此,除了寄存器外,还必须了解计算机的内存是怎么储存数据。
程序在计算机上运行的时候,操作系统就会给它分配一段内存空间出来,用来储存程序和运行时产生的数据。这段内存有起始地址和结束地址,比如从0x1000到0x8000的地址空间,起始地址是较小的那个地址,结束地址是较大的那个地址,如下图所示。
在这里插入图片描述

程序运行的过程中,对于动态的内存占用请求(比如对于程序中的新建对象,或者使用malloc命令这样的代码),计算机的操作系统就会从预先分配好的那段内存之中,划出一部分给用户,具体规则是从起始地址开始划分(实际上,起始地址会有一段静态数据,这里忽略)。举例来说,用户要求得到10个字节内存,那么从起始地址0x1000开始给他分配,一直分配到地址0x100A,如果再要求得到22个字节,那么就分配到0x1020

在这里插入图片描述

这种由用户(程序员)主动请求而划分出来的内存区域,叫做 Heap(堆)。它从起始地址开始,从低位(地址)向高位(地址)增长。Heap 的一个重要特点就是不会自动消失,必须手动释放,或者由垃圾回收机制来回收,这也是我们日常开发大型项目中,经常会给自己挖坑的地方,自己分配了一个静态的堆空间,忘记使用后把它释放了,最终导致计算机的内存不足而系统崩溃。

六、计算机的内存模型:Stack

上面介绍了计算机内存模型Heap,然而除了 Heap 之外,在计算机中其他的内存占用就叫做 Stack(栈)。简单地说,Stack 是由于函数调用、函数运行而临时占用的内存区域。

在这里插入图片描述

比如有下面这样一个例子。

int main() {int a = 2;int b = 3;
}

在上面的代码中,系统开始执行main函数的时候,系统就会为它在内存里面建立一个帧(frame)空间,所有main的内部变量(比如a和b)就都保存在这个帧里面。当main函数执行结束后,该帧就会被系统回收,以释放所有的内部变量,不再占用空间。

在这里插入图片描述

如果在函数的内部又调用了其他函数,会有什么情况发生呢?看下面这个例子:

int main() {int a = 2;int b = 3;return add_a_and_b(a, b);
}

从上面的程序代码中,在main函数的内部调用了add_a_and_b这个函数。当执行到这一行的时候,计算机操作系统也会为add_a_and_b创建一个帧空间区域,用来存储它的内部变量。也就是说,此时在栈空间中同时有两个帧存在:main和add_a_and_b。一般来说,调用栈有多少层,就认为有多少帧与之相对应。

在这里插入图片描述

等到add_a_and_b函数运行结束的时候,它的帧空间就会被回收,计算机操作系统会返回到函数main刚才中断执行的地方(操作系统的中断机制),继续往下执行。通过这种机制,就实现了函数的层层调用,并且每一层都能使用自己的本地变量。

所有的帧都存放在 Stack,由于我们的帧空间区域是一层层叠加起来的,所以 Stack 叫做栈。当生成新的帧空间时,就叫做"入栈",英文表示是 push(在汇编中通常也用这个标识表示入栈的命令);栈的回收叫做"出栈",英文表示是 pop。Stack 有一个重要的使用特点就是,最晚入栈的帧最早出栈(因为在最内层执行的函数调用,最先结束它的运行),这就叫做"后进先出"的数据结构(与数据结构中的队列是相反,队列是先进先出)。每一次函数执行结束,就自动释放掉一个帧,所有函数执行结束后,整个 Stack 就都被释放完了。

在这里插入图片描述

Stack 是由内存区域的结束地址开始,从高位(地址)向低位(地址)分配(这里是以英特尔Intel的为例,也有硬件是由低位向高位的,具体要看是什么硬件平台了。)。比如,内存区域的结束地址是0x8000,第一帧假定是16字节,那么下一次分配的地址就会从0x7FF0开始;第二帧假定需要64字节,那么地址就会移动到0x7FB0。

在这里插入图片描述

七、计算机的CPU 指令

7.1 一个栗子

在前面介绍了寄存器和内存模型后,我们对寄存器和内存模型也有了一定的认识和了解,接下来我们就可以来看看汇编语言到底是什么了。下面是一个简单的C语言写的程序代码example.c。

int add_a_and_b(int a, int b) {return a + b;
}int main() {return add_a_and_b(2, 3);
}

我们使用gcc编译器来 将这个程序转成汇编语言。

$ gcc -S example.c # -S 将C语言翻译成汇编的标识符

上面的命令执行完后,会生成一个新的文本文件example.s,里面就是汇编语言的代码,包含了几十行指令。可以这么理解,一个高级语言的简单操作,底层可能由几个,甚至几十个 CPU 指令构成。CPU 依次执行这些指令,完成这一步操作。(这里的每一行汇编代码对应了一个二进制的机器指令码)
example.s经过简化以后,大概是下面的样子。

_add_a_and_b:push   %ebxmov    %eax, [%esp+8] mov    %ebx, [%esp+12]add    %eax, %ebx pop    %ebx ret  _main:push   3push   2call   _add_a_and_b add    %esp, 8ret

可以看到,原程序的两个函数add_a_and_b和main,对应两个标签_add_a_and_b和_main。每个标签里面是该函数所转成的 CPU 运行流程。

每一行就是 CPU 执行的一次操作。它又分成两部分,就以其中一行为例。

push   %ebx

这一行里面,push是 CPU 指令,%ebx是该指令要用到的运算子。一个 CPU指令可以有零个到多个运算子。

下面我就一行一行讲解这个汇编程序,建议读者最好把这个程序,在另一个窗口拷贝一份,省得阅读的时候再把页面滚动上来。

7.2 push 指令

根据约定,程序从_main标签开始执行,这时会在 Stack 上为main建立一个帧,并将 Stack 所指向的地址,写入 ESP 寄存器。后面如果有数据要写入main这个帧,就会写在 ESP 寄存器所保存的地址。

然后,开始执行第一行代码。

push   3

push指令用于将运算子放入 Stack,这里就是将3写入main这个帧。

虽然看上去很简单,push指令其实有一个前置操作。它会先取出 ESP 寄存器里面的地址,将其减去4个字节,然后将新地址写入 ESP 寄存器。使用减法是因为 Stack 从高位向低位发展,4个字节则是因为3的类型是int,占用4个字节。得到新地址以后, 3 就会写入这个地址开始的四个字节。

push   2

第二行也是一样,push指令将2写入main这个帧,位置紧贴着前面写入的3。这时,ESP 寄存器会再减去 4个字节(累计减去8)。

在这里插入图片描述

7.3 call 指令

第三行的call指令用来调用函数。

call   _add_a_and_b

上面的代码表示调用add_a_and_b函数。这时,程序就会去找_add_a_and_b标签,并为该函数建立一个新的帧。

下面就开始执行_add_a_and_b的代码。

push   %ebx

这一行表示将 EBX 寄存器里面的值,写入_add_a_and_b这个帧。这是因为后面要用到这个寄存器,就先把里面的值取出来,用完后再写回去。

这时,push指令会再将 ESP寄存器里面的地址减去4个字节(累计减去12)。

7.4 mov 指令

mov指令用于将一个值写入某个寄存器。

mov    %eax, [%esp+8] 

这一行代码表示,先将 ESP 寄存器里面的地址加上8个字节,得到一个新的地址,然后按照这个地址在 Stack 取出数据。根据前面的步骤,可以推算出这里取出的是2,再将2写入 EAX寄存器。

下一行代码也是干同样的事情。

mov    %ebx, [%esp+12] 

上面的代码将 ESP 寄存器的值加12个字节,再按照这个地址在 Stack 取出数据,这次取出的是3,将其写入 EBX 寄存器。

7.5 add 指令

add指令用于将两个运算子相加,并将结果写入第一个运算子。

add    %eax, %ebx

上面的代码将 EAX 寄存器的值(即2)加上 EBX 寄存器的值(即3),得到结果5,再将这个结果写入第一个运算子 EAX 寄存器。

7.6 pop 指令

pop指令用于取出 Stack 最近一个写入的值(即最低位地址的值),并将这个值写入运算子指定的位置。

pop    %ebx

上面的代码表示,取出 Stack 最近写入的值(即 EBX 寄存器的原始值),再将这个值写回 EBX 寄存器(因为加法已经做完了,EBX 寄存器用不到了)。
注意,pop指令还会将 ESP 寄存器里面的地址加4,即回收4个字节。

7.7 ret 指令

ret指令用于终止当前函数的执行,将运行权交还给上层函数。也就是,当前函数的帧将被回收。

ret

可以看到,该指令没有运算子。

随着add_a_and_b函数终止执行,系统就回到刚才main函数中断的地方,继续往下执行。

add    %esp, 8 

上面的代码表示,将 ESP 寄存器里面的地址,手动加上8个字节,再写回 ESP 寄存器。这是因为 ESP 寄存器的是 Stack 的写入开始地址,前面的pop操作已经回收了4个字节,这里再回收8个字节,等于全部回收。

ret

最后,main函数运行结束,ret指令退出程序执行。


http://chatgpt.dhexx.cn/article/Mg1Bxn0n.shtml

相关文章

汇编入门(一)

汇编入门(一) 1.第一个汇编程序 目前利用的是keil软件,建立文件时扩展名一定要为asm 如何查看具体地址存储器中的值: 在工具栏上选择“VIEW”>“memory windows”>“memory1” 然后输入地址即可: 注意这里…

汇编语言入门--调试工具debug的使用(史上最全,11种常见命令)

汇编语言入门–调试工具debug的使用(史上最全,11种常见命令) 1.直接启动debug程序 详见:https://bingshuai.blog.csdn.net/article/details/119978461 2.检查和修改寄存器内容命令R 检查 -r 修改 这里用AX来举例: -…

汇编语言入门(二)

汇编语言入门(一) 文章目录 寄存器【内存访问】内存中字的存储DS和【address】字的传送mov、add、sub指令数据段栈栈顶越界的问题PUSH和POP指令栈的综述栈段【栈的综述】 寄存器【内存访问】 内存中字的存储 4E20是两个字节构成一个字。 上图中0地址单…

汇编语言学习入门+亲自实操+图形并茂

汇编器 汇编器是将汇编语言转化为机器码的程序。或许你会以为汇编转化到机器码没什么大不了的,毕竟几乎是一对一的转换。但nasm存在的意义在于它可以很好的适应多种处理器平台,让编写汇编这件事都变得可移植了。nasm可以在Ubuntu下汇编,使用…

ARM 汇编语言入门

[翻译]二进制漏洞利用(二)ARM32位汇编下的TCP Bind shell:https://bbs.pediy.com/thread-253511.htm ARM汇编语言入门 From:ARM汇编语言入门(一):https://zhuanlan.zhihu.com/p/109057983 原文地址:https://azeria-labs.com/writi…

基础汇编语言编程

目录 什么是汇编语言? 工程搭建 新建工程 环境设置 测试是否成功 正式学习汇编语言 数据处理指令 填充,加,减,乘 思考:我们可以看到R0寄存器可以存放8位十六进制数,那么0x12345678能不能用mov存入&am…

汇编入门学习

学自狂神:最通俗易懂的计算机底层教学,二进制到汇编学习! 1、概述 学习路线: 语言 进制 进制如何运算 二进制 数据宽度 有符号数和无符号数 原码反码补码 位运算 位运算计算 汇编 寄存器 内存 汇编指令 内存复制 …

【学习笔记】汇编语言入门

汇编语言入门 概述 指令格式(最多包含4个区段):各区段之间用规定的分隔符分开 [标号:] 操作码 [操作数] [;注释]标号区段(当前指令行的符号地址)操作码区段(指令的操作行为,由操作…

关于汇编语言入门的几个案例

哈喽,大家好呀~这篇呢我们来看看汇编语言的几个入门小案例。 🥇个人主页:个人主页 🥈系列专栏:日常解决的问题 🥉与这篇汇编相关的文章: …

什么是汇编语言

汇编语言(assembly language)是一种用于电子计算机、微处理器、微控制器或其他可编程器件的低级语言,亦称为符号语言。在汇编语言中,用助记符(Mnemonics)代替机器指令的操作码,用地址符号&#…

汇编语言系列教程之基础入门 (一)

汇编语言系列教程之基础入门 (一) http://www.cnblogs.com/flyor/p/7208948.html 机器字长 机器字长是指CPU一次运算所能处理的数据的位数,一般来说这个数的和CPU的通用寄存器长度、数据总线的宽度等相等,在8086中为16bit。由于历…

快速入门汇编语言

这篇是我在先后学习了《汇编原理》、《CSAPP》第三章和《x86 data sheet》,以及经历了大量google后写出的总结性文档,用于自查和复习。若能有所助益,不胜荣幸。如有错漏,烦请不吝赐教。 1. 从C到汇编 在初学C语言时,…

汇编语言(一)

一、编程语言 1)机器语言 机器语言是机器指令的集合。在计算机中是一串0、1二进制数,计算机转变为高低电平,器件受到震动,进行运算。由于硬件设计和内部结构的不同,需要不同的电平脉冲来控制使它工作,所以…

汇编语言程序设计入门

前言:公号 「编程有料」后台回复 “大礼包” 即可获取近1000本助力你编程之路的电子书 汇编语言程序设计 一,汇编语言程序设计概述1,程序设计语言2,汇编语言源程序3,汇编语言程序开发过程 二,汇编语言基本语…

手把手教你学习汇编语言——从入门到起飞

专接本交流群:1051749714 (有什么问题欢迎进群讨论) 关于这篇博客,笔者会尽量用最简单的方式教会大家汇编语言以便应付各种考试 有什么不足的忘大佬评论或私信指出。 汇编环境安装 汇编运行调试 汇编指令 希望大家看完这些找一些…

小白的入门之——汇编语言程序设计教程

目录 第一章 基础知识 1.1汇编语言的特点 1.2计算机中数据表示的特点 【BCD码表示法(Binary Coded Decimal)】 【定点数】 —原码表示法— —反码表示法— —补码表示法— 浮点数 1.3 ​​​​​​计算机的数据存储 寄存器 存储器 数据在主…

汇编入门基础

1.什么是汇编语言? 汇编语言就是低级语言,直接描述/控制 CPU 的运行,我们知道,CPU 只负责计算,本身不具备智能。你输入一条指令(instruction),它就运行一次,然后停下来&…

汇编语言入门教程

一、汇编语言是什么 ​ 汇编语言是二进制指令(操作码)的文本形式,与指令是一一对应的关系。比如加法指令00000011写成汇编语言就是ADD 二、前置知识——寄存器 ​ 1、寄存器不依靠地址区分,而依靠名称,CPU通过名字去…

南邮《汇编语言程序设计》期末复习 CH3: 汇编语言指令集

本文转载自厉害的 孔姐姐 的 博客 。 由一只孔姐姐的小迷弟负责排版和整理~ 一、基本概念 1.指令:单个的 CPU 操作,通知 CPU 执行某种操作的 “命令” 指令集:所有指令的集合 机器指令:用二进制序列(0、1&#xff09…

汇编语言程序格式

1 逻辑段 汇编语言源程序由一个或多个逻辑段组成。 ❖ 一个程序中可以有几个同一类型的逻辑段。 ❖ 必须至少有一个代码段。 [注]源程序分段的目的在于程序结构清晰、便 于内存分配,寻址方便,一个源程序需要设置 几个段应根据具体…