burpow
第05节-调试器的工作原理

第05节-调试器的工作原理

第 5 节:调试器的工作原理

所属课程:操作系统自学路线:面向网络安全、逆向工程与漏洞分析
所属周次:第 3 周
课程主题:调试器和逆向入门
本节目标:理解调试器为什么能控制程序运行,掌握断点、单步、寄存器、内存、调用栈等基本概念,并初步了解 Linux ptrace 和 Windows Debug API。


1. 本节课你要学会什么

学完这一节,你应该能回答下面几个问题:

  1. 调试器是什么?
  2. 调试器为什么能让程序暂停?
  3. 断点是什么?软件断点和硬件断点有什么区别?
  4. 单步执行是什么?
  5. 为什么调试器能查看寄存器和内存?
  6. GDB、x64dbg、WinDbg 这类工具分别适合做什么?
  7. Linux 下的 ptrace 大致是什么?
  8. Windows 下 Debug API 大致是什么?
  9. 恶意代码为什么经常检测调试器?
  10. 调试器在逆向工程和漏洞分析中有什么作用?

本节课的主线是:

1
进程运行 -> 调试器附加/启动 -> 设置断点 -> 程序暂停 -> 查看现场 -> 单步执行 -> 分析行为

你不需要现在就掌握所有高级调试技巧,但必须建立“程序运行现场”的概念。


2. 调试器是什么

调试器是一类可以观察和控制程序运行的工具。

它可以让你:

  • 启动一个程序
  • 附加到一个正在运行的程序
  • 让程序暂停
  • 设置断点
  • 单步执行指令
  • 查看寄存器
  • 查看内存
  • 查看调用栈
  • 查看线程
  • 修改寄存器或内存
  • 捕获崩溃现场

常见调试器:

工具 平台 常见用途
GDB Linux / Unix C/C++ 调试、漏洞分析、Pwn 入门
LLDB Linux / macOS C/C++/Swift 调试
x64dbg Windows 用户态 PE 程序动态调试、逆向工程
WinDbg Windows 用户态/内核态调试、崩溃分析
IDA Debugger 多平台 静态 + 动态结合分析
Ghidra Debugger 多平台 静态 + 动态结合分析

从操作系统角度看,调试器本质上是一个特殊的进程。

它可以通过操作系统提供的调试接口控制另一个进程。


3. 为什么要学调试器

对于网络安全方向来说,调试器非常重要。

3.1 写程序时用调试器

普通开发中,调试器可以帮助你找 bug。

例如:

  • 变量为什么是错误值
  • 程序在哪里崩溃
  • 函数是否被调用
  • 条件分支是否走对

3.2 逆向工程中用调试器

逆向时,你经常没有源码。

调试器能让你直接观察二进制程序运行过程:

  • 程序入口点
  • API 调用
  • 参数
  • 返回值
  • 解密后的字符串
  • 内存变化
  • 条件判断
  • 反调试逻辑

静态分析回答:

1
程序可能会做什么

动态调试回答:

1
程序实际运行时做了什么

3.3 漏洞分析中用调试器

漏洞分析中,调试器可以帮助你观察崩溃现场。

你可以看到:

  • 崩溃发生在哪条指令
  • rip 是什么
  • rsp 指向哪里
  • 输入是否进入栈或堆
  • 哪个寄存器异常
  • 返回地址是否被覆盖
  • 防护机制是否触发

例如:

1
RIP = 0x4141414141414141

这可能说明输入中的 A 影响了程序控制流。


3.4 恶意代码分析中用调试器

恶意代码分析中,调试器可以帮助你:

  • 跟踪解密函数
  • 观察 API 参数
  • 跳过无关逻辑
  • 捕获网络地址
  • 查看运行时配置
  • 分析反调试
  • 找到真实入口点

很多恶意代码会把关键字符串加密,静态看不到。

但运行时一定要解密使用。

调试器可以在解密后暂停程序,直接查看内存。


4. 调试器如何控制程序

普通进程之间不能随便互相控制。

操作系统提供了专门的调试机制。

Linux 下常见机制是:

1
ptrace

Windows 下常见机制是:

1
Debug API

调试器通过这些接口告诉操作系统:

1
我要调试这个进程

