嵌入式&桌面 Linux 下的 GCC与GDB 应用

栏目: C · 发布时间: 5年前

内容简介:GNU 编译器套件(Ubuntu、Mint 等使用 deb 格式软件包的 Linux 发行版通常会默认安装 GCC 编译器,但是由于相关的软件包可能并不完整,因此可以通过如下命令安装完整的 GCC 编译环境。由于当前需要编译的是 C 语言程序,因此需要使用到

GNU 编译套件

GNU 编译器套件( GCCGNU Compiler Collection )最初的目标是作为一款 GNU 操作系统的通用编译器,包含有 C、C++、Objective-C、Objective-C++、Fortran、Ada、 Go 、BRIG(HSAIL)等语言的前端及其相关的 libstdc++libgcj 等库,目前已经移植到 Windows、Mac OS X 等商业化操作系统。GCC 编译器套件当中包含了诸多的软件包,主要的软件包如下面表格所示:

名称 描述
cpp C 预处理器。
gcc C 编译器。
g++ C++ 编译器。
gccbug 用于创建 BUG 报告的 Shell 脚本。
gcov 覆盖测试工具,用于分析程序需要优化的位置。
libgcc GCC 运行库。
libstdc++ 标准 C++库。
libsupc++ C++语言支持函数库。

Ubuntu、Mint 等使用 deb 格式软件包的 Linux 发行版通常会默认安装 GCC 编译器,但是由于相关的软件包可能并不完整,因此可以通过如下命令安装完整的 GCC 编译环境。

sudo apt-get install build-essential

基本使用

由于当前需要编译的是 C 语言程序,因此需要使用到 gcc 软件包提供的命令,这些命令的基本使用格式如下:

gcc [-options] [filename]

将上一节编写的 Hello World 程序保存至一个 main.c 源代码文件当中,然后执行 gcc 编译命令得到可执行的 a.out 文件:

➜  gcc main.c
➜  ls
a.out  main.c

➜  ./a.out
hello world!

如果需要指定输出的可执行文件名称,那么可以添加 -o 选项:

➜  gcc main.c -o main
➜  ls
main  main.c

➜  ./main
hello world!

编译信息

如果需要查看编译的过程,那么可以使用 -v 命令选项:

➜  gcc -v main.c
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 7.3.0-27ubuntu1~18.04' --with-bugurl=file:///usr/share/doc/gcc-7/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++ --prefix=/usr --with-gcc-major-version-only --program-suffix=-7 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 7.3.0 (Ubuntu 7.3.0-27ubuntu1~18.04)
COLLECT_GCC_OPTIONS='-v' '-mtune=generic' '-march=x86-64'
 /usr/lib/gcc/x86_64-linux-gnu/7/cc1 -quiet -v -imultiarch x86_64-linux-gnu main.c -quiet -dumpbase main.c -mtune=generic -march=x86-64 -auxbase main -version -fstack-protector-strong -Wformat -Wformat-security -o /tmp/cc9yluga.s
GNU C11 (Ubuntu 7.3.0-27ubuntu1~18.04) version 7.3.0 (x86_64-linux-gnu)
	compiled by GNU C version 7.3.0, GMP version 6.1.2, MPFR version 4.0.1, MPC version 1.1.0, isl version isl-0.19-GMP
... ...

优化选项

GCC 编译优化等级由低到高分为 -O0-O1-O2-O3o 即单词 Optimization 的首字母,不同优化等级下得到的文件体积与执行效率各不相同。此外,嵌入式开发当中还经常使用到一个 -Os ,其优化等级介于 -O2-O3 之间。

➜  gcc -os main.c -o main
➜  ll
总用量 16K
-rwxrwxr-x 1 hank hank 8.2K 2月  24 17:51 main
-rw-rw-r-- 1 hank hank  136 2月  21 18:12 main.c

调试信息

开启优化选项后编译的代码,并不会保留任何关于调试与 debug 的信息,如果需要保留这些信息,可以开启 -g 选项( gdb ),此时得到的文件体积会增大。

➜  gcc main.c
➜  ll
-rwxrwxr-x 1 hank hank 8.2K 2月  24 17:58 a.out

➜  gcc -g main.c
➜  ll
-rwxrwxr-x 1 hank hank 11K 2月  24 17:58 a.out

包含头文件

Linux C 语言程序当中存在如下两种头文件的包含情况:

  • #include <head.h> :预处理程序会在 编译系统指定的目录 当中去搜索头文件。
  • #include "head.h" :预处理器会在当前 目标文件所在的文件夹内 搜索头文件,如果未找到则进入编译系统指定目录搜索。

GCC 当中可以通过 -I 参数( include )将指定目录添加到头文件的搜索列表当中:

➜  gcc -v main.c -I /workspace
#include "..." search starts here:
#include <...> search starts here:
 /workspace
 /usr/lib/gcc/x86_64-linux-gnu/7/include
 /usr/local/include
 /usr/lib/gcc/x86_64-linux-gnu/7/include-fixed
 /usr/include/x86_64-linux-gnu
 /usr/include

编译步骤

实质上从 hello.c 源代码到 helloa.out 可执行文件,GCC 的编译过程大致经历了下面 4 个步骤:

嵌入式&桌面 Linux 下的 GCC与GDB 应用

  • 预处理 :C 编译器对各种预处理命令进行处理,包括头文件包含、宏定义的扩展、条件编译的选择等( 使用 gcc -E );
➜  gcc -E main.c -o main.i
➜  ls
main.c  main.i
  • 编译 :对预处理得到的源代码文件进行翻译转换,产生由机器语言描述的 汇编文件 使用 gcc -S );
