《编译系统-自底向上研究方法》汇编器分析 - 弦外之音

/ 0评 / 0

之前的文章 《编译系统-自底向上研究方法》ELF符号段《编译系统-自底向上研究方法》链接器分析, 已经讲解了 可运行文件格式 ELF 以及 链接器的一些主要的作用。

无论是 操作系统运行 可执行文件,还是 链接器 把多个 .o 目标文件合并在一起,他们操作的都是二进制的数据。

而 本文要讲的汇编器,操作的是 ASII 码,也就是文本字符串。实际上,处理 二进制数据,比文本数据 方便得多,读二进制数据,只需要一个结构体不断解析就行了,二进制数据是非常规范化的数据。

而解析文本字符串,需要用到的技术复杂一点。



先给一个 main.c 的文件源码,如下:

int sum(int *a,int n);
int array[3] = {7,5,1};
​
int main() {
    int val = sum(array,3);
    return val;
}

对应的汇编指令如下:

    .file   "main.c"
    .text
    .globl  array
    .data
    .align 8
    .type   array, @object
    .size   array, 12
array:
    .long   7
    .long   5
    .long   1
    .text
    .globl  main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movl    $3, %esi
    leaq    array(%rip), %rdi
    call    sum@PLT
    movl    %eax, -4(%rbp)
    movl    $8, %eax
    movl    -4(%rbp), %eax
    movl    %ebx, %eax
    movl    %eax, -4(%rbp)
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
    .section    .note.GNU-stack,"",@progbits

C程序代码不用管,咱们主要看 汇编代码。先看这两条 汇编代码,movl $3, %esimovl -4(%rbp), %eax ,这两条指令都是从上面的代码摘下来的。我们看一下这两条汇编最后生成的机器码是怎样的,还是用的 objdump 命令。

如上图所示,8bbe 这两个机器码都是 mov 指令。实际上,汇编指令跟机器码是 一对多的关系,根据参数的不同,指令机器码会有所不同。我再多加几条不同参数的 mov 指令,方便对照。

一条 mov 指令 有 3个 符号(token),mov 指令机器码,源数据,目的地。源数据有 3 种类型:

1,立即数,例如 $8 ,第一条指令是把 8 直接放进去 eax 寄存器。AT&T 的汇编语法,立即数前面要加 $ 。

2,内存地址,-0x4(%rbp) 就是内存地址,把这个地址的4个字节内容 复制给 eax 寄存器。

3,寄存器,源数据 是寄存器,例如把 ebx 寄存器的内存 复制 给 eax 寄存器。

根据 源数据 类型的不同,mov 对应的机器码也是不一样的,上图可以看出,分别是 b8 ,8b,89,这3个都对应着 mov 指令。

所以 汇编器 as 在对 汇编指令 做词法分析的时候,只要拆出来 3 个符号 mov 指令机器码,源数据,目的地。判断源数据是哪一种,就能生成对应的机器码。这里的 ASII 码转成 二进制指令,实际上只是一个找对应的过程。由于 汇编器不会做 逻辑优化 ,所以他的编译过程实际上非常简单。逻辑优化已经被上面的 编译器给做了。


汇编器 as 除了根据 一对多 规则,把ASII 码指令 转成 二进制指令之外,还有一个比较重要的事情,汇编器也做了。

就是生成 ELF 文件格式,头部,段表啊,这些东西。汇编器会根据不同的平台生成不同的文件格式,例如 Windows 平台的是 PE 格式。

这里讲一个扩展的知识点,ASII码汇编指令 跟 二进制指令 跨平台都是很低的,但是他们的跨平台又有点区别,二进制指令实际上是 CPU 指令,只要 CPU 架构一样就能运行。但是 汇编指令 有很多种风格,例如 AT&T 跟 intel 风格。

举个例子,一个 intel I7 的机器装了个 windows10 系统,编译 main.c 生成的是 intel 风格的汇编指令。一个 intel I7 的机器装了个 ubuntu18系统,编译 main.c 生成的是 AT&T 风格的汇编指令,这两种汇编指令语法有点差异,互不通用的,但是他们编译出来的二进制机器码是一样的。因为大家都是 I7 的CPU。

下篇文章会用 clion 来搭建环境,调试 as 汇编器的内部逻辑。


由于笔者的水平有限, 加之编写的同时还要参与开发工作,文中难免会出现一些错误或者不准确的地方,恳请读者批评指正。如果读者有任何宝贵意见,可以加我微信 Loken1,QQ:2338195090。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注