函数调用图生成

昨天看别人文章里分析代码,突然看到了没见过的东西,如下:

//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

首先看的是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()

可能需要写个脚本来处理下。

Graphviz

在线生成

本地命令生成

# 将指定脚本文件 输出为 指定格式、指定名称的文件
dot <脚本文件名> -T <输出格式> -o <输出文件名>
# 将名为demo1.dot的脚本文件 输出为 格式为png、文件名为demo1.png的图片
dot demo1.dot -T png -o demo1.png
  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值