1. 程式人生 > >使用egypt+graphviz分析ffplay的函式呼叫關係圖

使用egypt+graphviz分析ffplay的函式呼叫關係圖

在《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>

檔案有點大,請自行儲存下來進行檢視。

ffplay呼叫流程圖

去除編譯優化

這個圖看上去有些奇怪,比如在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.
我們發現-O3是在-O2優化的基礎上,又添加了一系列的優化引數。那麼同理,-O2優化肯定也是在-O1優化基礎上新增一些優化引數。

因此,為了保持生成的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 -O3 -fno-math-errno -fno-signed-zeros -fno-tree-vectorize -Werror=implicit-function-declaration -Werror=missing-prototypes -Werror=return-type -Werror=vla
然後重新修改編譯ffplay.c,並生成RTL檔案和png圖片。生成的圖形如下:

未優化的函式呼叫流程圖

哇塞,通過這兩個圖形對比,我們發現編譯優化選項做了多大的工作啊,或者說原始程式碼為了可閱讀行,是多麼的爛啊!

手動調整

未優化的函式呼叫關係圖太複雜了,在分析問題時,感覺有些老虎吃刺蝟無處下嘴啊!

那麼我們就可以手動開啟剛才處理RTL後生成的dot檔案,比如去除一些孤立的點,去除一些細節處理等等。最後,我們得到一個比較概要的函式呼叫關係圖。

函式呼叫關係概要圖

PS:和上述圖片對應的dot檔案如下: