昨天看别人文章里分析代码,突然看到了没见过的东西,如下:
//uboot 2020.10版本
//引导命令 booti image_adr ramdisk_adr dtb_adr
//lmb: logical memory blocks 逻辑内存块
bootm_headers_t images;//(common/bootm.c) boot引导阶段用到的全局变量
0 run booti(cmd/booti.c) //命令
1-> do_booti(cmd/booti.c)
2-> booti_start(cmd/booti.c)
3-> do_bootm_states(common/bootm.c) //通过bootm_start初始化全局images->lmb
4-> bootm_start(common/bootm.c)
5-> 清空全局变量images
5-> boot_start_lmb(common/bootm.c)
6-> env_get_bootm_low(common/image.c) 从env("bootm_low")获取或使用宏CONFIG_SYS_SDRAM_BASE(include/configs/xxx.h) //ft2004为0x80000000
6-> env_get_bootm_size(common/image.c) 从env("bootm_size")获取或从全局gd struct global_data中获取:size = gd->bd->bi_dram[0].size;
6-> lmb_init_and_reserve_range(lib/lmb.c)
3-> 获取kernel入口物理地址,等于booti的第一个参数 或 CONFIG_SYS_LOAD_ADDR
3-> image_decomp_type 根据kernel前2字节判断压缩类型并解压
0x425a(bzip2)/ 0x1f8b(gzip)/ 0x5d00(lzma)/ 0x894c(lzo)
3-> booti_setup(arch/arm/lib/image.c) //根据image.magic判断是否为ARM64内核并重定向内核基地址。此处可以看出booti命令是专门加载ARM64 Linux Kernel的
3-> 拷贝内核到重定向地址,将新内核地址信息赋值给全局images变量
images->ep = relocated_addr; //ep:entry point
images->os.start = relocated_addr;
images->os.end = relocated_addr + image_size;
3-> lmb_reserve(lib/lmb.c) //保留内核地址
3-> bootm_find_images(common/boom.c)
4-> boot_get_ramdisk(common/image.c) //从第二个参数或image(如果为FIT uImage)中查找ramdisk根文件系统,并将查找到的ramdisk地址赋值给images.rd_start
4-> boot_get_fdt(common/image-fdt.c) //在image中查找fdt设备树,并将查找到的ramdisk地址赋值给images.ft_addr
4-> set_working_fdt_addr(cmd/fdt.c) //working_fdt=images.ft_addr
4-> boot_get_loadable //查找所有可加载的文件
2-> bootm_disable_interrupts(common/boom.c) //关闭中断
2-> images.os.os = IH_OS_LINUX;
images.os.arch = IH_ARCH_ARM64;//指定os的架构为ARM64
2-> do_bootm_states(common/bootm.c)
3-> boot_ramdisk_high(common/image.c) //重定位ramdisk
3-> boot_relocate_fdt(common/image-fdt.c) //重定位fdt
3-> bootm_os_get_boot_func(common/bootm_os.c) //获取对应os类型的boot函数
linux为:do_bootm_linux
3-> boot_fn(BOOTM_STATE_OS_PREP,...) //boot前准备工作,do_bootm_linux(arch/arm/lib/bootm.c)
4-> boot_prep_linux(arch/arm/lib/bootm.c)
5-> image_setup_linux(common/image.c) //主要处理fdt
6-> boot_fdt_add_mem_rsv_regions(common/image-fdt.c)//标记为不可用,防止存放fdt的内存被uboot使用
6-> boot_relocate_fdt(common/image-fdt.c)
6-> image_setup_libfdt(common/image-fdt.c)
7-> fdt_root(common/fdt_support.c)
7-> fdt_chosen(common/fdt_support.c)
8-> fdt_find_or_add_subnode(common/fdt_support.c)//查找或创设备树节点"chosen"
8-> env_get("bootargs"); //获取bootargs参数
8-> fdt_setprop(common/fdt_support.c) //通过该函数在"chosen"节点中添加"bootargs"属性,内容为bootargs参数。linux启动后可在/proc/device-tree/chosen/中查看bootargs属性
7-> arch_fixup_fdt(arch/arm/lib/bootm-fdt.c)
7-> optee_copy_fdt_nodes //什么都没做
7-> fdt_fixup_ethernet(common/fdt_support.c)
7-> fdt_shrink_to_minimum(common/fdt_support.c)
7-> lmb_reserve(lib/lmb.c)
7-> fdt_initrd(common/fdt_support.c)
7-> ft_verify_fdt(common/fdt_support.c)
5-> board_prep_linux(arch/arm/lib/bootm.c)//什么都没做
3-> boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,images, boot_fn)(common/boot-os.c)//run OS
4-> arch_preboot_os//什么都没做
4-> board_preboot_os//什么都没做
4-> boot_fn(BOOTM_STATE_OS_GO,...) //do_bootm_linux(arch/arm/lib/bootm.c)
5-> boot_jump_linux(arch/arm/lib/bootm.c)
6-> announce_and_cleanup //打印并准备引导kernel,"Starting kernel ...",关闭中断,关闭caches等
6-> do_nonsec_virt_switch //刷新caches
6-> armv8_switch_to_el2((u64)images->ft_addr, 0, 0, 0,images->ep,ES_TO_AARCH64); //跳转到内核 arch/arm/cpu/armv8/transition.c
虽然之前就知道有生成调用图的工具,但是没见过这种,我目前分析代码,都是用的流程图,横向表示调用,纵向表示顺序流程。但是一看上面这种,感觉更清晰一点,然后就去研究了。。
2023/12/29 去问了作者本人,他说这个是自己手动整理的。。。。
首先看的是cflow
命令原型:
NAME
cflow - generate a C-language flowgraph
SYNOPSIS
cflow [-ASTrxablnv] [-d NUMBER] [-f NAME] [-i CLASSES] [-o FILE] [-D NAME[=DEFN]] [-I DIR] [-m NAME] [-p NUMBER] [-s SYMBOL:[=]TYPE] [-U NAME] [--all] [--depth=NUMBER] [--format=NAME] [--include=CLASSES] [--output=FILE] [--reverse] [--xref] [--ansi]
[--define=NAME[=DEFN]] [--include-dir=DIR] [--main=NAME] [--no-main] [--pushdown=NUMBER] [--preprocess[=COMMAND]] [--cpp[=COMMAND]] [--symbol=SYMBOL:[=]TYPE] [--target=FUNCTION] [--use-indentation] [--undefine=NAME] [--brief] [--emacs] [--print-level]
[--level-indent=ELEMENT] [--number] [--omit-arguments] [--omit-symbol-names] [--tree] [--debug[=NUMBER]] [--verbose] FILE...
cflow [-?V] [--help] [--usage] [--version]
常用参数:
General-purpose options
-d, --depth=NUMBER 最大深度,默认无限制
-f, --format=NAME 输出格式,可选dot (DOT language), gnu (the default), and posix.
-i, --include=CLASSES 直接指定则包含指定的符号类。^或-符号排除它后面的类。有效的类有:
_(underscore) 以_为开头的符号
s Static symbols
t Typedefs (for cross-references only).
x All data symbols, both external and static
默认情况下cflow图只包含函数。但是,您也可以通过使用符号类' x '来请求显示变量。
-o, --output=FILE 指定输出文件名,默认输出到stdout
-r, --reverse 输出反向调用树
-x, --xref 列出所有引用的地方
Parser control
-I, --include-dir=DIR 添加用来搜索的头文件目录
-m, --main=NAME 指定最顶层调用,可以使用多次。
--no-main
该选项与——all具有相同的效果,除了,如果程序确实定义了main函数,它将被视为任何其他函数,即它不会被放置在输出的顶部,而是按照函数名的字典顺序放置在其位置上
--preprocess[=COMMAND], --cpp[=COMMAND]
默认情况下,'——cpp '运行' /usr/bin/cpp '。如果希望运行另一个预处理器命令,请将其指定为选项的参数,在等号后面。例如,cflow——cpp='cc -E'将把C编译器作为预处理器运行。
--no-preprocess, --no-cpp 不使用预处理器
-S, --use-indentation 使用源文件缩进作为提示。
--no-use-indentation 不使用源文件缩进作为提示。(默认)
--target=FUNCTION 结束函数,调用将在此处结束
Output control
-A, --all 为程序中的所有全局函数生成图形。如果程序包含不能从main()直接访问的函数,请使用此选项。
-b, --brief 简略输出
--no-brief 非简略输出
-l, --print-level 打印嵌套级别以及调用图。
--no-print-level 不打印嵌套级别以及调用图。
--level-indent=ELEMENT 指定缩进的空格数
-n, --number 带行号输出
--no-number 不带行号(默认)
--omit-arguments 不输出参数
--no-omit-arguments 输出参数(默认)
--omit-symbol-names 不输出符号名
--no-omit-symbol-names 输出符号名(默认)
-T, --tree 树型输出
--no-tree 非树型输出
但是目前看了很多选项,都不能达到上图的效果,最接近的是:
$ cflow hello_simple.c -l
{ 0} main() <int main (void) at hello_simple.c:57>:
{ 1} hello_athens() <void hello_athens () at hello_simple.c:15>:
{ 2} say_hello() <void say_hello (char *s) at hello_simple.c:10>:
{ 3} printf()
{ 1} fly() <void fly () at hello_simple.c:35>:
{ 2} take_off() <void take_off () at hello_simple.c:20>:
{ 3} printf()
可能需要写个脚本来处理下。
未完待续