我们知道gcc是一个强大的编译器,很多Linux下的GNU工具都是用C语言写的,并且用gcc编译的,那么gcc的编译过程是怎样的呢?
1. 首先是源文件经过预加载变成了.i结尾的文件,可以通过-E这个参数来生成这个中间文件,这里主要是把一些include的头文件和一些宏定义,放到源文件中。
2. 从预加载的文件经过编译就会变成汇编语言的文件,这一步可以通过-S这个参数来生成这个中间文件 。
3. 从汇编语言的文件通过汇编,就会变成目标代码.o的文件,这一步可以通过-C这个参数来生成这个中间文件 。
4. 最后经过链接,生成最终的可执行文件 。
接下来我们通过一个例子来说明下:
我新建了一个文件:
/* main.c */
/* $begin main */
int sum(int *a, int n);
int array[2] = {1, 2};
int main()
{
int val = sum(array, 2);
return val;
}
/* $end main */
现在我们开始尝试将这个文件在Linux环境下,利用GCC编译生成可执行文件:
我先生成.i结尾的预加载文件
gcc -E main.c -o main.i
# 1 "main.c"
# 1 ""
# 1 ""
# 31 ""
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "" 2
# 1 "main.c"
int sum(int *a, int n);
int array[2] = {1, 2};
int main()
{
int val = sum(array, 2);
return val;
}
大家会发现这个文件的内容非常多,但是最后是我们本来的代码,上面的代码都是头文件和一些宏的内容,全加载进来了 。
下面我们通过编译生成汇编文件.s
gcc -S main.i -o main.s
.file "main.c"
.text
.globl array
.data
.align 8
.type array, @object
.size array, 8
array:
.long 1
.long 2
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $2, %esi
leaq array(%rip), %rdi
call sum@PLT
movl %eax, -4(%rbp)
movl -4(%rbp), %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0"
.section .note.GNU-stack,"",@progbits
看到了我们熟悉的汇编语言
我们继续,经过汇编器,生成.o的目标文件
gcc -c main.s -o main.o
objdump main.o -D|more
最后通过链接器,生成可执行文件
gcc main.o -o mian
然后执行
makefile.txt:
CC = gcc
CFLAGS = -Wall -Og
PROGS = global \
mismatch \
prog\
prog2c\
prog2l\
prog2r\
static-local.o \
symbols.o \
# Do nothing
all: $(PROGS)
static-local.o: static-local.c
$(CC) $(CFLAGS) -c static-local.c
# example program 1
prog: main.o sum.o
$(CC) $(CFLAGS) -o prog main.o sum.o
$(CC) $(CFLAGS) -S main.c
objdump -dx main.o > main-relo.d
objdump -dx -j .data main.o > maindata-relo.d
readelf -s main.o > mainsym.d
objdump -dx sum.o > sum-relo.d
objdump -dx -j .data sum.o > sumdata-relo.d
objdump -dx prog > prog-exe.d
objdump -dx -j .data prog > progdata-exe.d
# Type mismatch example
mismatch: mismatch-main.c mismatch-variable.c
$(CC) $(CFLAGS) -o mismatch mismatch-main.c mismatch-variable.c
# Use of extern specifier
global: global-c1.c global-c2.c global.h
$(CC) $(CFLAGS) -o global global-c1.c global-c2.c
# Static library linked at compile time
prog2c: main2.o addvec.o multvec.o
$(CC) $(CFLAGS) -c main2.c addvec.c multvec.c
ar rcs libvector.a addvec.o multvec.o
$(CC) $(CFLAGS) -static -o prog2c main2.o -L. -lvector
# Alternative for above line
# $(CC) $(CFLAGS) -static -o prog2c main2.o libvector.a
objdump -d prog2c > prog2c-exe.d
# Shared library linked at load-time
prog2l: main2.o libvector.so
$(CC) $(CFLAGS) -o prog2l main2.o ./libvector.so
objdump -dx main2.o > main2-relo.d
objdump -dx prog2l > prog2l-exe.d
objdump -xs -j .data -j .got prog2l > prog2ldata-exe.d
# Shared library linked at run time
prog2r: dll.c
$(CC) $(CFLAGS) -rdynamic -o prog2r dll.c -ldl
libvector.so: addvec.c multvec.c
$(CC) $(CFLAGS) -shared -fpic -o libvector.so addvec.c multvec.c
objdump -xd libvector.so > libvector-relo.d
objdump -xRr -j .data -j .got -j .got.plt -j rela.dyn -j rela.plt libvector.so > libvectordata-relo.d
clean:
rm -f $(PROGS) *.o *~ *.d *.so *.a *.s