➜  gcc -S main.i
➜  ls
main.c  main.i  main.s
  • 汇编 :将汇编代码转译成为 机器码 使用 gcc -c );
➜  gcc -c main.s
➜  ls
main.c  main.i  main.s  main.o
  • 链接 :将机器码中的各种符号引用与定义转换为 可执行文件 中的相应信息(如虚拟地址)。
➜  gcc main.o -o main
➜  ls
main.c  main.i  main.o  main.s  main

为了便于查找,下表列出了编译和链接 C/C++ 程序时各类文件扩展名的释义:

后缀名称 描述内容
.c C 语言源码,必须经过预处理。
.C.cc.cxx C++源代码,必须经过预处理。
.h C/C++语言源代码的头文件。
.i .c 文件预处理后生成。
.ii .C.cc.cxx 源码预处理后生成。
.s 汇编语言文件,是 .i 文件编译后得到的中间文件。
.o 目标文件,是编译过程得到的中间文件。
.a 由目标文件构成的文件库,也称为 静态库
.so 共享对象库,也称为 动态库

链接库文件

GCC 对于库文件的链接存在 动态静态 两种方式:

  • 静态链接方式 :使用 静态链接库 进行链接,由于包含了程序运行所需的所有库,因此生成的文件体积较大但是能够直接运行。
  • 动态链接方式 :使用 动态链接库 进行链接,类似于 Windows 系统下的 .dll 文件,执行时需要依赖相应的动态链接库。

GCC 默认使用动态链接 -shared 方式,可以通过加入 -static 参数来指定使用静态链接方式。注意观察下面以不同方式编译代码后,所得到的可执行文件的体积:

➜  gcc main.c
➜  ll
总用量 16K
-rwxrwxr-x 1 hank hank 8.2K 2月  22 18:25 a.out
-rw-rw-r-- 1 hank hank  136 2月  21 18:12 main.c

➜  gcc main.c -static
➜  ll
总用量 832K
-rwxrwxr-x 1 hank hank 825K 2月  22 18:25 a.out
-rw-rw-r-- 1 hank hank  136 2月  21 18:12 main.c

创建静态链接库

静态链接库是由 GCC 在汇编阶段产生的 .o 文件构成的集合,以 .a 作为文档后缀名称,Linux 下也称存档( archive ),通常使用 ar 工具命令来进行打包管理。

➜  ls
func1.c  func2.c  main.c

➜  gcc -c func1.c func2.c
➜  ls
func1.c  func1.o  func2.c  func2.o  main.c