之后,操作系统允许调试器对被调试进程执行一些特殊操作,例如:

  • 读取寄存器
  • 修改寄存器
  • 读取内存
  • 修改内存
  • 暂停进程
  • 恢复进程
  • 捕获异常
  • 接收断点事件

可以理解为:

1
调试器不是靠魔法控制程序,而是通过操作系统授权的调试接口控制程序。

5. 被调试程序和调试器的关系

调试中通常有两个角色:

1
2
Debugger:调试器
Debuggee:被调试程序

例如:

1
gdb ./demo

这里:

  • gdb 是调试器
  • demo 是被调试程序

调试器可以:

  1. 启动被调试程序
  2. 在程序运行前设置断点
  3. 让程序运行
  4. 程序触发断点后暂停
  5. 调试器读取现场
  6. 用户决定继续、单步或退出

6. 什么是断点

断点就是:

让程序运行到某个位置时自动暂停。

例如在 GDB 中:

1
2
break main
run

意思是:

1
程序运行到 main 时暂停

在 x64dbg 中,你可以在某条指令上按 F2 设置断点。

程序运行到这条指令时会停下来。

断点的作用是让你在关键位置观察程序状态。

例如:

  • 函数刚进入时
  • API 被调用前
  • 崩溃前
  • 字符串解密后
  • 判断密码是否正确前
  • 内存被写入前后

7. 软件断点的基本原理

最常见的断点是软件断点。

在 x86/x86-64 中,软件断点常用指令:

1
int3

它的机器码是:

1
0xCC

调试器设置软件断点时,通常会:

  1. 读取目标地址原来的 1 字节指令
  2. 保存这个原始字节
  3. 把目标地址处的字节改成 0xCC
  4. 程序运行到这里时执行 int3
  5. CPU 触发断点异常
  6. 操作系统通知调试器
  7. 调试器暂停程序并恢复现场

例如原来代码:

1
55 48 89 e5

设置断点后可能变成:

1
CC 48 89 e5

当程序执行到 CC 时,就会触发断点异常。

这说明一个重要点:

软件断点本质上会临时修改被调试程序的代码字节。

这也是为什么某些反调试技术会检查代码中是否存在 0xCC


8. 硬件断点的基本概念

硬件断点不需要修改代码字节。

x86/x86-64 CPU 提供了一组调试寄存器,例如:

1
DR0, DR1, DR2, DR3, DR6, DR7

调试器可以利用这些寄存器设置断点。

硬件断点可以用于:

  • 执行断点
  • 读内存断点
  • 写内存断点
  • 访问内存断点

例如你想知道:

1
谁修改了这个变量?

可以对变量地址设置硬件写入断点。

当程序写这个地址时,CPU 会触发调试异常。

硬件断点优点:

  • 不修改代码
  • 可监控内存访问

缺点:

  • 数量有限
  • 也可能被反调试检测

9. 单步执行是什么

单步执行就是让程序一次只执行很小的一步。

常见有两类:

1
2
源码级单步
指令级单步

9.1 源码级单步

如果程序有调试符号,调试器可以按源代码行单步。

例如 GDB:

1
2
step
next
  • step:进入函数
  • next:不进入函数,把函数调用当作一行执行完

9.2 指令级单步

指令级单步是一次执行一条机器指令。

GDB:

1
2
stepi
nexti
  • stepi:执行一条指令,如果是 call 会进入函数
  • nexti:执行一条指令,如果是 call 通常不进入函数

x64dbg:

  • F7:单步进入
  • F8:单步跳过

指令级单步是逆向和漏洞分析中非常常用的能力。


10. Step Into 和 Step Over

调试器里常见两个概念:

1
2
Step Into
Step Over

10.1 Step Into

遇到函数调用时进入函数内部。

例如:

1
foo();

Step Into 会进入 foo

适合:

  • 你想分析这个函数内部逻辑
  • 你不知道它做了什么
  • 它可能是关键函数

10.2 Step Over

遇到函数调用时不进入函数内部,而是让它直接执行完。

适合:

  • 你不关心库函数内部
  • 你只想知道函数返回后结果
  • 避免进入大量系统库代码

例如:

1
printf("hello\n");

一般不需要进入 printf 内部。


11. Continue 和 Run Until

11.1 Continue

继续运行程序直到下一个事件。

