gcc gdb 1.18

 GCC 工作流程

C 程序的编译过程中,依次要进行预处理、编译、汇编、链接四个阶段。这里通过编译 C 文件 test.c 来展示 GCC 的工作流程。

例如:  test.c

#include <stdio.h>

int main()

{

printf(“Hello world!\n”);

return 0;

}

1. 预处理阶段

由于在 test.c 中使用了头文件 stdio.h,所以 GCC 在编译时首先要把头文件 stdio.h中的内容加载到 test.c 中的首部。在 shell 中输入命令“gcc -E test.c -o test.i”。其中,参数 E 告诉 gcc 命令只进行预编译,不做其他处理;参数 o 用来指明输出的文件名为 test.i。命令运行完毕后就会产生一个名为 test.i 的文件。如下所示:

[root@localhost home]#gcc -E test.c -o test.i

[root@localhost home]#ls

test.c test.i

test.i 文件的代码有一百多行,如下所示的是 test.i 文件的最后部分代码。

extern char *ctermid (char *__s) __attribute__ ((__nothrow__));

# 820 "/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__));

# 850 "/usr/include/stdio.h" 3 4

# 2 "test.c" 2

int main()

{

printf(“Hello world!\n”);

return 0;

}

2. 编译阶段

编译阶段是整个编译过程中最复杂的一个阶段。这里拿自然语言的翻译过程作个对比比如在把“I love China”翻译成中文前,需要依次完成以下几个步骤:

1)考察这个句子中每个单词的拼写是不是正确。

2)考察整个句子的语法(比如主谓宾、定状补的结构等)是不是正确。

3)考察整个句子的语义是不是正确。

只有以上三个步骤都正常通过了,才能保证句子被正确翻译。同样,高级编程语言的译阶段也必须实现这三个步骤。

1) 步骤 1 称为词法分析,主要负责检查关键字、标识符等是否正确。

2) 步骤 2 称为语法分析,主要负责检查程序中语句的语法是否正确。

3) 步骤 3 称为语义分析,主要负责检查程序中语句的逻辑意义是否正确。

shell 中输入命令“gcc -S test.i -o test.s”。其中,参数 S 告诉 gcc 命令只进行编译,不做其他处理。命令运行完毕后就会产生一个名为 test.s 的汇编文件。如下所示:

[root@localhost home]#gcc -S test.i -o test.s

[root@localhost home]#ls

test.c test.i test.s

在学习使用汇编语言编程的时候,对照 C 文件和其汇编程序是很好的办法。如下所示的是 test.s 的代码。

.file  "test.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.2.1"

.section .note.GNU-stack,"",@progbits

注意,以上所示的汇编代码是针对 x86 平台的。

3. 汇编阶段

汇编阶段的任务是把汇编程序翻译成 CPU 可以识别的二进制文件,该文件又称为目标文件。在 shell 中输入命令“gcc -c test.s -o test.o”,其中,参数 c 告诉 gcc 命令只进行汇编,不做其他处理。命令运行完毕后就会产生一个名为 test.o 的目标文件。如下所示:

[root@localhost home]#gcc -c test.s -o test.o

[root@localhost home]#ls

test.c test.i test.o test.s

Windows 系统中,目标文件的后缀是 obj

4. 链接阶段

目标文件虽然已经可以被 CPU 直接识别,但是单个目标文件一般是无法运行的。原因

在于一个程序往往是由多个源文件组成的,每个源文件只对应一个目标文件。也许有人会问,test 程序不就只有一个源文件 test.c 吗,为什么也不能直接运行呢?原因是 test.c 使用了 stdio.h 对应的库函数,所以必须要把 test.o 文件和函数库文件链接在一起才能运行。链接阶段的任务就是把程序中所有的目标文件和所需的库文件都链接在一起,最终生

成一个可以直接运行的文件,称为可执行文件。 shell 中输入命令gcc test.o -o test,运行完毕后就会产生一个名为 test 的可执行文件。输入命令“./test”执行该文件,就可以得到 test 文件的运行结果“Helloworld!”

如下所示:

[root@localhost home]#gcc test.o -o test

[root@localhost home]#./test

Hello world!

gcc 命令生成的可执行文件的有以下三种格式。

1a.outAssembler and Link editor output);

2COFFCommon object file format);

3ELFExecutable and linkable format);

其中,a.out COFF 格式都是比较老的格式,现在 Linux 平台上可执行文件的主流格

式是 ELF

GDB  调试器

程序的调试工作在整个程序的开发过程中占据了相当大的比例。使用 gcc 调试 C 程序时,只能依靠 gcc 发出的警告或错误信息来进行,所以调试的效率非常低。为此,GNU 开发了 GDB 调试器(GNU Debugger)。GDB 的调试功能非常强大,甚至可以和 Visual C++Visual BasicJbuilder 等开发工具的调试器相媲美。但 GDB 的缺点是没有图形调试界面。尽管如此,对于从事嵌入式 Linux 应用开发的人员还是有必要知道 GDB的使用方法的。

例如:test.c

#include <stdio.h>

int cal(int n)

{

if(n == 1)

return 1;

else

return n * cal(n - 1);

}

int main()

{

int n = 5;

n = cal(n);

printf(“%d”,n);

return 0;

}