➜  ar -r libmain.a func1.o func2.o
ar: 正在创建 libmain.a
➜  ll
总用量 36K
-rw-rw-r-- 1 hank hank   84 2月  24 20:06 func1.c
-rw-rw-r-- 1 hank hank 1.6K 2月  24 20:16 func1.o
-rw-rw-r-- 1 hank hank   84 2月  24 20:16 func2.c
-rw-rw-r-- 1 hank hank 1.6K 2月  24 20:16 func2.o
-rw-rw-r-- 1 hank hank 3.3K 2月  24 20:16 libmain.a
-rw-rw-r-- 1 hank hank  136 2月  21 18:12 main.c

创建动态链接库

动态链接库也称为共享对象( shared object ),通常以 .so 作为文件后缀名,由 GCC 编译器通过添加 -fpic 参数( pic 指位置独立代码,即 Position Independent Code 缩写 )方式生成,共享对象模块的每个地址( 函数调用和变量引用 )都是相对地址,允许程序在执行时动态的加载与运行。

➜  ls
func1.c  func2.c  main.c

➜  gcc -c -fpic func1.c func2.c
➜  ls
func1.c  func1.o  func2.c  func2.o  main.c

➜  gcc -shared func1.o func2.o -o libmain.so
➜  ll
总用量 28K
-rw-rw-r-- 1 hank hank   84 2月  24 20:06 func1.c
-rw-rw-r-- 1 hank hank 1.6K 2月  24 21:31 func1.o
-rw-rw-r-- 1 hank hank   84 2月  24 20:16 func2.c
-rw-rw-r-- 1 hank hank 1.6K 2月  24 21:31 func2.o
-rwxrwxr-x 1 hank hank 7.8K 2月  24 21:32 libmain.so
-rw-rw-r-- 1 hank hank  136 2月  21 18:12 main.c

上面的步骤比较繁琐,可以将 汇编链接 两条命令合并为一条命令,编译 C 语言代码的同时得到 .so 动态链接库文件。

➜  ls
func1.c  func2.c  main.c
➜  gcc -fpic -shared func1.c func2.c -o libmain.so
➜  ls
func1.c  func2.c  main.c  libmain.so

指定编译规范

由于 GCC 同时支持多套 C 程序语言规范,因而编译时可以通过选项指定当前需要遵循的语言规范,具体请参考下表:

规范 规范 选项 补充
C89 / C90 ANSI C (X3.159-1989) 或 ISO/IEC 9899:1990 -std=c90 -std=iso9899:1990-ansi
C94 / C95 95 年发布的 C89/C90 修正版,此次修正通常称作 AMD1 - -std=iso9899:199409
C99 ISO/IEC 9899:1999 -std=c99 -std=iso9899:1999
C11 ISO/IEC 9899:2011 -std=c11 -std=iso9899:2011
GNU C89 / C90 带 GNU 扩展的 C89/C90 -std=gnu90 -
GNU C99 带 GNU 扩展的 C99 -std=gnu99 -
GNU C11 带 GNU 扩展的 C11 -std=gnu11 -

例如下面代码当中,指定了 GCC 的编译过程遵循 C89/C90 规范,结果编译时提示错误信息: C++ style comments are not allowed in ISO C90

➜  gcc main.c -std=c90

main.c: In function ‘main’:
main.c:7:36: error: C++ style comments are not allowed in ISO C90
     printf("hello world!\n");      // 行注释
                                    ^
main.c:7:36: error: (this will be reported only once per input file)

缺省情况下,GCC 默认使用的是 -std=gnu11 规范,即带携带 GNU 扩展的 C11 标准。

GNU 项目调试器

GNU 项目调试器( GDBGNU Project Debugger )是一款可以调试 Ada、汇编、C\C++、D、Fortran、Go、Objective-C、OpenCL、Modula-2、Pascal、Rust 等多种语言的跨平台程序调试工具。为了捕获程序中的各类 Bug,GNU 项目调试器可以胜任下面 4 方面的工作:

  • 启动程序,并指定能够影响其行为的任意内容。
  • 在指定条件下停止程序的执行。
  • 当程序停止时,检查发生了什么问题。
  • 通过修改程序中的内容,从而尝试修复 bug。