GDB:

1
continue

x64dbg:

1
F9

事件可能是:

  • 命中断点
  • 程序崩溃
  • 程序退出
  • 收到信号或异常

11.2 Run Until

运行到某个指定位置。

GDB:

1
until

x64dbg 中可以右键选择运行到选中位置,或使用快捷操作。

适合跳过循环或无关代码。


12. 寄存器窗口怎么看

调试器中,寄存器窗口非常重要。

x86-64 常重点看:

寄存器 关注原因
RIP 当前执行位置
RSP 当前栈顶
RBP 当前栈帧基址
RAX 返回值、计算结果
RDI/RSI/RDX/RCX/R8/R9 Linux x64 参数相关
RCX/RDX/R8/R9 Windows x64 参数相关
EFLAGS/RFLAGS 条件跳转相关标志

例如你在 Linux 下停在某个函数入口:

1
2
rdi = 0x7fffffffe3a0
rsi = 0x10

根据调用约定,可以推测:

1
2
第 1 个参数可能是 0x7fffffffe3a0
第 2 个参数可能是 0x10

如果第 1 个参数是字符串指针,可以用 GDB 查看:

1
x/s $rdi

13. 内存窗口怎么看

调试器可以查看进程内存。

GDB 常用命令:

1
2
3
4
x/20gx $rsp
x/20i $rip
x/s $rdi
x/32bx $rsp

含义:

命令 用途
x/20gx $rsp 从栈顶开始,以 8 字节十六进制显示 20 个单位
x/20i $rip 从当前指令开始显示 20 条汇编指令
x/s $rdi 把 rdi 指向的内存当字符串显示
x/32bx $rsp 从栈顶开始,以字节形式显示 32 个字节

x64dbg 中常见内存区域:

  • Dump 窗口:查看原始内存
  • Stack 窗口:查看栈
  • CPU 窗口:查看当前指令

内存窗口能帮助你判断:

  • 输入是否进入内存
  • 字符串是否被解密
  • 栈上是否有返回地址
  • 堆数据是否被修改
  • 指针指向哪里

14. 调用栈怎么看

调用栈表示当前函数是如何被调用到的。

GDB:

1
bt

可能输出:

1
2
3
#0  bar (z=11) at demo.c:4
#1 foo (x=10) at demo.c:9
#2 main () at demo.c:14

这表示:

1
2
3
main 调用了 foo
foo 调用了 bar
当前停在 bar

调用栈对分析崩溃非常有用。

例如程序崩溃时,bt 可以告诉你:

  • 崩溃发生在哪个函数
  • 是谁调用了它
  • 调用链是否正常
  • 是否因为栈破坏导致调用栈异常

15. 调试符号是什么

调试符号是编译时附加的信息。

它可以告诉调试器:

  • 源文件名
  • 行号
  • 函数名
  • 变量名
  • 类型信息

例如使用:

1
gcc -g demo.c -o demo

-g 会生成调试信息。

有调试符号时,GDB 可以显示:

1
2
3
4
break main
print variable
list
bt

没有调试符号时,调试器仍然可以调试,但更多只能看到:

  • 地址
  • 汇编
  • 寄存器
  • 原始内存

逆向工程中,真实程序经常没有完整符号。

所以你既要会源码级调试,也要逐渐适应汇编级调试。


16. Linux 的 ptrace 是什么

Linux 下调试器常依赖 ptrace

ptrace 是一个系统调用。

它允许一个进程观察和控制另一个进程。

GDB 调试程序时,大致会用 ptrace 做这些事:

  • 启动或附加到目标进程
  • 暂停目标进程
  • 读取寄存器
  • 修改寄存器
  • 读取目标进程内存
  • 修改目标进程内存
  • 让目标进程继续执行
  • 捕获信号和异常

所以:

1
GDB 能调试程序,是因为 Linux 内核提供了 ptrace 这种机制。

从安全角度看,ptrace 也受权限限制。

普通用户通常不能随便调试其他用户的进程。


17. Windows Debug API 是什么

Windows 提供了一组调试相关 API。

