Pwn 基础之C/C++(八):编译、链接与 GDB 调试
本篇目标:理解 C 程序如何变成二进制,并能使用 GDB 和 Sanitizer 验证内存模型。
预计阅读时间:20~25 分钟。
1. 预处理器
#include <stdio.h>
#define MAX_COUNT 100
预处理器在正式编译前处理:
#include;#define;- 条件编译。
#ifdef DEBUG
printf("debug information\n");
#endif
编译时定义:
gcc -DDEBUG program.c -o program
2. 宏与内联函数
带参数宏:
#define SQUARE(x) ((x) * (x))
必须谨慎加括号。简单函数逻辑通常更适合:
static inline int square(int x)
{
return x * x;
}
函数具有类型检查,也避免宏参数重复求值等问题。
3. 头文件保护
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
int add(int a, int b);
#endif
它防止头文件内容在同一翻译单元中被重复包含。
4. 多文件项目
math_utils.h:
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
int add(int a, int b);
#endif
math_utils.c:
#include "math_utils.h"
int add(int a, int b)
{
return a + b;
}
main.c:
#include <stdio.h>
#include "math_utils.h"
int main(void)
{
printf("%d\n", add(10, 20));
return 0;
}
编译:
gcc main.c math_utils.c -o program
5. 声明与定义
声明说明名字及其类型存在:
extern int global_value;
int add(int a, int b);
定义真正创建对象或提供函数体:
int global_value = 100;
int add(int a, int b)
{
return a + b;
}
6. 四阶段构建过程
预处理 → 编译 → 汇编 → 链接
预处理:
gcc -E program.c -o program.i
生成汇编:
gcc -S program.c -o program.s
生成目标文件:
gcc -c program.c -o program.o
链接:
gcc program.o -o program
目标文件已经包含机器代码和符号信息,但通常还需要链接才能形成最终可执行程序。
7. 调试编译选项
gcc -std=c17 -Wall -Wextra -Wpedantic -g -O0 program.c -o program
-Wall -Wextra -Wpedantic:开启警告;-g:加入调试信息;-O0:关闭大部分优化,便于观察源码关系。
优化编译:
gcc -O2 program.c -o program
优化后变量可能进入寄存器、函数可能被内联、无用代码可能消失。源码和汇编不再保证逐行对应。
8. 未定义行为
未定义行为表示 C/C++ 标准不再保证程序结果。常见来源:
- 使用未初始化值;
- 解引用无效指针;
- 访问数组范围之外;
- 使用生命周期已经结束的对象;
- 错误管理动态内存;
- 有符号整数溢出;
- 格式说明符与参数类型不匹配。
未定义行为不等于“一定崩溃”。它可能暂时看似正常,也可能在优化、平台或输入变化后表现不同。
9. GDB 基础
示例:
#include <stdio.h>
int add(int a, int b)
{
int result = a + b;
return result;
}
int main(void)
{
int x = 10;
int y = 20;
int result = add(x, y);
printf("%d\n", result);
return 0;
}
编译:
gcc -g -O0 example.c -o example
启动:
gdb ./example
10. 断点与单步
break main
break add
run
next
step
continue
next:执行下一行,不进入普通函数调用;step:执行下一行并进入函数;continue:继续到下一个断点或程序结束。
11. 查看变量、类型和地址
print x
print &x
ptype x
info locals
查看调用栈:
backtrace
12. 查看内存
x/4xb &x
解释:
x examine
4 查看 4 个单位
x 十六进制
b 每单位 1 字节
单位:
| 字母 | 大小 |
|---|---|
b |
1 字节 |
h |
2 字节 |
w |
4 字节 |
g |
8 字节 |
查看字符串:
x/s pointer
13. 寄存器和反汇编
info registers
disassemble main
Intel 语法:
set disassembly-flavor intel
源码与汇编布局:
layout split
学习时用 GDB 验证,而不是只背结论。
14. Sanitizer
gcc \
-g \
-O1 \
-fsanitize=address,undefined \
-fno-omit-frame-pointer \
program.c \
-o program
AddressSanitizer 和 UndefinedBehaviorSanitizer 可以辅助发现部分错误,但不能代替语言规则和人工推理。
15. 推荐编译命令
C:
gcc -std=c17 -Wall -Wextra -Wpedantic -Wconversion -Wshadow -g -O0 program.c -o program
C++:
g++ -std=c++17 -Wall -Wextra -Wpedantic -Wconversion -Wshadow -g -O0 program.cpp -o program
本篇练习
- 分别生成
.i、.s、.o和可执行文件。 - 建立一个包含头文件和两个源文件的小项目。
- 在 GDB 中查看整数的逐字节表示。
- 对比
-O0和-O2的反汇编结果。 - 使用 Sanitizer 检查自己的数组和动态内存练习。
本篇小结
- C 源码经过预处理、编译、汇编和链接;
- 头文件主要放声明,源文件提供定义;
- 优化会显著改变生成代码;
- 未定义行为不能用“本机正常”证明正确;
- GDB 能验证变量、地址、内存、寄存器和汇编之间的关系。
下一篇
下一篇从 C 进入 C++,学习引用、类、对象、构造析构和 RAII。

说些什么吧!