Python 系列(六十四) 从零开始学习 pdb 命令行调试
1 | 作者:李晓辉 |
- Python 系列(一) 从零开始认识 Python,入门其实没那么难
- Python 系列(二) 学 Python 的第一步,把开发环境搭建好
- Python 系列(三) 基础语法避坑指南,缩进、大小写、续行与注释
- Python 系列(四) Python 输入输出详解,print()、input() 与常见避坑指南
- Python 系列(五) 数值类型与运算符详解,整除、浮点精度与进制转换实战
- Python 系列(六) 字符串处理实战,查找、替换、切片与日志分析
- Python 系列(七) 类型转换详解,int()、float()、str() 与金额计算避坑
- Python 系列(八) Python 流程控制入门,掌握 if、elif 和 else
- Python 系列(九) Python 逻辑运算符详解,and、or、not 一篇搞懂
- Python 系列(十) while、for 循环实战,彻底搞懂 range、break 与 continue
- Python 系列(十一) list、tuple、set、dict,到底该怎么选?
- Python 系列(十二) List 列表实战,搞定数据增删改查与批量处理
- Python 系列(十三) Tuple 元组实战,搞懂不可变特性与 enumerate() 遍历
- Python 系列(十四) Set 集合实战,搞定数据去重与集合运算
- Python 系列(十五) Dict 字典详解,掌握快速查找与键值操作
- Python 系列(十六) sort() 和 sorted() 详解,掌握数据排序技巧
- Python 系列(十七) 自定义函数入门,让重复代码一次编写多次使用
- Python 系列(十八) 函数参数详解,搞懂位置参数、关键字参数和默认参数
- Python 系列(十九) 从 *args 到 **kwargs,让函数调用更灵活
- Python 系列(二十) 变量作用域是什么?一文搞懂 global 和 nonlocal
- Python 系列(二十一) 搞懂变量、对象和引用,理解函数参数传递机制
- Python 系列(二十二) map() 和 filter() 详解,轻松实现批量数据处理与筛选
- Python 系列(二十三) 嵌套函数与 lambda 表达式详解,搞懂作用域与闭包
- Python 系列(二十四) 搞懂递归函数,从基线条件到实际应用
- Python 系列(二十五) 聊聊模块与 import,让代码告别重复编写
- Python 系列(二十六) Python 模块入门,从 import 到模块复用
- Python 系列(二十七) Python dir() 函数详解,快速查看模块中的函数与属性
- Python 系列(二十八) 不会用 Python 标准库?教你快速查找模块和函数
- Python 系列(二十九) 搞懂 Python sys 模块,让脚本更适合自动化运维
- Python 系列(三十) 为什么 0.1 + 0.2 不等于 0.3?彻底搞懂 Decimal
- Python 系列(三十一) 搞懂 datetime 日期时间,轻松处理时区与时间计算
- Python 系列(三十二) 面向对象编程入门,从零理解类、对象、封装与继承
- Python 系列(三十三) 什么是类和对象?零基础理解 Python class
- Python 系列(三十四) Python 属性详解,搞懂类属性、实例属性和 setter
- Python 系列(三十五) 学会特殊方法,让对象打印出你想要的内容
- Python 系列(三十六) 多个对象如何共享数据?彻底搞懂类变量
- Python 系列(三十七) 搞懂父类、子类和方法重写,真正理解继承与多态
- Python 系列(三十八) 如何判断数据类型?三个内置函数一次讲明白
- Python 系列(三十九) 搞懂异常模型与常见异常类型,读懂程序报错
- Python 系列(四十) 从零学会异常处理,掌握 try、except、else 和 finally
- Python 系列(四十一) ValueError、TypeError 到底是什么?一文搞懂常见异常
- Python 系列(四十二) ValueError、TypeError 到底是什么?一文搞懂常见异常
- Python 系列(四十三) 别只会处理异常!学会自定义异常,让程序更容易维护
- Python 系列(四十四) 从零认识 assert 断言,让程序学会检查自己
- Python 系列(四十五) 使用 sys 模块处理标准输入与输出
- Python 系列(四十六) 使用 open() 读取和写入文件
- Python 系列(四十七) 学会读写文本文件,让程序保存运行结果
- Python 系列(四十八) 搞懂 bytes、bytearray 和二进制读写
- Python 系列(四十九) 使用 seek() 和 tell() 灵活控制文件读取位置
- Python 系列(五十) 掌握 os 和 os.path,实现文件与目录自动化管理
- Python 系列(五十一) 掌握列表推导式,轻松实现列表生成与数据筛选
- Python 系列(五十二) 掌握字典推导式,让字典创建更简单
- Python 系列(五十三) 从零理解生成器,轻松处理大量数据
- Python 系列(五十五) 使用 zip() 同时遍历多个集合
- Python 系列(五十六) 使用 re 模块进行正则表达式匹配
- Python 系列(五十七) 理解正则表达式的字符类和量词
- Python 系列(五十八) 搞懂正则表达式中的分组与量词
- Python 系列(五十九) 学会正则捕获组,轻松提取文本数据
- Python 系列(六十) 掌握 re 模块的常用函数
- Python 系列(六十一) JSON 是什么?语法规则、数据类型与校验方法
- Python 系列(六十二) 使用 pdb 逐步调试程序,定位代码问题
- Python 系列(六十三) 在交互式 Python shell 中启动调试器
- Python 系列(六十四) 从零开始学习 pdb 命令行调试
- Python 系列(六十五) 使用 pdb 设置断点并检查程序执行上下文
编写 Python 程序时,即使代码没有语法错误,也可能出现结果不符合预期的情况。例如,服务器巡检脚本明明读取到了 CPU 使用率,却没有输出预期的告警信息。遇到这种问题,我们就需要检查程序的执行过程,找出问题出现在哪里。
前面我们学习了 Python 标准库 pdb,知道可以通过 pdb.set_trace() 在程序中设置调试入口,也可以使用 python -m pdb 从命令行启动调试器。
进入调试器之后,应该怎样一步步检查程序?如何查看变量、进入函数、设置断点,以及让程序继续执行?
本篇文章将通过一个完整的 Linux 服务器巡检示例,统一介绍命令行调试流程和常用的 pdb 调试命令。
准备需要调试的 Python 程序
假设我们正在编写一个服务器巡检程序,需要根据 CPU 使用率判断服务器是否存在性能风险。
创建一个名为 cpu_check.py 的文件,写入以下代码:
1 | def calculate_cpu(cpu_usage): |
这段代码包含两个函数:
calculate_cpu():判断 CPU 使用率是否达到 80%。check_server():调用calculate_cpu(),根据返回结果输出告警信息。
程序最后执行 check_server(85),传入 CPU 使用率 85。
正常情况下,运行程序会得到以下输出:
1 | 开始检查服务器 |
这段程序虽然很简单,但包含了函数调用、变量赋值、条件判断和函数返回等常见操作,适合用来练习调试器命令。
从命令行启动 pdb 调试器
pdb 是 Python 标准库提供的调试器,不需要额外安装第三方软件。
进入保存 cpu_check.py 的目录,在终端中执行:
1 | python -m pdb cpu_check.py |
如果当前环境使用 python3 命令,则执行:
1 | python3 -m pdb cpu_check.py |
这里的 -m pdb 表示让 Python 以模块运行方式启动 pdb,并由它调试后面的 cpu_check.py 文件。
首次启动时,调试器通常会显示类似下面的信息:
1 | > cpu_check.py(1)<module>() |
这里需要理解几个关键内容:
cpu_check.py:当前正在调试的 Python 文件。(1):当前显示的源代码行号。<module>:表示当前位于模块顶层代码中,还没有进入某个函数。->:表示当前显示的待执行代码行。(Pdb):表示调试器正在等待输入命令。
使用 python -m pdb cpu_check.py 启动时,调试器默认会先在模块第一行进入调试状态,而不是直接把程序执行到底。
因此,首次启动后,我们可以先检查代码、设置断点,也可以直接使用 n、s 等命令开始调试。
后续所有调试命令都在 (Pdb) 提示符下输入,而不是写进 Python 源代码文件。
使用 l 命令查看源代码
刚进入调试器时,我们可能还不知道程序会执行哪些代码。
此时可以输入:
1 | l |
l 是 list 的缩写,用于查看当前调试位置附近的源代码。
调试器会显示类似下面的内容:
1 | (Pdb) l |
实际显示的行号和格式可能因代码内容而有所不同。
-> 表示调试器当前关注的代码位置。刚启动时,箭头指向第一行函数定义。此时函数还没有执行,程序也还没有调用 check_server(85)。
如果想查看更多代码,可以继续输入:
1 | l |
也可以指定希望查看的行号,例如:
1 | l 9 |
这样就可以查看第 9 行附近的源代码。
使用 b 命令设置断点
逐行执行整个程序虽然可以了解执行过程,但当代码很多时,这种方式会比较耗时。
例如,我们只关心 check_server() 函数中的条件判断,不想每次都从第一行开始逐步执行。这时就可以设置断点。
断点可以理解为程序中的一个暂停位置。程序运行到断点对应的位置时,调试器会暂停执行,让我们检查变量或执行其他调试命令。
在指定行设置断点
在前面的程序中,第 14 行是条件判断:
1 | if result: |
我们希望程序运行到这里时暂停,可以输入:
1 | b 14 |
这里的 b 是 break 的缩写,14 表示希望设置断点的源代码行号。
调试器会显示类似下面的结果:
1 | (Pdb) b 14 |
这表示已经成功设置了编号为 1 的断点,位置是 cpu_check.py 的第 14 行。
需要注意,设置断点时应选择可以执行的代码位置。对于空行、注释行或其他不可执行的位置,调试器可能无法按预期暂停。
查看已经设置的断点
断点设置完成后,可以输入:
1 | b |
或者使用完整命令:
1 | break |
调试器会列出当前的断点信息,输出类似:
1 | (Pdb) b |
这里最值得关注的是:
Num:断点编号,后续可以通过编号管理断点。Enb:断点是否启用,yes表示启用。Where:断点所在的位置。
断点编号不一定永远是 1,它取决于设置断点的顺序。
通过函数名设置断点
除了指定行号,也可以通过函数名设置断点。
例如:
1 | b calculate_cpu |
这表示在 calculate_cpu() 函数入口设置断点。
调试器会找到这个函数对应的位置,并在执行到该函数入口时暂停。
这种方式适合我们只关心某个函数的执行过程,而不想记住具体行号的情况。
禁用和启用断点
如果暂时不想让某个断点生效,但又不想删除它,可以使用 disable 命令。
例如,禁用编号为 1 的断点:
1 | disable 1 |
如果之后需要重新启用,可以输入:
1 | enable 1 |
其中,1 是前面显示的断点编号。
删除断点
如果不再需要某个断点,可以通过 clear 命令删除。
例如:
1 | clear 1 |
如果调试器要求确认删除,根据提示输入 y 并按回车即可。
也可以使用:
1 | clear |
按照调试器提示清除所有断点。
不同操作的区别如下:
| 命令 | 作用 |
|---|---|
b 14 | 在第 14 行设置断点 |
b calculate_cpu | 在指定函数入口设置断点 |
b | 查看断点列表 |
disable 1 | 禁用编号为 1 的断点 |
enable 1 | 启用编号为 1 的断点 |
clear 1 | 删除编号为 1 的断点 |
设置断点后,还需要让程序继续执行,程序才有机会运行到断点所在的位置。接下来,我们就通过 c 命令完成这个过程。
使用 c 命令运行到断点
在刚才的命令行调试过程中,我们已经设置了第 14 行断点。
此时输入:
1 | c |
c 是 continue 的缩写,表示继续执行程序。
调试器会从当前暂停的位置继续运行,直到遇到下一个有效断点,或者程序正常结束、发生异常等情况。
由于我们在第 14 行设置了断点,因此程序会依次执行函数定义、调用 check_server(85),然后进入 calculate_cpu() 函数完成 CPU 使用率判断。
随后,程序进入 check_server() 函数,在执行到第 14 行时暂停。
调试器会显示类似下面的信息:
1 | 开始检查服务器 |
这时我们可以确认,程序已经运行到了目标位置。
注意,断点通常会让程序暂停在对应语句即将执行的位置。因此,虽然我们已经看到前面的输出,但条件判断以及后续分支代码还没有执行。
断点最重要的用途,就是让程序自动运行到我们关心的位置,而不是要求我们手动执行每一行代码。
使用 p 命令查看变量
程序暂停在第 14 行时,我们想知道条件判断为什么会进入某个分支,就需要检查变量的值。
p 是 print 的缩写,可以计算并显示变量或表达式的结果。
输入:
1 | p result |
预期输出:
1 | True |
这说明当前 check_server() 函数中的 result 值为 True。
我们还可以继续查看:
1 | p cpu_usage |
预期输出:
1 | 85 |
这说明当前函数接收到的 CPU 使用率是 85。
也可以直接检查条件:
1 | p cpu_usage >= 80 |
预期输出:
1 | True |
因为 85 大于等于 80,所以条件成立,程序应该进入 if 分支。
这里的 p 只负责显示表达式的计算结果,不会自动把结果保存到变量中。
例如:
1 | p cpu_usage + 5 |
输出:
1 | 90 |
这并不会改变 cpu_usage 的值。
如果我们想知道一个程序为什么进入了某个分支,可以先查看条件相关的变量,再检查条件表达式的计算结果。
使用 pp 命令查看复杂数据
在服务器巡检程序中,我们可能会处理多个服务器的信息。这些信息通常使用字典或列表保存。
例如:
1 | servers = { |
如果程序当前作用域中存在 servers 变量,可以输入:
1 | pp servers |
pp 是 pprint 的缩写,会以更便于阅读的方式显示复杂数据结构。
对于简单变量,使用 p 就足够了;如果需要查看嵌套字典、较长的列表或 API 返回的数据,pp 往往更方便。
使用 n 命令逐行执行代码
断点让程序停在目标位置之后,我们还可以通过 n 逐步检查后面的执行过程。
n 是 next 的缩写,表示执行下一行代码,然后再次暂停。
在第 14 行断点处,输入:
1 | n |
调试器会执行条件判断。由于 result 为 True,接下来会停在 if 分支中的:
1 | print("警告:CPU 使用率过高") |
此时再次输入:
1 | n |
程序就会执行这条 print() 语句,输出:
1 | 警告:CPU 使用率过高 |
之后,调试器会停在后续待执行的代码位置。
需要注意,n 会实际执行当前代码,而不仅仅是移动到下一行。如果执行的是函数调用,通常会执行完整个函数调用,再停在调用语句之后的位置。
例如:
1 | result = calculate_cpu(cpu_usage) |
如果使用 n 执行这一行,调试器通常不会进入 calculate_cpu() 函数内部逐行检查,而是运行函数后返回结果。
如果我们希望进入函数内部检查,就需要使用 s。
使用 s 命令进入函数内部
s 是 step 的缩写,表示单步执行。
与 n 不同,如果当前代码调用了另一个函数,s 可以进入函数内部。
例如,重新启动调试器:
1 | python -m pdb cpu_check.py |
在 (Pdb) 提示符下,先输入:
1 | b calculate_cpu |
再输入:
1 | c |
程序会继续运行,并在进入 calculate_cpu() 函数时暂停。
调试器会显示类似下面的信息:
1 | > cpu_check.py(2)calculate_cpu() |
此时可以检查函数参数:
1 | p cpu_usage |
输出:
1 | 85 |
然后输入:
1 | n |
程序执行打印语句,并停在后续代码位置。
继续输入:
1 | n |
程序执行:
1 | result = cpu_usage >= 80 |
这时可以查看:
1 | p result |
输出:
1 | True |
这说明条件判断的结果已经保存在变量中。
n 和 s 的区别可以总结为:
n:执行下一行,通常不进入函数调用内部。s:执行下一步,遇到函数调用时可以进入函数内部。
如果怀疑某个函数内部的逻辑存在问题,可以使用 s 进入函数逐步检查。
使用 w 命令查看函数调用关系
w 是 where 的缩写,用于显示当前的调用栈。
调用栈可以理解为:程序为了到达当前执行位置,经过了哪些尚未返回的函数调用。
例如,我们的程序调用关系是:
1 | 模块顶层代码 |
当程序在 calculate_cpu() 内部暂停时,输入:
1 | w |
调试器会显示当前调用路径,帮助我们了解程序是如何进入这个函数的。
当程序包含很多层函数调用,或者某个函数是通过其他模块间接调用时,调用栈就特别有用。
需要注意,调用栈描述的是当前仍处于调用过程中的函数关系,并不是程序执行过的所有代码记录。
使用 r 命令运行到当前函数返回
r 是 return 的缩写,在 pdb 中表示继续执行,直到当前函数返回。
例如,我们已经进入 calculate_cpu() 函数,并且确认函数参数没有问题。如果不想再逐行检查函数剩余部分,可以输入:
1 | r |
调试器会继续执行当前函数,直到函数即将返回调用者的位置。
这适合我们只关心函数最终返回结果,而不需要检查每一行代码的情况。
需要注意,r 的目标是当前函数返回,而不是无条件运行到整个程序结束。
使用 h 命令查看帮助
如果忘记了某个调试命令,可以输入:
1 | h |
调试器会显示可用命令的帮助信息。
如果只想查看某个命令的用法,可以输入:
1 | h next |
或者:
1 | h break |
这样就能查看 next 或 break 命令的具体说明。
不必一次记住所有命令。遇到不熟悉的命令时,可以先通过帮助信息了解用法。
使用 q 命令退出调试器
当我们已经完成检查,或者决定不再继续调试时,可以输入:
1 | q |
q 是 quit 的缩写,会退出当前调试会话并终止被调试的程序。
需要注意,q 和 c 的作用完全不同:
c:继续运行程序,直到下一个断点或程序结束。q:退出调试器,终止当前被调试程序。
如果只是暂时不想逐行检查,但仍然希望程序继续运行,应使用 c,而不是 q。
完整命令行调试实战
前面分别介绍了常用命令,现在将它们整合到一个完整的命令行调试流程中。
继续使用前面的 cpu_check.py 文件。为了避免每次都手动执行多条 n 命令,我们先设置断点,再让程序自动运行到指定位置。
启动调试器并设置断点
执行:
1 | python -m pdb cpu_check.py |
在 (Pdb) 提示符下输入:
1 | b 14 |
这会在第 14 行的条件判断处设置断点。
接着输入:
1 | b |
确认断点已经设置成功。
继续运行到断点
输入:
1 | c |
程序开始执行,输出:
1 | 开始检查服务器 |
随后在第 14 行暂停:
1 | > cpu_check.py(14)check_server() |
这时说明程序已经执行完函数调用,并准备判断 result 的值。
检查变量和条件表达式
依次输入:
1 | p cpu_usage |
输出:
1 | 85 |
继续输入:
1 | p result |
输出:
1 | True |
再输入:
1 | p result == True |
输出:
1 | True |
通过这些检查,我们可以确认变量值和条件判断都符合预期。
单步执行并观察输出
输入:
1 | n |
程序执行条件判断,并停在告警语句附近。
再次输入:
1 | n |
程序输出:
1 | 警告:CPU 使用率过高 |
调试器接着停在后续代码位置。
如果希望查看当前调用栈,可以输入:
1 | w |
如果想查看附近的源代码,可以输入:
1 | l |
这两条命令不会像 n 那样推进程序执行位置,而是帮助我们了解当前的执行环境。
继续运行到程序结束
确认当前分支没有问题后,输入:
1 | c |
由于后面没有其他断点,程序会继续执行,输出:
1 | 服务器检查完成 |
调试会话结束后,就完成了这次命令行调试。
整个过程可以概括为:
1 | 启动 pdb |
这就是一个比较完整的命令行调试流程:先确定关注的位置,再通过断点让程序自动运行到那里,然后检查变量和代码,最后决定继续执行还是退出调试。
动手练习:自己设置和管理断点
现在请尝试独立完成一次调试。
继续使用前面的 cpu_check.py 文件。先确认源代码没有发生变化,再按照下面的步骤操作。
启动调试器并设置两个断点
在终端中执行:
1 | python -m pdb cpu_check.py |
在调试器中设置两个断点:
1 | b 4 |
第一个断点位于 calculate_cpu() 函数内部,用于检查 CPU 使用率的计算过程;第二个断点位于 check_server() 函数中的条件判断位置。
输入:
1 | b |
查看断点列表。假设第一个断点编号为 1,第二个断点编号为 2,后续请以实际显示的编号为准。
运行到第一个断点
输入:
1 | c |
程序会运行到第 4 行:
1 | result = cpu_usage >= 80 |
此时第 4 行尚未执行。
输入:
1 | p cpu_usage |
预期结果为:
1 | 85 |
接着输入:
1 | n |
调试器执行第 4 行,完成条件判断。
输入:
1 | p result |
预期结果为:
1 | True |
这说明 CPU 使用率判断的结果已经保存到局部变量 result 中。
运行到第二个断点
在第一个断点处完成检查后,输入:
1 | c |
程序会继续执行 calculate_cpu() 的剩余代码,返回判断结果,然后回到 check_server() 函数。
接下来程序会在第 14 行暂停:
1 | if result: |
注意,此时第 14 行的条件判断还没有执行。
输入:
1 | p result |
预期结果为:
1 | True |
这说明 check_server() 接收到的返回结果为 True,程序接下来将进入 if 分支。
禁用第一个断点
假设我们已经检查完第一个断点,暂时不希望程序下次再停在那里,但还想保留它以便以后使用。
输入:
1 | disable 1 |
然后输入:
1 | b |
查看断点列表。第一个断点应显示为禁用状态,第二个断点仍然处于启用状态。
接着输入:
1 | enable 1 |
重新启用第一个断点,再输入:
1 | b |
确认第一个断点已经恢复启用状态。
禁用断点不会让程序立即跳转,也不会撤销已经执行的代码。它只是让调试器在后续执行过程中暂时忽略该断点。
删除第一个断点并验证执行流程
为了清楚观察删除断点后的执行过程,重新启动调试器:
1 | python -m pdb cpu_check.py |
在 (Pdb) 提示符下设置两个断点:
1 | b 4 |
查看断点列表:
1 | b |
假设第一个断点编号为 1,第二个断点编号为 2。后续操作请以实际显示的编号为准。
输入:
1 | c |
程序会运行到第 4 行:
1 | result = cpu_usage >= 80 |
此时第 4 行还没有执行。
现在删除第一个断点:
1 | clear 1 |
如果调试器要求确认,按照提示输入 y 并按回车。
删除完成后,程序仍然暂停在第 4 行。删除断点不会自动执行当前语句,也不会自动跳转到下一个断点。
输入:
1 | c |
程序从当前暂停位置继续执行,完成 CPU 使用率判断,并返回 True。随后程序回到 check_server() 函数,最终在第 14 行的第二个断点处暂停。
调试器会显示类似下面的信息:
1 | > cpu_check.py(14)check_server() |
此时输入:
1 | p result |
预期输出:
1 | True |
这说明程序已经完成 CPU 使用率判断,并将返回值传递给了 check_server() 函数。
接下来输入:
1 | c |
程序执行条件判断,并输出:
1 | 警告:CPU 使用率过高 |
至此,删除第一个断点后的执行流程验证完成。
删除断点只会移除暂停条件,不会改变当前执行位置。程序从哪里继续执行,取决于当前暂停的位置和下一条调试命令。
继续练习删除断点后的行为
如果想进一步验证删除断点的效果,可以再次启动调试器:
1 | python -m pdb cpu_check.py |
重新设置两个断点:
1 | b 4 |
输入:
1 | c |
程序会在第 4 行暂停。此时输入:
1 | clear 1 |
按照提示确认删除。
接着输入:
1 | c |
程序会继续执行,不会再因为第 4 行的断点而暂停。如果第 14 行断点仍然启用,程序依然会在第 14 行暂停。
这说明:删除某个断点,只会让程序不再因为该断点而暂停,不会影响其他断点,也不会改变程序本身的业务逻辑。
常用调试命令速查表
| 命令 | 作用 |
|---|---|
l | 查看附近的源代码 |
n | 执行下一行,通常不进入函数内部 |
s | 单步执行,可以进入函数内部 |
p expression | 查看变量或表达式的值 |
pp expression | 更整齐地显示复杂数据 |
b 14 | 在指定行设置断点 |
b function_name | 在指定函数入口设置断点 |
b | 查看断点列表 |
disable 1 | 禁用编号为 1 的断点 |
enable 1 | 启用编号为 1 的断点 |
clear 1 | 删除编号为 1 的断点 |
c | 继续执行程序 |
r | 运行到当前函数返回 |
w | 查看当前调用栈 |
h | 查看调试器帮助 |
q | 退出调试器并终止被调试程序 |
写在最后
本篇文章学习了 Python 标准库 pdb 中常用的调试命令,并通过完整的命令行调试过程,将启动调试器、设置断点、检查变量、逐步执行和继续运行等操作串联起来。
对于刚开始学习 Python 的同学,建议优先掌握 l、b、c、n、s、p 和 q。这些命令已经能够帮助我们完成许多常见的调试任务。
下一篇将进一步学习如何设置和管理断点,包括如何在不同位置暂停程序,以及如何根据实际调试需求控制断点。
