感谢我的读者,感谢您有耐心这么一篇篇地看下来[呲牙]
到这里,我们已经说完了编译器的技术细节,也同时完成了一门新的编程语言。
(Here,We introduced a compiler's basic techniques, meanwhile completed a new program language.)
按照计算机领域的惯例,当一门语言制作完成时,要打印一行"hello world"。
打开一个文本编译器,输入以下代码:
int printf(const char* fmt, …);
int main()
{
printf("hello world\n");
return 0;
}
然后保存为一个文件hello.c。
之所以跟C语言用同样的扩展名,是因为这样文本编译器就可以显示语法颜色。
当然,文件名也可以随便起,反正编译器都会把它当作一个文本文件。
我在语法上尽量保持了与C语言的类似,但是没有支持宏,所以把printf的函数声明直接写在第1行,而不是include "stdio.h"。
接下来编译它,Linux上的shell命令是:./a.out hello.c
(当然,在这之前要先用gcc编译scf框架的源码。)
./a.out hello.c命令的输出如下:
编译器启动之后,初始化语法分析的各个模块。
然后,开始源代码的语法分析,首先分析printf()的函数声明,
然后,分析main()函数的代码:
分析完main()函数之后,也就到达了文件的结尾eof。
到了这里,语法分析就结束了。
接下来是编译器的后端流程:
1,中间代码优化的日志,可以看到中间代码、基本块、循环和分组信息:
2,然后开始生成机器码,CPU平台是x86_64,
3,给gdb生成debug信息,生成.o目标文件,
到了这里,编译就结束了。
接下来是连接,即昨天那篇文章的内容。
4,这段代码很简单,连接时只需要从动态库里查找printf这一个函数,
下图是查找时打印的日志。
5,找到printf之后,完成动态连接,并且生成可执行文件。
6,用readelf -a 1.out查看生成的可执行文件,信息如下:
scf编译的默认文件名是1.out。
从ELF头可以看出,文件类型是EXEC可执行文件,平台是x86_64,入口地址0x400817。
"hello world"字符串常量在.rodata节,动态连接的printf()需要plt和got,如上图。
所需的动态库,以及动态重定位的函数。
calloc和free函数是因为默认把自动内存管理的scf_object.c文件也连接进去了。
7,运行结果:
给一张main()函数的最终内容:
可以看到,连接器已经修改了加载"hello world"字符串的内存地址,也修改了调用printf的内存地址。
如若转载,请注明出处:https://www.vsaren.com/133218.html