有人预言,RISC-V或将是继Intel和Arm之后的第三大主流处理器体系。欢迎访问全球首家只专注于RISC-V单片机行业应用的中文网站
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
本帖最后由 小飞飞 于 2020-8-21 02:41 编辑
随着国内第一本RISC-V中文书籍《手把手教你设计CPU——RISC-V处理器篇》正式上市,越来越多的爱好者开始使用开源的蜂鸟E203 RISC-V处理核,很多初学者留言询问有关RISC-V工具链使用的问题,因此本公众号将开始陆续发表若干篇有关RISC-V软件工具链使用的文章,包括: 本文为RISC-V嵌入式开发准备篇1:编译过程简介。本文的目的是对编译过程进行简单的科普与回顾,为后续详细介绍“RISC-V GCC工具链”和“RISC-V汇编语言程序设计”打下基础。
注:本文力求通俗易懂,主要面向初学者,对编译过程有所了解的读者可以忽略此文。
1 本文概述本文将介绍如何将高层的C/C++语言编写的程序转换成为处理器能够执行的二进制代码的过程,该过程即一般编译原理书籍所介绍的过程,包括四个步骤:
- 预处理(Preprocessing)
- 编译(Compilation)
- 汇编(Assembly)
- 链接(Linking)
本文限于篇幅,将不会对各个步骤的原理进行详解,将仅仅结合Linux自带的GCC工具链对其过程进行简述。感兴趣的读者可以自行查阅其他资料深入学习编译原理的相关知识。 注意: - 本文为了简化描述与便于初学者理解,将在Linux操作系统平台上编译一个Hello World程序并在此Linux平台上运行作为示例。而嵌入式开发所使用的交叉编译的使用方法与本文所述的编译过程有所差异,本公众号将在后续发文《嵌入式开发的特点介绍》中对嵌入式系统编译进行更多介绍。
- 本文使用的是Linux自带的GCC工具链作为演示,而未涉及到如何使用RISC-V GCC工具链,本公众号将在后续发文《RISC-V GCC工具链的介绍》中对RISC-V GCC工具链进行更多介绍。
2 GCC工具链介绍
通常所说的GCC是GUN Compiler Collection的简称,是Linux系统上常用的编译工具。GCC实质上不是一个单独的程序,而是多个程序的集合,因此通常称为GCC工具链。工具链软件包括GCC、C运行库、Binutils、GDB等。
- GCC
- GCC(GNU C Compiler)是编译工具。本文所要介绍的将C/C++语言编写的程序转换成为处理器能够执行的二进制代码的过程即由编译器完成。有关编译过程的更多介绍请参见后文。
- GCC既支持本地编译(即在一个平台上编译该平台运行的程序),也支持交叉编译(即在一个平台上编译供另一个平台运行的程序)。
本文为了简化描述与便于初学者理解,将在Linux操作系统平台上编译一个Hello World程序并在此Linux平台上运行作为示例,即为一种本地编译的开发方式。
交叉编译多用于嵌入式系统的开发,有关交叉编译,本公众号将在后续发文《嵌入式开发的特点介绍》中对嵌入式系统交叉编译进行更多介绍。
- C运行库
- 由于C运行库的相关背景知识较多,请参见后文对其单独进行介绍。
- Binutils
- 由于Binutils的相关信息较多,请参见后文对其单独进行介绍。
- GDB
- GDB(GNU Project Debugger)是调试工具,可以用于对程序进行调试。
2.2 Binutils
一组二进制程序处理工具,包括:addr2line、ar、objcopy、objdump、as、ld、ldd、readelf、size等。这一组工具是开发和调试不可缺少的工具,分别简介如下:
- addr2line:用来将程序地址转换成其所对应的程序源文件及所对应的代码行,也可以得到所对应的函数。该工具将帮助调试器在调试的过程中定位对应的源代码位置。
- as:主要用于汇编,有关汇编的详细介绍请参见后文。
- ld:主要用于链接,有关链接的详细介绍请参见后文。
- ar:主要用于创建静态库。为了便于初学者理解,在此介绍动态库与静态库的概念:
- 如果要将多个.o目标文件生成一个库文件,则存在两种类型的库,一种是静态库,另一种是动态库。
- 在windows中静态库是以 .lib 为后缀的文件,共享库是以 .dll 为后缀的文件。在linux中静态库是以.a为后缀的文件,共享库是以.so为后缀的文件。
- 静态库和动态库的不同点在于代码被载入的时刻不同。静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。在Linux系统中,可以用ldd命令查看一个可执行程序依赖的共享库。
- 如果一个系统中存在多个需要同时运行的程序且这些程序之间存在共享库,那么采用动态库的形式将更节省内存。但是对于嵌入式系统,大多数情况下都是整个软件就是一个可执行程序且不支持动态加载的方式,即以静态库为主。
- ldd:可以用于查看一个可执行程序依赖的共享库。
- objcopy:将一种对象文件翻译成另一种格式,譬如将.bin转换成.elf、或者将.elf转换成.bin等。
- objdump:主要的作用是反汇编。有关反汇编的详细介绍,请参见后文。
- readelf:显示有关ELF文件的信息,请参见后文了解更多信息。
- size:列出可执行文件每个部分的尺寸和总尺寸,代码段、数据段、总大小等,请参见后文了解使用size的具体使用实例。
- Binutils的每个工具的功能均很强大,本节限于篇幅无法详细介绍其功能,读者可以自行查阅资料了解其详情。Binutils还有其他工具,在此不一一赘述,感兴趣的读者可以自行查阅其他资料学习。
2.3 C运行库
为了解释C运行库,需要先回忆一下C语言标准。C语言标准主要由两部分组成:一部分描述C的语法,另一部分描述C标准库。C标准库定义了一组标准头文件,每个头文件中包含一些相关的函数、变量、类型声明和宏定义,譬如常见的printf函数便是一个C标准库函数,其原型定义在stdio头文件中。 C语言标准仅仅定义了C标准库函数原型,并没有提供实现。因此,C语言编译器通常需要一个C运行时库(C Run Time Libray,CRT)的支持。C运行时库又常简称为C运行库。与C语言类似,C++也定义了自己的标准,同时提供相关支持库,称为C++运行时库。 如上所述,要在一个平台上支持C语言,不仅要实现C编译器,还要实现C标准库,这样的实现才能完全支持C标准。glibc(GNU C Library)是Linux下面C标准库的实现,其要点如下: - glibc本身是GNU旗下的C标准库,后来逐渐成为了Linux的标准C库。glibc 的主体分布在Linux系统的/lib与/usr/lib目录中,包括 libc 标准 C 函式库、libm数学函式库等等,都以.so做结尾。
- 注意:Linux系统下面的标准C库不仅有这一个,如uclibc、klibc、以及Linux libc,但是glibc使用最为广泛。而在嵌入式系统中使用较多的C运行库为Newlib,有关Newlib的详细介绍将在本公众号后续发文《嵌入式开发的特点介绍》中进行。
- Linux系统通常将libc库作为操作系统的一部分,它被视为操作系统与用户程序的接口。譬如:glibc不仅实现标准C语言中的函数,还封装了操作系统提供的系统服务,即系统调用的封装。
- 通常情况,每个特定的系统调用对应了至少一个glibc 封装的库函数,如系统提供的打开文件系统调用sys_open对应的是glibc中的open函数;其次,glibc 一个单独的API可能调用多个系统调用,如glibc提供的 printf 函数就会调用如 sys_open、sys_mmap、sys_write、sys_close等系统调用;另外,多个 glibc API也可能对应同一个系统调用,如glibc下实现的malloc、free 等函数用来分配和释放内存,都利用了内核的sys_brk的系统调用。
- 对于C++语言,常用的C++标准库为libstdc++。注意:通常libstdc++与GCC捆绑在一起的,即安装gcc的时候会把libstdc++装上。而glibc并没有和GCC捆绑于一起,这是因为glibc需要与操作系统内核打交道,因此其与具体的操作系统平台紧密耦合。而libstdc++虽然提供了c++程序的标准库,但其并不与内核打交道。对于系统级别的事件,libstdc++会与glibc交互,从而和内核通信。
2.4 GCC命令行选项
GCC有着丰富的命令行选项支持各种不同的功能,本文由于篇幅有限,无法一一赘述,请读者自行查阅相关资料学习。 对于RISC-V的GCC工具链而言,还有其特有的编译选项,本公众号将在后续发文《RISC-V GCC工具链的介绍》中介绍RISC-V GCC工具链的更多详情。
|