从头开发一个RISC-V的操作系统(三)编译与链接
文章目录
- 前提
- GCC
- GCC简介
- GCC的主要执行步骤
- GCC涉及的文件类型
- ELF
- ELF简介
- ELF文件格式
- ELF文件处理工具:Binutils
- 练习
- 参考链接
目标:通过这一个系列课程的学习,开发出一个简易的在RISC-V指令集架构上运行的操作系统。
前提
这个系列的大部分文章和知识来自于:[完结] 循序渐进,学习开发一个RISC-V上的操作系统 - 汪辰 - 2021春,以及相关的github地址。
在这个过程中,这个系列相当于是我的学习笔记,做个记录。
GCC
GCC简介
我们在Linux下经常使用的GCC是由GNU开发的编译器套件。GCC的初衷是为GNU操作系统专门编写一款编译器,现在已经被大多数“Unix-like”操作系统采纳为标准的编译器。
现在来说,另一个常见的编译器套件还有LLVM,它是由苹果公司开发的。
GCC的主要执行步骤
一个.c程序到一个可执行文件主要包括这么几步:源程序->预处理->编译->汇编->链接。预处理有一些宏替换,注释取消等操作;编译则针对预处理的结果进行词法分析,语法分析,语义分析,优化后生成汇编指令;编译则是编译器将汇编语言代码转为CPU可执行的指令;链接是将汇编器生成的目标文件和一些标准库文件组合,生成最终的可执行程序。
GCC涉及的文件类型
- .c:C源文件
- .cc/.cpp:C++源文件
- .i:经过预处理的C源文件
- .s/.S:汇编语言源文件
- .o:目标文件
- .a/.so:编译后的静态文件和共享库(shared object)文件
- .out:可执行文件
ELF
ELF简介
ELF(Executable Linkable Format)是一种Unix-like系统上的二进制文件格式标准。
ELF标准中定义的采用ELF格式的文件分为4类:
这里可以看下第四个是核心转储文件,如果你程序崩溃过,那你估计可能对这个文件有所了解。默认情况下程序崩溃时的dump文件是不会存储的,挺有意思。
ELF文件格式
ELF文件格式分为两种:运行视图(Execution View)和链接视图(Linking View)。
- ELF Header: 描述文件的主要特性:类型,CPU架构,入口地址,现有部分的大小和偏移等等。
- Program Header Table: 列举了所有有效的段(segments)和他们的属性。 程序头表需要加载器将文件中的节加载到虚拟内存段中(Execution View);
- Section: 按照类型划分不同的节(Linking View)。
- Section Header Table: 包含对每个节(sections)的具体描述。
ELF文件处理工具:Binutils
关于Binutils工具的具体知识自行百度即可。
这里阐述几个可能会使用的命令。
- as:被gcc调用,输入汇编文件,输出目标文件。因此我们使用gcc来编译c程序生成可执行文件过程中汇编那一个步骤其实是调用了这个工具。
- ld:GNU链接器,被gcc调用。
- objcopy:执行文件格式转换。
- objdump:显示ELF文件的信息。
- readelf:显示更多ELF格式文件的信息
练习
-
编写一个简单的打印 “hello world!” 的程序源文件:hello.c
#include void main() { printf("hello world!"); }
-
对源文件进行本地编译,生成针对支持 x86_64 指令集架构处理器的目标文件 hello.o。
生成目标文件.o我们要使用-c参数
$gcc - c hello.c -o hello.o
-
查看 hello.o 的文件的文件头信息。
使用readelf查看hello.o文件,加上-h参数查看头信息。
$readelf -h hello.o ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: REL (Relocatable file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x0 Start of program headers: 0 (bytes into file) Start of section headers: 712 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 0 (bytes) Number of program headers: 0 Size of section headers: 64 (bytes) Number of section headers: 13 Section header string table index: 12
-
查看 hello.o 的 Section header table。
$readelf -S hell.o There are 13 section headers, starting at offset 0x2c8: Section Headers: [Nr] Name Type Address Off Size ES Flg Lk Inf Al [ 0] NULL 0000000000000000 000000 000000 00 0 0 0 [ 1] .text PROGBITS 0000000000000000 000040 000018 00 AX 0 0 1 [ 2] .rela.text RELA 0000000000000000 000218 000030 18 I 10 1 8 [ 3] .data PROGBITS 0000000000000000 000058 000000 00 WA 0 0 1 [ 4] .bss NOBITS 0000000000000000 000058 000000 00 WA 0 0 1 [ 5] .rodata PROGBITS 0000000000000000 000058 00000d 00 A 0 0 1 [ 6] .comment PROGBITS 0000000000000000 000065 00002a 01 MS 0 0 1 [ 7] .note.GNU-stack PROGBITS 0000000000000000 00008f 000000 00 0 0 1 [ 8] .eh_frame PROGBITS 0000000000000000 000090 000038 00 A 0 0 8 [ 9] .rela.eh_frame RELA 0000000000000000 000248 000018 18 I 10 8 8 [10] .symtab SYMTAB 0000000000000000 0000c8 000120 18 11 9 8 [11] .strtab STRTAB 0000000000000000 0001e8 00002b 00 0 0 1 [12] .shstrtab STRTAB 0000000000000000 000260 000061 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), l (large), p (processor specific)
-
对 hello.o 反汇编,并查看 hello.c 的 C 程序源码和机器指令的对应关系。
$gcc -c -g hello.c -o hello.o $objdump -S hello.c hello.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 : #include void main() { 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp printf("hello world!"); 4: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # b b: b8 00 00 00 00 mov $0x0,%eax 10: e8 00 00 00 00 callq 15 } 15: 90 nop 16: 5d pop %rbp 17: c3 retq
-g是为了添加调试信息,-S是为了反汇编的同时显示源码。
参考链接
- https://xinqiu.gitbooks.io/linux-inside-zh/content/Theory/linux-theory-2.html
-