U-Boot源码学习和调试快速定位函数代码位置技巧
在我们学习U-Boot源码和调试过程中,是否经常遇到这样的场景?程序运行到某个函数时突然崩溃,屏幕上只有晦涩的寄存器值和内存地址,却不知道这段代码究竟对应源码的哪个位置。或者在研究U-Boot启动流程时,想追踪某个关键函数的实现,面对众多源码中的实现和条件编译不能精准确定对应代码位置?
假设我们在学习U-Boot源码或者源码移植时,遇到一个函数实现,但是通过 grep 发现,结果包含众多编译条件下的函数实现。如何精准快速地定位到相应位置呢?
我们以 board_init_f_init_reserve 函数中调用的 memset 函数为例。
最基本的方法就是在源码目录下使用 grep 命令。
要查找的模式包含:
- *memset( - 星号紧挨着memset
- * memset( - 星号后有一个或多个空格,然后是memset
grep -r "\* *memset("
去掉不相关的可以得到以下几个实现:
arch/arm/lib/memset.S 中的汇编函数实现,以及各个编译条件下的C函数实现,比如lib/string.c、lib/efi_loader/efi_freestanding.c、lib/efi/efi_stub.c 等。
实际上只要我们认真追踪每一个函数的实现的编译条件等总是能找到对应的实现源码。最终我们得到源码为 arch/arm/lib/memset.S 中的实现。
grep 方法传统而强大,但是这样会花费我们大量的时间,下面我们将分享一套实用技巧,帮助你在U-Boot源码学习中高效定位问题。
我们在学习或移植U-Boot源码时,肯定是存在一套可编译的或不同平台的源码,此时我们可以将其先编译得到编译后的二进制程序。
在实际编译中,我们知道最后符号表中每一个符号都是独一无二的,不可能同名。
第一步是获取函数的符号地址信息:
1)反汇编
arm-linux-gnueabi-objdump -d u-boot | grep -A 20 "<memset>:"
可得到类似输出:
87801740 <memset>:
其中 87801740 就是 memset 函数在符号表中的地址。
2)编译 U-Boot 时会生成 System.map 文件,包含所有符号的地址信息:
# 查找函数地址
grep "函数名" System.map
其输出结果:
87801740 T memset
3)nm:符号表查询工具
# 查找特定函数地址
arm-linux-gnueabi-nm u-boot | grep memset
输出结果:
87801740 T memset
4)readelf:ELF文件分析器
查看更详细的重定位信息和段信息:
# 查看符号表
arm-linux-gnueabi-readelf -s u-boot | grep memset
输出结果:
497: 00000000 0 FILE LOCAL DEFAULT ABS arch/arm/lib/memset.o
7878: 87801741 158 FUNC GLOBAL DEFAULT 3 memset
第二步将符号地址转换为源码文件位置:
- addr2line 工具,将程序地址转换为源码文件位置。
arm-linux-gnueabi-addr2line -e u-boot 87801740
输出结果:
/Work/arm/uboot/u-boot-2025.04/out/../arch/arm/lib/memset.S:21
- u-boot.map 中查找
.text 0x0000000087801740 0xa0 arch/arm/lib/memset.o
0x0000000087801740 memset
通过以上两步即可快速准确地定位到函数源码的实现位置。
同样的当我们在调试U-Boot启动过程时,遇到程序崩溃,如果得到程序计数器指向的地址,我们同样可以使用 addrline 工具快速的定位到源码文件位置。
掌握U-Boot源码的定位技巧,提高学习和开发效率。随着经验的积累,你将能够在庞大的U-Boot代码库中自如导航,快速定位问题,提高开发效率。