Linux程序编译过程的示例分析

发布时间:2021-10-27 14:31:14 作者:小新
来源:亿速云 阅读:129

小编给大家分享一下Linux程序编译过程的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!

本文将介绍如何将高层的C/C++语言编写的程序转换成为处理器能够执行的二进制代码的过程,包括四个步骤:

Linux程序编译过程的示例分析

GCC 工具链介绍

通常所说的GCC是GUN Compiler Collection的简称,是Linux系统上常用的编译工具。GCC工具链软件包括GCC、Binutils、C运行库等。

GCC

GCC(GNU C Compiler)是编译工具。本文所要介绍的将C/C++语言编写的程序转换成为处理器能够执行的二进制代码的过程即由编译器完成。

Binutils

一组二进制程序处理工具,包括:addr2line、ar、objcopy、objdump、as、ld、ldd、readelf、size等。这一组工具是开发和调试不可缺少的工具,分别简介如下:

C运行库

C语言标准主要由两部分组成:一部分描述C的语法,另一部分描述C标准库。C标准库定义了一组标准头文件,每个头文件中包含一些相关的函数、变量、类型声明和宏定义,譬如常见的printf函数便是一个C标准库函数,其原型定义在stdio头文件中。

C语言标准仅仅定义了C标准库函数原型,并没有提供实现。因此,C语言编译器通常需要一个C运行时库(C Run Time Libray,CRT)的支持。C运行时库又常简称为C运行库。与C语言类似,C++也定义了自己的标准,同时提供相关支持库,称为C++运行时库。

准备工作

由于GCC工具链主要是在Linux环境中进行使用,因此本文也将以Linux系统作为工作环境。为了能够演示编译的整个过程,本节先准备一个C语言编写的简单Hello程序作为示例,其源代码如下所示:

#include <stdio.h>   //此程序很简单,仅仅打印一个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文件可以作为普通文本文件打开进行查看,其代码片段如下所示:

// 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.编译

编译过程就是对预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码。

使用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中的汇编器as根据汇编指令和处理器指令的对照表一一翻译即可。

当程序由多个源代码文件构成时,每个文件都要先完成汇编工作,生成.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(Executable and Linkable Format)格式的可重定向文件。

4.链接

链接也分为静态链接和动态链接,其要点如下:

由于链接动态库和静态库的路径可能有重合,所以如果在路径中有同名的静态库文件和动态库文件,比如libtest.a和libtest.so,gcc链接时默认优先选择动态库,会链接libtest.so,如果要让gcc选择链接libtest.a则可以指定gcc选项-static,该选项会强制使用静态库进行链接。以Hello World为例:

$ gcc hello.c -o hello      $ size hello  //使用size查看大小         text    data     bss     dec     hex filename         1183     552       8    1743     6cf     hello      $ ldd hello //可以看出该可执行文件链接了很多其他动态库,主要是Linux的glibc动态库              linux-vdso.so.1 =>  (0x00007fffefd7c000)              libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fadcdd82000)              /lib64/ld-linux-x86-64.so.2 (0x00007fadce14c000)
$ gcc -static hello.c -o hello    $ size hello //使用size查看大小         text    data     bss     dec     hex filename     823726    7284    6360  837370   cc6fa     hello //可以看出text的代码尺寸变得极大    $ ldd hello           not a dynamic executable //说明没有链接动态库

链接器链接后生成的最终文件为ELF格式可执行文件,一个ELF可执行文件通常被链接为不同的段,常见的段譬如.text、.data、.rodata、.bss等段。

分析ELF文件

1.ELF文件的段

ELF文件格式如下图所示,位于ELF Header和Section Header Table之间的都是段(Section)。一个典型的ELF文件包含下面几个段:

Linux程序编译过程的示例分析

可以使用readelf -S查看其各个section的信息如下:

$ 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  &hellip;&hellip;    [11] .init             PROGBITS         00000000004003c8  000003c8         000000000000001a  0000000000000000  AX       0     0     4  &hellip;&hellip;    [14] .text             PROGBITS         0000000000400430  00000430         0000000000000182  0000000000000000  AX       0     0     16    [15] .fini             PROGBITS         00000000004005b4  000005b4  &hellip;&hellip;

2.反汇编ELF

由于ELF文件无法被当做普通文本文件打开,如果希望直接查看一个ELF文件包含的指令和数据,需要使用反汇编的方法。

使用objdump -D对其进行反汇编如下:

$ objdump -D hello  &hellip;&hellip;  0000000000400526 <main>:  // 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 <puts@plt>    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)  &hellip;&hellip;

使用objdump -S将其反汇编并且将其C语言源代码混合显示出来:

$ gcc -o hello -g hello.c //要加上-g选项  $ objdump -S hello  &hellip;&hellip;  0000000000400526 <main>:  #include <stdio.h>  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 <puts@plt>    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)  &hellip;&hellip;

以上是“Linux程序编译过程的示例分析”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注亿速云行业资讯频道!

推荐阅读:
  1. RTX8111驱动程序编译过程
  2. golang的编译过程

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

linux

上一篇:Linux中SSH图形界面工具有哪些

下一篇:在java中如何编写规范的代码

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》