1. 概述
  2. 运行时库简介
  3. Binutils 工具集
  4. GCC C/C++ 编译器
  5. GDB 调试工具
  6. IDE 环境

1. 概述

GCC 一般有两个解释(下文中除非特殊说明,GCC 指广义工具链集合):

  • GNU Compiler Collection:GNU 编译工具链集合,能编译多种语言
  • GNU C Compiler:GNU C/C++ 编译器

同时 GCC 还有 windows 版本:MinWG(Minimalist GNU for Windows)

接下来一共分为个部分总结:

  1. 运行时库简介
  2. Binutils 工具集
  3. GCC C/C++ 编译器
  4. GNU Project Debugger 调试工具

2. 运行时库简介

2.1 关于库的基础知识

众所周知,库文件有两种:

  • 静态链接库(.a,.lib):编译时链接
  • 动态链接库(.so,.dll):运行时链接

其中动态链接库也叫共享库,因为其在内存中只有一份副本,所有的程序共用这些函数

2.2 C/C++标准库

参考链接:[理清 gcc、libc、libstdc++的关系 | 唐装鼠的博客 | CSDN]
该文标注了来源,但是源网站似乎已经挂了…

C/C++标准一部分描述了语法定义,一部分定义了标准库的接口(但并没有实现)
而 GCC 库则是对标准中定义的库的实现

库文件 描述
glibc GNU C 标准库,后成为 Linux 的标准 C 库(动态库)
libstdc++ GNU C++标准库,与 GCC 绑定安装
eglibc 一个面向嵌入式系统的 C 库
newlib 另一个面向嵌入式系统的 C 库
  • C 标准库与 Linux 操作系统高度绑定(Linux 自带 C 标准库)
    C 标准库是 Linux 系统中最底层的 API,封装了很多系统调用
    同时其也封装了一些上层应用函数的必要功能(如 string,malloc,signal 等)
    最开始 Linux 使用的 C 标准库是 libc,后来用过 klibc,uclibc.现在使用最多的就是 glibc(Debian,Redhat 都是 glibc 及其变种)
  • C++标准库不与操作系统绑定(以下以 libstdc++举例)
    libstdc++与 GCC 绑定(安装 GCC 时自动安装 libstdc++)
    但是 libstdc++不与内核打交道,而是通过 glibc 调用内核功能

3. Binutils 工具集

参考资料链接:[GCC 工具链基础 | suda-morris 个人博客]

Binutils 是一组二进制程序的处理工具:

工具 作用
addr2line 可以从程序地址得到源文件中对应的代码行,可以帮助调试器定位代码位置
as 汇编工具
ld 链接工具
ar 用于创建静态库
ldd 可以用于查看一个可执行程序依赖的共享库
objcopy 转换可执行文件的格式(如.bin 转换为.elf)
objdump 反汇编工具
readelf elf 文件查看工具
size 列出可执行文件每个部分的尺寸和总尺寸,代码段,数据段,总大小等

4. GCC C/C++ 编译器

参考资料链接:[GCC 工具链基础 | suda-morris 个人博客]

4.1 GCC 编译选项

参数 功能
-o 指定生成文件名
-E 生成.i 格式的预处理文件
-S 生成.s 格式的汇编文件
-c 生成.o 格式的中间文件
-g 生成必要的符号信息(调试时要用)
-m32 生成 32 位程序
-I\ 添加头文件搜索目录
-nostdinc 不使用标准库

4.2 gcc 与 g++的异同

有一说一,国内各个博客网站瞎抄严重(csdn,博客园,知乎,甚至包括 github pages)
最后在 stackoverflow 上找到了一个比较靠谱的回答:[What is the difference between g++ and gcc? | stackoverflow]

  • gcc 和 g++都是 GCC 的编译器驱动器,他们会根据具体的文件类型调用合适的编译器(可以使用-x language 显式指定语言)
  • gcc,g++最大的区别在于其自动链接的库不同
  • g++完全相当于gcc -xc++ -lstdc++ -shared-libgcc(相关资料:link options, how g++ is invoked,参考自最高赞,没有考证)
  • 对于.cpp,二者都看做 c++文件,对于.c,g++看做 c++文件,gcc 看做 c 文件

5. GDB 调试工具

