深入理解汇编

本文最后更新于:2022年7月22日 下午

深入理解汇编

汇编对于软工人来说已不在陌生,但是如果仅限于将汇编理解成一种更底层的编程语言未免太过肤浅。很多时候,我们需要探究计算机底层的机制,比如操作系统和计算机架构这类比较深层的东西时,我们总觉得自己的汇编还是不够看。我们似乎将汇编脱离出了计算机底层,而汇编(assembly)本就是底层知识树的纷繁分枝之一。所以,对于汇编,我们有必要再深入了解一下,补充一些我们可能会遗漏的东西🧐

0. 写在前头

  1. 本文的汇编基于intel的处理器,简单来说就是默认采用x86架构的处理器运行汇编(ARM架构的处理器就别想了,对,说的就是你Apple Silicon!)

  2. 本文全篇使用AT&T风格的assembly,当然,大多数人在学校课程中接触到的汇编基本上是Intel风格的assembly(毕竟是intel官方手册的风格)。

    如果你想了解两种风格的主要区别,可以看一下这篇知乎文章——AT&T 和 Intel 风格的区别

  3. 本文使用C语言作为汇编前置高级语言,gcc作为编译器。因为C语言其较为偏底层的特性,用来配合assembly很好用,而gcc编译器本身就是支持AT&T风格的鼻祖

一句话总结:本文环境为x86处理器和Unix-like操作系统

1. mov和lea的区别?

小白开始学习assembly的时候总是觉得mov和lea不是差不多的嘛,两种指令的使用场景到底有什么不同?我现在才解决了这个困惑。

首先两种指令的格式:

mov S, D
lea S, D

乍一看确实没有区别,都是将源地址数据传入到目的地址。

一般的mov用法:

movq $1, %rax
movq %rax, 8(%rsp)

一般的lea用法:

leaq (%rdi,%rsi,4), %rax
leaq 7(%rdx,%rdx,4), %rax

x86有一个限制——传送指令的两个操作数不能都指向内存位置。所以mov不会出现第一个操作数是一个地址,只会是立即数或者是寄存器值。lea确实按照这个规矩来了,但是它是将地址当作数来传入

有点绕,我们看看lea的定义,load effective address,形式是从内存读取数据到寄存器,但该指令并不是从指定的位置读入数据,而是将有效地址写入到目的操作数

也就是说(%rdi,%rsi,4)在lea这里并不是内存地址中存入的一个数据,而就是这个内存地址!

所以我们明白了:

在mov中,寻址操作只要涉及到内存的就是需要找到内存地址中的那个值,但是lea,只需要计算出内存地址就行,地址即为值

2. 浅谈大小端

就像粽子的咸甜之争一样,在计算机的数据内存存储格式这个问题也有使用“大端”还是“小端”的争议。我先在此声明,本人是小端派的😜

那么啥是大端,啥是小端?

简单来说一个数,如0x1234,想要在内存中存储下来,就得划分片区,到底是12在低地址区域,34在高地址区域(这就是大端),还是34在低地址区域,12在高地址区域(这就是小端)

比如(0x101)=12,(0x102)=34,这就是大端,反之是小端

那么为啥我更推崇小端?

因为小端更符合计算的哲学。如果要分个类的话,我认为大端更适合读数,即语言的习惯。因为我们的语言是先读高位数字,再读低位数字,比如一百一十一;而小端更适合计算,因为计算的时候都是从低位数字开始,然后依次向高位进位计算。我们的数据是给机器计算的,不是给人类来读的,所以从低位开始存入低地址(也就是小端)更符合计算机的哲学。

那么有人想过为啥读数是从高位开始的?

我觉得那是因为人的语言重点在于信息的获取效率,数字对于人的重要信息就是这个数的数量级,可以快速得到这个数的概念。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!