test.c文件是一个通过递归调用来计算5的阶乘的程序。通过运行命令gcc –g test.c-o test” test.c 进行编译,其中参数 g 的作用是把调试信息加入生成的 test 可执行文件中,否则 GDB 就无法对 test 进行调试。接下来可以使用命令“gdb test”启动 GDB test 进行调试了。

如下所示:

[root@localhost home]#  gdb test

GNU gdb Everest Linux (6.4-1)

Copyright 2005 Free Software Foundation, Inc.

GDB is free software, covered by the GNU General Public License, and you are

welcome to change it and/or distribute copies of it under certain conditions.

Type "show copying" to see the conditions.

There is absolutely no warranty for GDB. Type "show warranty" for details.

This GDB was configured as "i686-pc-linux-gnu"...Using host libthread_db

library "/lib/libthread_db.so.1".

可以看到,GDB 首先显示了版本信息和库信息。随后 GDB 停留在符号gdb处等待用户输入调试命令。GDB 提供了大量的命令来实现各种调试功能,下面仅对一些常用的命令进行介绍。

1)查看源文件

在调试程序时,gcc 会给出产生警告或错误的代码行数。但在普通的文本环境中是无直接获得语句行数的。在 GDB 中通过命令 llist 的缩写)可以查看所有的代码行数。如下所示:

(gdb)  l

2 int cal(int n)

3 {

4 if(n == 1)

5 return 1;

6 else

7 return n * cal(n - 1);

8 }

9

10 int main()

11 {

(gdb) l

12 int n = 5;

13 n = cal(n);

14 printf("%/d",n);

15 return 0;

16 }

(gdb) l

Line number 17 out of range; test.c has 16 lines.

可以看到,GDB 10 行为单位进行显示。再运行一次命令 l 就会显示下 10 行代码。这样设计方便了源代码的阅读。

2)设置断点

断点是调试程序的重要方法,通过断点可以知道程序每一步的执行状况(比如当前变量的值、函数是否调用、堆栈使用情况等)。在 GDB 中通过命令 bbreakpoint 的缩写)进行断点设置。如下所示:

(gdb) b 7

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

可以看到,命令 b 在程序的第 7 行处设置了第一个断点,并显示了该断点在内存中的物理地址。

3)查看断点情况

由于使用命令 b 可以设置多个断点,所以用户需要能够随时查看各个断点的情况。在GDB 中通过命令“info b”查看所有的断点情况。如下所示:

(gdb) info b

Num Type Disp Enb Address What

1 breakpoint keep y 0x08048389 in cal at test.c:7

可以看到,GDB 在程序的第 7 行处设置了第一个断点,并显示了断点的位置信息。

4)运行程序

GDB 中通过命令 rrun 的缩写)运行程序。GDB 默认从代码的首行开始运行(也可以通过“r 行数的方式让程序从指定行数开始运行)。如果程序中有断点,则程序会在断点行数的前一行暂停运行,结果如下所示:

(gdb) r

Starting program: /home/test

Breakpoint 1, cal (n=5) at test.c:7

7 return n * cal(n - 1);

可以看到,程序在运行到第 7 行时就暂停了,没有继续执行第 8 行的代码。

5)查看变量值

程序暂停运行后就可以查看当前的状态了。在 GDB 中通过命令“p 变量名print

缩写)查看当前变量 n 值。如下所示:

(gdb) p n

$1 = 5

GDB 通过“$N”“$1”“$2”)来显示变量的值。这样在下次查看变量值时,就可以用“$N”代替变量名了。可以看到,当前变量 n 的值为 5

6)继续运行程序

查看完当前程序的情况后,就可以让程序继续往下运行了。在 GDB 中通过命令 c 让程序继续往下运行。在 test.c 中,由于函数 cal 是递归调用运行,所以程序会再次在断点处暂停。如下所示:

(gdb) c

Continuing.

Breakpoint 1, cal (n=4) at test.c:7

7 return n * cal(n - 1);

程序暂停后可以再次查看当前变量 n 的值。如下所示:

(gdb) p n

$2 = 4

7)单步运行

在程序逻辑比较复杂的时候往往需要程序能一步一步的往下运行,但如果每行都设置一个断点的话又会很麻烦。在 GDB 中可以通过命令 sstep 的缩写)和 nnext 的缩写)让程序一步一步的往下运行。其中 s 可以在发生函数调用时进入函数内部运行,而 n 不会进入函数内部运行。在 test.c 中。由于函数 cal 是递归调用运行,所以只能选择 s 才能看到变量n 的值。如下所示:

(gdb) s

cal (n=3) at test.c:4

4 if(n == 1)

(gdb) s

Breakpoint 1, cal (n=3) at test.c:7

7 return n * cal(n - 1);

(gdb) s

cal (n=2) at test.c:4

4 if(n == 1)

(gdb) s

104

Breakpoint 1, cal (n=2) at test.c:7

7 return n * cal(n - 1);

(gdb) s

cal (n=1) at test.c:4

4 if(n == 1)

(gdb) s

5 return 1;

由于在使用 s 前函数 cal 已经调用了两次,所以运行 s 后当前变量 n 的值为 3。可以看到,函数 cal 进行 3 次调用后返回了值 1

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值