JackCin's blog JackCin's blog
首页
  • 页面

    • Html
    • CSS
  • 核心

    • JavaScript基础
    • JavaScript高级
  • 框架

    • Vue
  • jQuery
  • Node
  • Ajax
Linux
  • 操作系统
  • 数据结构与算法
  • 51单片机
  • CC2530
  • 网站
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

JackCin

前端小菜鸡(✪ω✪)
首页
  • 页面

    • Html
    • CSS
  • 核心

    • JavaScript基础
    • JavaScript高级
  • 框架

    • Vue
  • jQuery
  • Node
  • Ajax
Linux
  • 操作系统
  • 数据结构与算法
  • 51单片机
  • CC2530
  • 网站
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Linux系统编程入门

    • Linux开发工具
      • 环境搭建
      • 一、GCC
        • 1、什么是GCC
        • 2、编程语言的发展
        • 3、GCC的工作流程
        • 4、gcc 和 g++ 的区别
        • 5、GCC常用参数选项
      • 二、静态库与动态库
        • 1、什么是库
        • 2、静态库的制作
        • 2.1 命名规则
        • 2.2 制作
        • 3、静态库的使用
        • 4、动态库的制作
        • 4.1 命名规则
        • 4.2 制作
        • 4.3 制作流程
        • 5、静态/动态库的工作原理
        • 6、💛解决动态库使用的问题
        • 6.1 在终端中配置
        • 6.2 用户级别的配置
        • 6.3 系统级别的配置
        • 6.4 配置/etc/ld.so.cache文件列表
        • 6.5 方式三
        • 7、静态库和动态库对比
        • 7.1 程序编译成可执行文件的过程
        • 7.2 静态库制作过程
        • 7.3 动态库制作过程
        • 7.4 静态库优缺点
        • 7.5 动态库优缺点
      • 三、Makfile
        • 1、什么是Makefile
        • 2、Makefile 文件命名和规则
        • 3、工作原理
        • 4、变量
        • 5、匹配模式
        • 6、函数
        • 6.1 获取文件函数
        • 6.2 替换字符串函数
        • 6.3 clean 和 PHONY
      • 四、GDB调试
        • 1、什么是GDB
        • 2、准备工作
        • 3、✨GDB命令 - 启动,退出,查看
        • 3.1 启动与退出
        • 3.2 给程序设置参数/获取设置参数
        • 3.3 GDB使用帮助
        • 3.4 查看当前文件代码
        • 3.5 查看非当前文件代码
        • 3.6 设置显示的行数
        • 4、GDB命令 - 断点
        • 5、GDB命令 - 调试命令
        • 5.1 运行GDB程序
        • 5.2 继续运行,到下一个断点停
        • 5.3 向下执行一行代码(不会进入函数体)
        • 5.4 变量操作
        • 5.5 向下单步调试(遇到函数进入函数体)
        • 5.6 自动变量操作
        • 5.7 设置变量值与跳出循环
        • 5.8 命令测试
    • 文件IO
  • Linux 多进程开发

  • Linux 多线程开发

  • Linux 网络编程

  • Linux
  • Linux系统编程入门
JackCin
2023-09-12
目录

Linux开发工具

# Linux 开发工具

课程列表_牛客网 (nowcoder.com) (opens new window)

# 环境搭建

  1. 安装Linux系统(虚拟机安装、云服务器) https://releases.ubuntu.com/bionic/

  2. 安装XSHELL、XFTP https://www.netsarang.com/zh/free-for-home-school/

    1. 安装Visual Studio Code https://code.visualstudio.com/

# 一、GCC

# 1、什么是GCC

  • ◼ GCC 原名为 GNU C语言编译器(GNU C Compiler)
  • ◼ GCC(GNU Compiler Collection,GNU编译器套件)是由 GNU 开发的编程语言 译器。GNU 编译器套件包括 C、C++、Objective-C、Java、Ada 和 Go 语言前端,也包括了这些语言的库(如 libstdc++,libgcj等)
  • ◼ GCC 不仅支持 C 的许多“方言”,也可以区别不同的 C 语言标准;可以使用命令行 选项来控制编译器在翻译源代码时应该遵循哪个 C 标准。例如,当使用命令行参数 -std=c99 启动 GCC 时,编译器支持 C99 标准。
  • ◼ 安装命令 sudo apt install gcc g++ (版本 > 4.8.5)
  • ◼ 查看版本 gcc/g++ -v/--version

