GCC 编译过程

一、四个阶段

源文件 ──预处理──> 编译文件 ──编译──> 汇编文件 ──汇编──> 目标文件 ──链接──> 可执行文件
hello.c ──> hello.i ──> hello.s ──> hello.o ──> hello
阶段 输入 → 输出 命令
预处理 .c → .i gcc -E
编译 .i → .s gcc -S
汇编 .s → .o gcc -c
链接 .o → 可执行 gcc(默认)

gcc hello.c -o hello 一步到位。
加上 -v 看具体命令。

二、常用编译选项

1. 优化

-O0  无优化(调试)
-O1  基本
-O2  常用
-O3  激进(可能引入 bug)
-Os  优化大小(嵌入式首选)
-Og  优化但保留调试信息(GCC 4.8+)

2. 调试

-g           生成调试信息
-ggdb        为 GDB 优化
-g3          宏信息
-fno-omit-frame-pointer  保留帧指针以便调用栈回溯

3. 警告

-Wall         常用警告
-Wextra       更多警告
-Werror       警告当错误(CI 必备)
-Wshadow      变量遮蔽
-Wpointer-arith 指针算术
-Wstrict-prototypes

4. 语言标准

-std=c99 / -std=c11 / -std=c17 / -std=c2x
-ansi / -pedantic  严格 ISO

5. 预处理

-DMACRO=val    定义宏
-UMACRO        取消宏
-include file  强制包含
-Idir          添加头文件路径
-M -MM -MMD    依赖生成

6. 输出

-c             只编译不链接
-o file        输出文件名
-S             只到汇编
-E             只到预处理
-v             详细输出
--save-temps   保存所有中间文件
-fverbose-asm  汇编加注释

7. 架构相关(嵌入式)

-march=armv7-a       指令集
-mtune=cortex-a9     调优
-mfpu=neon           启用 NEON
-mfloat-abi=hard     硬浮点 ABI
-mthumb              Thumb 指令
-mlittle-endian      小端

8. 安全

-fstack-protector / -fstack-protector-strong  栈保护
-fcf-protection      控制流保护(CFI)
-D_FORTIFY_SOURCE=2  运行时检查
-pie -fPIE           位置无关可执行
-fPIC                位置无关代码

三、ELF 文件结构

可执行可链接格式 (ELF)
┌────────────────────┐
│   ELF Header       │ 魔数、架构、入口点
├────────────────────┤
│   Program Headers  │ 段(segment):运行时视图
├────────────────────┤
│                    │
│   .text            │ 代码
│   .rodata          │ 只读数据
│   .data            │ 已初始化数据
│   .bss             │ 未初始化(不占文件空间)
│   .symtab          │ 符号表
│   .strtab          │ 字符串表
│   .debug_*         │ 调试信息
│   .dynamic         │ 动态链接信息
│                    │
├────────────────────┤
│   Section Headers  │ 节(section):链接视图
└────────────────────┘

查看工具:

file hello                 # 文件类型
readelf -h hello           # ELF 头
readelf -l hello           # 段
readelf -S hello           # 节
readelf -s hello           # 符号
readelf -d hello           # 动态段
nm      hello              # 符号列表
objdump -d hello           # 反汇编
objdump -h hello           # 节摘要
objdump -t hello           # 符号
size   hello               # 各段大小
strip  hello               # 删除符号

四、链接脚本(Linker Script)

嵌入式必须掌握。.lds 文件控制链接布局:

ENTRY(_start)
SECTIONS {
    . = 0x40000000;          /* 加载地址 */

    .text : { *(.text*) }
    .rodata : { *(.rodata*) }

    . = ALIGN(4);
    _data_load = LOADADDR(.data);
    .data : AT(_data_load) {
        *(.data*)
    }

    .bss : {
        __bss_start = .;
        *(.bss*) *(COMMON)
        __bss_end = .;
    }

    . = ALIGN(16);
    .stack : {
        . = . + 0x10000;
        _stack_top = .;
    }

    /DISCARD/ : { *(.comment) }
}

五、动态库 vs 静态库

静态库:.a  → 链接时拷贝到可执行
动态库:.so → 运行时按需加载(位置无关)

嵌入式常用:musl libc + 静态链接,方便部署。
带复杂应用时:glibc + 动态链接

六、链接器优化(黄金选项)

-Wl,--gc-sections       删除未引用节
-Wl,--as-needed         精简动态库依赖
-Wl,-z,relro -Wl,-z,now RELRO
-Wl,-z,noexecstack      非执行栈

七、踩坑清单

现象 原因
undefined reference to xxx 没链接对应库 / 没声明 extern
multiple definition 头文件没加 extern / include .c 文件
stack smashing detected 栈溢出 / -fstack-protector 触发
segfault 空指针 / 越界 / 写只读段
内核 BUG: scheduling while atomic 持锁调用睡眠函数