参考资料:
[GDB 调试指南 | 守望的个人博客]
[gdb 调试利器 | Linux Tools Quick Tutorial]

GDB 全称 GNU Project Debugger,是一个开源调试器

5.1 启动调试

5.1.1 准备被调试程序

GDB 调试会使用到程序的符号表,所有在编译的时候需要添加-g选项

1
gcc -g hello.cpp -o hello

5.1.2 启动并调试一个程序

1
2
3
4
# gdb 直接跟被调试程序名(需要有符号表段)
gdb hello
# 加载完之后,使用 run 接参数执行
run args...

5.1.3 根据 core 文件调试

参考资料:
[Linux 下 gdb 调试生成 core 文件并调试 core 文件 | wkd_007 的博客 | CSDN]
[Linux core 文件介绍 | dzqabc 的博客]

程序在崩溃的时候,Linux 系统会把其内存的数据和调试信息保存为一个 core 文件以备后续调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 使用该命令可以查看系统对core文件的大小设置
# 如果输出0则表示系统不生成任何core文件
ulimit -c
# 可以手动设置允许生成core文件的大小上限
ulimit -c [filesize]
# 以下命令设置core大小无上限(最常用选项)
ulimit -c unlimited
# 以上命令在关机后自动失效,需要开机生效可以去搜搜(用的时候开启就行了,没必要开机设置)
# 使用如下命令可以查看core文件的存储路径
cat /proc/sys/kernel/core_pattern
# 其中输出后的格式化字符中,特殊符号含义如下
# %p - insert pid into filename
# %u - insert current uid into filename
# %g - insert current gid into filenamed
# %s - insert signal that caused the coredump into the filename
# %t - insert UNIX time that the coredump occurred into filename
# %h - insert hostname where the coredump happened into filename
# %e - insert coredumping executable name into filename

gdb 可以利用 core 文件进行调试

1
2
3
4
5
6
7
8
# 1. 直接调试core文件,然后通过查看堆栈来手动定位错误位置
# (bt命令查看调用堆栈)
gdb [exec file] [core file]
bt
# 2. 先使用core载入gdb,然后指定源文件,再查看调用堆栈
gdb -c [core file]
file [exec file]
bt

5.1.4 连接一个运行中的程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 以下命令如果出现如下错误提示
# Could not attach to process. If your uid matches the uid of the target
# process, check the setting of /proc/sys/kernel/yama/ptrace_scope, or try
# again as the root user. For more details, see /etc/sysctl.d/10-ptrace.conf
# ptrace: Operation not permitted.
# 可以把/proc/sys/kernel/yama/ptrace_scope文件中的内容从1改为0
# 执行以下命令查看关于这个设置的细节
cat /etc/sysctl.d/10-ptrace.conf

# 1. 直接输入gdb进入交互界面
# 使用attach命令连接目标进程
gdb
file [exec file]
attach PID

# 2. 直接在阐述中指定pid
gdb hello 20829
gdb hello --pid 20829

5.2 GDB 命令