常见包括:

  • CreateProcess 配合调试标志启动进程
  • DebugActiveProcess 附加到进程
  • WaitForDebugEvent 等待调试事件
  • ContinueDebugEvent 继续执行
  • ReadProcessMemory 读取进程内存
  • WriteProcessMemory 修改进程内存
  • GetThreadContext 读取线程寄存器上下文
  • SetThreadContext 修改线程寄存器上下文

x64dbg、WinDbg 这类工具底层会使用这些机制。

Windows 调试器可以接收到各种调试事件,例如:

  • 进程创建
  • 线程创建
  • DLL 加载
  • 异常
  • 断点
  • 进程退出

这也是为什么 x64dbg 中能看到 DLL 加载和异常事件。


18. 异常和崩溃

程序崩溃通常和异常有关。

常见异常包括:

  • 访问非法内存
  • 除零错误
  • 执行非法指令
  • 栈溢出
  • 断点异常

Linux 下常见信号:

信号 含义
SIGSEGV 段错误,常见于非法内存访问
SIGILL 非法指令
SIGFPE 算术异常,例如除零
SIGTRAP 跟踪/断点陷阱
SIGABRT 程序主动中止

Windows 下常见异常:

异常 含义
Access Violation 访问违规
Breakpoint Exception 断点异常
Illegal Instruction 非法指令
Stack Overflow 栈溢出

调试器可以在异常发生时暂停程序,让你观察崩溃现场。


19. 反调试是什么

反调试是程序检测或干扰调试器的技术。

恶意代码、加壳程序、商业保护软件中都可能出现反调试。

常见反调试思路包括:

  • 检测是否被调试
  • 检查软件断点 0xCC
  • 检查调试寄存器
  • 检测时间差
  • 使用异常干扰调试流程
  • 检查进程名或窗口名
  • 检查父进程
  • 调用系统 API 判断调试状态

例如 Windows 中常见:

1
2
3
IsDebuggerPresent
CheckRemoteDebuggerPresent
NtQueryInformationProcess

Linux 中可能检查:

1
2
/proc/self/status 中的 TracerPid
ptrace(PTRACE_TRACEME)

本阶段只需要知道:

恶意代码可能会判断自己是否处于调试器中,并根据结果改变行为。


20. 不要把反调试理解成神秘技术

反调试不是魔法。

它本质上还是在利用操作系统和调试机制的特征。

例如:

  • 被调试时,系统中会有调试关系
  • 软件断点会修改代码字节为 0xCC
  • 单步执行会影响时间
  • 调试器可能改变异常处理流程
  • 父进程可能是调试器

所以学习反调试的正确方式是:

1
2
先理解调试器如何工作
再理解程序如何检测这些痕迹

不要一开始就背反调试技巧。


21. Linux 实验:GDB 基础调试

创建 debug_demo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int add(int a, int b) {
int c = a + b;
return c;
}

int main() {
int x = 10;
int y = 20;
int z = add(x, y);

printf("z = %d\n", z);
return 0;
}

编译:

1
gcc -g -O0 debug_demo.c -o debug_demo

启动 GDB:

1
gdb ./debug_demo

执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
break main
run
list
next
next
step
bt
info registers
print a
print b
finish
print z
continue

观察:

  • break main 如何让程序停在 main
  • nextstep 的区别
  • bt 如何显示调用栈
  • print 如何查看变量
  • finish 如何执行到当前函数返回

22. Linux 实验:指令级单步

继续使用 debug_demo

在 GDB 中执行:

1
2
3
4
5
6
7
8
9
10
11
break main
run
disassemble main
x/10i $rip
info registers
stepi
x/10i $rip
info registers
nexti
x/10i $rip
info registers

观察:

  • 每次 stepirip 如何变化
  • 遇到 callstepinexti 的区别
  • 调用 add 前参数寄存器是什么
  • add 返回后 rax/eax 是什么

23. Linux 实验:观察崩溃现场

创建 crash_demo.c

1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main() {
int *p = NULL;
printf("before crash\n");
*p = 123;
printf("after crash\n");
return 0;
}

编译:

1
gcc -g -O0 crash_demo.c -o crash_demo

调试:

1
gdb ./crash_demo

GDB 中执行:

1
2
3
4
run
bt
info registers
x/10i $rip

你可能看到:

1
Program received signal SIGSEGV

思考:

  • 程序为什么崩溃?
  • 崩溃时 rip 指向哪条指令?
  • 哪个寄存器或内存访问有问题?
  • bt 显示的调用栈是什么?

24. Windows 实验:x64dbg 基础使用

准备一个简单 Windows 程序,例如:

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

int add(int a, int b) {
return a + b;
}

int main() {
int z = add(10, 20);
printf("z = %d\n", z);
return 0;
}

用 x64dbg 打开 exe。

练习:

  1. 运行到入口点
  2. 找到 main 附近
  3. 设置断点
  4. 使用 F7 单步进入
  5. 使用 F8 单步跳过
  6. 查看寄存器窗口
  7. 查看栈窗口
  8. 查看 Dump 内存窗口
  9. 找到一次 call
  10. 观察 RAX 中的返回值

重点关注:

  • RIP 当前在哪条指令
  • RCX/RDX/R8/R9 是否用于参数
  • RSP 指向的栈内容
  • call 前后程序如何变化

25. 从逆向工程角度看本节内容

逆向工程中,调试器主要帮助你确认程序真实行为。

例如静态分析时你看到:

1
程序可能会解密字符串

动态调试时,你可以:

  1. 在解密函数下断点
  2. 运行程序
  3. 等它解密完成
  4. 查看内存
  5. 直接看到明文字符串

又比如你看到一个判断:

1
2
cmp eax, 0
jne fail

你可以在这里下断点,观察:

  • eax 是多少
  • 为什么跳到失败分支
  • 修改输入后是否能改变分支

调试器让你从“猜测程序逻辑”变成“观察程序逻辑”。


26. 从漏洞分析角度看本节内容

漏洞分析中,调试器最重要的价值是保存崩溃现场。

崩溃现场包括:

  • 当前指令
  • 寄存器
  • 调用栈
  • 输入数据
  • 信号或异常类型

例如栈溢出分析时,你会关注:

1
2
3
4
5
输入是否出现在栈上
RIP 是否被输入影响
RSP 指向哪里
返回地址是否异常
程序是否触发 Canary 检测

堆漏洞分析时,你会关注:

1
2
3
4
堆块地址
free 后是否继续使用
崩溃时访问了哪个地址
指针是否悬垂

调试器不会自动告诉你漏洞原因,但它会提供现场证据。


27. 从恶意代码分析角度看本节内容

恶意代码分析中,调试器可以帮助你绕过静态混淆。

例如:

  • 静态字符串是加密的
  • API 是动态解析的
  • 控制流被混淆
  • 程序有多层解包逻辑

动态调试可以观察运行时状态:

  • 解密后的字符串
  • 真实 API 地址
  • 真实网络地址
  • 真实配置内容
  • 解包后的代码

但要注意:

分析恶意样本必须在隔离、安全、授权的实验环境中进行。

不要在自己的主力系统上随便运行不可信样本。


28. 调试时的安全注意事项

学习调试器时,建议遵守这些原则:

  1. 只调试自己写的程序、教学样本或授权样本
  2. 恶意代码样本必须放在隔离虚拟机中
  3. 虚拟机尽量断网或使用受控网络
  4. 不要在宿主机运行未知 exe
  5. 不要调试系统关键进程做危险修改
  6. 不要对未授权目标进行动态分析或攻击测试
  7. 实验前保存快照

调试器很强大,也可能造成破坏。

例如修改内存、跳过逻辑、继续执行恶意代码,都可能影响系统状态。


29. 本节重点总结

你需要记住这些核心结论:

  1. 调试器可以观察和控制程序运行。
  2. 调试器依赖操作系统提供的调试机制,不是魔法。
  3. Linux 下常见调试机制是 ptrace
  4. Windows 下有 Debug API,例如 DebugActiveProcessReadProcessMemoryGetThreadContext
  5. 软件断点常通过 int3 / 0xCC 实现。
  6. 硬件断点利用 CPU 调试寄存器,不需要修改代码字节。
  7. 单步执行可以按源码行或机器指令逐步运行。
  8. 寄存器、内存、调用栈是调试现场的核心信息。
  9. 调试器是逆向工程、漏洞分析、恶意代码分析的重要工具。
  10. 恶意代码可能使用反调试技术检测或干扰调试器。

30. 本节课后作业

作业 1:GDB 源码级调试