# 2、编程语言的发展

编程语言的发展

# 3、GCC的工作流程

GCC的工作流程

# 4、gcc 和 g++ 的区别

  • gcc 和 g++都是GNU(组织)的一个编译器。

  • 误区一:gcc 只能编译 c 代码,g++ 只能编译 c++ 代码。两者都可以,请注意:

    • 后缀为 .c 的,gcc 把它当作是 C 程序,而 g++ 当作是 c++ 程序
    • 后缀为 .cpp 的,两者都会认为是 C++ 程序,C++ 的语法规则更加严谨一些
    • 编译阶段,g++ 会调用 gcc,对于 C++ 代码,两者是等价的,但是因为 gcc 命令不能自动和 C++ 程序使用的库联接,所以通常用 g++ 来完成链接,为了统 一起见,干脆编译/链接统统用 g++ 了,这就给人一种错觉,好像 cpp 程序只能用 g++ 似的
  • 误区二:gcc 不会定义 __cplusplus 宏,而 g++ 会

    • 实际上,这个宏只是标志着编译器将会把代码按 C 还是 C++ 语法来解释
    • 如上所述,如果后缀为 .c,并且采用 gcc 编译器,则该宏就是未定义的,否则, 就是已定义
  • 误区三:编译只能用 gcc,链接只能用 g++

    • 严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用 gcc/g++,而链接可以用 g++ 或者 gcc -lstdc++。
      • gcc -lstdc++ (-l表示链接,std是标准的意思,所以这个语句就是按c++标准链接的意思)
    • gcc 命令不能自动和C++程序使用的库联接,所以通常使用 g++ 来完成联接。 但在编译阶段,g++ 会自动调用 gcc,二者等价

# 5、GCC常用参数选项

GCC常用参数选项1

  • -E 命令对应生成 预处理后源代码(一般以 .i 为后缀)

  • -S 命令对应生成 汇编代码(一般以 .s 为后缀)

  • -c 命令对应生成库代码(一般以 .o 为后缀),为二进制不能直接运行

  • GCC编译测试

    • 上图,倒数第一个 gcc test.c -S 和gcc test.c 指令表明,就算跳过前面 -E ,-S,-O指令,直接执行后面的指令,gcc也会自动把前面的转换完成,如 执行gcc test.c -S 后,直接跳过了test.i 生成 test.c 生成了 test.s文件(其实不是跳过,只是内部执行了)

    • 如果不使用 -o 给生成文件重命名,gcc会自动给文件命名,好像只有 gcc test.c 执行后,会自动生成 a.out 可执行程序(如果执行的是 gcc test.c -S 那默认生成的好像就是 test.s 文件,其他指令生成的都是原名)

    • 以上代码的 gcc 改成 g++ 也能一样完成

  • -o 可以有两种写法

    1. gcc test.c -o a.out
    2. gcc -o test.c a.out
    
    1
    2
  • -D (看下图,shell里面的语句就相当于在test.c里定义了 DEBUG )

    • -D的写法可以像下图一样连起来写,也可以不连

    gcc编译选项-D与-Wall

  • -Wall 作用看上图(这里的警告就是,未使用的变量a)

GCC常用参数选项2

-On 优化解释:

//好比我们有这样一串代码
int a,b,c,d;
a=10;
b=a;
c=b;
b=d;

//那经过优化就会变成这样c
int a,b,c,d;
a=10;
b=10;
c=10;
d=10;

//这样可以防止一些反汇编工具,对我们的可执行文件进行逆向处理,生成我们的源代码(因为优化后代码逻辑没有被发现,好比这里并不是全都赋值10,而是b=a,c=b。。。)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • -I, -L ,-l 具体使用可以看下面静态库使用部分

# 二、静态库与动态库

# 1、什么是库

  • 库文件是计算机上的一类文件,可以简单的把库文件看成一种代码仓库,它提供给使用 者一些可以直接拿来用的变量、函数或类。
  • 库是特殊的一种程序,编写库的程序和编写一般的程序区别不大,只是库不能单独运行。
  • 库文件有两种,静态库和动态库(共享库),区别是:静态库在程序的链接阶段被复制到了程序中;动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加 载到内存中供程序调用。
  • 库的好处:1.代码保密 2.方便部署和分发

# 2、静态库的制作

# 2.1 命名规则

    1. 命名规则:
    • Linux : libxxx.a
      • lib : 前缀(固定)
      • xxx : 库的名字,自己起
      • .a : 后缀(固定)
    • Windows : libxxx.lib

