嵌入式 Linux 最常用到的一套工具链,以 GCC 为例,前缀通常是 arm-linux-gnueabihf-
。下面列出关键工具及常见参数。
工具 | 用途 | 常用参数 |
---|---|---|
arm-linux-gnueabihf-gcc |
交叉编译器 | -march=armv7-a 指定架构-mfpu=neon 指定浮点单元-mfloat-abi=hard 硬浮点-O2 优化等级-g 生成调试信息-Wall 全部警告 |
arm-linux-gnueabihf-gdb |
调试用户态程序 / 内核(Remote) | -q 静默启动-ex "target remote :1234" 连接 QEMU/GDBstub-ex "set arch arm" 设置架构-ex "file vmlinux" 预装内核符号 |
arm-linux-gnueabihf-addr2line |
符号地址 → 源码位置 | -e 指定可执行/符号文件-f 打印函数名-i 打印 inline 路径-C demangle C++ 名称 |
arm-linux-gnueabihf-objdump |
反汇编 / 查看符号 | -d 反汇编所有可执行段-D 反汇编全部段(含数据)-l 打印源文件行号-S 源+汇一体-t 列出符号表 |
arm-linux-gnueabihf-readelf |
查看 ELF 头 & 段信息 | -h ELF Header-S 段表-r 重定位表-s 符号表 |
示例:
# 把内核崩溃地址还原到源码 arm-linux-gnueabihf-addr2line -e vmlinux -f -i -C 0xc08a2345
git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux
# 可切到稳定版或长期支持版
git checkout v6.4
# 1) 生成默认配置
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- defconfig
# 2) 开启常用调试项(可选)
scripts/config --enable DEBUG_INFO
scripts/config --enable FRAME_POINTER
scripts/config --enable KALLSYMS
# 3) 重新生成 .config,并编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc)
# 4) 安装模块(可选)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=./mods modules_install
文件/目录 | 说明 |
---|---|
vmlinux |
ELF 格式、带符号表的未压缩内核镜像 |
System.map |
符号 ↔ 地址映射表,用于 crash 时地址还原 |
arch/arm/boot/zImage |
压缩内核镜像(U-Boot 加载用) |
arch/arm/boot/Image |
未压缩内核镜像 |
arch/arm/boot/dts/ |
各板级支持包的设备树二进制(.dtb) |
modules/ |
安装好的模块文件(.ko),位于 lib/modules/ |
include/config/ |
编译时的配置快照 |
小白常见疑惑是:为什么开启了调试选项仍然看不到完整的 Backtrace?下面一一拆解。
在 make menuconfig → Kernel hacking
中务必开启:
Compile the kernel with debug info (DEBUG_INFO)
Keep frame pointers (FRAME_POINTER)
— 支持栈回溯Enable loadable module support (MODVERSIONS)
+ KALLSYMS
— 模块 & 内核符号Kernel debugging (DEBUG_KERNEL)
Compile the kernel with debug info (KGDB)
(可选,用于 KGDB 远程单步)Magic SysRq key
— 在 panic 后手工触发 dump在引导加载器(如 U-Boot)中加入:
console=ttyAMA0,115200 earlyprintk=serial,ttyAMA0,115200 loglevel=7 nokaslr
earlyprintk
:在 very-early 阶段打印日志loglevel=7
:输出所有级别(0–7)nokaslr
:关闭地址随机化,符号映射更稳定# 本地查看
dmesg --level=err,warn,info
# 实时跟踪
tail -f /dev/kmsg
# ftrace 追踪函数调用
echo function_graph > /sys/kernel/debug/tracing/current_tracer
cat /sys/kernel/debug/tracing/trace
当出现 Oops
或 BUG
,终端会打印类似:
BUG: unable to handle kernel NULL pointer dereference at 00000000deadbeef
R0 : 0000000000000000 R1 : ffffffff81c23456
LR : ffffffff810ab123 PC : ffffffff810abcde (my_func+0x20/0x58)
Stack: 0000ffff82a9f1b0 0000ffff82a9f1d0 ...
Call trace:
[] my_func+0x20/0x58
[] another_func+0x100/0x110
还原地址 → 源码:
arm-linux-gnueabihf-addr2line -e vmlinux -f -i -C ffffffff810abcde
KASAN 可捕获 Use-After-Free / 越界读写等难以定位的内存错误。
在 make menuconfig → Kernel hacking
中:
KASAN: runtime memory debugger
→ 选择
记得:DEBUG_INFO
、FRAME_POINTER
均需开启。
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc)
# 引导参数加上:kasan=on kasan.policy=0
==================================================================
BUG: KASAN: use-after-free in my_alloc_func+0x34/0x60
Read of size 4 at addr ffff800012345678 by task test_process/1234
...
Use $CWD/vmlinux + addr → 找到源代码上下文
arm-linux-gnueabihf-addr2line -e vmlinux -f -i -C ffffffff81012345
寄存器 | 用途 | 存“地址”还是“数据”? |
---|---|---|
r0–r3 | 参数寄存器 / 临时 | 可存数据,也可存指针(第一参数通常是指针) |
r4–r11 | 保留寄存器(callee-saved) | 多为局部变量或地址 |
sp (r13) | 栈顶指针 | 地址 |
lr (r14) | 返回地址 | 地址 |
pc (r15) | 程序计数器 | 地址 |
cpsr | 程序状态寄存器 | 标志位 / 状态 |
0xffff0000_00000000
以上,数据(如小整数)远小于此。objdump -d -l
查看汇编指令,若寄存器用作 [r0, #offset]
,它即为地址基址。# 查某行是谁加的
git blame net/core/neighbour.c -L 100,120
# 查谁改过 bucket 初始化
git log -S 'state->bucket' -p net/core/neighbour.c
# 查看具体提交
git show <commit-id>
[email protected]
, [email protected]
:订阅获得最新 Patchscripts/get_maintainer.pl
:快速获取相关维护者邮件列表本文从工具链、编译产物、日志配置、KASAN、寄存器、社区协作全方位展开,既有参数详解、操作范例,也有定位思路。掌握这些内容,能够让你在遇到嵌入式 Linux 内核问题时,快速定位、调试并提交高质量 Patch。
若需落地实战脚本、图文并茂版本或发布到博客平台,欢迎继续交流!