5.2.1 断点命令

  1. 查看断点信息

    1
    info breakpints
  2. 设置断点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    # 1. 设置普通断点(break命令可以简写为b)
    break [file:]函数名/行号

    # 2. 设置条件断点
    break [file:]函数名/行号 if <expression>
    # 可以使用condition修改条件断点行号(其中断点号属于断点信息)
    condition 断点号 <expression>

    # 3. 根据规则批量设置断点
    # 在所有函数头处设置断点
    rbreak [file:].
    # 在所有以print开头的函数头处设置断点
    rbreak [file:]^print

    # 4. 设置单次临时断点
    tbreak [file:]函数名/行号

    # 5. 跳过指定断点指定次数
    ignore 断点号 跳过次数

    # 6. 设置变量断点(变量变化时中断)
    watch 变量名
  3. 开启/禁用断点

    1
    2
    3
    4
    # 如果没有指定断点号,则对所有断点生效
    # 如果在enable指令中添加了delete选项,在断点触发后删除该断点
    enable [delete] [断点号]
    disable [断点号]
  4. 清除断点

    1
    2
    3
    4
    # 如果没有指定断点号,对所有断点生效(清除所有breakpoint)
    clear [file:]函数名/行号
    # 如果没有指定断点号,对所有断点生效(清除所有breakpoint,watchpoint,catchpoint)
    delete [断点号]

    5.2.2 变量操作

  5. 查看普通变量

    1
    2
    3
    # print可以简写为p
    # 可以打印基本数值类型,数组,字符数组
    print ['filename'::]变量名
  6. 打印指针目标内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    # 其中数量可以使一个数值型的变量
    # 格式符指定显示格式
    # x 按十六进制格式显示变量
    # d 按十进制格式显示变量
    # u 按十六进制格式显示无符号整型
    # o 按八进制格式显示变量
    # t 按二进制格式显示变量
    # a 按十六进制格式显示变量
    # c 按字符格式显示变量
    # f 按浮点数格式显示变量
    p[/格式符] ['filename'::]*指针名[@数量]

    # 可以使用特殊变量 $ 表示上一个变量
    (gdb) p *linkNode
    (这里显示linkNode节点内容)
    (gdb) p *$.next
    (这里显示linkNode节点下一个节点的内容)

    # 最后,还可以设置shell风格的变量来辅助操作
    (gdb) set $index=0
    (gdb) p b[$index++]
    (gdb) p b[$index++]
  7. 显示内存操作

    1
    2
    3
    4
    5
    6
    7
    8
    # n 为要显示的内存单元数量
    # f 为显示格式,同变量查看格式符一致
    # u 为单元长度
    # b 字节
    # h 双字节
    # w 四字节
    # g 八字节
    x/[n][f][u] 内存地址
  8. 监视变量(每次中断时显示)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 设置监视变量
    display 变量名

    # 查看监视变量信息
    info display

    # 禁用/使能监视变量
    disable display 监视变量编号
    enable display 监视变量编号

    # 清除监视变量
    delete display 监视变量编号
  9. 查看寄存器

    1
    info registers

    5.2.3 调试指令

  10. 单步调试

    1
    2
    # next可以简写为n
    next [步数]
  11. 进入/退出函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 进入当前函数,step 可以简写为 s
    # 需要有这个函数的调试信息,否则会默认跳过(使用默认行为就好)
    step

    # 退出当前函数
    finish

    # 还有一个版本的step:stepi(执行一个机器指令),可以简写为si
    stepi
  12. 执行到下一个中断点

    1
    2
    # 可以简写为 c ,可以设置忽略下几次中断
    continue [忽略断点次数]
  13. 执行到指定位置

    1
    2
    3
    # 可以忽略为u
    # 和临时断点基本一样
    until [目标行数]
  14. 跳过执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 查看跳过设置信息
    info skip

    # 设置跳过
    skip [file filename|function funcname]

    # 禁用/使能/清除
    skip disable 跳过设置信息编号
    skip enable 跳过设置信息编号
    skip delete 跳过设置信息编号

    5.2.4 查看源码

相关链接:GDB 调试指南 | 守望的个人博客

查看源码,修改源码再用 GDB 可就太费劲了
vscode 加上插件不要太舒服哈哈哈哈(调试控制台可以使用上述的 GDB 操作)

6. IDE 环境

6.1 vscode 套装

vscode 本身配合插件可以当做一个极致精简的 IDE 来用(功能少但是非常轻便)
本来想单独写一个关于 vscode 的总结,但是需要写的东西也只有一个 launch.json 和一个 task.json
发现知乎上有一个很好的总结就直接省了哈哈哈哈
(一定要安装 Native Debug 插件,非常好用)

参考资料: [VScode tasks.json 和 launch.json 的设置 | 雾色 | 知乎]

6.2 CLion

Clion 是一个 Jetbrains 家的 C/C++ IDE,跨平台,收费但是非常好用
网上的破解码感觉不太靠谱(虽然以前用的也是这个…),去淘宝搜了一圈感觉也不太靠谱
最后决定可以先认证一个 Github 的学生包(有 jetbrains 家 ide 的学生认证),以后有钱再搞正版

首先申请 Github 学生包,如果账号里有学校邮箱,两分钟就申请好了:
[Github 学生包申请 | Github Docs]
然后去 Jetbrains 家注册账号,绑定 Github 学生包(或者自己搜一个教程):
[Jetbrains 学生认证 | CSDN]

Clion 的构建系统也是基于 Cmake 的,下一篇就学 Cmake