# 2.2 制作

    1. 静态库的制作🛠
    • gcc 获得 .o 文件 (gcc 的-c 指令获得库代码😎)
    • 将 .o 文件打包,使用 ar 工具(archive)
    • ar rcs libxxx.a xxx.o xxx.o
      • r – 将文件插入备存文件中 (插入 .o 文件)
      • c – 建立备存文件 (我们用ar指令创建出来的文件就是备存文件,c:create创建)
      • s – 索引 (因为 .o 文件不止一个会有很多,所以要有索引去查找)

制作静态库1

  • tree 命令是将文件以树状显示

# 3、静态库的使用

静态库的使用1

    1. head.c 和 和要生成.o文件的c文件并不在同一目录下,但是.c 文件却include了head.c

      • //sub.c
        #include <stdio.h>
        #include "head.h"
        
        int subtract(int a, int b)
        {
            return a-b;
        }
        
        1
        2
        3
        4
        5
        6
        7
        8
    1. gcc 编译使用 -I 指定 include 包含文件的搜索目录
    1. 制作静态库(注意命名规则)
    1. 因为此时我们制作的静态库在src目录下,但是我们想要把他放得到 lib目录下,所以使用 mv 命令移动文件

静态库的使用2

    1. head.c和main.c 不在同一目录下,所以要指定 head.c的搜索目录,但是因为我们制作的库和mian也不在同一目录,所以必须指定库的位置和使用的库
    1. -l在程序编译时,指定使用的库(注意,我们的库名是 **suanshu**而不是 libsuanshu 也不要写 libsuanshu.a)
    1. -L指定编译时,搜索库的路径

# 4、动态库的制作

# 4.1 命名规则

  • Linux : libxxx.so
    • lib : 前缀(固定)
    • xxx : 库的名字,自己起
    • .so : 后缀(固定)
    • 在Linux下是一个可执行文件
  • Windows : libxxx.dll

# 4.2 制作

  • gcc 得到 .o 文件,得到和位置无关的代码
    • gcc -c –fpic/-fPIC a.c b.c
  • gcc 得到动态库
    • gcc -shared a.o b.o -o libcalc.so
    • 上面命令里的 libcalc.so 不是固定的,而是libXXX.so ,XXX就是库名

# 4.3 制作流程

# 4.3.1 文件准备

动态库制作1

  • 创建目录文件 mkdir 目录名
  • 拷贝文件,cp calc library ../lesson06
    • 将 当前目录下的 calc文件夹 和 library 文件夹拷贝到 上层目录的lessson06目录下
    • 注意:这里这样拷贝没成功,下面有提示,这时要在 上面命令后面加上 -r ,表示以递归方式拷贝
# 4.3.2 制作动态库

制作动态库2

  • 先将拷贝过来的文件清理成我们需要的样子,如上图(就是把之前我们制作静态库时生成的 .o和库文件删除),如果我们以来就没有这些东西,就不用啦。

  • gcc -c –fpic add.c div.c mult.c sub.c gcc 得到 .o 文件,得到和位置无关的代码

  • gcc -shared add.o sub.o mult.o div.o -o libcalc.so gcc 得到动态库 calc

  • 显示绿🐍的文件说明是可执行文件

# 4.3.3 使用动态库文件的问题

动态库的制作3

  • 这里是已经把我们制作的 libcalc.so文件 使用cp命令copy到了 library 目录的lib目录下
  • 黄色框的问题我们在制作静态库时有解释,就不再说了
  • 在我们制作完动态库后,并使用其编译 main.c 的可执行文件时,我们可以正确生成可执行文件 main,但是在运行可执行文件时就出现了蓝色框内的错误(加载共享库失败,找不到libcalc.so文件),这就与动态库的工作原理有关了

# 5、静态/动态库的工作原理

  • 静态库:GCC 进行链接时,会把静态库中代码打包到可执行程序中
  • 动态库:GCC 进行链接时,动态库的代码不会被打包到可执行程序中
  • 程序启动之后,动态库会被动态加载到内存中,通过 ldd (list dynamic dependencies)命令检查动态库依赖关系
  • 如何定位共享库文件呢?
    • 当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统的动态载入器来获取该绝对路径。对于elf格式的可执行程序,是 由ld-linux.so来完成的,它先后搜索elf文件的 DT_RPATH段 ——> 环境变量 LD_LIBRARY_PATH ——> /etc/ld.so.cache文件列表 ——> /lib/,/usr/lib 目录找到库文件后将其载入内存。