向 Linux 命令控制台键入 gdb 即可运行 GDB 调试程序

➜  gdb

GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb) |

进入 GDB 之后直接输入 help 即可以获取各类型命令的使用帮助,如果需要进一步查看指定类型命令的帮助则可以键入相应的命令分类,例如 help data

(gdb) help
List of classes of commands:

aliases -- Aliases of other commands
breakpoints -- Making program stop at certain points
data -- Examining data
files -- Specifying and examining files
internals -- Maintenance commands
obscure -- Obscure features
running -- Running the program
stack -- Examining the stack
status -- Status inquiries
support -- Support facilities
tracepoints -- Tracing of program execution without stopping the program
user-defined -- User-defined commands

Type "help" followed by a class name for a list of commands in that class.
Type "help all" for the list of all commands.
Type "help" followed by command name for full documentation.
Type "apropos word" to search for commands related to "word".
Command name abbreviations are allowed if unambiguous.

如果需要退出当前的 GDB 命令行调试界面,则可以输入 quit ,下面的表格列出了 GDB 当中常用的一些命令:

命令 描述 命令 描述
break 设置断点, break 断点所在行号 list 列出产生执行文件的部分源码。
clear 清除断点, clear 断点所在行号 next 执行一行源码,但是不进入函数内部。
delete 清除断点和自动显示的表达式 step 执行一行源码,并且进入函数内部。
disable 使所设断点暂时失效,多个行号可用空格分隔。 run 正常执行当前被调试的程序。
enable 生效所设的断点,与 disable 作用相反。 quit 退出当前 GDB 命令行调试。
run 运行调试程序。 watch 监视指定变量的值。
countinue 继续执行正在调试的程序。 make 在 GDB 重新生成可执行文件。
file 装载需要调试的可执行文件。 shell 在 GDB 当中执行 UNIX Shell 命令。
kill 终止正在调试的程序。 file 加载可执行的文件。

提示:GDB 当中即可以像 Bash 或 Z-Shell 那样使用 Tab 键命令自动补齐,也能够通过方向键上下翻阅历史命令。

debug 范例

接下来,下面例程用于打印当前执行程序的名称以及命令行执行时所携带的参数,我们将会通过它来演示 GDB 调试程序的过程。

#include <stdio.h>

int main(int argc, char *argv[]) {
  printf("当前执行程序的名称:%s\n", argv[0]);
  int index;
  for (index = 1; index < argc; index++) {
    printf("执行命令时输入的第%d个参数为:%s\n", index, argv[index]);
  }
  return 0;
}

(1)首先需要使用 gcc -g main.c 命令编译程序并保留调试 debug 信息:

➜  gcc -g main.c
➜  ls
a.out  main.c

(2)进入 GDB 然后装载需要进行调试的可执行文件:

➜  gdb
... ...
(gdb) file a.out
Reading symbols from a.out...done.

(3)输入 GDB 的 run 命令,执行已经装载的 bugging 文件,并在命令后跟随需要传入程序的参数。

(gdb) run 这是一个Hank的测试程序!
Starting program: /workspace/c-test/a.out 这是一个Hank的测试程序!
当前执行程序的名称:/workspace/c-test/a.out
执行命令时输入的第1个参数为:这是一个Hank的测试程序!
[Inferior 1 (process 7997) exited normally]

(4)通过 where 命令,查看程序运行中出现的错误堆栈:

(gdb) where
No stack.

(5)使用 list 命令查看当前执行程序的源码,每次能够查看 10 行,需要查看更多可以直接回车重新执行上一次输入的命令。

(gdb) list
1 #include <stdio.h>
2
3 int main(int argc, char *argv[]) {
4   printf("当前执行程序的名称:%s\n", argv[0]);
5   int index;
6   for (index = 1; index < argc; index++) {
7     printf("执行命令时输入的第%d个参数为:%s\n", index, argv[index]);
8   }
9   return 0;
10 }(gdb)

(6)利用 break 命令在程序的第 5 行位置设置一个断点:

