Linux程序编译执行原理之一:预处理-编译-汇编-链接过程分析

时间:2022-06-07 11:55:31

本系列导航:

Linux程序编译执行原理之一:预处理-编译-汇编-链接过程分析

Linux程序编译执行原理之二:gcc编译出的elf文件分析


每次编译一个linux下的应用程序,好像只需要执行一下gcc,然后看到没有error就大功告成了(有时warning都不管的-_-),就可以高高兴兴的去执行啦。但是其中的原理是什么呢?看到这篇文章的同学肯定都有这样的疑惑,让我们一起来了解一下。

 

编译过程细节:

 

test.c(文本格式c程序) -> (预处理器cpp)-> test.i(文本格式c程序) -> (编译器 cc1) ->test.s(文本格式汇编程序) -> (汇编器 as) ->test.o(二进制elf格式可重定向文件) -> (链接器 ld) ->(二级制elf可执行程序)test 

 

以一个实例来看逐步看一下编译过程:

1,  这个一个基本的.c程序

test.c

/*************************************************************************
> File Name: test.c
> Author:radianceblau
> Mail: radianceblau@163.com
> Created Time: Thu 22 Jun 2017 03:03:41 PM CST
************************************************************************/

#include<stdio.h>
#define NUM 17


void main()
{
int a = 10;
printf("test for gcc compile! a is %d\n", a + NUM);
}

2,通过下面的命令将.c程序预编译为.i格式

gcc -E test.c -o test.i

或(gcc同样为调用cpp程序)

cpp test.c -o test.i

其中使用的预处理器为cpp

得到如下文件

test.i

//省略了大部分stdio.h的文件内容

extern char *ctermid (char *__s) __attribute__ ((__nothrow__ ));
# 910 "/usr/include/stdio.h" 3 4
extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ ));
extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ )) ;
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ ));
# 940 "/usr/include/stdio.h" 3 4

# 9 "test.c" 2

void main()
{
int a = 10;
printf("test for gcc compile! a is %d\n", a + 17);
}

可以看到预处理过程的结果是

a,  将stdio.h的文件内容放到了原来.c 文件的开头。stdio.h位于/usr/include/stdio.h

b,  将其中的宏#defineNUM 17进行的替换

 

3,通过下面的命令将.i程序预编译为.S格式

gcc -S test.i -o test.S

或:

/usr/lib/gcc/x86_64-linux-gnu/4.4/cc1 test.i -o tset.S

其中使用的编译器为cc1,得到了汇编文件。cc1并不在系统环境变量中,需要给出全路径

test.S

.file"test.c"
.section.rodata
.align 8
.LC0:
.string"test for gcc compile! a is %d\n"
.text
.globl main
.typemain, @function
main:
.LFB0:
.cfi_startproc
pushq%rbp
.cfi_def_cfa_offset 16
movq%rsp, %rbp
.cfi_offset 6, -16
.cfi_def_cfa_register 6
subq$16, %rsp
movl$10, -4(%rbp)
movl-4(%rbp), %eax
leal17(%rax), %edx
movl$.LC0, %eax
movl%edx, %esi
movq%rax, %rdi
movl$0, %eax
callprintf
leave
ret
.cfi_endproc
.LFE0:
.sizemain, .-main
.ident"GCC: (Ubuntu/Linaro 4.4.7-1ubuntu2) 4.4.7"
.section.note.GNU-stack,"",@progbits

结果是编译器将c语言翻译成了汇编语言。


4,通过下面的命令将.S程序预编译为.o格式

gcc -c test.S -o test.o

或:

as test.S -o test.o

其中使用的汇编器为as,得到了二进制可重定向文件。

其内容已经为二进制的了,下一篇文章中分析这种elf格式文件。

 

5,通过下面的命令将.o程序链接为最终的可执行程序

gcc test.o -o test

或:

 ld test.o -o test (缺少printf的链接库,如何添加呢?)

其中使用的链接器为ld

其内容已经为二进制的了,下一篇文章中分析这种elf格式文件。

结果是将之前的可重定向文件中的各个section进行了链接。

执行结果没有悬念:

Linux程序编译执行原理之一:预处理-编译-汇编-链接过程分析