检查动态库依赖关系

  • 可以看出找不到main 找不到我们的 calc 库
  • 倒数第三行 libc.so.6就标准的c库, =》后面的就是它的路径
  • 倒数第二行那个就是系统的动态载入器(后面括号就是其在内存地址)

# 6、💛解决动态库使用的问题

方法1、配置环境变量,有3中配置方式

在配置系统级环境变量时,有时在配置完使用 ldd 指令检测时检测不到或者出现ehco $LD_LIBRARY_PATH后显示的路径为多条正确路径,可以关闭终端再重新链接就好了(反正就是确定配置好了,但是找不到依赖就出现打开终端,当然在终端配置方式除外,毕竟终端配置环境变量,一关闭终端就没了)

# 6.1 在终端中配置

缺点是这种配置方式是临时的,如果我们关闭终端,再次执行 main文件 ,就又找不到 calc 文件了

  • 在终端中配置 LD_LIBRARY_PATH 环境变量

终端配置LD_LIBRARY_PATH

  • 黄色框内命令解释:

    • export 设置或显示环境变量;
    • $表示获取,上面代码中就表示获取 LD_LIBRARY_PATH 的值;
    • : 因为环境变量里每个路径都是用 :隔开的,所以我们添加环境变量也要有:隔开;
    • 这整句话就是获取原来LD_LIBRARY_PATH的值再添加上我们要加入的库文件的绝对路径,再赋给LD_LIBRARY_PATH
  • 蓝色框命令解释:

    • echo 功能:显示文字;
    • 整句就是 获取 LD_LIBRARY_PATH 显示出来
  • 从紫色(感觉像深粉)框:可以看到我们查找main依赖时可以找到库文件 calc了,因此执行 main文件成功

# 6.2 用户级别的配置

  • cd回到主目录 ,vim .bashrc ,在文件末尾添加下图框内语句配置环境变量,然后保存退出

用户级配置

  • 但这时我们的配置并没有生效,我们还需要执行
    • . .bashrc 或者 source .bashrc 这两个命令使其立即生效(这两个命令作用一样,. 是简写)
    • source命令也称为“点命令”,也就是一个点符号(.),是bash的内部命令,通常用于重新执行刚修改的初始化文件,使之立即生效,而不必注销并重新登录。
  • 用户级配置2

# 6.3 系统级别的配置

  • 在使用系统配置时,记得把之前终端配置(重启终端,感觉也可以按配置时的写法来删除)或者用户配置(vim .bashrc删除我们写进去的配置 )的配置删除

系统级配置

  • 红色框是写错了文件,不用管😂,但是里面那个 ~表示的是在home目录(主目录里)
  • 系统级就是所以vim /etc/profile 命令,在 /etc/profile 文件末尾添加 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库文件路径,这个语句,然后保存并退出,使用 source /etc/profile使修改立即生效
  • 注意:修改 /etc/profile 文件需要一定权限,所以要用sudo

# 6.4 配置/etc/ld.so.cache文件列表

方法2:配置 /etc/ld.so.cache文件列表

ld

  • 因为 /etc/ld.so.cache 是二进制文件我们不能修改所以我们配置 /etc/ld.so.conf 文件
  • 加入 /etc/ld.so.conf 文件 后,我们在里面写下我们要配置的库的绝对路径,这里我们就是写
    • /home/nowcoder/Linux/lesson06/library/lib,保存退出
  • 再使用sudo ldconfig 命令更新配置

# 6.5 方式三

将我们的动态库文件放到/lib/,/usr/lib 这两个目录下,但是最好别这样做,因为这两个目录里本来就包含很多系统自带的文件,如果加到这里面,可能会导致和本来的文件重名,引起冲突

# 7、静态库和动态库对比

# 7.1 程序编译成可执行文件的过程

程序编译成可执行文件的过程

# 7.2 静态库制作过程

静态库制作过程

# 7.3 动态库制作过程

动态库制作过程

  • 动态库加载不知道什么时候加载,也不知道加载到哪一块区域,所以要生成与位置无关代码 -fpic

# 7.4 静态库优缺点

静态库优缺点

# 7.5 动态库优缺点

动态库优缺点

# 三、Makfile