(gdb) break 5
Breakpoint 1 at 0x555555554674: file main.c, line 5.

(7)重新输入 run 命令,此时程序会运行到第 5 行断点位置并停止:

(gdb) run
Starting program: /workspace/c-test/a.out 这是一个Hank的测试程序!
当前执行程序的名称:/workspace/c-test/a.out

Breakpoint 1, main (argc=2, argv=0x7fffffffded8) at main.c:6
6   for (index = 1; index < argc; index++) {

(8)输入 next 命令,在断点位置开始单步执行:

(gdb) next
7     printf("执行命令时输入的第%d个参数为:%s\n", index, argv[index]);
(gdb) next
执行命令时输入的第1个参数为:这是一个Hank的测试程序!
6   for (index = 1; index < argc; index++) {

(9)断点执行过程中,可以使用 print 命令查看程序中指定变量的当前值:

(gdb) print index
$1 = 1
(gdb) print argc
$2 = 2
(gdb) print argv
$3 = (char **) 0x7fffffffded8

(10)当发现程序状态出现错误的原因之后,就可以使用 kill 退出当前 debug 的程序,然后 quit 离开 GDB 调试器。

(gdb) kill
Kill the program being debugged? (y or n) y
(gdb) quit

TUI 模式

直接通过 GDB 命令行进行 debug 工作显然比较繁琐,因此 GDB 内置的 TUI TextUser Interface )模式提供了一套文本 UI 界面,能够方便的显示源码、汇编、寄存器的状态。可以直接通过 gcb -tui 命令直接进入 TUI 模式,或者在进入 GDB 命令行后使用 CTRL+X+A 快捷键进入。进入 TUI 模式后,GDB 窗口划分为 源代码查看GDB 命令行 两个子窗口。

   ┌──main.c───────────────────────────────────────────────────────────────────┐
   │1       #include <stdio.h>                                                 │
   │2                                                                          │
   │3       int main(int argc, char *argv[]) {                                 │
   │4         printf("当前执行程序的名称:%s\n", argv[0]);                       │
   │5         int index;                                                       │
   │6         for (index = 1; index < argc; index++) {                         │
   │7           printf("执行命令时输入的第%d个参数为:%s\n", index, argv[index]); │
   │8         }                                                                │
   │9         return 0;                                                        │
   │10      }^?                                                                │
   │11                                                                         │
   │12                                                                         │
   │13                                                                         │
   │14                                                                         │
   └───────────────────────────────────────────────────────────────────────────┘
exec No process In:                                                L??   PC: ??
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from a.out...done.
---Type <return> to continue, or q <return> to quit---2 in /workspace/c-test/main.c
(gdb) |

DDD 图形前端

DDD( Data Display Debugger ) 是一款简洁的 GDB 图形调试界面,Ubuntu 系统当中可以通过如下命令进行安装:

sudo apt-get install ddd

DDD 的使用与 TUI 类似,窗口依然被划分为 源代码查看GDB 命令行 两个子窗口,同时右侧还增加了一个方便的操作面板。

嵌入式&桌面 Linux 下的 GCC与GDB 应用

注意:DDD 不支持中文注释,如果打开携带有中文注释的源代码,会导致这些代码在 DDD 窗口显示不完整。


以上所述就是小编给大家介绍的《嵌入式&桌面 Linux 下的 GCC与GDB 应用》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

程序员第二步

程序员第二步

尹华山 / 人民邮电出版社 / 2013-11 / 45.00元

这本书是写给程序员和项目经理的。作者结合自身的丰富成长历程,通俗易懂地讲述了一名程序员如何才能成为一名优秀的项目经理。内容涉及职业规划、学习方法、自我修炼、团队建设、项目管理等,书中理清了项目管理领域中典型的误区及具有迷惑性的观点,并对项目中的难点问题提出了针对性的解决方法。 全书行文流畅,严谨中带着活泼,理智中透着情感,给读者带来轻松愉快的阅读感受。书中诸多富有创见的观点,让人耳目一新,引......一起来看看 《程序员第二步》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

随机密码生成器
随机密码生成器

多种字符组合密码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码