当前位置:文档之家› GCC编译的背后(预处理和编、汇编和链接)

GCC编译的背后(预处理和编、汇编和链接)

GCC编译的背后(预处理和编、汇编和链接)
GCC编译的背后(预处理和编、汇编和链接)

论坛搜索

全文

ZOL 论坛 > 技术论坛 > C/C++论坛 > GCC编译的背后( 预处理和编译 汇编和链接 )本主题共6楼ZOL_酋长 MySQL 版主

离线 帖子 382 精华 24 技术分 1107

注册时间 2005-08-25

所在地 北京 所有帖子>> 复制本页连接给好友

GCC编译的背后( 预处理和编译 汇编和链接 )

by falcon

2008-02-22 平时在Linux下写代码,直接用"gcc -o out in.c"就把代码编译好了,但是这后面到底做了什么事情呢?如果学习过编

译原理则不难理解,一般高级语言程序编译的过程莫过于:预处理、编译、汇编、链接。gcc在后台实际上也经历了这几个

过程,我们可以通过-v参数查看它的编译细节,如果想看某个具体的编译过程,则可以分别使用-E,-S,-c和 -O,对应的后台工具则分别

为cpp,cc1,as,ld 。下面我们将逐步分析这几个过程以及相关的内容,诸如语法检查、代码调试、汇编语言等。 1、预处理

开篇简述:预处理是C语言程序从源代码变成可执行程序的第一步,主要是C语言编译器对各种预处理命令进行处理,包

括头文件的包含、宏定义的扩展、条件编译的选择等。

以前没怎么“深入”预处理,脑子对这些东西总是很模糊,只记得在编译的基本过程(词法分析、语法分析)之前还需

要对源代码中的宏定义、文件包含、条件编译等命令进行处理。这三类的指令很常见,主要有#define, #include和#ifdef ...

#endif ,要特别地注意它们的用法。(更多预处理的指令请查阅相关资料)

#define除了可以独立使用以便灵活设置一些参数外,还常常和#ifdef ... #endif结合使用,以便灵活地控制代码块的

编译与否,也可以用来避免同一个头文件的多次包含。关于#include貌似比较简单,通过man找到某个函数的头文件,copy

进去,加上<>就okay。这里虽然只关心一些技巧,不过预处理还是蕴含着很多潜在的陷阱(可参考),我们也需要注意的。

下面仅介绍和预处理相关的几个简单内容。

打印出预处理之后的结果:gcc -E hello.c

这样我们就可以看到源代码中的各种预处理命令是如何被解释的,从而方便理解和查错。

实际上gcc在这里是调用了cpp的(虽然我们通过gcc的-v仅看到cc1),cpp即The C Preprocessor,主要用来预处理宏定

义、文件包含、条件编译等。下面介绍它的一个比较重要的选项-D。

在命令行定义宏:gcc -Dmacro hello.c

等同于在文件的开头定义宏,即#define maco,但是在命令行定义更灵活。例如,在源代码中有这些语句。

#ifdef DEBUG

printf("this code is for debuggingn");

#endif

如果编译时加上-DDEBUG选项,那么编译器就会把printf所在的行编译进目标代码,从而方便地跟踪该位置的某些程序状态。这样-DDEBUG就可

以当作一个调试开关,编译时加上它就可以用来打印调试信息,发布时则可以通过去掉该编译选项把调试信息去掉。

本节参考资料:

[1] C语言教程第九章:预处理

https://www.doczj.com/doc/ca8920074.html,/Article/kfyy/cyy/jc/200409/9.html

[2] 更多

https://www.doczj.com/doc/ca8920074.html,/kfyy/c/6626.html

https://www.doczj.com/doc/ca8920074.html,/html/article/program/cpp/20071203/8745.html

https://www.doczj.com/doc/ca8920074.html,/bbs/programmer/2006-10-13/327.html

2、编译(翻译)

楼主 发表于 2010-05-31 11:36:59 只看楼主 大 小

登录 | 注册 | 论坛帮助

ZOL首页 | 产品报价 | ZOL论坛首页 | 更多论坛

言,即汇编语言。如果想看到这个中间结果,可以用-S选项。需要提到的是,诸如shell等解释语言也会经历一个词法分析和语法分析的阶段,不过之后并不会进行“翻译”,而是“解释”,边解释边执行。

************************

A、解释程序

所谓解释程序是高级语言翻译程序的一种,它将源语言(如BASIC)书写的源程序作为输入,解释一句后就提交计算机执行一句,并不形成目标程序。就像外语翻译中的“口译”一样,说一句翻一句,不产生全文的翻译文本。这种工作方式非常适合于人通过终端设备与计算机会话,如在终端上打一条命令或语句,解释程序就立即将此语句解释成一条或几条指令并提交硬件立即执行且将执行结果反映到终端,从终端把命令打入后,就能立即得到计算结果。这的确是很方便的,很适合于一些小型机的计算问题。但解释程序执行速度很慢,例如源程序中出现循环,则解释程序也重复地解释并提交执行这一组语句,这就造成很大浪费。

B、编译程序

这是一类很重要的语言处理程序,它把高级语言(如FORTRAN、COBOL、Pascal、C等)源程序作为输入,进行翻译转换,产生出机器语言的目标程序,然后再让计算机去执行这个目标程序,得到计算结果。

编译程序工作时,先分析,后综合,从而得到目标程序。所谓分析,是指词法分析和语法分析;所谓综合是指代码优化,存储分配和代码生成。为了完成这些分析综合任务,编译程序采用对源程序进行多次扫描的办法,每次扫描集中完成一项或几项任务,也有一项任务分散到几次扫描去完成的。下面举一个四遍扫描的例子:第一遍扫描做词法分析;第二遍扫描做语法分析;第三遍扫描做代码优化和存储分配;第四遍扫描做代码生成。

值得一提的是,大多数的编译程序直接产生机器语言的目标代码,形成可执行的目标文件,但也有的编译程序则先产生汇编语言一级的符号代码文件,然后再调用汇编程序进行翻译加工处理,最后产生可执行的机器语言目标文件。

在实际应用中,对于需要经常使用的有大量计算的大型题目,采用招待速度较快的编译型的高级语言较好,虽然编译过程本身较为复杂,但一旦形成目标文件,以后可多次使用。相反,对于小型题目或计算简单不太费机时的题目,则多选用解释型的会话式高级语言,如BASIC,这样可以大大缩短编程及调试的时间

************************

把源代码翻译成汇编语言,实际上是编译的整个过程中的第一个阶段,之后的阶段和汇编语言的开发过程没有什么区别。这个阶段涉及到对源代码的词法分析、语法检查(通过-std指定遵循哪个标准),并根据优化(-O)要求进行翻译成汇编语言的动作。

如果仅仅希望进行语法检查,可以用-fsyntax-only选项;而为了使代码有比较好的移植性,避免使用gcc的一些特性,可以结合-std和 -pedantic(或者-pedantic-erros)选项让源代码遵循某个C语言标准的语法。这里演示一个简单的例子。

$ cat hello.c

#include

int main()

{

printf("hello, worldn")

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

int main()

{

printf("hello, worldn");

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

ZOL_酋长 MySQL 版主

语法错误是程序开发过程中难以避免的错误(人的大脑在很多条件下都容易开小差),不过编译器往往能够通过语法检

查快速发现这些错误,并准确地告诉你语法错误的大概位置。因此,作为开发人员,要做的事情不是“恐慌”(不知所

措),而是认真阅读编译器的提示,根据平时积累的经验(最好在大脑中存一份常见语法错误索引,很多资料都提供了常见

语法错误列表,如和最后面的参考资料[12]也列出了很多常见问题)和编辑器提供的语法检查功能(语法加亮、括号匹配提

示等)快速定位语法出错的位置并进行修改。

语法检查之后就是翻译动作,gcc提供了一个优化选项-O,以便根据不同的运行平台和用户要求产生经过优

化的汇编代码。例如,

根据上面的简单演示,可以看出gcc有很多不同的优化选项,主要看用户的需求了,目标代码的大小和效率之间貌似存

在一个“纠缠”,需要开发人员自己权衡。

$ 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

相关热帖

·我自己研发的极品五子棋,不服的进来比一比

·大家来分析一下近来最火的电影《盗梦空间》的程序

·实现线程分发

·用C++连接MYSQL

·浙大网新科技股份有限公司招IPHONE平台C++研发工

个性签名 相濡以沫,不如相忘于江湖

TOP 下面我们通过-S选项来看看编译出来的中间结果,汇编语言,还是以之前那个hello.c为例。

1楼 沙发 发表于 2010-05-31 11:37:25

只看该作者

帖子 382

精华 24

技术分 1107

注册时间 2005-08-25

所在地 北京 所有帖子>>

不知道看出来没?和我们在课堂里学的intel的汇编语法不太一样,这里用的是AT&T语法格式。如果之前没接触过AT&T

的,可以看看参考资料[2]。如果想学习Linux下的汇编语言开发,从下一节开始哦,下一节开始的所有章节基本上覆盖了

Linux下汇编语言开发的一般过程,不过这里不介绍汇编语言语法。

这里需要补充的是,在写C语言代码时,如果能够对编译器比较熟悉(工作原理和一些细节)的话,可能会很有帮助。

包括这里的优化选项(有些优化选项可能在汇编时采用)和可能的优化措施,例如字节对齐(可以看看这本

书"Linux_Assembly_Language_Programming"的第六小节)、条件分支语句裁减(删除一些明显分支)等。

本节参考资料

[1] Guide to Assembly Language Programming in Linux(pdf教程,社区有下载)

https://www.doczj.com/doc/ca8920074.html,/modules/wfdownloads/singlefile.php?cid=5&lid=94

[2] Linux汇编语言开发指南(在线):

https://www.doczj.com/doc/ca8920074.html,/developerworks/cn/linux/l-assembly/index.html

[3] PowerPC 汇编

https://www.doczj.com/doc/ca8920074.html,/developerworks/cn/linux/hardware/ppc/assembly/index.html

[4] 用于 Power 体系结构的汇编语言

https://www.doczj.com/doc/ca8920074.html,/developerworks/cn/linux/l-powasm1.html

[5] Linux Assembly HOWTO

https://www.doczj.com/doc/ca8920074.html,/tldp/HOWTO/Assembly-HOWTO/

[6] Linux 中 x86 的内联汇编

https://www.doczj.com/doc/ca8920074.html,/developerworks/cn/linux/sdk/assemble/inline/index.html

[7] Linux Assembly Language Programming

https://www.doczj.com/doc/ca8920074.html,/doc/incoming/ebooks/linux-unix/Linux_EN_Original_Books

3、汇编

.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

可以运行。如果要产生这一中间结果,可用gcc的-c选项,当然,也可通过as命令_汇编_汇编语言源文件来产生。

汇编是把汇编语言翻译成目标代码的过程,在学习汇编语言开发时,大家应该比较熟悉nasm汇编工具(支持Intel格式的汇编语言)了,不过这里主要用 as汇编工具来汇编AT&T格式的汇编语言,因为gcc产生的中间代码就是AT&T格式的。下面来演示分别通过gcc的-c选项和as来产生目标代码。

Quote:

$ 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和as默认产生的目标代码都是ELF格式[6]的,因此这里主要讨论ELF格式的目标代码(如果有时间再回顾一下a.out和coff格式,当然你也可以参考资料[15],自己先了解一下,并结合objcopy来转换它们,比较异同)。

目标代码不再是普通的文本格式,无法直接通过文本编辑器浏览,需要一些专门的工具。如果想了解更多目标代码的细节,区分relocatable(可重定位)、executable(可执行)、shared libarary(共享库)的不同,我们得设法了解目标代码的组织方式和相关的阅读和分析工具。下面我们主要介绍这部分内容。

"BFD is a package which allows applications to use the same routines to operate on object files whatever the object file format. A new object file format can be supported simply by creating a new BFD

back end and adding it to the library."[24][25]。

binutils(GNU Binary Utilities)的很多工具都采用这个库来操作目标文件,这类工具有objdump,objcopy,nm,strip

等(当然,你也可以利用它。如果你深入了解ELF格式,那么通过它来分析和编写Virus程序将会更加方便),不过另外一款非常优秀的分析工具readelf并不是基于这个库,所以你也应该可以直接用elf.h头文件中定义的相关结构来操作ELF文件。

下面将通过这些辅助工具(主要是readelf和objdump,可参考本节最后列出的资料[4]),结合ELF手册[6](建议看第三篇中文版)来分析它们。

下面大概介绍ELF文件的结构和三种不同类型ELF文件的区别。

ELF文件的结构:

ELF Header(ELF文件头)

Porgram Headers Table(程序头表,实际上叫段表好一些,用于描述可执行文件和可共享库)

Section 1

Section 2

Section 3

...

Section Headers Table(节区头部表,用于链接可重定位文件成可执行文件或共享库)

对于可重定位文件,程序头是可选的,而对于可执行文件和共享库文件(动态连接库),节区表则是可选的。这里的可选是指没有也可以。可以分别通过 readelf文件的-h,-l和-S参数查看ELF文件头(ELF Header)、程序头部表(Program Headers Table,段表)和节区表(Section Headers Table)。

文件头说明了文件的类型,大小,运行平台,节区数目等。先来通过文件头看看不同ELF的类型。为了说明问题,先来几段代码吧。

Code:

/* myprintf.c */ #include void myprintf(void) { printf("hello, world!n"); }

[Ctrl+A Select All]

Code:

/* myprintf function declaration */ #ifndef _TEST_H_ #define _TEST_H_ void myprintf(void); #endif

[Ctrl+A Select All]

Code:

/* test.c */ #include "test.h" int main() { myprintf(); return 0; }

[Ctrl+A Select All]

下面通过这几段代码来演示通过readelf -h参数查看ELF的不同类型。期间将演示如何创建动态连接库(即可共享文件)、静态连接库,并比较它们的异同。

Quote:

$ 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

ZOL_酋长

MySQL版主

离线

帖子 382

精华 24

技术分 1107

注册时间 2005-08-25

所在地 北京

所有帖子>>

$ LD_LIBRARY_PATH=./ ./test #LD_LIBRARY_PATH为动态链接库的搜索路径

$ gcc -static -o test test.o -llib -L./ #在不指定static时会优先使用动态链接库,指定时则阻止使用动态连接库

#这个时候会把所有静态连接库文件加入到可执行文件中,使得执行文件很大

#而且加载到内存以后会浪费内存空间,因此不建议这么做

个性签名相濡以沫,不如相忘于江湖

TOP

经过上面的演示基本可以看出它们之间的不同。可重定位文件本身不可以运行,仅仅是作为可执行文件、静态连接库(也是可重定位文件)、动态连接库的 “组件”。静态连接库和动态连接库本身也不可以执行,作为可执行文件的“组件”,它们两者也不同,前者也是可重定位文件(只不过可能是多个可重定位文件的集合),并且在连接时加入到可执行文件中去;而动态连接库在连接时,库文件本身并没有添加到可执行文件中,只是在可执行文件中加入了该库的名字等信息,以便在可执行文件运行过程中引用库中的函数时由动态连接器去查找相关函数的地址,并调用它们。从这个意义上说,动态连接库本身也具有可重定位的特征,含有可重定位的信息。对于什么是重定位?如何进行静态符号和动态符号的重定位,我们将在链接部分和《动态符号链接的细节》一节介绍。

下面来看看ELF文件的主体内容,节区(Section)。ELF文件具有很大的灵活性,它通过文件头组织整个文件的总体结构,通过节区表 (Section Headers Table)和程序头(Program Headers Table或者叫段表)来分别描述可重定位文件和可执行文件。但不管是哪种类型,它们都需要它们的主体,即各种节区。在可重定位文件中,节区表描述的就是各种节区本身;而在可执行文件中,程序头描述的是由各个节区组成的段(Segment),以便程序运行时动态装载器知道如何对它们进行内存映像,从而方便程序加载和运行。

下面先来看看一些常见的节区,而关于这些节区(section)如何通过重定位构成成不同的段(Segments),以及有哪些常规的段,我们将在链接部分进一步介绍。

可以通过readelf的-S参数查看ELF的节区。(建议一边操作一边看文档,以便加深对ELF文件结构的理解)先来看看可重定位文件的节区信息,通过节区表来查看:

Quote:

$ 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:

2楼 椅子 发表于 2010-05-31 11:37:51 只看该作者

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

13: 83 c4 10 add $0x10,%esp

16: c9 leave

17: c3 ret

$ readelf -r myprintf.o #用-r选项可以看到有关重定位的信息,这里有两部分需要重定位

Relocetion 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 https://www.doczj.com/doc/ca8920074.html,ment

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

Hex dump of section '.strtab':

0x00000000 006d7970 72696e74 662e6300 6d797072 .myprintf.c.mypr

0x00000010 696e7466 00707574 7300 intf.puts.

从上表可以看出,对于可重定位文件,会包含这些基本节区.text, .rel.text, .data, .bss, .rodata, .comment, .note.GNU-stack, .shstrtab, .symtab和.strtab。为了进一步理解这些节区和源代码的关系,这里来看一看myprintf.c产生的汇编代码。

Quote:

$ 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

是不是可以从中看出可重定位文件中的那些节区和汇编语言代码之间的关系?在上面的可重定位文件,可以看到有一个可重定位的节区,即. rel.text,它标记了两个需要重定位的项,.rodata和puts。这个节区将告诉编译器这两个信息在链接或者动态链接的过程中需要重定位,具体如何重定位?将根据重定位项的类型,比如上面的R_386_32和R_386_PC32(关于这些类型的更多细节,请查看ELF手册[6])。

到这里,对可重定位文件应该有了一个基本的了解,下面将介绍什么是可重定位,可重定位文件到底是如何被链接生成可执行文件和动态连接库的,这个过程除了进行了一些符号的重定位外,还进行了哪些工作呢?

本节参考资料:

[1] 了解编译程序的过程

https://www.doczj.com/doc/ca8920074.html,/Program_Data/linuxunix-3125.html

https://www.doczj.com/doc/ca8920074.html,/article/server/00070002/0621409075078127.htm

[2] C track: compiling C programs.

https://www.doczj.com/doc/ca8920074.html,/courses/cs11/material/c/mike/misc/compiling_c.html

[3] Dissecting shared libraries

https://www.doczj.com/doc/ca8920074.html,/developerworks/linux/library/l-shlibs.html

4、链接

开篇:重定位是将符号引用与符号定义进行链接的过程。因此链接是处理可重定位文件,把它们的各种符号引用和符号定义转换为可执行文件中的合适信息(一般是虚拟内存地址)的过程。链接又分为静态链接和动态链接,前者是程序开发阶段程序员用ld(gcc实际上在后台调用了ld)静态链接器手动链接的过程,而动态链接则是程序运行期间系统调用动态链接器(ld-linux.so)自动链接的过程。比如,如果链接到可执行文件中的是静态连接库libmyprintf.a,那么. rodata节区在链接

ZOL_酋长 MySQL 版主 离线 帖子 382 精华 24 技术分 1107 注册时间 2005-08-25 所在地 北京

所有帖子>>

连接库libc.so中定义的函数,所以会在程序运行时通过动态符号链接找出puts 函数在内存中的地址,以便程序调用该函数。

在这里主要讨论静态链接过程,动态链接过程见《动态符号链接的细节》。

静态链接过程主要是把可重定位文件依次读入,分析各个文件的文件头,进而依次读入各个文件的

节区,并计算各个节区的虚拟内存位置,对一些需要重定位的符号进行处理,设定它们的虚拟内存地址等,

并最终产生一个可执行文件或者是动态链接库。这个链接过程是通过ld来完成的,ld在链接时使用了一个链

接脚本(linker scripq),该链接脚本处理链接的具体细节。由于静态符号链接过程非常复杂,特别是计算

符号地址的过程,考虑到时间关系,相关细节请参考ELF手册[6]。这里主要介绍可重定位文件中的节区(节

区表描述的)和可执行文件中段(程序头描述的)的对应关系以及gcc编译时采用的一些默认链接选项。

个性签名 相濡以沫,不如相忘于江湖

TOP 下面先来看看可执行文件的节区信息,通过程序头(段表)来查看:

Quote:

$ 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

3楼 板凳 发表于 2010-05-31 11:38:18

只看该作

03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss

04 .dynamic

05 .note.ABI-tag

06

上表给出了可执行文件的如下几个段(segment),

PHDR: 给出了程序表自身的大小和位置,不能出现一次以上。

INTERP: 因为程序中调用了puts(在动态链接库中定义),使用了动态连接库,因此需要动态装载器/链接器(ld-linux.so) LOAD: 包括程序的指令,.text等节区都映射在该段,只读(R)

LOAD: 包括程序的数据,.data, .bss等节区都映射在该段,可读写(RW)

DYNAMIC: 动态链接相关的信息,比如包含有引用的动态连接库名字等信息

NOTE: 给出一些附加信息的位置和大小

GNU_STACK: 这里为空,应该是和GNU相关的一些信息

这里的段可能包括之前的一个或者多个节区,也就是说经过链接之后原来的节区被重排了,并映射到了不同的段,这些段将告诉系统应该如何把它加载到内存中。

从上表中,通过比较可执行文件(test)中拥有的节区和可重定位文件(test.o和myprintf.o)中拥有的节区后发现,链接之后多了一些之前没有的节区,这些新的节区来自哪里?它们的作用是什么呢?先来通过gcc的-v参数看看它的后台链接过程。

Quote:

$ 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

从上边的演示看出,gcc在连接了我们自己的目标文件test.o和myprintf.o之外,还连接了crt1.o,crtbegin.o等额外的目标文件,难道那些新的节区就来自这些文件?

另外gcc在进行了相关配置(./configure)后,调用了collect2,却并没有调用ld,通过查找gcc文档中和collect2相关的部发现collect2在后台实际上还是去寻找ld命令的。为了理解gcc默认连接的后台细节,这里直接把collect2替换成ld,并把一些径换成绝对路径或者简化,得到如下的ld命令以及执行的效果。

Quote:

$ 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

不出我们所料,它完美的运行了。下面通过ld的手册(man ld)来分析一下这几个参数。

--eh-frame-hdr

要求创建一个.eh_frame_hdr节区(貌似目标文件test中并没有这个节区,所以不关心它)。

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

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

Quote:

$ gcc -g -c test.c myprintf.c #产生目标代码, 非交叉编译,不指定-m也可以链接成功,所以下面可以去掉-m参数 $ ld -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 00000000080481d8

$ ./test

hello, world!

Segmentation fault

$ gdb ./test

...

(gdb) l

1 #include "test.h"

2

3 int main()

4 {

5 myprintf();

6 return 0;

7 }

(gdb) break 7 #在程序的末尾设置一个断点

Breakpoint 1 at 0x80481bf: file test.c, line 7.

(gdb) r #程序都快结束了都没问题,怎么会到最后出个问题呢?

Starting program: /mnt/hda8/Temp/c/program/test

hello, world!

Breakpoint 1, main () at test.c:7

7 }

(gdb) n #单步执行看看,怎么下面一条指令是0x00000001,肯定是程序退出以后出了问题

0x00000001 in ?? ()

(gdb) n #诶,当然找不到边了,都跑到0x00000001了

Cannot find bounds of current function

(gdb) c #原来是这么回事,估计是return 0返回之后出问题了,看看它的汇编去。

Continuing.

Program received signal SIGSEGV, Segmentation fault.

0x00000001 in ?? ()

$ gcc -S test.c #产生汇编代码

$ cat test.s #后面就这么几条指令,难不成ret返回有问题,不让它ret返回,把return改成_exit直接进入内核退出

movl $0, %eax

addl $4, %esp

popl %ecx

popl %ebp

leal -4(%ecx), %esp

ret

...

$ vim test.c

$ cat test.c #就把return语句修改成_exit了。

#include "test.h"

#include /* _exit */

int main()

{

myprintf();

_exit(0);

}

$ gcc -g -c test.c myprintf.c

$ ld -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 00000000080481d8

$ ./test #竟然好了,再看看汇编有什么不同

hello, world!

$ gcc -S test.c

$ cat test.s #貌似就把ret指令替换成了_exit函数调用,直接进入内核,然内核让处理了,那为什么ret有问题呢? ...

call myprintf

subl $12, %esp

pushl $0

call _exit

...

$ gdb ./test #把代码改回去(改成return 0;),再调试看看调用main函数返回时的下一条指令地址eip

...

(gdb) l

warning: Source file is more recent than executable.

1 #include "test.h"

2

3 int main()

4 {

5 myprintf();

6 return 0;

7 }

(gdb) break 5

Breakpoint 1 at 0x80481b5: file test.c, line 5.

(gdb) break 7

Breakpoint 2 at 0x80481bc: file test.c, line 7.

(gdb) r

Starting program: /mnt/hda8/Temp/c/program/test

Breakpoint 1, main () at test.c:5

5 myprintf();

(gdb) x/8x $esp #发现0x00000001刚好是之前我们调试时看到的程序返回后的位置,即eip,说明程序在初始化的时候 #这个eip就是错误的。为什么呢?因为我们根本没有链接进来初始化的代码,而是在编译器自己给我们 #初始化了一个程序入口即00000000080481d8,也就是说,没有任何人调用main,main不知道返回哪里去 #所以,我们直接让main结束时进入内核调用_exit而退出则不会有问题

0xbf929510: 0xbf92953c 0x080481a4 0x00000000 0xb7eea84f

0xbf929520: 0xbf92953c 0xbf929534 0x00000000 0x00000001

Quote:

$ ld --verbose | grep ^ENTRY #非交叉编译,可不用-m参数;ld默认找_start入口,并不是main哦!

ENTRY(_start)

Quote:

$ cat test.c

#include "test.h"

#include /* _exit */

int main()

{

myprintf();

_exit(0);

}

$ gcc -S test.c

$ sed -i -e "s#main#_start#g" test.s #把汇编中的main全部修改为_start,即修改程序入口为_start

$ gcc -c test.s myprintf.c

$ ld -dynamic-linker /lib/ld-linux.so.2 -o test test.o myprintf.o -L/usr/lib/ -lc #果然没问题了 :-) $ ./test

hello, world!

Quote:

$ ld --verbose | grep PROVIDE | grep -v HIDDEN

PROVIDE (__executable_start = 0x08048000); . = 0x08048000 + SIZEOF_HEADERS;

PROVIDE (__etext = .);

PROVIDE (_etext = .);

PROVIDE (etext = .);

_edata = .; PROVIDE (edata = .);

_end = .; PROVIDE (end = .);

z

这里面有几个我们比较关心的,第一个是程序的入口地址__executable_start,另外三个是etext,edata,end,分别对应序的代码段(text)、初始化数据(data)和未初始化的数据(bss)(可以参考资料[6]和man etext),如何引用这些变量呢?看看个例子。

Code:

/* predefinevalue.c */ #include extern int __executable_start, etext, edata, end; int main () { printf ("program entry: 0x%x n", &__executable_start); printf ("etext address(text segment): 0x%x n", &etext); printf ("edata address(initilized data): 0x%x n", &edata); printf ("end address(uninitilized data): 0x%x n", &end); return 0; }

[Ctrl+A Select All]

到这里,程序链接过程的一些细节都介绍得差不多了。在《动态符号链接的细节》中将主要介绍ELF文件的动态符号链接过程。

本节参考资料

ZOL_酋长

MySQL版主

离线

帖子 382

精华 24

技术分 1107

注册时间 2005-08-25

所在地 北京

所有帖子>>

[1] An beginners guide to compiling programs under Linux.

http://www.luv.asn.au/overheads/compile.html

[2] gcc manual

https://www.doczj.com/doc/ca8920074.html,/onlinedocs/gcc-4.2.2/gcc/

[3] A Quick Tour of Compiling, Linking, Loading, and Handling Libraries on Unix

http://efrw01.frascati.enea.it/Software/Unix/IstrFTU/cern-cnl-2001-003-25-link.html

[4] Unix 目标文件初探

https://www.doczj.com/doc/ca8920074.html,/developerworks/cn/aix/library/au-unixtools.html

[5] Before main()分析

https://www.doczj.com/doc/ca8920074.html,/articles/200109/269.html

[6] A Process Viewing Its Own /proc//map Information

https://www.doczj.com/doc/ca8920074.html,/forum/linux-kernel/51790-process-viewing-its-own-proc-pid-map-information.html

个性签名相濡以沫,不如相忘于江湖

T

_start竟然是真正的程序入口,那在有main的情况下呢?为什么在_start之后能够找到main呢?这个看看alert7大叔的"Before main分析"[5]吧,这里不再深入介绍。总之呢,通过修改程序的return语句为_exit(0)和修改程序的入口为

_start,我们的代码不链接gcc默认链接的那些额外的文件同样可以工作得很好。并且打破了一个学习C语言以来的常识:

main函数作为程序的主函数,是程序的入口,实际上则不然。

再补充一点内容,在ld的链接脚本中,有一个特别的关键字PROVIDE,由这个关键字定义的符号是ld的预定义字符,我

们可以在C语言函数中扩展它们后直接使用。这些特别的符号可以通过下面的方法获取, 原来是这样,程序的入口(entry)

竟然不是main函数,而是_start。那干脆把汇编里头的main给改掉算了,看行不行?

通过上面的演示和解释发现只要把return语句修改为_exit语句,程序即使不链接任何额外的目标代码都可以正常运行

(原因是不连接那些额外的文件时相当于没有进行初始化操作,如果在程序的最后执行ret汇编指令,程序将无法获得正确

的eip,从而无法进行后续的动作)。但是为什么会有“找不到 _start符号”的警告呢?通过readelf -s查看crt1.o发现里

头有这个符号,并且crt1.o引用了main这个符号,是不是意味着会从_start进入main呢?是不是程序入口是 _start,而并

非main呢?

先来看看刚才提到的链接器的默认链接脚本(ld -m elf_386 --verbose),它告诉我们程序的入口(entry)是_start,而

一个可执行文件必须有一个入口地址才能运行,所以这就是说明了为什么ld一定要提示我们“_start找不到”,找不到以后

就给默认设置了一个地址。

貌似程序运行完了,不过结束时冒出个段错误?可能是程序结束时有问题,用gdb调试看看:

这样却说没有找到入口符号_start,难道crt1.o中定义了这个符号?不过它给默认设置了一个地址,只是个警告,说明test已经生成,不管怎样先运行看看再说。

竟然是crt1.o调用了__libc_csu_init函数,而该函数却引用了我们没有链接的crti.o文件中定义的_init符号。这样的

话不链接 crti.o和crtn.o文件就不成了罗?不对吧,要不干脆不用crt1.o算了,看看gcc额外连接进去的最后一个文件

crt1.o到底干了个啥子?

貌似不行,竟然有人调用了__libc_csu_init函数,而这个函数引用了_init。这两个符号都在哪里呢?

完全可以工作,而且发现.ctors(保存着程序中全局构造函数的指针数组), .dtors(保存着程序中全局析构函数的指针

数组),.jcr(未知),.eh_frame节区都没有了,所以crtbegin.o和crtend.o应该包含了这些节区。

而对于另外两个文件crti.o和crtn.o,通过readelf -S查看后发现它们都有.init和.fini节区,如果我们不需要让程序

进行一些初始化和清理工作呢?是不是就可以不链接这个两个文件?试试看。

z-m elf_i386

这里指定不同平台上的链接脚本,可以通过--verbose命令查看脚本的具体内容,如ld -m elf_i386 --verbose,它实际上

被存放在一个文件中(/usr/lib/ldscripqs目录下),你可以去修改这个脚本,具体如何做?请参考ld的手册。在后面我们

4楼 马扎 发表于 2010-05-31 11:38:37 只看该作者

parid123

离线

帖子 8

精华 0

技术分 8

注册时间 2010-07-27

所在地 北京

所有帖子>>

jklovelovelove

离线

编译,那么无须指定该选项。

z-dynamic-linker /lib/ld-linux.so.2

指定动态装载器/链接器,即程序中的INTERP段中的内容。动态装载器/连接器负责连接有可共享库的可执行文件的装载和动态符号连接。

z-o test

指定输出文件,即可执行文件名的名字

z/usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc/i486-slackware-linux/4.1.2/crtbegin.o

链接到test文件开头的一些内容,这里实际上就包含了.init等节区。.init节区包含一些可执行代码,在main函数之前被调用,以便进行一些初始化操作,在C++中完成构造函数功能,更多细节请参考资料[9]

z test.o myprintf.o

链接我们自己的可重定位文件

z-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

链接libgcc库和libc库,后者定义有我们需要的puts函数

z/usr/lib/gcc/i486-slackware-linux/4.1.2/crtend.o /usr/lib/crtn.o

链接到test文件末尾的一些内容,这里实际上包含了.fini等节区。.fini节区包含了一些可执行代码,在程序退出时被执行,作一些清理工作,在C++中完成析构造函数功能。我们往往可以通过atexit来注册那些需要在程序退出时才执行的函数。

对于crtbegin.o和crtend.o这两个文件,貌似完全是用来支持C++的构造和析构工作的[9],所以可以不链接到我们的可执行文件中,链接时把它们去掉看看,

个性签名相濡以沫,不如相忘于江湖

TOP 经典,学习了.

5楼 地毯 发表于 2010-07-27 09:44:17 只看该作者

个性签名无签名~

TOP 分享啦~!顶

6楼 凉席 发表于 2010-08-01 08:38:38 只看该作者

gcc编译器使用简明指南

gcc编译器使用简明指南 gcc对文件的处理需要经过预处理->编译->汇编->链接的步骤,从而产生一个可执行文件,各部分对应不同的文件类型,具体如下: file.c c程序源文件 file.i c程序预处理后文件 file.cxx c++程序源文件,也可以是https://www.doczj.com/doc/ca8920074.html, / file.cpp / file.c++ file.ii c++程序预处理后文件 file.h c/c++头文件 file.s 汇编程序文件 file.o 目标代码文件 gcc [选项]文件列表 -ansi 强制完全ANSI一致 -c 仅编译或汇编,生成目标代码文件,将.c、.i、.s等文件生成.o文件,其余文件被忽略 -S 仅编译,不进行汇编和链接,将.c、.i等文件生成.s文件,其余文件被忽略 -E 仅预处理,并发送预处理后的.i文件到标准输出,其余文件被忽略 -o file 创建可执行文件并保存在file中,而不是默认文件a.out -g 产生用于调试和排错的扩展符号表,用于GDB调试,切记-g和-O通常不能一起使用 -w 取消所有警告 -W 给出更详细的警告 -O [num]优化,可以指定0-3作为优化级别,级别0表示没有优化 -x language 默认为-x none,即依靠后缀名确定文件类型,加上-x lan确定后面所有文件类型,直到下一个-x出现为止 -D macro[=]类似于源程序里的#define,在-D macro中的macro可被源程序识别,例如gcc -D NUM -D FILE=\"bbs.txt\" hello.c -o hello,第一个-D选项定义宏NUM,在程序中可以使用#ifdef来检查是否被设置,第二个-D定义宏FILE,在源程序中可用 -U macro 类似于源程序开头定义#undef macro,也就是取消源程序中的某个宏定义

GCC常见错误解析

GCC常见错误解析 一、错误类型 第一类∶C语法错误 错误信息∶文件source.c中第n行有语法错误(syntex errror)。 这种类型的错误,一般都是C语言的语法错误,应该仔细检查源代码文件中第n行及该行之前的程序,有时也需要对该文件所包含的头文件进行检查。 有些情况下,一个很简单的语法错误,gcc会给出一大堆错误,此时要保持清醒的头脑,不要被其吓倒,必要的时候再参考一下C语言的基本教材。 第二类∶头文件错误 错误信息∶找不到头文件head.h(Can not find include file head.h)。 这类错误是源代码文件中的包含头文件有问题,可能的原因有头文件名错误、指定的头文件所在目录名错误等,也可能是错误地使用了双引号和尖括号。 第三类∶档案库错误 错误信息∶连接程序找不到所需的函数库,例如∶ld: -lm: No such file or directory. 这类错误是与目标文件相连接的函数库有错误,可能的原因是函数库名错误、指定的函数库所在目录名称错误等,检查的方法是使用find命令在可能的目录中寻找相应的函数库名,确定档案库及目录的名称并修改程序中及编译选项中的名称。第四类∶未定义符号 错误信息∶有未定义的符号(Undefined symbol)。 这类错误是在连接过程中出现的,可能有两种原因∶一是使用者自己定义的函数或者全局变量所在源代码文件,没有被编译、连接,或者干脆还没有定义,这需要使用者根据实际情况修改源程序,给出全局变量或者函数的定义体;二是未定义的符号是一个标准的库函数,在源程序中使用了该库函数,而连接过程中还没有给定相应的函数库的名称,或者是该档案库的目录名称有问题,这时需要使用档案库维护命令ar检查我们需要的库函数到底位于哪一个函数库中,确定之后,修改gcc 连接选项中的-l和-L项。 排除编译、连接过程中的错误,应该说这只是程序设计中最简单、最基本的一个步骤,可以说只是开了个头。这个过程中的错误,只是我们在使用C语言描述一个算法中所产生的错误,是比较容易排除的。我们写一个程序,到编译、连接通过为止,应该说刚刚开始,程序在运行过程中所出现的问题,是算法设计有问题,说得更玄点是对问题的认识和理解不够,还需要更加深入地测试、调试和修改。一个程序,稍为复杂的程序,往往要经过多次的编译、连接和测试、修改。 二、常见错误信息解析与处理 1

1、GCC编译器的使用

linux下gcc编译器的使用 1、文件后缀名 .c C 源程序 .C C++ 源程序 .cc C++ 源程序 .cxx C++ 源程序 .m Objective –C源程序 .i 预处理过的c源程序 .ii 预处理过的C++源程序 .s 组合语言源程序 .S 组合语言源程序 .h 头文件 .o 目标文件 .a 存档文件 2、GCC常用选项 -c 通知GCC取消链接步骤,即编译源码并在最后生成目标文件; -Dmacro定义指定的宏,使它能够通过源码中的#ifdef进行检验 #define -static 指定程序编译时采用静态编译的方法; -E 不经过编译预处理程序的输出而输送至标准输出; -g获得有关调试程序的详细信息,它不能与-o选项联合使用; -Idirectory在包含文件搜索路径的起点处添加指定目录; -llibrary提示链接程序在创建最终可执行文件时包含指定的库; -O、-O2、-O3将优化状态打开,该选项不能与-g选项联合使用; -S要求编译程序生成来自源代码的汇编程序输出; -v启动所有警报; -Wall发生警报时取消编译操作,即将警报看作是错误; -Werror在发生警报时取消编译操作,即把报警当作是错误; -w 禁止所有的报警。 目前Linux下最常用的C语言编译器是GCC(GNU Compiler Collection),它是GNU项目中符合ANSI C标准的编译系统,能够编译用C、C++和Object C等语言编写的程序。GCC不仅功能非常强大,结构也异常灵活。最值得称道的一点就是它可以通过不同的前端模块来支持各种语言,如Java、 Fortran、Pascal、Modula-3和Ada等。开放、自由和灵活是Linux的魅力所在,而这一点在GCC上的体现就是程序员通过它能够更好地控制整个编译过程。

GCC编译选项

Linux中gcc,g++常用编译选项 -x language filename 设定文件所使用的语言,使后缀名无效,对以后的多个有效.也就是根据约定,C语言的后缀名称是.c的,而C++的后缀名是.C或者.cpp,如果你很个性,决定你的C代码文件的后缀名是. pig 哈哈,那你就要用这个参数,这个参数对他后面的文件名都起作用,除非到了下一个参数的使用。可以使用的参数有下面的这些: `c', `objective-c', `c-header', `c++', `cpp-output', `assembler', and `a ssembler-with-cpp'. 看到英文,应该可以理解的。 例子用法: cd.. gcc -x c hello.pig -x none filename 关掉上一个选项,也就是让gcc根据文件名后缀,自动识别文件类型 例子用法: gcc -x c hello.pig -x none hello2.c -c 只激活预处理,编译,和汇编,也就是他只把程序做成obj文件 例子用法: gcc -c hello.c 他将生成.o的obj文件 -S 只激活预处理和编译,就是指把文件编译成为汇编代码。 例子用法 gcc -S hello.c 他将生成.s的汇编代码,你可以用文本编辑器察看 -E 只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面. 例子用法: gcc -E hello.c > pianoapan.txt gcc -E hello.c | more 慢慢看吧,一个hello word 也要预处理成800行的代码 -o 制定目标名称,缺省的时候,gcc 编译出来的文件是a.out,很难听,如果你和我有同感,改掉它,哈哈 例子用法 gcc -o hello.exe hello.c (哦,windows用习惯了) gcc -o hello.asm -S hello.c -pipe 使用管道代替编译中临时文件,在使用非gnu汇编工具的时候,可能有些问题 gcc -pipe -o hello.exe hello.c

arm-linux-gcc 常用参数讲解 gcc编译器使用方法

arm-linux-gcc常用参数讲解gcc编译器使用方法 我们需要编译出运行在ARM平台上的代码,所使用的交叉编译器为arm-linux-gcc。下面将arm-linux-gcc编译工具的一些常用命令参数介绍给大家。 在此之前首先介绍下编译器的工作过程,在使用GCC编译程序时,编译过程分为四个阶段: 1. 预处理(Pre-Processing) 2. 编译(Compiling) 3. 汇编(Assembling) 4. 链接(Linking) Linux程序员可以根据自己的需要让GCC在编译的任何阶段结束,以便检查或使用编译器在该阶段的输出信息,或者对最后生成的二进制文件进行控制,以便通过加入不同数量和种类的调试代码来为今后的调试做好准备。和其它常用的编译器一样,GCC也提供了灵活而强大的代码优化功能,利用它可以生成执行效率更高的代码。 以文件example.c为例说明它的用法 0. arm-linux-gcc -o example example.c 不加-c、-S、-E参数,编译器将执行预处理、编译、汇编、连接操作直接生成可执行代码。 -o参数用于指定输出的文件,输出文件名为example,如果不指定输出文件,则默认输出 a.out 1. arm-linux-gcc -c -o example.oexample.c -c参数将对源程序example.c进行预处理、编译、汇编操作,生成example.0文件 去掉指定输出选项"-o example.o"自动输出为example.o,所以说在这里-o加不加都可以 2.arm-linux-gcc -S -o example.sexample.c -S参数将对源程序example.c进行预处理、编译,生成example.s文件 -o选项同上 3.arm-linux-gcc -E -o example.iexample.c -E参数将对源程序example.c进行预处理,生成example.i文件(不同版本不一样,有的将预处理后的内容打印到屏幕上) 就是将#include,#define等进行文件插入及宏扩展等操作。 4.arm-linux-gcc -v -o example example.c 加上-v参数,显示编译时的详细信息,编译器的版本,编译过程等。 5.arm-linux-gcc -g -o example example.c -g选项,加入GDB能够使用的调试信息,使用GDB调试时比较方便。 6.arm-linux-gcc -Wall -o example example.c -Wall选项打开了所有需要注意的警告信息,像在声明之前就使用的函数,声明后却没有使用的变量等。 7.arm-linux-gcc -Ox -o example example.c -Ox使用优化选项,X的值为空、0、1、2、3 0为不优化,优化的目的是减少代码空间和提高执行效率等,但相应的编译过程时间将较长并占用较大的内存空间。 8.arm-linux-gcc -I /home/include -o example example.c -Idirname: 将dirname所指出的目录加入到程序头文件目录列表中。如果在预设系统及当前目录中没有找到需要的文件,就到指定的dirname目录中去寻找。 9.arm-linux-gcc -L /home/lib -o example example.c

最新GCC编译器选项及优化提示

G C C编译器选项及优 化提示

GCC编译器选项及优化提示 GCC编译器选项及优化提示2010-08-01 19:41很多弟兄可能都很关心如何优化编译自己的程序,虽然本人不赞成"骨灰"玩法,却也不得不承认这是掌握gcc的绝佳途径; 因此献上此帖,以供各位玩家参考,绝对原创噢 = 大多数程序和库在编译时默认的优化级别是"2"(使用gcc选项:"-O2")并且在Intel/AMD平台上默认按照i386处理器来编译。 如果你只想让编译出来的程序运行在特定的平台上,就需要执行更高级的编译器优化选项,以产生只能运行于特定平台的代码。 一种方法是修改每个源码包中的Makefile文件,在其中寻找CFLAGS和CXXFLAGS变量(C和C++编译器的编译选项)并修改它的值。 一些源码包比如binutils,gcc,glibc等等,在每个子文件夹中都有Makefile文件,这样修改起来就太累了! 另一种简易做法是设置CFLAGS和CXXFLAGS环境变量。大多数configure 脚本会使用这两个环境变量代替Makefile文件中的值。 但是少数configure脚本并不这样做,他们必须需要手动编辑才行。 为了设置CFLAGS和CXXFLAGS环境变量,你可以在bash中执行如下命令(也可以写进.bashrc以成为默认值): export CFLAGS="-O3-march="&&CXXFLAGS=$CFLAGS 这是一个确保能够在几乎所有平台上都能正常工作的最小设置。

"-march"选项表示为特定的cpu类型编译二进制代码(不能在更低级别的cpu上运行), Intel通常是: pentium2,pentium3,pentium3m,pentium4,pentium4m,pentium- m,prescott,nocona 说明:pentium3m/pentium4m是笔记本用的移动P3/P4;pentium-m是迅驰I/II代笔记本的cpu; prescott是带SSE3的P4(以滚烫到可以煎鸡蛋而闻名);nocona则是最新的带有EMT64(64位)的P4(同样可以煎鸡蛋) AMD通常是:k6,k6-2,k6-3,athlon,athlon-tbird,athlon-xp,athlon-mp,opteron,athlon64,athlon-fx 用AMD的一般都是DIYer,就不必解释了吧。 如果编译时没有抱怨"segmentation fault,core dumped",那么你设定的"-O"优化参数一般就没什么问题。 否则请降低优化级别("-O3"-"-O2"-"-O1"-取消)。 个人意见:服务器使用"-O2"就可以了,它是最安全的优化参数(集合);桌面可以使用"-O3"; 不鼓励使用过多的自定义优化选项,其实他们之间没什么明显的速度差异(有时"-O3"反而更慢)。 编译器对硬件非常敏感,特别是在使用较高的优化级别的时候,一丁点的内存错误都可能导致致命的失败。 所以在编译时请千万不要超频你的电脑(我编译关键程序时总是先降频然的)。

vi编辑器及GCC编译器的使用

实验三vi编辑器及GCC编译器的使用 【实验目的】 一、掌握文本编辑器vi的使用方法 二、了解GNU gcc编译器 三、掌握使用GCC编译C语言程序的方法 【实验内容】 一、vi的三种工作模式: 1、命令模式:执行相关文本编辑命令 2、输入模式:输入文本 3、末行模式:实现查找、替换、保存、多文件操作等等功能 二、进入vi,直接在Shell提示符下键入vi [文件名称],如果该文件在当前目录不存在,则vi创建之。 三、退出vi 1、在命令模式下输入“:wq”,保存文件并退出vi 2、若不需要保存文件,输入“:q” 3、若文件已修改,但不保存,输入“:q!”强制退出vi 4、其它一些不常用的方法在此省略。 四、命令模式下的常用编辑命令 1、启动vi后,进入的是vi的命令模式 2、按i键,进入输入模式,可以进行文本的编辑,在输入模式下,按esc 键,可切换回命令模式 i:光标位置不变,可在光标左侧插入正文 a:光标位置向后退一格,可在光标左侧插入正文 o:在光标所在行的下一行增添新行 O:在光标所在行的上一行增添新行 I:光标跳到当前行的开头 A:光标跳到当前行的末尾 3、光标的移动 k、j、h、l分别等同于上、下、左、右箭头键 Ctrl+b,向上翻一页 Ctrl+f,向下翻一页 nH,将光标移到屏幕的第n行 nL,将光标移到屏幕的倒数第n行 4、删除文本 nX,删除光标所指向的前n个字符 D,删除光标右侧的所有字符(包括光标所指向的字符) db,删除光标左侧的全部字符 ndd,删除当前行和当前行以后的n行内容 5、粘贴和复制 p,将缓冲区的内容粘贴到当前字符的右侧

P,将缓冲区的内容粘贴到当前字符的左侧 yy,复制当前行到内存缓冲区 nyy,复制n行内容到内存缓冲区 6、搜索字符串 /str1,正向搜索字符串str1 n,继续搜索 ?str2,反向搜索字符串str2 7、撤销和重复 u,撤销前一条命令的执行结果 .,重复最后一条命令 五、末行模式下的命令 :n,将光标移动到第n行 :nw file,将第n行写入file文件 :n,mw file,将第n行至第m行写入file文件 :w,将编辑的内容写入原始文件 :wq,将编辑的内容写入原始文件并退出编辑程序 :w file,将编辑的内容写入file文件,保持原有文件的内容不变 :f file,将当前文件重命名为file :e file,编辑新文件file代替原有内容 :f,打印当前文件的状态,如文件的行数,光标所在的行号等 :!<命令>,执行相应shell命令 六、三种工作模式的切换 1、在Linux shell下,键入vi或vi <文件名>进入命令模式 2、在命令模式下,键入:进入末行模式 3、在命令模式下,键入文本编辑命令如i,a,o等进入文本输入模式 4、在文本输入模式下,按esc键进入命令模式 5、在末行模式下,按backspace键或del键进入命令模式 6、在末行模式下,键入q或wq,退出vi,饭后到Linux shell下 GCC编译器的使用 一、使用vi或其它文本编辑器,输入C语言程序,并保存为test.c 二、在Linux shell下,输入命令gcc –o test test.c 三、编译正确后,输入命令./test运行程序,观察程序运行结果 四、若编译错误,根据提示信息,进入程序查错,再回到第二步,直至程序 语法无误。 附:GCC使用方法和常用选项 使用GCC编译C程序生成可执行文件需要经历4个步骤: 1、预处理,这一步需要分析各种命令,如#define、#include、#ifdef 等。Gcc调用cpp程序来进行预处理 2、编译,这一步将根据输入文件产生汇编语言,gcc调用ccl进行编 译工作

gcc编译器 CFLAGS 标志参数说明

gcc编译器 CFLAGS 标志参数说明2012-11-14 15:10:28 分类:LINUX CFLAGS = -g -O2 -Wall -Werror -Wno-unused 编译出现警告性错误unused-but-set-variable,变量定义但没有使用,解决方法: 增加CFLAGS 或CPPFLAGS参数如下: CPPFLAGS=" -Werror -Wno-unused-but-set-variable" || exit 1 Gcc总体选项列表 后缀名所对应的语言 -S只是编译不汇编,生成汇编代码 -E只进行预编译,不做其他处理 -g在可执行程序中包含标准调试信息 -o file把输出文件输出到file里 -v打印出编译器内部编译各过程的命令行信息和编译器的版本 -I dir在头文件的搜索路径列表中添加dir目录 -L dir在库文件的搜索路径列表中添加dir目录 -static链接静态库 -llibrary连接名为library的库文件 ·“-I dir” 正如上表中所述,“-I dir”选项可以在头文件的搜索路径列表中添加dir目录。由于Linux 中头文件都默认放到了“/usr/include/”目录下,因此,当用户希望添加放置在其他位置的头文件时,就可以通过“-I dir”选项来指定,这样,Gcc就会到相应的位置查找对应的目录。 比如在“/root/workplace/Gcc”下有两个文件: #include int main() { printf(“Hello!!\n”); return 0; } #include

这样,就可在Gcc命令行中加入“-I”选项: [root@localhost Gcc] Gcc hello1.c –I /root/workplace/Gcc/ -o hello1 这样,Gcc就能够执行出正确结果。 小知识 在include语句中,“<>”表示在标准路径中搜索头文件,““”” 表示在本目录中搜索。故在上例中,可把hello1.c的“#include” 改为“#include “my.h””,就不需要加上“-I”选项了。 ·“-L dir” 选项“-L dir”的功能与“-I dir”类似,能够在库文件的搜索路径列表中添加dir目录。 例如有程序hello_sq.c需要用到目录“/root/workplace/Gcc/lib”下的一个动态库 libsunq.so,则只需键入如下命令即可: [root@localhost Gcc] Gcc hello_sq.c –L /root/workplace/Gcc/lib –lsunq –o hello_sq 需要注意的是,“-I dir”和“-L dir”都只是指定了路径,而没有指定文件,因此不能在 路径中包含文件名。 另外值得详细解释一下的是“-l”选项,它指示Gcc去连接库文件libsunq.so。由于在Linux 下的库文件命名时有一个规定:必须以lib三个字母开头。因此在用-l选项指定链接的库 文件名时可以省去lib三个字母。也就是说Gcc在对”-lsunq”进行处理时,会自动去链接 名为 libsunq.so的文件。 (2)告警和出错选项 Gcc的告警和出错选项如表3.8所示。 Gcc总体选项列表 选项含义 -ansi 支持符合ANSI标准的C程序 -pedantic 允许发出ANSI C标准所列的全部警告信息 -pedantic-error 允许发出ANSI C标准所列的全部错误信息 -w 关闭所有告警 -Wall 允许发出Gcc提供的所有有用的报警信息 -werror 把所有的告警信息转化为错误信息,并在告警发生时终止编译过程 下面结合实例对这几个告警和出错选项进行简单的讲解。 如有以下程序段: #include void main() { long long tmp = 1; printf(“This is a bad code!\n”);

gcc编译器使用说明

要想读懂本文,你需要对C语言有基本的了解,本文将介绍如何使用gcc编译器。首先,我们介绍如何在命令行方式下使用编译器编译简单的C源代码。然后,我们简要介绍一下编译器究竟作了那些工作,以及如何控制编译过程。我们也简要介绍了调试器的使用方法。 GCC rules 你能想象使用封闭源代码的私有编译器编译自由软件吗?你怎么知道编译器在你的可执行文件中加入了什么?可能会加入各种后门和木马。Ken Thompson是一个著名的黑客,他编写了一个编译器,当编译器编译自己时,就在'login'程序中留下后门和永久的木马。请到这里阅读他对这个杰作的描述。幸运的是,我们有了gcc。当你进行 configure; make; make install 时, gcc在幕后做了很多繁重的工作。如何才能让gcc为我们工作呢?我们将开始编写一个纸牌游戏,不过我们只是为了演示编译器的功能,所以尽可能地精简了代码。我们将从头开始一步一步地做,以便理解编译过程,了解为了制作可执行文件需要做些什么,按什么顺序做。我们将看看如何编译C程序,以及如何使用编译选项让gcc按照我们的要求工作。步骤(以及所用工具)如下:预编译 (gcc -E),编译 (gcc),汇编 (as),和连接 (ld)。 开始... 首先,我们应该知道如何调用编译器。实际上,这很简单。我们将从那个著名的第一个C程序开始。(各位老前辈,请原谅我)。 #include int main() { printf("Hello World!\n"); } 把这个文件保存为 game.c。你可以在命令行下编译它: gcc game.c 在默认情况下,C编译器将生成一个名为 a.out 的可执行文件。你可以键入如下命令运行它:a.out Hello World 每一次编译程序时,新的 a.out 将覆盖原来的程序。你无法知道是哪个程序创建了 a.out。

GCC编译器选项及优化提示12页word

GCC编译器选项及优化提示 GCC编译器选项及优化提示2010-08-01 19:41很多弟兄可能都很关心如何优化编译自己的程序,虽然本人不赞成"骨灰"玩法,却也不得不承认这是掌握gcc的绝佳途径; 因此献上此帖,以供各位玩家参考,绝对原创噢 大多数程序和库在编译时默认的优化级别是"2"(使用gcc选项:"-O2")并且在Intel/AMD平台上默认按照i386处理器来编译。 如果你只想让编译出来的程序运行在特定的平台上,就需要执行更高级的编译器优化选项,以产生只能运行于特定平台的代码。 一种方法是修改每个源码包中的Makefile文件,在其中寻找CFLAGS和CXXFLAGS变量(C和C++编译器的编译选项)并修改它的值。 一些源码包比如binutils,gcc,glibc等等,在每个子文件夹中都有Makefile文件,这样修改起来就太累了! 另一种简易做法是设置CFLAGS和CXXFLAGS环境变量。大多数configure脚本会使用这两个环境变量代替Makefile文件中的值。 但是少数configure脚本并不这样做,他们必须需要手动编辑才行。 为了设置CFLAGS和CXXFLAGS环境变量,你可以在bash中执行如下命令(也可以写进.bashrc以成为默认值): export CFLAGS="-O3-march="&&CXXFLAGS=$CFLAGS 这是一个确保能够在几乎所有平台上都能正常工作的最小设置。 "-march"选项表示为特定的cpu类型编译二进制代码(不能在更低级别的cpu上运行), Intel通常是: pentium2,pentium3,pentium3m,pentium4,pentium4m,pentium- m,prescott,nocona 说明:pentium3m/pentium4m是笔记本用的移动P3/P4;pentium-m 是迅驰I/II代笔记本的cpu; prescott是带SSE3的P4(以滚烫到可以煎鸡蛋而闻名);nocona则是最新的带有EMT64(64位)的P4(同样可以煎鸡蛋)

gcc编译详解

Gcc编译流程解析 如本章开头提到的,Gcc的编译流程分为了4个步骤,分别为: ? 预处理(Pre-Processing); ? 编译(Compiling); ? 汇编(Assembling); ? 链接(Linking)。 下面就具体来查看一下Gcc是如何完成4 个步骤的。 首先,有以下hello.c源代码: #include int main() { printf("Hello! This is our embedded world!\n"); return 0; } (1)预处理阶段 在该阶段,编译器将上述代码中的stdio.h编译进来,并且用户可以使用Gcc的选项“-E”进行查看,该选项的作用是让Gcc在预处理结束后停止编译过程。 注意 Gcc指令的一般格式为:Gcc [选项] 要编译的文件[选项] [目标文件] 其中,目标文件可缺省,Gcc默认生成可执行的文件,命为:编译文件.out 《嵌入式Linux应用程序开发详解》——第3章、Linux下的C编程基础 [root@localhost Gcc]# Gcc –E hello.c –o hello.i 在此处,选项“-o”是指目标文件,由表3.6 可知,“.i”文件为已经过预处理的C 原始程序。以下列出了hello.i文件的部分内容: typedef int (*__gconv_trans_fct) (struct __gconv_step *, struct __gconv_step_data *, void *, __const unsigned char *, __const unsigned char **, __const unsigned char *, unsigned char **, size_t *); … # 2 "hello.c" 2 int main() { printf("Hello! This is our embedded world!\n"); return 0; } 由此可见,Gcc确实进行了预处理,它把“stdio.h”的内容插入到hello.i文件中。 (2)编译阶段 接下来进行的是编译阶段,在这个阶段中,Gcc 首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,Gcc 把代码翻译成汇编语言。用户 可以使用“-S”选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码。

ARM-LINUX-GCC 编译选项介绍

ARM-LINUX-GCC 编译选项介绍 我们需要编译出运行在ARM平台上的代码,所使用的交叉编译器为 arm-linux-gcc。下面将arm-linux-gcc编译工具的一些常用命令参数介绍给大家。在此之前首先介绍下编译器的工作过程,在使用GCC编译程序时,编译过程分为四个阶段: 1)预处理(Pre-Processing) 2)编译(Compiling) 3)汇编(Assembling) 4)链接(Linking) Linux程序员可以根据自己的需要让 GCC在编译的任何阶段结束,以便检查或使用编译器在该阶段的输出信息,或者对最后生成的二进制文件进行控制,以便通过加入不同数量和种类的调试代码来为今后的调试做好准备。和其它常用的编译器一样,GCC也提供了灵活而强大的代码优化功能,利用它可以生成执行效率更高的代码。 以文件example.c为例说明它的用法 1.arm-linux-gcc -o example example.c 不加-c、-S、-E参数,编译器将执行预处理、编译、汇编、连接操作直接 生成可执行代码。 -o参数用于指定输出的文件,输出文件名为example,如果不指定输出文件,则默认输出a.out 2.arm-linux-gcc -c -o example.o example.c -c参数将对源程序example.c进行预处理、编译、汇编操作,生成example.0文件

去掉指定输出选项"-o example.o"自动输出为example.o,所以说在这里-o 加不加都可以 3.arm-linux-gcc -S -o example.s example.c -S参数将对源程序example.c进行预处理、编译,生成example.s文件 -o选项同上 4.arm-linux-gcc -E -o example.i example.c -E参数将对源程序example.c进行预处理,生成example.i文件(不同版本不一样,有的将预处理后的内容打印到屏幕上) 就是将#include,#define等进行文件插入及宏扩展等操作。 5.arm-linux-gcc -v -o example example.c 加上-v参数,显示编译时的详细信息,编译器的版本,编译过程等。 6.arm-linux-gcc -g -o example example.c -g选项,加入GDB能够使用的调试信息,使用GDB调试时比较方便。 7.arm-linux-gcc -Wall -o example example.c -Wall选项打开了所有需要注意的警告信息,像在声明之前就使用的函数,声明后却没有使用的变量等。 8.arm-linux-gcc -Ox -o example example.c -Ox使用优化选项,X的值为空、0、1、2、3 0为不优化,优化的目的是减少代码空间和提高执行效率等,但相应的编译过程时间将较长并占用较大的内存空间。

gcc调试总结

使用GCC编译与GDB调试 一:VMwareTools安装过程 1.sudo apt-get update 2.sudo apt-get dist-upgrade 3. 3.sudo apt-get install build-essential 4.sudo apt-get install linux-headers-3.0.0-29-generic-pae 5.Ubuntu与windows间的文件共享 (选择VM中设置,选项,共享文件夹,添加路径,如果不能在Ubuntu下的 /mnt/hgfs下找到共享文件夹还需安装 Sudo apt-get instatll open-vm-dkms Sudo mount -t vmhgfs .host:/ /mnt/hgfs 二:C 编程中相关文件后缀 .a 静态库(archive) .c C源代码(需要编译预处理) .h C源代码头文件 .i C源代码(不需编译预处理) .o 对象文件 .s 汇编语言代码 .so 动态库 三:Gcc的执行过程 gcc and g++分别是gnu的c & c++编译器gcc/g++在执行编译工作的时候,总共需要4步

1.预处理,生成.i的文件[预处理器cpp] 2.将预处理后的文件不转换成汇编语言,生成文件.s[编译器egcs] 3.有汇编变为目标代码(机器代码)生成.o的文件[汇编器as] 4.连接目标代码,生成可执行程序[链接器ld] 四. 常用编译命令选项 假设源程序文件名为test.c。 1.无选项编译链接 用法:#gcc test.c 作用:将test.c预处理、汇编、编译并链接形成可执行文件。这里未指定输出文件,默认输出为a.out。 2. 选项 -o 用法:#gcc test.c -o test 作用:将test.c预处理、汇编、编译并链接形成可执行文件test。 -o选项用来指定输出文件的文件名。 3. 选项 -E 用法:#gcc -E test.c -o test.i 作用:将test.c预处理输出test.i文件。 4. 选项 -S 用法:#gcc -S test.i 作用:将预处理输出文件test.i汇编成test.s文件。 5. 选项-c 用法:#gcc -c test.s 作用:将汇编输出文件test.s编译输出test.o文件。 6. 无选项链接 用法:#gcc test.o -o test 作用:将编译输出文件test.o链接成最终可执行文件test。 7. 选项-O 用法:#gcc -O1 test.c -o test 作用:使用编译优化级别1编译程序。级别为1~3,级别越大优化效果越好,但编译时间越长。 五. 多源文件的编译方法

目前最全的GCC+中文手册

GCC 中文手册 作者:徐明 -msvr4 -msvr3 打开(`-msvr4')或关闭(`-msvr3')和System V第四版(SVr4)相关的编译器扩展.效果如下: * 输出哪种汇编语法(你可以使用`-mversion-03.00'选项单独选择). * `-msvr4'使C预处理器识别`#pragma weak'指令 * `-msvr4'使GCC输出额外的声明指令(declaration directive),用于SVr4. 除了SVr4配置, `-msvr3'是所有m88K配置的默认选项. -mtrap-large-shift -mhandle-large-shift 包含一些指令,用于检测大于31位的位移(bit-shift);根据相应的选项,对这样的位移发出自陷 (trap)或执行适当的处理代码.默认情况下, GCC对大位移不做特别处理. -muse-div-instruction 很早以前的88K型号没有(div)除法指令,因此默认情况下GCC避免产生这条指令.而这个选项告诉GCC该指令是安全的. -mversion-03.00 在DG/UX配置中存在两种风格的SVr4.这个选项修改-msvr4 ,选择hybrid-COFF或 real-ELF风格.其他配置均忽略该选项. -mwarn-passed-structs 如果某个函数把结构当做参数或结果传递, GCC发出警告.随着C语言的发展,人们已经改变了传递结构的约定, 它往往导致移植问题.默认情况下, GCC不会发出警告. 下面的选项用于IBM RS6000: -mfp-in-toc -mno-fp-in-toc 控制是否把浮点常量放到内容表(TOC)中,内容表存放所有的全局变量和函数地址.默认情况下, GCC把浮点常量放到这里;如果TOC溢出, `-mno-fp-in-toc'选项能够减少TOC的大小,这样就可以避免溢出. 下面的`-m'选项用于IBM RT PC: -min-line-mul 对于整数乘法使用嵌入代码.这是默认选项. -mcall-lib-mul 对于整数乘法使用lmul$$ . -mfull-fp-blocks

对gcc编译汇编码解析

对gcc编译汇编码解析 本文通过对由gcc对简单C语言代码编译生成的汇编码进行逐句分析解读,来学习x86的汇编结构和堆栈机制。文章涉及细节较多,难免出错,望读者不吝赐教! 一、代码 C语言代码: /* file: hello.c */ 1 #include <stdio.h> 2 3 int add(int a, int b){ 4 return (a+b); 5 } 6 7 int main(int argc, char **argv){ 8 int a, b, c; 9 a = 3; 10 b = 4; 11 c = add(a, b); 12 printf("a+b=%d\n", c); 13 printf("Hello World!\n");

14 return 0; 15 } 16 gcc -S -ohello.s hello.c输出文件:/* file: hello.s */ 1 .file "hello.c" 2 .text 3 .globl add 4 .type add, @function 5 add: 6 pushl %ebp 7 movl %esp, %ebp 8 movl 12(%ebp), %edx 9 movl 8(%ebp), %eax 10 addl %edx, %eax 11 popl %ebp 12 ret 13 .size add, .-add 14 .section .rodata 15 .LC0: 16 .string "a+b=%d\n" 17 .LC1: 18 .string "Hello World!"

GCC -o 优化选项说明

GCC编译器优化选项分析及具体优化了什么收藏 起因: 目前项目使用nios IDE作为开发平台,其使用的编译器为gcc的交叉编译器。在设定编译条件时,在debug模式下生成的程序正常,但是在release模式下会出现LCD显示的开端显示不全,缺少一个字节或字的状况。为了了解具体为什么造成该问题,对两种模式下的配置做了对比,编译器皆为nios2-elf-gcc交叉编译器,debug模式编译器参数为:-DALT_DEBUG -O0 -g –Wall。release模式编译器参数为: -DALT_RELEASE -O2 -g –Wall。 两种模式下的参数简单说明如下 -DALT_DEBUG:目前没有明确资料显示该项的具体作用,根据命名可认为与调试有关选项。且两种模式下都有,暂时认为不会造成差异。 -O0: gcc编译器默认优化等级。 -g:gdb调试器支持选项用于在编译时生成相关调试信息。 -Wall:打开所有编译器告警选项,即编译器最严格告警模式。 -O2:gcc编译高于O0低于O3的编译优化选项。 通过对比可以发现两种模式主要的不同在于编译器优化程度不同,那么编译器在两种优化下究竟做了什么优化那?是否由这些问题造成的显示丢失问题那??现在我们来看看gcc编译器的优化参数到底做了什么优化。(注:由于关于nios2-elf-gcc的文档资料十分稀少,不能形成可分析的文档,所以以通用的gcc作为分析,毕竟同出一源) 正文: GCC编译器优化选项介绍: GCC编译器在目前是不是用最多的编译器也相去不远,尤其在嵌入式领域很多编译器都是基于GCC的cross gcc版本。毕竟功能成熟而且有开放的源代码。 这里只介绍优化编译的参数 -O用来开启优化编译选项。 -O0:默认模式,不做任何优化。 -O1:优化。该模式下对于一个大的函数或功能会花费更多的时间和内存。

gcc编译及调试

前言 本文译自《Slackware Linux Unleashed》(第三版) 一书的第27章: Programming in C. 关于本译文有任何的话请与我联系: mailto:. Linux的发行版中包含了很多软件开发工具. 它们中的很多是用于 C 和 C++应用程序开发的. 本文介绍了在 Linux 下能用于 C 应用程序开发和调试的工具. 本文的 主旨是介绍如何在 Linux 下使用 C 编译器和其他 C 编程工具, 而非 C 语言编程 的教程. 在本文中你将学到以下知识: 什么是 C GNU C 编译器 用 gdb 来调试GCC应用程序 你也能看到随 Linux 发行的其他有用的 C 编程工具. 这些工具包括源程序美 化程序(pretty print programs), 附加的调试工具, 函数原型自动生成工具(automatic function prototypers). ------------------------------------------------------------------------ -------- 注意: 源程序美化程序(pretty print programs)自动帮你格式化源代码产生始终 如一的缩进格式. ------------------------------------------------------------------------ -------- 什么是 C? C 是一种在 UNIX 操作系统的早期就被广泛使用的通用编程语言. 它最早是由贝尔实验室的 Dennis Ritchie 为了 UNIX 的辅助开发而写的, 开始时 UNIX 是用 汇编语言和一种叫 B 的语言编写的. 从那时候起, C 就成为世界上使用最广泛计 算机语言. C 能在编程领域里得到如此广泛支持的原因有以下一些: 它是一种非常通用的语言. 几乎你所能想到的任何一种计算机上都有至少一种能用的 C 编译器. 并且它的语法和函数库在不同的平台上都是统一的, 这个特性对开 发者来说很有吸引力. 用 C 写的程序执行速度很快. C 是所有版本的UNIX上的系统语言. C 在过去的二十年中有了很大的发展. 在80年代末期美国国家标准协会(American National Standards Institute)发布了一个被称为 ANSI C 的 C 语言 标准.这更加保证了将来在不同平台上的 C 的一致性. 在80年代还出现了一种 C 的面向对象的扩展称为 C++. C++ 将在另一篇文章 "C++ 编程"中描述. Linux 上可用的 C 编译器是 GNU C 编译器, 它建立在自由软件基金会的编程 许可证的基础上, 因此可以自由发布. 你能在 Linux 的发行光盘上找到它.

相关主题
文本预览
相关文档 最新文档