初识汇编
写在前面
盼望着盼望着,终于在前些日子买了人生中第一台 MacBook Pro 15。欣喜之余,第一件事就是去拜读大别写的 「macOS 高手进阶 v3」,之前 v2 就已经受益良多,但是受限于没有 Mac 而不能亲自上手实践一下书中的例子,使得阅读功效大打折扣。
学习汇编的动机
当看到 Process and Thread 这章的时候,书中出现了汇编代码的例子,加之之前就一直想学习一下汇编,总感觉本科阶段这门课被砍了是一大遗憾,于是乎说干就干,打开 Chrome 就搜索到了一篇阮老师的文章,正式迈入了汇编世界的大门。本篇也只是我在阅读阮老师文章过程中所做的笔记,定有不详细或者不当之处,还恳请谅解并大方指出,多谢。
汇编语言是什么?
学习编程其实就是学习高级语言,即那些为人类设计的计算机语言。而计算机不懂高级语言,所以必须通过编译器转成二进制代码才可以运行。而汇编就属于低级语言,可以与硬件进行直接交互。
CPU 本身只负责计算,执行一条条指令,运行结束就停下来等待下一条指令。这些指令都是二进制的,称为操作码 (opcode) ,比如加法指令就是 00000011。
对于人类而言,二进制程序是不可读的,根本无法知晓机器干了什么,为了解决可读性需求和可编辑性,就诞生了汇编语言。
汇编语言是二进制指令的文本形式,与指令是一一对应的关系。比如,加法指令 00000011 写成汇编语言就是 ADD。只要把汇编语言还原成二进制指令,它就可以被 CPU 直接执行,所以它是最底层的低级语言。
基础知识
学习汇编语言首先必须了解两个知识点:寄存器 & 内存模型。
寄存器
CPU 本身只负责运算,不负责存储数据,数据一般都存储在内存之中,CPU 要用的时候就去内存读写数据。但是 CPU 的运算速度远高于内存的读写速度,为了避免 CPU 长时间等待 I/O 操作,CPU 都自带一级、二级缓存,甚至三级缓存,缓存可以看作是读写速度较快的内存。
数据存储在缓存中的地址是不固定的,CPU 每次都要读写都需要寻址也会拖慢运算的速度,因此除了缓存之外,CPU 还自带了寄存器 (rigister) ,用来存储最常用的数据。
CPU 优先读写寄存器,再由寄存器和内存交换数据。
每一个寄存器都有自己的名字,CPU 直接通过名字找到具体的寄存器读写数据,这样的速度是最快的,有人比喻寄存器为 CPU 的零级缓存。
寄存器的种类
早期的 x86 CPU 只有 8 个寄存器,而且每个都有不同的用途。现代 CPU 已经有上百个之多,都变成通用寄存器。不特别指定用途了,但是早起寄存器的名字都被保留了下来。
- EAX
- EBX
- ECX
- EDX
- EDI
- ESI
- EBP
- ESP
这 8 个寄存器之中,前七个都是通用的,ESP 寄存器有特定用途,用于保存当前 Stack 的地址。
我们常常看到 32 位 CPU、64 位 CPU 这样的名称,其实指的就是寄存器的大小。32 位 CPU 的寄存器大小就是 4 个字节。
内存模型
Heap
因为用户主动请求而划分出来的内存区域叫做 Heap (堆)。它由起始地址开始,从低位向高位增长。堆的一个重要特点就是不会自动消失,必须手动释放,或者由垃圾回收机制回收。
Stack
除了 Heap 外,其他的内存占用叫做 Stack (栈)。
栈是由于函数运行而临时占用的内存区域。
栈是由内存区域的结束地址开始,从高位向低位分配。
CPU 指令
push 指令:push 指令用于将运算子放入 Stack
call 指令:call 指令用来调用函数
mov 指令:mov 指令用于将一个值写入某个寄存器
add 指令:add 指令用于将两个运算子相加,并将结果写入第一个运算子
pop 指令:pop 指令用于去除 Stack 最近一个写入的值(即最低位地址的值),并将这个值写入运算子指定的位置
ret 指令:ret 指令用于终止当前函数的执行,将运行权交还给上层函数,也就是当前函数的帧将被回收
#EOF