# 1、什么是Makefile

  • 一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中, Makefile 文件定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编 译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 Makefile 文件就 像一个 Shell 脚本一样,也可以执行操作系统的命令。
  • Makefile 带来的好处就是“自动化编译” ,一旦写好,只需要一个 make 命令,整 个工程完全自动编译,极大的提高了软件开发的效率。make 是一个命令工具,是一个 解释 Makefile 文件中指令的命令工具,一般来说,大多数的 IDE 都有这个命令, 比如 Delphi 的 make,Visual C++ 的 nmake,Linux 下 GNU 的 make。

# 2、Makefile 文件命名和规则

  • 文件命名

    • makefile 或者 Makefile
  • Makefile 规则

    • 一个 Makefile 文件中可以有一个或者多个规则

      • 目标 ...: 依赖 ... 
        	命令(Shell 命令) 
        	...
        
        1
        2
        3
      • 目标:最终要生成的文件(伪目标除外)

      • 依赖:生成目标所需要的文件或是目标

      • 命令:通过执行命令对依赖操作生成目标(命令前必须 Tab 缩进)

    • Makefile 中的其它规则一般都是为第一条规则服务的。

初始Makefile

  • make命令执行 Makefile 文件

# 3、工作原理

  • 命令在执行之前,需要先检查规则中的依赖是否存在
    • 如果存在,执行命令
    • 如果不存在,向下检查其它的规则,检查有没有一个规则是用来生成这个依赖的, 如果找到了,则执行该规则中的命令
  • 检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间
    • 如果依赖的时间比目标的时间晚,需要重新生成目标
    • 如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被执行

Makefile工作原理

    1. 将 Makefile 写成左边这样
    1. 然后使用make 指令在线 Makefile 文件,因为我们缺乏 .o 的文件,所以就执行了下面的规则,去为我们第一个语句生成我们需要的依赖,然后再执行第一个规则
    1. 执行完第一次 make后再执行一次 make 显示 “app” 已是最新,执行规则中的命令时,会比较目标和依赖文件的时间,我们刚执行完第一次 make 后文件都没去改变,所以比较结果就是 目标文件的时间比依赖文件的时间晚,说明依赖文件没被修改过,如果已经有目标文件了,然后修改依赖文件,那依赖文件的时间就比目标文件晚了,说明被修改过
  • 4、我们修改 main.c文件,然后再执行 make命令,只使用了生成main.o 的规则,和第一条规则,这就是有比较时间的好处(就是不用一次修改就全部重复执行)

# 4、变量

  • 自定义变量 变量名=变量值 var=hello $(var)
  • 预定义变量
    • AR : 归档维护程序的名称,默认值为 ar
    • CC : C 编译器的名称,默认值为 cc
    • CXX : C++ 编译器的名称,默认值为 g++
    • $@ : 目标的完整名称
    • $< : 第一个依赖文件的名称
    • $^ : 所有的依赖文件
  • 获取变量的值 $(变量名)
app:main.c a.c b.c
	gcc -c main.c a.c b.c
#自动变量只能在规则的命令中使用
app:main.c a.c b.c
	$(CC) -c $^ -o $@
#@ 就相当于app ,$^ main.c a.c b.c ,$(CC) 就是 gcc
1
2
3
4
5
6
  • shell 里 #是注释的标识符

  • 注意,命令前必须 tab缩进

Makefile-变量

  • 上面的$(CC) $(src) -o $(targrt) 也可以写作

    $(CC) $^ -o $@
    
    1

# 5、匹配模式

add.o:add.c
	gcc -c add.c
div.o:div.c
	gcc -c div.c
sub.o:sub.c
	gcc -c sub.c
mult.o:mult.c
	gcc -c mult.c
main.o:main.c
	gcc -c main.c
1
2
3
4
5
6
7
8
9
10
  • 我们正常是这样写一旦文件较多就很麻烦,所以要所以通配符 %
%.o:%.c
	gcc -c $< -o $@
1
2
  • %: 通配符,匹配一个字符串
    • 两个%匹配的是同一个字符串
    • 一个规则中的两个或多个%,表示的是同一字符串

通配符

# 6、函数

# 6.1 获取文件函数

  • $(wildcard PATTERN...)
    • 功能:获取指定目录下指定类型的文件列表
    • 参数:PATTERN 指的是某个或多个目录下的对应的某种类型的文件,如果有多个目录,一般使用空格间隔
    • 返回:得到的若干个文件的文件列表,文件名之间使用空格间隔
    • 示例:
      • $(wildcard *.c ./sub/\*.c)
      • 返回值格式: a.c b.c c.c d.c e.c f.c
      • 就是获取当前目录下和当前目录下sub目录里所有 .c 文件

