在《Ffplay视频播放流程》文章中我给出了一个ffplay的函数调用关系图,在分析代码上会有不小的帮助。那么本文就详细的描述如何从源码中一步步的得到我们想要的函数调用关系图。
前置条件
下载ffmpeg源码
安装graphviz:sudo>http://www.gson.org/egypt/
编译整个ffmepg
我采用的是默认配置+直接编译的方式,即./configure &&>ffmpeg$ makeCC ffplay.oLD ffplay_gCP ffplaySTRIP ffplay
我们从上述输出中可以看到,编译ffplay主要有四步:编译(CC),链接(LD),重命名(CP),去除符号表操作(STRIP),其中编译阶段是我们重点要分析的,因为编译是对源码的直接分析和处理。
生成RTL文件
确定了需要在编译ffplay的步骤后,我们在makefile中找到具体的编译函数:
define COMPILE $(call $(1)DEP,$(1)) $($(1)) $($(1)FLAGS) $($(1)_DEPFLAGS) $($(1)_C) $($(1)_O) $< endef
因为是编译ffplay.c文件,即此处的$(1)指的是CC,对应的$($(1)FLAGS)就是$(CCFLAGS),而CCFLAGS的定义中包含$(CFLAGS),即按照 egypt 中的说明,我们在$(CFLAGS)的定义中添加-fdump-rtl-expand参数即可在make的时候成成RTL文件:
CFLAGS += $(ECFLAGS) -fdump-rtl-expand并且在COMPILE函数中将$($(1)FLAGS)的值打印出来,那么再次修改ffplay.c并编译后的输出如下:
-I. -I./ -D_ISOC99_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_POSIX_C_SOURCE=200112 -D_XOPEN_SOURCE=600 -std=c99 -fomit-frame-pointer -pthread -D_GNU_SOURCE=1 -D_REENTRANT -I/usr/include/SDL -g -Wdeclaration-after-statement -Wall -Wno-parentheses -Wno-switch -Wno-format-zero-length -Wdisabled-optimization -Wpointer-arith -Wredundant-decls -Wno-pointer-sign -Wwrite-strings -Wtype-limits -Wundef -Wmissing-prototypes -Wno-pointer-to-int-cast -Wstrict-prototypes -O3 -fno-math-errno -fno-signed-zeros -fno-tree-vectorize -Werror=implicit-function-declaration -Werror=missing-prototypes -Werror=return-type -Werror=vla -fdump-rtl-expand -D_GNU_SOURCE=1 -D_REENTRANT -I/usr/include/SDL CC ffplay.o LD ffplay_g CP ffplay STRIP ffplay
并且在当前目录下生成ffplay.c.144r.expand文件,即我们需要的RTL文件。
生成DOT文件
生成完RTL文件后,我们可以使用现成的一个工具来分析它:egypt,具体命令如下:
egypt ffplay.c.144r.expand > ffplay.dot
生成函数调用图
有了dot文件,使用graphviz提供的工具就可以直接生成图形了:
dot ffplay.dot -Tpng -o ffplay.png其中ffplay.dot是输入文件,-Tpng表示生成png格式的文件,-o>
文件有点大,请自行保存下来进行查看。
去除编译优化
这个图看上去有些奇怪,比如在main函数中非常显眼的event_loop函数,哪里去了呢?
神奇之处就在于CFLAYGS中的-O3参数,gcc的man手册中如是说:
- -O3 Optimize yet more. -O3 turns on all optimizations specified by -O2
- and also turns on the -finline-functions, -funswitch-loops,
- -fpredictive-commoning, -fgcse-after-reload, -ftree-vectorize and
- -fipa-cp-clone options.
因此,为了保持生成的RTL文件和源代码保持一致性,我们去除所有的编译优化选项,即在config.mak文件中,从CFLAGS定义中删除-O3字符串:
- CFLAGS= -std=c99 -fomit-frame-pointer -pthread -D_GNU_SOURCE=1 -D_REENTRANT -I/usr/include/SDL -g -Wdeclaration-after-statement -Wall -Wno-parentheses -Wno-switch -Wno-format-zero-length -Wdisabled-optimization -Wpointer-arith -Wredundant-decls -Wno-pointer-sign -Wwrite-strings -Wtype-limits -Wundef -Wmissing-prototypes -Wno-pointer-to-int-cast -Wstrict-prototypes <span style="color:#FF0000;"><del>-O3 </del></span>-fno-math-errno -fno-signed-zeros -fno-tree-vectorize -Werror=implicit-function-declaration -Werror=missing-prototypes -Werror=return-type -Werror=vla
哇塞,通过这两个图形对比,我们发现编译优化选项做了多大的工作啊,或者说原始代码为了可阅读行,是多么的烂啊!
手动调整
未优化的函数调用关系图太复杂了,在分析问题时,感觉有些老虎吃刺猬无处下嘴啊!
那么我们就可以手动打开刚才处理RTL后生成的dot文件,比如去除一些孤立的点,去除一些细节处理等等。最后,我们得到一个比较概要的函数调用关系图。
PS:和上述图片对应的dot文件如下: