size:列出可执行文件各部分的规格和总规格、代码段、数据段、总大小等,使用size的具体使用示例请参考下面。
C 运行时库
C语言标准主要由两部分组成:一部分描述C的语法,另一部分描述C标准库。 C标准库定义了一组标准头文件,每个头文件都包含一些相关的函数、变量、类型声明和宏定义。 例如,常见的printf函数是C标准库函数,其原型定义在stdio头文件中。
C语言标准只定义了C标准库函数的原型,并没有提供实现。 因此,C语言编译器一般需要C运行时库(C Run Time Library,CRT)的支持。 C 运行时库通常简称为 C 运行时库。 与C语言类似,C++也定义了自己的标准并提供了相关的支持库,称为C++运行时库。
准备
由于GCC工具链主要使用在Linux环境下,因此本文也将使用Linux系统作为工作环境。 为了演示整个编译过程,本节首先以一个用C语言编写的简单Hello程序为例,其源码如下:
#include
//此程序很简单,仅仅打印一个Hello World的字符串。
int main(void)
{
printf("Hello World! n");
return 0;
}
编译过程
1. 预处理
预处理过程主要包括以下过程:
$ gcc -E hello.c -o hello.i // 将源文件hello.c文件预处理生成hello.i
// GCC的选项-E使GCC在进行完预处理后即停止
hello.i文件可以像普通文本文件一样打开查看,其代码片段如下:
C语言学习资源汇总[最新版]
// hello.i代码片段
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 942 "/usr/include/stdio.h" 3 4
# 2 "hello.c" 2
# 3 "hello.c"
int
main(void)
{
printf("Hello World!" "n");
return 0;
}
2.编译
编译过程就是对预处理后的文件进行一系列词法分析、语法分析、语义分析和优化编译c源码,生成相应的汇编代码。
用gcc编译的命令如下:
$ gcc -S hello.i -o hello.s // 将预处理生成的hello.i文件编译生成汇编程序hello.s
// GCC的选项-S使GCC在执行完编译后停止,生成汇编程序
上述命令生成的汇编程序hello.s的代码片段如下所示,均为汇编代码。
// hello.s代码片段
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $.LC0, %edi
call puts
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
3. 编译
汇编过程调用处理汇编代码,生成处理器可以识别的指令,并将它们保存在后缀为.o的目标文件中。 由于每个汇编字对应一条处理器指令,因此与编译过程相比,汇编过程相对简单。 通过像Binutils中那样调用汇编器,可以根据汇编指令和处理器指令的对照表进行一一翻译。
当程序由多个源代码文件组成时,每个文件首先要完成汇编工作,只有生成.o目标文件后,才能进行下一步的链接工作。 注意:目标文件已经是最终程序的一部分,但在链接之前无法执行。
用gcc编译的命令如下:
$ gcc -c hello.s -o hello.o // 将编译生成的hello.s文件汇编生成目标文件hello.o
// GCC的选项-c使GCC在执行完汇编后停止,生成目标文件
//或者直接调用as进行汇编
$ as -c hello.s -o hello.o //使用Binutils中的as将hello.s文件汇编生成目标文件
注意:hello.o 目标文件是 ELF(可执行和可链接格式)格式的可重定向文件。
4.链接
链接也分为静态链接和动态链接,要点如下:
由于动态库和静态库链接的路径可能会重叠,因此如果路径中存在同名的静态库文件和动态库文件,例如libtest。 .so,如果你想让gcc选择链接libtest.a,你可以指定gcc选项-static,这将强制使用静态库进行链接。 以Hello World为例:
链接器最终生成的文件是ELF格式的可执行文件。 ELF可执行文件通常链接成不同的段,例如.text、.data、.rodata和.bss。
分析ELF文件
1. ELF文件的各个部分
ELF文件格式如下图所示。 ELF 头和节头表之间的节是节。 典型的 ELF 文件包含以下部分:
可以使用readelf -S查看各节的信息,如下:
$ readelf -S hello
There are 31 section headers, starting at offset 0x19d8:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
……
[11] .init PROGBITS 00000000004003c8 000003c8
000000000000001a 0000000000000000 AX 0 0 4
……
[14] .text PROGBITS 0000000000400430 00000430
0000000000000182 0000000000000000 AX 0 0 16
[15] .fini PROGBITS 00000000004005b4 000005b4
……
2. 反汇编ELF
由于ELF文件很难像普通文本文件一样打开,如果想直接查看ELF文件中包含的指令和数据,就需要使用反汇编技术。
使用 objdump -D 反汇编它编译c源码,如下所示:
$ objdump -D hello
……
0000000000400526 : // main标签的PC地址
//PC地址:指令编码 指令的汇编格式
400526: 55 push %rbp
400527: 48 89 e5 mov %rsp,%rbp
40052a: bf c4 05 40 00 mov $0x4005c4,%edi
40052f: e8 cc fe ff ff callq 400400
400534: b8 00 00 00 00 mov $0x0,%eax
400539: 5d pop %rbp
40053a: c3 retq
40053b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
……
使用 objdump -S 反汇编并显示其 C 语言源代码:
$ gcc -o hello -g hello.c //要加上-g选项
$ objdump -S hello
……
0000000000400526 :
#include
int
main(void)
{
400526: 55 push %rbp
400527: 48 89 e5 mov %rsp,%rbp
printf("Hello World!" "n");
40052a: bf c4 05 40 00 mov $0x4005c4,%edi
40052f: e8 cc fe ff ff callq 400400
return 0;
400534: b8 00 00 00 00 mov $0x0,%eax
}
400539: 5d pop %rbp
40053a: c3 retq
40053b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
……
如果你年满18周岁以上,又觉得学【C语言】太难?想尝试其他编程语言,那么我推荐你学Python,现有价值499元Python零基础课程限时免费领取,限10个名额!
▲扫描二维码-免费领取