看懂汇编
最后更新于
最后更新于
文章是自己学习汇编基础知识的记录,如有不正确请指正,谢谢各位。
⚠️️️️️️️️⚠️⚠️ 这篇文章不是非常专业系统的带你学习汇编的文章,这篇文章是对于汇编新手小白,从来没碰过的哥们快速了解,大致能够看懂汇编,遇到不慌张的目的,如果想专业学习汇编的童鞋请直接绕路,因为作者也是现学现卖 ⚠️⚠️⚠️
汇编语言(英语:assembly language)是一种用于电子计算机、微处理器、微控制器,或其他可编程器件的低级语言。在不同的设备中,汇编语言对应着不同的机器语言指令集。一种汇编语言专用于某种计算机系统结构,而不像许多高级语言,可以在不同系统平台之间移植。
使用汇编语言编写的源代码,然后通过相应的汇编程序将它们转换成可执行的机器代码。这一过程被称为汇编过程。
汇编语言使用助记符(Mnemonics)来代替和表示特定低级机器语言的操作。特定的汇编目标指令集可能会包括特定的操作数。许多汇编程序可以识别代表地址和常量的标签(Label)和符号(Symbols),这样就可以用字符来代表操作数而无需采取写死的方式。普遍地说,每一种特定的汇编语言和其特定的机器语言指令集是一一对应的。
许多汇编程序为程序开发、汇编控制、辅助调试提供了额外的支持机制。有的汇编语言编写工具经常会提供宏,它们也被称为宏汇编器。
现在汇编语言已不像其他大多数的程序设计语言一样被广泛用于程序设计,在今天的实际应用中,它通常被应用在底层硬件操作和高要求的程序优化的场合。驱动程序、嵌入式操作系统和实时运行程序都会需要汇编语言。
👆以上信息来自维基百科 👆
为了让大家比较形象的了解汇编,我们先看下汇编的代码。 .s
文件结尾的,代码就长这个样子,没学过这东西 谁也不知道它写的是个啥,我们接着往下看。
X86 寄存器 (这里不谈,首先我不熟悉,ARM64 大同小异) ARM 寄存器(这里不谈,首先我不熟悉,ARM64 大同小异)
各位童鞋,如果你现在也和我一样,看到汇编代码一脸懵逼,变量也不知道,命令也不懂,那么一定把下面的这几个东西看懂,如果看我的文章描述不明白,自己去 Google 也搞明白再看汇编代码才能看的明白哈。
一,CPU 不同的架构的指令集有哪些,这里只说 ARM64 二,有哪些寄存器?有哪些特殊功能的寄存器? 三,命令指令有哪些,分别作用是啥?
👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇
好了,我开始说人话了,这就一个新手文章搞这些确实没啥卵用,其实在我们的移动开发当中(做手机软件的码农),比较常用的就是 ARM
和 X86
系列的,ARM
是真正的手机上跑的指令集,X86
是模拟器上跑的指令集。
再进一步说话,说句实话对于咱们新手来说,真的,了解 ARM64
和 ARMv7
就可以了,X86
都没啥必要,毕竟是模拟器,不是真正跑在客户手机上的指令集。
2.0.1.1 向下兼容
这里需要指明的是,新的指令集是对旧的的指令集有兼容的,比如 ARMV7
就会对 ARMV7
之前的指令集兼容。
2.0.1.2 ARM64
ARM64
的指令集,比如 ARM64
ARM64e
是要在设备运行在 64 位操作系统
2.0.1.3 ARM
ARM
的指令集,比如 ARMV7
ARMV7s
是要在设备运行在 32 位操作系统
2.0.1.4 X86
X86
的指令集,是要在设备运行在 AMD
和 Intel
的处理器上。
2.0.1.5 看看编译出来的文件
这里以 iOS 应用为例,我们看下我们编译出来的可执行文件指令集是怎样的,接入看图环节。
从上图可以看出,我们在操作系统 12.2 以上的版本下,选择模拟器,编译出来的可执行文件,由于我们的操作系统是 64 位的,所以是 X86_64
正是我们之前提到的 X86
指令集系列。
从上图可以看出,我们在操作系统 12.2 以上的版本下,选择真机,编译出来的可执行文件,只有 ARM64
,因为我们的操作系统是 64 位的,所以是 ARM64
不是 ARM其他
,这时有的同学可能会问为什么我的项目编译出来的可执行文件是两种架构呢,请继续往下看,下面就是为什么会有俩中架构。
从上图可以看出,我们在操作系统 10.0
以上的版本,选择真机进行编译可以看到有两种编译指令分别是 arm64
和 arm_v7
,前者是 64bit
操作系统使用,后者是 32bit
操作系统使用,其实调最低支持操作系统版本可知,当最低系统版本小于 11.0
的时候就会打出 arm64
和 arm_v7
两个指令集的可执行文件,当最低系统版本大于等于 11.0
的时候只会打出 arm64
指令集的可执行文件。iOS11
操作系统,以及以后的操作系统都是 64 位操作系统,所以只需要 arm64
就可以了,但是如果你的最低版本选择的是 10.0
,意味着你的包要支持最低 10.0
~13.0(目前最高)
的版本,其中 10.0 ~ 10.xxx
用的 32位ARM指令集
,11.0~13.0
用的 64位ARM指令集
,所以我们看到包里面有两种指令集,因为指令集的兼容性,其实你可以 10.0~13.0
都使用 arm64
指令集也可以,看下面的截图。
四: 多个指令集的可执行文件对包大小的影响
从上图可以看出来两个架构的包比一个架构的包要大 100多KB
咱们先聊一下,为啥要知道寄存器。
上面标注的就是寄存器,其实我们看到的汇编代码都是在操作寄存器,所以我们得知道有哪些寄存器,这玩意咋使唤,注意点啥,才能看得懂汇编代码,你说是不是这个理。
ARM64 寄存器分为好几种,分别介绍以下:
2.0.2.1 通用寄存器
我们在项目实际打个 breakpoint 来看下 X[n]
W[n]
寄存器的区别,下面直接上截图为了说明这,我一开始也是一脸懵逼,因为在 arm_v7
等非 64 位的指令集,都是 R[n]
的寄存器,当我用 arm64
指令集发现一会 w[n]
x[n]
始终不知道差别,所以就做了下面的测试。
2.0.2.2 向量和浮点寄存器
浮点寄存器
因为浮点数的存储以及其运算的特殊性 CPU 中专门提供浮点数寄存器来处理浮点数 浮点寄存器 64 位: D0 – D31 32 位: S0 – S31
向量寄存器
现在的 CPU 支持向量运算。(向量运算在图形处理相关的领域用得非常的多)为了支持向量计算系统了也提供了众多的向量寄存器。 向量寄存器 128 位:V0-V31
2.0.2.3 特殊寄存器
小伙伴们如果想知道一共有哪些寄存器,可以断点使用 lldb
的命令 register read
来查看。
一,PC:(Program Counter)
为指令指针寄存器,它指示了 CPU 当前要读取指令的地址
在内存或者磁盘上,指令和数据没有任何区别,都是二进制信息
CPU 在工作的时候把有的信息看做指令,有的信息看做数据,为同样的信息赋予了不同的意义。
CPU 根据什么将内存中的信息看做指令?
CPU 将 pc 指向的内存单元的内容看做指令
如果内存中的某段内容曾被 CPU 执行过,那么它所在的内存单元必然被 pc 指向过
保存将要执行的指令的地址(pc 就是指令 读/写等) pc 指向那里就是读那里,它读的是内存地址 (指令保存到高速缓存中)首先去高速缓存映射关系表中找,如果有就直接在高速读,如果没有把内存一堆 copy 到高速缓存中。
实践:
如果把 pc 寄存器改了下一个即将执行 可以使用 register write pc 0x100a0ab20
二,SP/FP
sp 寄存器在任意时刻会保存我们栈顶的地址
sp 寄存器指向哪里,哪里就是栈
fp 为栈基址寄存,用于保存栈底地址
状态寄存器 CPSR
我们知道汇编的世界里,除了寄存器,我们还要学会使用 汇编指令才行。
我看了一些 ARM 的汇编指令还真不算少,找到了一个小哥写的文章,看了一下感觉蛮多的,有兴趣的话,可以去找找看 https://juejin.im/post/5bd2d182e51d457abb354c75 我这里就介绍几个简单常用的。
一,STR
将寄存器的数据存入栈里面 看下面例子
再来看一个 例子
二,LDR
是从栈里面取值赋值给寄存器。
这里我想说的是,请一定把 STR 命令 看懂,看懂 STR 命令 这个就很简单了 一样的道理。
三,SUB(求差)
三,BL(跳转指令)
CPU 从何处执行指令是由 pc 中的内容决定的,我们可以通过改变 pc 的内容来控制 CPU 执行目标指令
ARM64 提供了一个 mov 指令(传送指令),可以用来修改大部分寄存器的值(#10:就是数字),比如 mov x0,#10、mov x1,#20
但是,mov 指令不能用于设置 pc 的值,ARM64 没有提供这样的功能
ARM64 提供了另外的指令来修改 PC
的值,这些指令统称为转移指令,最简单的是 bl
指令
在 main
中执行
四,RET(结束指令)
ARM64 平台的特色指令 它面向硬件做了优化处理的
默认使用 lr(x30)
寄存器的值,通过底层指令提示 CPU 此处作为下条指令地址!
x30
寄存器存放的是函数的返回地址。当 ret
指令执行时刻,会寻找 x30
寄存器保存的地址值!
CPU 指令集有哪些 我们看看维基百科上查到的典型的指令集: 链接: https://en.wikipedia.org/wiki/Instruction_set_architecture 如果有兴趣可以点进去看看每个系列的指令集具体包括啥 X86
ARM
等等。
一: 先看看模拟器下编译的是什么样子
二: 再看看真机在最低系统版本12.2以上编译的是什么样子
三: 再看看真机在最低系统版本10.0以上编译的是什么样子
当使用 x0
- x30
访问时,是一个 64 位的数;当使用 w0
- w30
访问时,是一个 32 位的数,访问的是寄存器的 低 32 位,如图:
先看一个例子