本系列导航:
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进行了链接。
执行结果没有悬念: