GCC/G++编译器以及GDB调试

3.1  GCC/G++编译器以及GDB调试

3.1.1GCC/G++编译器

开发者通常选择GCC来编译C语言编写的源代码,选择G++来编译C++源代码。

GCC/G++编译器没有图形界面,只能在终端上以命令方式运行。编译命令由命令名、选项和源文件名组成,格式如下所示:

gcc [-选项 1] [-选项 2]…[-选项 n] <源文件名>

g++ [-选项 1] [-选项 2]…[-选项 n] <源文件名>

命令名、选项和源文件名之间使用空格分隔,一行命令中可以有多个选项,也可以只有一个选项。文件名可以包含文件的绝对路径,也可以使用相对路径。如果文件名中不包含路径,那么源文件被视为存在于工作目录中。如果命令中不包含输出的可执行文件名称,默认情况下将在工作目录中生成后缀为“.out”的可执行文件。

 

常用的GCC和G++编译选项见表所示:

GCC/G++编译器以及GDB调试_第1张图片

 

GCC和G++编译器执行过程可总结为四步:预处理、编译、汇编、连接。

GCC/G++编译器以及GDB调试_第2张图片

1.预处理,生成.i的文件[预处理器cpp]

2.将预处理后的文件转换成汇编语言,生成文件.s[编译器egcs]

3.由汇编变为目标代码(机器代码)生成.o的文件[汇编器as]

4.连接目标代码,生成可执行程序[链接器ld]


以前我们常用gcc main.c -o main命令编译一个程序,其实也可以分三步做,第一步生成汇编代码,第二步生成目标文件,第三步生成可执行文件:

$gcc -S main.c$ gcc -c main.s$ gcc main.o

-S选项生成汇编代码,-c选项生成目标文件, -E选项只做预处理而不编译,如果不加这些选项则gcc执行完整的编译步骤,直到最后链接生成可执行文件为止。如下图所示。

GCC/G++编译器以及GDB调试_第3张图片

这些选项都可以和-o搭配使用,给输出的文件重新命名而不使用gcc默认的文件名(xxx.c、xxx.s、xxx.o和a.out),例如gcc main.o -o main将main.o链接成可执行文件main。

-v可以了解详细的编译过程。

一个好的习惯是打开gcc-Wall选项,也就是让gcc提示所有的警告信息,不管是严重的还是不严重的,然后把这些问题从代码中全部消灭。

 

gcc main.c(编译)

eg: gcc a.c b.c

gcc –E main.c –o main.i (生成预处理后的文件)

gcc -S main.c -o main.s(生成汇编指令,catmain.s可查看)

gcc main.c -o main(生成可执行文件main ./main执行)

也可编译后直接./a.out 默认生成这个可执行文件(因为当前的工作目录已经是“/home/用户名/mainworld”了,所以可用“./”替代工作目录的路径)

gcc main.s

gcc -c main.c –o main.o生成.o 文件.o 结尾为机器语言

gcc main.c –o main –DMY_DEBUG  加入调试信息,加入后,会多出Enter 和 Leave

如下程序

#include

#define MY_DEBUG 1         //gcc中不加-D的话这里define一下

int main(int argc,char *argv[])

{

#if MY_DEBUG

       printf("Enter %s\n",__func__);

#endif

       int i;

       printf("hello\n");

#if 0

       printf("world\n");

       //for (i=0;i

       //      printf("%s\n",argv[i]);

#endif

 

#if MY_DEBUG

       printf("Leave %s\n",__func__);

#endif

       return 0;

}

 

gcc -E x.c -o x.i

cat x.i

-E选项,表示让gcc只进行“预处理”就行了。 所谓的预处理,就是把程序中的宏展开, 把头文件的内容展开包含进来等等一些编译前的预处理操作。

 

预处理

cpp x.c(文件名) -o x.i

 

 

如果目标文件是由C代码编译生成的,用gcc做链接就没错了(汇编不能用gcc来编译),整个程序的入口点是crt1.o中提供的_start,它首先做一些初始化工作(以下称为启动例程,StartupRoutine),然后调用C代码中提供的main函数。所以,以前我们说main函数是程序的入口点其实不准确,_start才是真正的入口点,而main函数是被_start调用的。

 

 

如果分两步编译,第二步gcc main.o –o main其实是调用ld做链接的,相当于这样的命令:

$ ld/usr/lib/crt1.o /usr/lib/crti.o main.o -o main -lc -dynamic-linker/lib/ld-linux.so.2

 

 

gcc的编译优化选项有-O0、-O、-O1、-O2、-O3、-Os几种。-O0表示不优化,这是缺省的选项。-O1、-O2和-O3这几个选项一个比一个优化得更多,编译时间也更长。-O和-O1相同。-Os表示为缩小目标代码尺寸而优化。

 

使用math.h中的函数还有一点特殊之处,gcc命令行必须加-lm选项,因为数学函数位于libm.so库文件中(通常在/lib目录下),-lm选项告诉编译器,我们程序中用到的数学函数要到这个库文件里找。本书用到的大部分库函数(例如printf)位于libc.so库文件中,以后称为libc,使用libc中的库函数在编译时不需要加-lc选项,当然加了也不算错,因为这个选项是gcc默认的。

 

3.1.2 GDB调试器

gdb

调试工具

GDB调试器调试的对象是可执行文件,使用GCC或G++编译器编译源代码时,必须加上选项“-g”才能使目标可执行文件包含可被调试的信息。

GDB常用调试命令
GCC/G++编译器以及GDB调试_第4张图片
GCC/G++编译器以及GDB调试_第5张图片 GCC/G++编译器以及GDB调试_第6张图片

b 12 if i == 4 断点可以设置条件 此句表示i等于4中断


GCC/G++编译器以及GDB调试_第7张图片

x/7b input

x命令打印存储器中的内容。7b是打印格式,b表示每个字节一组,7表示打印7组

 

其中像 break(b) list(s) run(r) print(p) quit(q)next(n) step(s) continue(c)等都可用简写

eg:调试hello可执行文件

gcc hello.c -o hello –g

(gdb) hello

(gdb) l (list显示源程序)

(gdb) l

 

(gdb)b 6 在某一行设置断点Breakpoint ..  b main   main开始

(gdb)r 运行,设置断点后

(gdb)p打印检查常量c的值可输入下列命令:(gdb) print c// 显示变量c的值

(gdb)n下一句运行

(gdb)s进到函数里面运行(没函数和n一样)

(gdb)q退出

 

bt(baketrace)

frame 1 (选择1号栈帧)

set var sum = 50 (修改某变量的值)

finish 执行到函数返回,观察返回值(return value)

 

如果某个函数中发生访问越界,很可能并不立即产生段错误,而在函数返回时却产生段错误.

 

调试方法:

DEBUG调试快速定位,gdb调试逐条细化调试。

在头文件中定义MY_DEBUG即可

#define MY_DEBUG

可在代码中加入下列语句

#ifdef MY_DEBUG

printf("enterinto func %s\n",__func__);

#endif

 

#ifdef MY_DEBUG

printf("leavefunc %s\n",__func__);

#endif

 

几个新的gdb命令:

disassemble可以反汇编当前函数或者指定的函数,单独用disassemble命令是反汇编当前函数,如果disassemble命令后面跟函数名或地址则反汇编指定的函数。

以前我们讲过step命令可以一行代码一行代码地单步调试,而这里用到的si命令可以一条指令一条指令地单步调试。

info registers可以显示所有寄存器的当前值。

在gdb中表示寄存器名时前面要加个$,例如p $esp可以打印esp寄存器的值,在上例中esp寄存器的值是0xbff1c3f4,所以x/20 $esp命令查看内存中从0xbff1c3f4地址开始的20个32位数。


你可能感兴趣的:(个人笔记,linux,gdb,gcc)