# 6.2 替换字符串函数

  • $(patsubst <pattern>,<replacement>,<text>)
    • 功能:查找<test>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern>,如果匹配的话,则以<replacement>替换。
    • <pattern>可以包括通配符%,表示任意长度的字串。如果<replacement> 中也包含%,那么<replacement>中的这个%将是<pattern>中的那个% 所代表的字串。(可以用\来转义,以\%来表示真实含义的%字符)
    • 返回:函数返回被替换过后的字符串
    • 示例:
      • $(patsubst %.c, %.o, x.c bar.c)
      • 返回值格式: x.o bar.o
      • x.c 和 bar.c 都和 %.c匹配 ,所以就将其替换为 x.o bar.o

函数

# 6.3 clean 和 PHONY

clean-和-PHONY

  • 💙:我们注意到,我们每次执行完 make 都会有很多 .o文件,但是我们其实不需要这些 .o文件,所以有没有上面方法可以执行make 并且删除 .o 文件呢?

  • 💛:我们在 Makefile 里添加黄色框部分内容

    • 使用 make执行,表示最新,因为我们 make第一个规则与 clean规则是无关的,第一条规则相关文件都没有发生改变,所以make无效
  • 💚: 这时我们要用**make clean** 指定执行Makefile 里的 clean 规则,可以看到 .o 文件都被删除了

  • 💜: 重新执行 make ,因为clean 与第一条规则无关,所以不会被执行

  • 🧡: touch clean 我们自己创建一个**clean** 文件 ,再 make clean ,显示最新?

    • 原因是:我们的clean语句是不需要依赖的,但是我们有 clean文件,因为Makefile 检测更新的原理,clean文件始终比依赖晚创建(都没有依赖嘛),所以它更新,因此显示已最新
  • ❤:所以我们必须在 Makefile 里添加 .PHONY:clean语句,表示clean是一个伪目标,他就不会产生clean目标文件,就不会和我们文件里clean文件进行对比

# 四、GDB调试

GDB吉祥物

  • GDB的吉祥物,弓箭鱼🐟

# 1、什么是GDB

  • GDB 是由 GNU 软件系统社区提供的调试工具,同 GCC 配套组成了一套完整的开发环境,GDB 是 Linux 和许多类 Unix 系统中的标准开发环境。
  • 一般来说,GDB 主要帮助你完成下面四个方面的功能:
      1. 启动程序,可以按照自定义的要求随心所欲的运行程序
      2. 可让被调试的程序在所指定的调置的断点处停住(断点可以是条件表达式)
      3. 当程序被停住时,可以检查此时程序中所发生的事
      4. 可以改变程序,将一个 BUG 产生的影响修正从而测试其他 BUG

# 2、准备工作

  • 通常,在为调试而编译时,我们会关掉编译器的优化选项(-O), 并打开调试选项(-g)。另外,-Wall在尽量不影响程序行为的情况下选项打开所有 warning,也可以发现许多问题,避免一些不必要的 BUG。
  • gcc -g -Wall program.c -o program
  • -g 选项的作用是在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证 gdb 能找到源文件。

GDB-打开选项(-g)

  • 因为打开了调试选项(-g),所以文件也会比普通文件更大

在调试时必须保证-gdb-能找到源文件

  • mv test.c test1.c把test.c文件转移到了统一目录下的test1.c里(相当于重命名) ,所以在gdb test 时因为我们找不到 test.c文件了,就出现了上图的问题

# 3、✨GDB命令 - 启动,退出,查看

一下演示使用的test.c文件代码如下:

#test.c
#include <stdio.h>
#include <stdlib.h>
 
int test(int a);
 