debug_demo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int add(int a, int b) {
int c = a + b;
return c;
}

int main() {
int x = 10;
int y = 20;
int z = add(x, y);

printf("z = %d\n", z);
return 0;
}

编译:

1
gcc -g -O0 debug_demo.c -o debug_demo

GDB 执行:

1
2
3
4
5
6
7
8
9
10
break main
run
next
step
bt
info registers
print x
print y
print z
continue

提交内容:

1
2
3
4
5
1. 源代码
2. break main 后程序停在哪里
3. step 和 next 的区别
4. bt 输出
5. print x/y/z 的结果

作业 2:GDB 指令级调试

执行:

1
2
3
4
5
6
7
8
9
break main
run
disassemble main
x/10i $rip
info registers
stepi
stepi
nexti
info registers

提交内容:

1
2
3
4
5
1. 至少 5 条你看到的汇编指令
2. stepi 前后 rip 如何变化
3. 是否遇到 call 指令
4. call 前参数寄存器是什么
5. 函数返回后 rax/eax 是什么

作业 3:崩溃现场分析

crash_demo.c

1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main() {
int *p = NULL;
printf("before crash\n");
*p = 123;
printf("after crash\n");
return 0;
}

调试:

1
2
gcc -g -O0 crash_demo.c -o crash_demo
gdb ./crash_demo

GDB 中:

1
2
3
4
run
bt
info registers
x/10i $rip

提交内容:

1
2
3
4
5
1. 程序收到什么信号
2. 崩溃发生在哪一行
3. 崩溃时 rip 指向哪条指令
4. 为什么这是非法内存访问
5. 调用栈是什么

作业 4:x64dbg 基础观察

如果你有 Windows 环境:

  1. 编译一个简单 exe
  2. 用 x64dbg 打开
  3. main 或关键函数附近下断点
  4. 使用 F7 / F8 单步
  5. 查看寄存器、栈、Dump 窗口

提交内容:

1
2
3
4
5
1. 程序名称
2. 断点位置
3. RIP 当前地址
4. 一次 call 前后的寄存器变化
5. RSP 指向的栈内容截图或记录

31. 自测题

题 1

调试器的主要作用是什么?

题 2

软件断点常用哪个机器码字节实现?

题 3

为什么软件断点可能被反调试检测到?

题 4

stepnext 有什么区别?

题 5

stepinexti 有什么区别?

题 6

GDB 中 bt 命令用来做什么?

题 7

Linux 下 GDB 常依赖哪个系统调用控制被调试进程?

题 8

Windows 下读取目标进程内存常见 API 是什么?

题 9

恶意代码为什么要反调试?


32. 自测题参考答案

答 1

调试器用于观察和控制程序运行,包括设置断点、单步执行、查看寄存器、查看内存、查看调用栈和分析崩溃现场。

答 2

x86/x86-64 中软件断点常用 int3 指令实现,它的机器码字节是 0xCC

答 3

因为软件断点通常会把目标代码位置的原始字节临时改成 0xCC。程序可以扫描自身代码,检查是否出现异常的 0xCC 字节。

答 4

step 会进入函数调用内部;next 会把函数调用当作一行执行完,不进入函数内部。

答 5

stepi 按机器指令单步执行,遇到 call 会进入函数;nexti 也是指令级单步,但通常会跳过函数调用,不进入函数内部。

答 6

bt 用于查看调用栈,显示当前函数是通过哪些函数调用到达的。

答 7

Linux 下 GDB 常依赖 ptrace 系统调用控制被调试进程。

答 8

Windows 下读取目标进程内存常见 API 是 ReadProcessMemory

答 9

恶意代码反调试是为了发现自己是否在分析环境中运行,从而隐藏真实行为、干扰分析人员、延迟执行或直接退出。


33. 下一节预告

下一节课会讲:

1
从逆向视角看程序结构

你会学习:

  • 源代码视角和二进制视角的区别
  • 符号表是什么
  • strip 后为什么函数名消失
  • 反汇编和反编译的区别
  • Ghidra / IDA 的基本使用思路
  • 字符串、交叉引用和函数调用图为什么重要
隐藏
换装
本文作者:burpow
本文链接:https://youthfulnesszxx.github.io/2026/05/28/第05节-调试器的工作原理/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可