想好好熟悉一下llvm开发一个新后端都要干什么,于是参考了老师的系列文章:
LLVM 后端实践笔记
代码在这里(还没来得及准备,先用网盘暂存一下):
链接: https://pan.baidu.com/s/1yLAtXs9XwtyEzYSlDCSlqw?pwd=vd6s 提取码: vd6s
这一章介绍如何生成 ELF 文件,ELF 文件是一种通用的可执行文件、目标文件和共享库与核心转储文件标准,最早是由 System V 应用二进制接口发布,之后成为一种标准,并很快被类 Unix 操作系统接受。几乎所有支持编译的后端平台都需要生成一种可执行文件格式来执行代码,现在主流的三种可执行文件分别是 Linux 系统及裸机系统支持的 ELF 文件、Windows 系统支持的 COFF 文件以及 MacOS 系统支持的 Mach-O 文件格式。我们让 Cpu0 后端生成 ELF 文件格式。
之前章节我们介绍了 Cpu0 后端生成各种指令编码的代码,所以有关于指令编码的行为,是由 td 文件中的描述来确定的,LLVM 的公共部分已经帮我们生成了指令编码的功能。但目前还没有定义生成 ELF 文件的头部、段组成、重定位信息等内容,这一章主要实现这部分内容。
这一章,我们会使用二进制解析工具来检查 ELF 文件,比如 objdump 和 readelf 文件。LLVM 中有类似于 objdump 的工具,默认生成名称为 llvm-objdump,基本使用和 objdump 一致,我们这一章也会试用这个工具。
一、ELF文件的简单介绍
这一节只是简单介绍,有关于详细的学习材料,在网上可以找到很多。ELF 文件格式的支持是 LLVM 默认便已经完成的,不需要我们做更多的工作(少量工作在第 2 章中已经完成)。ELF 文件格式有两种视图,分别是目标文件视图和可执行文件视图。目标文件视图是为链接器服务的,它的划分标准是段(Section),不同的段有 .text 段存放代码内容、.data 段存放数据、.rodata 段存放只读数据等,这些段的索引保存在 ELF 文件末尾的段头部表(Section header table)中。链接器通过访问段头部表来检索到各段。
可执行文件视图是为执行服务的,它的划分标准是节(Segment),不同的节可能是多个段的组合,比如执行时,因为要关心数据的访问权限,.text 段和 .rodata 段会合并为一个只读数据的节。加载器会访问位于 ELF 文件开头文件头部表之后的节头部表(Segment header table),来访问各节。
二、工具的使用
2.1 llvm-objdump和llvm-readelf的工具选择
我一般反汇编用的llvm-objdump,读字段用llvm-readelf较多,主要看大家习惯。虽然readelf写着是读elf文件,但是llvm对于llvm-readelf进行了扩展,也能用来读取coff和macho文件。
选项有多个的情况是有缩写
2.2 llvm-objdump
2.2.1 --mattr=a1, +a2, -a3...
指定架构特殊的属性,与llc的选项类似。
2.2.2 --mcpu=cpu-name
指定一个cpu类型,与llc的选项类似。
2.2.3 --disassemble-options=options/-M <value>
使用目标架构特殊的反汇编器选项
2.2.4 --disassemble-symbols=<value>
反汇编某一个符号,例如:llvm-objdump --disassemble-symbols='_Z3fooSt6vectorIiSaIiEE' test.o
2.2.5 --disassemble/-d
对文件的执行字段进行反汇编,llvm-objdump -d test.o
2.2.6 --disassemble-all/-D
对文件的所有字段进行反汇编,llvm-objdump -D test.o
2.2.7 --dynamic-reloc/-R
打印文件的动态重定位信息,llvm-objdump -R test.so
2.2.8 --dynamic-syms/-T
打印文件的动态符号表
2.2.9 --syms/-t
打印文件的符号表
2.2.10 --file-headers/-f
打印文件头信息
2.2.11 --section-headers
打印各个section的概述信息
2.2.12 --headers/-h
打印section header信息,我看效果貌似跟--section-headers是差不多的
2.2.13 --all-headers/-x
打印可用的头信息,重定位信息和符号表。
2.2.14 --full-contents/-s
打印所有内容
2.2.15 --x86-asm-syntax
--x86-asm-syntax=att/intel,打印AT&T还是Intel风格的反汇编代码。
2.3 llvm-readelf
2.3.1 -program-headers/-l/--segments
打印segments信息
2.3.2 --section-headers/--sections/-S
打印sections headers信息
2.3.3 --section-details/-t
打印sections细节信息(不知道为啥说成细节,我觉得与-S类似)
2.3.4 --file-header/-h
打印文件头
2.3.5 --section-relocations/--sr
打印sections的重定位信息
2.3.6 --relocations/--relocs/-r
打印重定位信息
2.3.7 --symbols/--syms/-s
打印符号表,包括动态符号表
2.3.8 --dynamic-table/--dynamic/-d
打印dynamic section信息
2.3.9 --dyn-symbols/--dyn-syms/--dt
打印动态符号表
2.3.10 --dyn-relocations
打印动态重定位信息。
2.3.11 --headers/-e
相当于--file-header, --program-headers, --section-headers
2.3.12 --all/-a
打印所有信息,相当于--file-header, --program-header, --section-headers, --symbols, --relocations, --dynamic-table, --notes, --verson-info, --unwind, --section-groups and --histogram
三、修改前的效果
我们使用llvm-objdump -d打印我们编出的cpu0架构的目标文件的反汇编试一下:
会提示我们cpu0架构没有反汇编器,这是我们这一章要适配的内容。
四、修改部分
4.1 新增的文件
4.1.1 Disassembler/Cpu0Disassembler.cpp
在这个反汇编文件中,实现了 td 文件中所有反汇编函数引用的函数,即 DecoderMethod 关键字指定的函数,尤其是对应一些特殊操作数的反汇编,比如内存引用的反汇编,因为这种特殊操作数格式是我们自定义的 td 类来定义的,所以也需要指定其反汇编方法。
4.2 修改的文件
4.2.1 Cpu0InstrInfo.td
对一些基本类添加反汇编函数的引用。这里添加了 JumpFR 类的引用。
五、修改后的效果
能正确打印出反汇编的内容。