int main(int argc, char* argv[]) {
    int a, b;
    printf("argc = %d\n", argc);
 
    if(argc < 3) {
        a = 10;
        b = 30;
    } else {
        a = atoi(argv[1]);
        b = atoi(argv[2]);
    }
    printf("a = %d, b = %d\n", a, b);
    printf("a + b = %d\n", a + b);
 
    for(int i = 0; i < a; ++i) {
        printf("i = %d\n", i);
        // 函数调用c
        int res = test(i);
        printf("res value: %d\n", res);
    }
 
    printf("THE END !!!\n");
    return 0;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 3.1 启动与退出

  • gdb 可执行程序

  • quit

    • 退出gdb,也可以直接写个 q

# 3.2 给程序设置参数/获取设置参数

  • set args 10 20
  • show args
  • 注意:有一些程序需要设置参数,例如上述的test.c程序,main函数需要指定参数。

gdb-启动、退出、传参、获取参数

  • 以下是普通文件传参数

普通文件传参数

# 3.3 GDB使用帮助

  • help
  • help 具体命令 如 help set
  • 需要加入 GDB后才能查看

# 3.4 查看当前文件代码

  • 普通文件查看代码行数(还是以test.c 为例)
    • vim test.c
    • 在命令模式下输入 :set nu

普通模式显示代码行数

  • GDB 查看当前代码
    • list/l (从默认位置显示)
      • list默认位置显示,每次显示10行
      • 蓝色框是按了回车,默认执行上一次执行的命令

GDB查看代码,默认查看

  • list/l 行号 (从指定的行显示)
    • 注意: 是把行号内容放在中间显示

GDB按行查找

  • list/l 函数名(从指定的函数显示)
    • 注意:是把函数名行放中间显示

GDB按函数名查找

# 3.5 查看非当前文件代码

  • list/l 文件名:行号
  • list/l 文件名:函数名

查看非当前文件代码1

  • 这里我们用的 cpp 文件,所以要使用 g++
  • 进入列表后默认显示的main文件,因为我们可执行文件的入口就是main

查看非当前文件的代码

  • 两个语句的效果就是,从mian.cpp 里跳到 bubble.cpp 里的第一行,再跳到 select.cpp 里的第10行

查看非当前文件代码

  • 这里是查看 select.cpp 文件下的 selectSort 函数位置(中间显示)
    • 这里什么还是 select.cpp 文件,其实应该不算非当前文件跳转,不过意思知道就好
  • 第二个黄色框,我们按的回车,按理说回车应该是执行上次执行的代码,但是这里其实执行的是list,所以就继续往下显示

# 3.6 设置显示的行数

  • 设置显示的行数
    • show list/listsize
    • set list/listsize 行数

设置与显示行数

  • 这里是接着 3.5 最后图片的代码写的
  • 💛: 我们在查看非当前文件代码时要加设置行数,不能直接写文件名
  • 💦:查看 显示的行数
  • 🪀:设置显示的行数

# 4、GDB命令 - 断点

  • 设置断点
    • b/break 行号
    • b/break 函数名
    • b/break 文件名:行号
    • b/break 文件名:函数
      • 文件:函数名方法打的断点不是在

设置断点

设置断点2

注意:上图最后的断点, b bubble.cpp:bubbleSort 
这里的断点不是打在 定义函数这一行,而是进入这个函数体的第一行
//以下是 bullee.cpp的部分代码,可见,我们断点是打在函数体的第一个语句位置

1	#include "sort.h"
2	#include <iostream>
3	
4	using namespace std;
5	
6	void bubbleSort(int *array, int len) {
7	
8	    for (int i = 0; i < len - 1; i++) {
9			for (int j = 0; j < len - 1 - i; j++) {
10				if (array[j] > array[j + 1]) {
(gdb) 
11					int temp = array[j];
12					array[j] = array[j + 1];
13					array[j + 1] = temp;
14				}
15			}
16		}
17	
18	}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  • 查看断点
    • i/info b/break
  • 删除断点
    • d/del/delete 断点编号
    • 注意删除断点时使用的是断点编号

删除与查看断点

  • 设置断点无效
    • dis/disable 断点编号
  • 设置断点生效
    • ena/enable 断点编号

设置断点是否生效

  • 设置条件断点(一般用在循环的位置)
    • b/break 10 if i==5

设置条件断点

# 5、GDB命令 - 调试命令

# 5.1 运行GDB程序

  • start(程序停在第一行)

    • 想从头开始就用 start
    • 执行程序入口就是main()函数,所以start会停在mian()这一行
  • run(遇到断点才停)

    • 想从第一个断点开始就用 run
  • 这里是gdb main启动调试程序,因为是重新启动,所以之前打的断点就都没了

  • 因为我们这里没断点,所以无论run还是continue都直接到完成执行了

运行GDB程序

# 5.2 继续运行,到下一个断点停

  • c/continue

# 5.3 向下执行一行代码(不会进入函数体)

  • n/next

    • //main.cpp
      8	    int array[] = {12, 27, 55, 22, 67};
      9	    int len = sizeof(array) / sizeof(int);
      10	
      11	    bubbleSort(array, len);
      12	    
      
          //好比我们把断点打在第8行,然后我们run到这个断点,再执行三次next,会发现第二次next是跳到了 14行  
          //cout << "冒泡排序之后的数组: ";
          //而不是进入了这个函数体内,step就会进入到函数体里
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
    • 总结:

      • next会 直接执行完函数调用
      • step 会进入函数内部

# 5.4 变量操作

  • p/print 变量名(打印变量值)
  • ptype 变量名(打印变量类型)

# 5.5 向下单步调试(遇到函数进入函数体)

  • s/step
  • finish(跳出函数体)
    • 注意:要跳出函数,函数内不能有断点,有断点要先删除断点再跳出

GDB进入与退出函数体

# 5.6 自动变量操作

  • display 变量名(自动打印指定变量的值)
  • i/info display
  • undisplay 编号
一下我们用test 来测试,所以我们这里看一下test.c 文件的代码
  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 
  4 int test(int a);
  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 
  4 int test(int a);
  5 
  6 int main(int argc, char* argv[]) {
  7     int a, b;
  8     printf("argc = %d\n", argc);
  9 
 10     if(argc < 3) {
 11         a = 10;
 12         b = 30;
 13     } else {
 14         a = atoi(argv[1]);
 15         b = atoi(argv[2]);
 16     }
 17     printf("a = %d, b = %d\n", a, b);
 18     printf("a + b = %d\n", a + b);
 19 
 20     for(int i = 0; i < a; ++i) {
 21         printf("i = %d\n", i);
 22         // 函数调用
 23         int res = test(i);
 24         printf("res value: %d\n", res);
 25     }
 26 
 27     printf("THE END !!!\n");
 28     return 0;
 29 }
 30 
 31 int test(int a) {c
 32     int num = 0;
 33     for(int i = 0; i < a; ++i) {
 34         num += i;
 35     }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
  • 先打几个断点 , 注意: 第二个断点是条件断点
  • run 到第一个断点
  • print a和 print b 打印变量a , b 的值
  • n进入下一个语句,这里的语句是循环
  • display a和 display b 将变量 a 和 b设置为自动显示

自动变量操作1

  • info display 查看自动变量
  • undisplay 1 删除编号为1的 自动变量
  • 注意:红色框执行 next后,没有自动显示变量是因为,next后进入了 text() 函数,函数内没有 b (应该是因为没有b?)

自动变量操作2

# 5.7 设置变量值与跳出循环

  • set var 变量名=变量值 (循环中用的较多)
  • until (跳出循环)

gdb设置变量值

# 5.8 命令测试

  • 先进入 gdb 打下断点,然后 run到第一个断点处,再使用 continue/c继续运行到下一个断点

GDB命令测试1

  • 接上图,我们跳到了第二个断点处,即bubble.cpp 的 bubbleSort 函数里
    • next 向下执行一行代码,所以到了 9
    • step/s 向下单步调试 ,就到了 10行
      • step 和 next很像,但是step遇到函数会进入函数体,next不会进入函数体

GDB命令测试2

  • 再次使用 continue/c命令,跳到了第3个断点处
    • 可以看到这个断点是在一个for循环里,因此我们再使用continue命令,就是进入下次循环的当前位置
    • 同时,查看断点会发现,gdb会记录断点被击中次数

GDB命令测试3

  • 注意,下图我们击中2次后继续contimue,那就是击中3次,但是输出i=2是因为,i是从0开始的

GDB命令测试4

  • 此时,我们不想继续在循环里了,那就要用 until跳出循环
    • 但是这里我们发现我们执行完 until后,到了第15行,就是循环判断这一行,是因为,我们跳出循环,就是在循环判断失败时跳出
  • 再执行 step,向下单步调试,又进入到了循环里,为什么呢?
    • 是因为挑出循环需要满足两个条件
        1. 循环里面不能有断点
        2. 要执行循环的最后一行,就是循环判断那行(这个我们使用 until就满足了)
  • 所以我们必须把循环里的断点删除(delete/del/d 4) ,再 until跳出循环,就成功了
  • 使用 step发现成功跳出,继续 continue,无断点了

GDB命令测试5

编辑 (opens new window)
上次更新: 2023/09/13, 12:29:52
文件IO

文件IO→

最近更新
01
51单片机及补充知识
09-13
02
独立按键
09-13
03
LCD1602液晶显示器
09-13
更多文章>
Theme by Vdoing | Copyright © 2019-2023 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式