介绍
汇编是一类编程语言,每种cpu对应一种cpu语言,这些语言语法大同小异,指令集有所不同,
那么这些cpu语言统称为汇编语言,与java,c++,python等高级语言无异, 只不过汇编更加接近硬件,代码执行效率高
二进制>汇编>c>java
所有编程语言都有相应语法,汇编也不例外, 语法是人定的,只是一套公共协议,目的是方便程序员进行程序开发
为什么要学汇编
- 了解程序的本质, 利于日常开发
- 从事硬件相关开发工作
- 进行反编译逆向
- 装X
常见的cpu架构
x86 架构 : PC 端主流 高性能高功耗
ARM 架构: 移动端主流 体积小低功耗
MIPS 架构: 龙芯3号 国产cpu
不同架构使用的指令集也不一样, X86使用了CISC复杂指令集 ARM采用了RISC精简指令集
RISC可以说是从CISC中取其精华去其糟粕,简化指令功能,让指令的平均执行周期减少,达到提升计算机工作主频的目的,同时引入大量通用寄存器减少不必要的读写过程,提高子程序执行速度,这样一来程序运行时间缩短并且减少了寻址,提高了编译效率,最终达到高性能目的
不同cpu架构所对应的汇编语法大致相同, 只是指令集不同
寄存器
顾名思义, 寄存器可以理解为是寄生在cpu上存放数据的容器, 在计算机当中,用于存放数据的容器有很多,比如内存条,硬盘等等, 那寄存器有什么不一样呢?
- 寄存器靠近cpu,读写数据速度远大于内存
- 进行数据的临时存储
当然 cpu内部除了有寄存器之外,还有运算器和控制器, 对于我们程序员来讲,只需要学习寄存器即可
缓存
寄存器和缓存是两个概念, 由于cpu执行速度太快, 而内存读写数据远远跟不上, 这时需要借助缓存进行数据缓冲,相当于是寄存器和内存之间的中间桥梁, 这样cpu在执行指令的时候能够有源源不断的数据供给
了解:寄存器–>一级缓存–>二级缓存–>三级缓存–>内存
拓展: 如果内存条的读写性能过差, 那么cpu再强悍也使不上劲,巧妇难为无米之炊, 因此平常我们再自己组装电脑时,除了内存条的容量之外,还需要考虑到内存条的品质, 否则影响cpu性能,硬盘同理
为什么要了解寄存器
因为程序员如果想要操控cpu或者修改内存, 不能直接操控, 需要借助寄存器, 更改寄存器当中的数据间接地操控cpu和内存
寄存器的数量
在高级语言中如果要对两个变量进行数据交换,我们通常的做法是使用一个temp临时变量,比如:
1 | int a=1; |
寄存器是一个存储容器,也可以通俗理解为是一个变量, 那么cpu在进行数据交换时明显一个寄存器是不够的, 在8086cpu中,通用寄存器有好几个,比如ax,bx,cx,dx 这些名称是固定的, 根据cpu的不同名称也各不相同, 咱们只需知道每种cpu都有相应的通用寄存器, 寄存器数量越多,自然运算效率越高
寄存器的分类
- 通用寄存器 (通用): 用于存放临时数据, 可以简单理解为高级语言中的临时变量
- 段寄存器 (特有): 内存分段管理 x86架构中分为数据段,代码段和栈段 ARM架构中没有段寄存器
- 浮点寄存器 (特有): 专用于浮点数的运算
- 向量寄存器 (特有): 专用于向量运算
- 标志寄存器
- 状态寄存器
- ……….
通用寄存器的命名
在x86架构中 , 一共有四个通用寄存器,以16位x86为例, 分别取名为ax,bx,cx,dx 最大只能装16位的数据
在ARM架构中, 一共有31个通用寄存器,以64位arm为例 从x0到x30
在MIPS架构中,, 一共有32个通用寄存器 ,从$0到$31
在x86架构中,不同精度cpu 通用寄存器名称有所区分:
1 | ;在x86架构中,不同精度cpu 通用寄存器名称有所区分: |
e是扩展的意思,在386以前,CPU的寄存器的16位的,用AX,BX等表示,
386及以后的CPU,它们的寄存器的32位的,所以就用多一个E来表示
在ARM架构中,不同精度cpu 通用寄存器名称同样有所区分:
1 | 0x1122334455667788 |
在64位cpu中WN是XN的低32位, 属于xN的一部分, WN数据改了,xN也会跟着一块改, 也就是说WN无法独立存在, 同时ARM中并没有提供16位和8位寄存器的访问和使用
汇编代码初探
进制转换基础
- 计算机只识别二进制, 汇编中一般使用十六进制表示数据, 使用十六进制是为了方便程序员阅读和开发
- 二进制和十六进制转换
1 | 0101 1100 1001 0010 //二进制 |
在x86汇编代码中,十六进制写法为如下:
1 | mov ax,2000H ;h结尾表示十六进制, h不区分大小写 |
在ARM汇编代码中,十六进制写法为如下:
1 | mov R0,#0x2020202A ;使用0x开头表示 #是固定写法,暂不用理会 |
数据单位
- 位 :一个二进制位
- 字节 :8个二进制位表示一个字节
由于八个二进制位转换成十六进制后是两位十六进制数, 所以两个十六进制数占用一个字节:
1 | 0x20 ;占用一个字节 |
在计算机中最小的数据单位是位,但在内存中,最小的数据单位是字节,一个内存单元占用一个字节, 内存单元就是一个存放数据的容器,可以比喻为一栋公寓里面的小单间, 每个单间住着一对夫妻
内存单元和地址
内存单元相当于一栋公寓里的小单间, 每个单间里面住着一个字节(一对夫妻) , 一对夫妻是两个人, 形容两个十六进制数
地址就相当于这个房间的门牌号, 通常使用十六进制表示(也叫物理地址)
cpu想要读写内存中的数据, 需要通过地址来需要对应的内存单元,也叫寻址
那么问题来了, 一栋公寓里一共有多少个单间呢, 或者说单间的数量跟什么有关呢?
- 内存条容量: 既然每个内存单元占的空间是固定的, 那么内存容量越大,房间自然就越多
给房间贴门牌号
1 | 0x1 |
现实中门牌编号最大值和什么有关?
装修师傅的计算能力
门牌金属板的宽度(字体大小不变的前提)
在计算机的世界中
装修师傅的计算能力—–>cpu运算能力
门牌金属板的宽度——->地址总线宽度(地址总线数量)
如果对应的cpu是16位的, 同时地址总线也是16位,那么最大只能运算表示16位数也就是0xFFFF
,
如果对应的cpu是32位的, 同时地址总线也是32位,那么最大只能表示32位数也就是0xFFFFFFFF
如果对应的cpu是64位的, 同时地址总线也是64位,那么最大只能表示64位数也就是0xFFFFFFFFFFFFFFFF
在8086cpu中 cpu是16位 但是地址总线却是20位, 本来最大只能表示16位地址值, cpu设计者为了让其能表示20位地址,使用了段地址*16+偏移地址的形式来表示20位地址
物理地址=段地址*16+偏移地址
如果要表示一个20位物理地址0xFFFFA,可以有一下四种写法:
1 | 0xFFFFA=0xFFFF*16+0x000A //FFFF0 10*10=100 |
因此计算机的寻址能力不单单和cpu有关还和地址总线有关, 32位操作系统对应32位地址总线, 这也就是为什么即便你用的是64位cpu,如果只装了32位操作系统,无法完全发挥cpu和内存的性能
栈和队列
栈和队列都是数据存储结构,数据结构大致包含以下几种存储结构:
- 线性表,还可细分为顺序表,链表、栈和队列;
- 树结构,包括普通树,二叉树,线索二叉树;
- 图存储结构
- 队列结构 :先进先出, 和排队一样
- 栈存储结构 : 先进后出, 类似于往往杯子里放饼干, 第一个放的最后一个取出
栈作用 :
- 用于存储临时数据,
- 对数据进行暂时性保护,不被复写
- 寄存器不够用时,使用栈临时代替中转
寄存器和栈同样用于存放临时数据, 那么它们两者有什么区别呢?
寄存器类似于全局变量,是个公共容器,可以被所有函数读写,寄存器中的数据容易被覆盖, 常用于短周期使用
栈空间是累加型结构: 如果想要复写第一个放入的数据,必须先将后面存放的数据丢弃, 类似于递归, 适合嵌套数据,这也是为什么函数和函数中的局部变量都存放在栈中的原因
总线
存在的意义, 内存中的数据不能直接运算,必须将其读取到寄存器中进行处理, cpu运算完毕后,将其保存至内存中, 那么这一系列过程中,涉及到数据传输, 那么这三条线就是干这个用的
x86汇编语法
- 注释
1 | ;我是注释 |
了解: arm汇编注释同为; 而mips汇编注释为#
- 变量取值和赋值(传送指令)
1 | ;赋值 |
无论是x86还是arm传送指令都是mov
存放的数据大小根据使用的寄存器而定, 比如ax是16位寄存器,最大只能存放16位数据,也就是4位十六进制数据
- 函数声明
结构如下:
1 | 函数名: |
示例:
1 | print: ;函数名 |
- 函数调用
x86架构中使用关键指令call
, ARM架构中使用关键指令bl
x86架构汇编示例:
1 | call print ;调用print函数 |
ARM架构汇编代码示例:
1 | .text |
C语言内嵌汇编代码(GCC内联汇编)
格式
1 | asm volatile( ;asm也可写成 __asm__ 或者__asm |
代码示例:
1 | //将input的值赋值给result |
我们看到,movl指令的操作数(operand)中,出现了%1、%0,这往往让新手摸不着头脑。其实只要知道下面的规则就不会产生疑惑了:
在内联汇编中,操作数通常用数字来引用,具体的编号规则为:若命令共涉及n个操作数,则第1个输出操作数(the first output operand)被编号为0,第2个output operand编号为1,依次类推,最后1个输入操作数(the last input operand)则被编号为n-1。
具体到上面的示例代码中,根据上下文,涉及到2个操作数变量a、b,这段汇编代码的作用是将a的值赋给b,可见,a是input operand,而b是output operand,那么根据操作数的引用规则,不难推出,a应该用%1来引用,b应该用%0来引用。
常用限制符参照:
限制符 | 说明 |
---|---|
r | 通用寄存器 |
a | eax,ax,al |
b | ebx,bx,bl |
c | ecx,cx,cl |
d | edx,dx,dl |
S | esi,si |
D | edi,di |
q | 寄存器a,b,c,d |
m | 使用合法的内存代表参数 |
g | 任意寄存器,内存,立即数 |
为什么有些汇编语法不一致
C语言外链汇编
新建一个汇编原文件, linux平台.s结尾 ,windows平台.asm结尾
1 |
|
然后在C中进行相应调用即可
本文为作者原创 转载时请注明出处 谢谢
乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站