gcc编译的背后
来源:互联网 发布:网络视频服务器使用 编辑:程序博客网 时间:2024/05/01 02:29
- 打印出预处理之后的结果:gcc -E hello.c这样我们就可以看到源代码中的各种预处理命令是如何被解释的,从而方便理解和查错。实际上gcc在这里是调用了cpp的(虽然我们通过gcc的-v仅看到cc1),cpp即The C Preprocessor,主要用来预处理宏定义、文件包含、条件编译等。下面介绍它的一个比较重要的选项-D。
- 在命令行定义宏:gcc -Dmacro hello.c等同于在文件的开头定义宏,即#define maco,但是在命令行定义更灵活。例如,在源代码中有这些语句。#ifdef DEBUGprintf("this code is for debugging\n");#endif如果编译时加上-DDEBUG选项,那么编译器就会把printf所在的行编译进目标代码,从而方便地跟踪该位置的某些程序状态。这样-DDEBUG就可以当作一个调试开关,编译时加上它就可以用来打印调试信息,发布时则可以通过去掉该编译选项把调试信息去掉。本节参考资料:[1] C语言教程第九章:预处理http://www.bc-cn.net/Article/kfyy/cyy/jc/200409/9.html[2] 更多http://www.hemee.com/kfyy/c/6626.htmlhttp://www.91linux.com/html/article/program/cpp/20071203/8745.htmlhttp://www.janker.org/bbs/programmer/2006-10-13/327.html2、编译(翻译)开篇简要:编译之前,C语言编译器会进行词法分析、语法分析(-fsyntax-only),接着会把源代码翻译成中间语言,即汇编语言。如果想看到这个中间结果,可以用-S选项。需要提到的是,诸如shell等解释语言也会经历一个词法分析和语法分析的阶段,不过之后并不会进行“翻译”,而是“解释”,边解释边执行。************************
$ cat hello.c
#include <stdio.h>
int main()
{
printf("hello, world\n")
return 0;
}
$ gcc -fsyntax-only hello.c
hello.c: In function ‘main’:
hello.c:5: error: expected ‘;’ before ‘return’
$ vim hello.c
$ cat hello.c
#include <stdio.h>
int main()
{
printf("hello, world\n");
int i;
return 0;
}
$ gcc -std=c89 -pedantic-errors hello.c #默认情况下,gcc是允许在程序中间声明变量的,但是turboc就不支持
hello.c: In function ‘main’:
hello.c:5: error: ISO C90 forbids mixed declarations and code
$ gcc -o hello hello.c #采用默认选项,不优化
$ gcc -O2 -o hello2 hello.c #优化等次是2
$ gcc -Os -o hellos hello.c #优化目标代码的大小
$ ls -S hello hello2 hellos #可以看到,hellos比较小,hello2比较大
hello2 hello hellos
$ time ./hello
hello, world
real 0m0.001s
user 0m0.000s
sys 0m0.000s
$ time ./hello2 #可能是代码比较少的缘故,执行效率看上去不是很明显
hello, world
real 0m0.001s
user 0m0.000s
sys 0m0.000s
$ time ./hellos #虽然目标代码小了,但是执行效率慢了些
hello, world
real 0m0.002s
user 0m0.000s
sys 0m0.000s
$ gcc -S hello.c #默认输出是hello.s,可自己指定,输出到屏幕-o -,输出到其他文件-o file
$ cat hello.s
cat hello.s
.file "hello.c"
.section .rodata
.LC0:
.string "hello, world"
.text
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $4, %esp
movl $.LC0, (%esp)
call puts
movl $0, %eax
addl $4, %esp
popl %ecx
popl %ebp
leal -4(%ecx), %esp
ret
.size main, .-main
.ident "GCC: (GNU) 4.1.3 20070929 (prerelease) (Ubuntu 4.1.2-16ubuntu2)"
.section .note.GNU-stack,"",@progbits
$ file hello.s
hello.s: ASCII assembler program text
$ gcc -c hello.s #用gcc把汇编语言编译成目标代码
$ file hello.o #file命令可以用来查看文件的类型,这个目标代码是可重定位的(relocatable),需要通过ld进行进一步的链接成可执行程序(executable)和共享库(shared)
hello.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
$ as -o hello.o hello.s #用as把汇编语言编译成目标代码
$ file hello.o
hello.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
$ gcc -c myprintf.c test.c #编译产生两个目标文件myprintf.o和test.o,它们都是可重定位文件(REL)
$ readelf -h test.o | grep Type
Type: REL (Relocatable file)
$ readelf -h myprintf.o | grep Type
Type: REL (Relocatable file)
$ gcc -o test myprintf.o test.o #根据目标代码连接产生可执行文件,这里的文件类型是可执行的(EXEC)
$ readelf -h test | grep Type
Type: EXEC (Executable file)
$ ar rcsv libmyprintf.a myprintf.o #用ar命令创建一个静态连接库,静态连接库也是可重定位文件(REL)
$ readelf -h libmyprintf.a | grep Type #因此,使用静态连接库和可重定位文件一样,它们之间唯一不
#同是前者可以是多个可重定位文件的“集合”。
Type: REL (Relocatable file)
$ gcc -o test test.o -llib -L./ #可以直接连接进去,也可以使用-l参数,-L指定库的搜索路径
$ gcc -Wall myprintf.o -shared -Wl,-soname,libmyprintf.so.0 -o libmyprintf.so.0.0
#编译产生动态链接库,并支持major和minor版本号,动态链接库类型为DYN
$ ln -sf libmyprintf.so.0.0 libmyprintf.so.0
$ ln -sf libmyprintf.so.0 libmyprintf.so
$ readelf -h libmyprintf.so | grep Type
Type: DYN (Shared object file)
$ gcc -o test test.o -llib -L./ #编译时和静态连接库类似,但是执行时需要指定动态连接库的搜索路径
$ LD_LIBRARY_PATH=./ ./test #LD_LIBRARY_PATH为动态链接库的搜索路径
$ gcc -static -o test test.o -llib -L./ #在不指定static时会优先使用动态链接库,指定时则阻止使用动态连接库
#这个时候会把所有静态连接库文件加入到可执行文件中,使得执行文件很大
#而且加载到内存以后会浪费内存空间,因此不建议这么做
$ gcc -c myprintf.c #默认编译好myprintf.c,将产生一个可重定位的文件myprintf.o
$ readelf -S myprintf.o #通过查看myprintf.o的节区表查看节区信息
There are 11 section headers, starting at offset 0xc0:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 000018 00 AX 0 0 4
[ 2] .rel.text REL 00000000 000334 000010 08 9 1 4
[ 3] .data PROGBITS 00000000 00004c 000000 00 WA 0 0 4
[ 4] .bss NOBITS 00000000 00004c 000000 00 WA 0 0 4
[ 5] .rodata PROGBITS 00000000 00004c 00000e 00 A 0 0 1
[ 6] .comment PROGBITS 00000000 00005a 000012 00 0 0 1
[ 7] .note.GNU-stack PROGBITS 00000000 00006c 000000 00 0 0 1
[ 8] .shstrtab STRTAB 00000000 00006c 000051 00 0 0 1
[ 9] .symtab SYMTAB 00000000 000278 0000a0 10 10 8 4
[10] .strtab STRTAB 00000000 000318 00001a 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
$ objdump -d -j .text myprintf.o #这里是程序指令部分,用objdump的-d选项可以看到反编译的结果,
#-j指定需要查看的节区
myprintf.o: file format elf32-i386
Disassembly of section .text:
00000000 <myprintf>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 ec 08 sub $0x8,%esp
6: 83 ec 0c sub $0xc,%esp
9: 68 00 00 00 00 push $0x0
e: e8 fc ff ff ff call f <myprintf+0xf>
13: 83 c4 10 add $0x10,%esp
16: c9 leave
17: c3 ret
$ readelf -r myprintf.o #用-r选项可以看到有关重定位的信息,这里有两部分需要重定位
Relocation section '.rel.text' at offset 0x334 contains 2 entries:
Offset Info Type Sym.Value Sym. Name
0000000a 00000501 R_386_32 00000000 .rodata
0000000f 00000902 R_386_PC32 00000000 puts
$ readelf -x .rodata myprintf.o #.rodata节区包含只读数据,即我们要打印的hello, world!.
Hex dump of section '.rodata':
0x00000000 68656c6c 6f2c2077 6f726c64 2100 hello, world!.
$ readelf -x .data myprintf.o #没有这个节区,.data应该包含一些初始化的数据
Section '.data' has no data to dump.
$ readelf -x .bss mmyprintf.o #也没有这个节区,.bss应该包含一些未初始化的数据,程序默认初始为0
Section '.bss' has no data to dump.
$ readelf -x .comment myprintf.o #是一些注释,可以看到是是GCC的版本信息
Hex dump of section '.comment':
0x00000000 00474343 3a202847 4e552920 342e312e .GCC: (GNU) 4.1.
0x00000010 3200 2.
$ readelf -x .note.GNU-stack myprintf.o #这个也没有内容
Section '.note.GNU-stack' has no data to dump.
$ readelf -x .shstrtab myprintf.o #包括所有节区的名字
Hex dump of section '.shstrtab':
0x00000000 002e7379 6d746162 002e7374 72746162 ..symtab..strtab
0x00000010 002e7368 73747274 6162002e 72656c2e ..shstrtab..rel.
0x00000020 74657874 002e6461 7461002e 62737300 text..data..bss.
0x00000030 2e726f64 61746100 2e636f6d 6d656e74 .rodata..comment
0x00000040 002e6e6f 74652e47 4e552d73 7461636b ..note.GNU-stack
0x00000050 00 .
$ readelf -symtab myprintf.o #符号表,包括所有用到的相关符号信息,如函数名、变量名
Symbol table '.symtab' contains 10 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS myprintf.c
2: 00000000 0 SECTION LOCAL DEFAULT 1
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 SECTION LOCAL DEFAULT 4
5: 00000000 0 SECTION LOCAL DEFAULT 5
6: 00000000 0 SECTION LOCAL DEFAULT 7
7: 00000000 0 SECTION LOCAL DEFAULT 6
8: 00000000 24 FUNC GLOBAL DEFAULT 1 myprintf
9: 00000000 0 NOTYPE GLOBAL DEFAULT UND puts
$ readelf -x .strtab myprintf.o #字符串表,用到的字符串,包括文件名、函数名、变量名等。
Hex dump of section '.strtab':
0x00000000 006d7970 72696e74 662e6300 6d797072 .myprintf.c.mypr
0x00000010 696e7466 00707574 7300 intf.puts.
$ gcc -S myprintf.c
$ cat myprintf.s
.file "myprintf.c"
.section .rodata
.LC0:
.string "hello, world!"
.text
.globl myprintf
.type myprintf, @function
myprintf:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
subl $12, %esp
pushl $.LC0
call puts
addl $16, %esp
leave
ret
.size myprintf, .-myprintf
.ident "GCC: (GNU) 4.1.2"
.section .note.GNU-stack,"",@progbits
$ readelf -S test.o #为了比较,先把test.o的节区表也列出
There are 10 section headers, starting at offset 0xb4:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 000024 00 AX 0 0 4
[ 2] .rel.text REL 00000000 0002ec 000008 08 8 1 4
[ 3] .data PROGBITS 00000000 000058 000000 00 WA 0 0 4
[ 4] .bss NOBITS 00000000 000058 000000 00 WA 0 0 4
[ 5] .comment PROGBITS 00000000 000058 000012 00 0 0 1
[ 6] .note.GNU-stack PROGBITS 00000000 00006a 000000 00 0 0 1
[ 7] .shstrtab STRTAB 00000000 00006a 000049 00 0 0 1
[ 8] .symtab SYMTAB 00000000 000244 000090 10 9 7 4
[ 9] .strtab STRTAB 00000000 0002d4 000016 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
$ gcc -o test test.o libmyprintf.o
$ readelf -l test #我们发现,test和test.o,libmyprintf.o相比,多了很多节区,如.interp和.init等
Elf file type is EXEC (Executable file)
Entry point 0x80482b0
There are 7 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x000e0 0x000e0 R E 0x4
INTERP 0x000114 0x08048114 0x08048114 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x0047c 0x0047c R E 0x1000
LOAD 0x00047c 0x0804947c 0x0804947c 0x00104 0x00108 RW 0x1000
DYNAMIC 0x000490 0x08049490 0x08049490 0x000c8 0x000c8 RW 0x4
NOTE 0x000128 0x08048128 0x08048128 0x00020 0x00020 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame
03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag
06
$ gcc -v -o test test.o myprintf.o #把可重定位文件链接成可执行文件
Reading specs from /usr/lib/gcc/i486-slackware-linux/4.1.2/specs
Target: i486-slackware-linux
Configured with: ../gcc-4.1.2/configure --prefix=/usr --enable-shared --enable-languages=ada,c,c++,fortran,java,objc --enable-threads=posix --enable-__cxa_atexit --disable-checking --with-gnu-ld --verbose --with-arch=i486 --target=i486-slackware-linux --host=i486-slackware-linux
Thread model: posix
gcc version 4.1.2
/usr/libexec/gcc/i486-slackware-linux/4.1.2/collect2 --eh-frame-hdr -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o test /usr/lib/gcc/i486-slackware-linux/4.1.2/../../../crt1.o /usr/lib/gcc/i486-slackware-linux/4.1.2/../../../crti.o /usr/lib/gcc/i486-slackware-linux/4.1.2/crtbegin.o -L/usr/lib/gcc/i486-slackware-linux/4.1.2 -L/usr/lib/gcc/i486-slackware-linux/4.1.2 -L/usr/lib/gcc/i486-slackware-linux/4.1.2/../../../../i486-slackware-linux/lib -L/usr/lib/gcc/i486-slackware-linux/4.1.2/../../.. test.o myprintf.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/i486-slackware-linux/4.1.2/crtend.o /usr/lib/gcc/i486-slackware-linux/4.1.2/../../../crtn.o
$ ld --eh-frame-hdr \
-m elf_i386 \
-dynamic-linker /lib/ld-linux.so.2 \
-o test \
/usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc/i486-slackware-linux/4.1.2/crtbegin.o \
test.o myprintf.o \
-L/usr/lib/gcc/i486-slackware-linux/4.1.2 -L/usr/i486-slackware-linux/lib -L/usr/lib/ -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed \
/usr/lib/gcc/i486-slackware-linux/4.1.2/crtend.o /usr/lib/crtn.o
$ ./test
hello, world!
Quote:
$ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o test /usr/lib/crt1.o /usr/lib/crti.o test.o myprintf.o -L/usr/lib -lc /usr/lib/crtn.o #后面发现不用链接libgcc,也不用--eh-frame-hdr参数
$ readelf -l test
Elf file type is EXEC (Executable file)
Entry point 0x80482b0
There are 7 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x000e0 0x000e0 R E 0x4
INTERP 0x000114 0x08048114 0x08048114 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x003ea 0x003ea R E 0x1000
LOAD 0x0003ec 0x080493ec 0x080493ec 0x000e8 0x000e8 RW 0x1000
DYNAMIC 0x0003ec 0x080493ec 0x080493ec 0x000c8 0x000c8 RW 0x4
NOTE 0x000128 0x08048128 0x08048128 0x00020 0x00020 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata
03 .dynamic .got .got.plt .data
04 .dynamic
05 .note.ABI-tag
06
$ ./test
hello, world!
Quote:
$ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o test /usr/lib/crt1.o test.o myprintf.o -L/usr/lib/ -lc
/usr/lib/libc_nonshared.a(elf-init.oS): In function `__libc_csu_init':
(.text+0x25): undefined reference to `_init'
Quote:
$ readelf -s /usr/lib/crt1.o | grep __libc_csu_init
18: 00000000 0 NOTYPE GLOBAL DEFAULT UND __libc_csu_init
$ readelf -s /usr/lib/crti.o | grep _init
17: 00000000 0 FUNC GLOBAL DEFAULT 5 _init
Quote:
$ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o test test.o myprintf.o -L/usr/lib/ -lc
ld: warning: cannot find entry symbol _start; defaulting to 00000000080481a4
Quote:
$ ./test
hello, world!
Segmentation fault
- GCC编译的背后
- gcc编译的背后
- Gcc 编译的背后
- Gcc 编译的背后--预处理
- GCC编译的背后( 预处理和编译 汇编和链接 )
- GCC编译的背后( 预处理和编译 汇编和链接)
- GCC编译的背后( 预处理和编译 汇编和链接 )
- GCC编译的背后( 预处理和编译 汇编和链接 )
- GCC编译的背后( 预处理和编译 汇编和链接 )
- gcc编译的背后(预处理、编译、汇编和链接) 一
- GCC编译的背后( 预处理和编译 汇编和链接 )
- GCC编译的背后( 预处理和编译 汇编和链接 )
- GCC编译的背后( 预处理和编译 汇编和链接 )
- GCC编译的背后( 预处理和编译 汇编和链接 )
- GCC编译的背后( 预处理和编译 汇编和链接 )
- GCC编译的背后( 预处理和编译 汇编和链接 )
- GCC编译的背后( 预处理和编译 汇编和链接 )
- [授权发表]GCC 编译背后的奥秘
- java 抽象类的应用(模板设计模式)
- Guibs 的 Python学习_ 函数
- lambda
- servlet涉及对象
- SQL语句
- gcc编译的背后
- TreeSet的comparable接口
- 2016.9.4
- mysql 导入txt文档的问题
- 字典树合并 ifrog1028 Bob and Alice are playing numbers
- Linux用户和群组基本命令
- 直接插入排序
- CD检测代码的定位练习-20160905
- C/C++开发者必不可少的15款编译器+IDE