似てる、似てない?(2)

もう先月の事になるけど、紅葉見物に行ってきた。去年は近場だったので、今年は遠い所を選択。 岐阜高山の外れにある、新穂高へ。そこで、2階建てロープーウェイに初めて乗ったよ。2本の 路線を乗り継いで頂上を目指すんだが、1本目は平屋のロープーウェイ。所要時間4分。箱は60人乗り。 二本目が2階建てで、7分、箱は120人乗り。 何となく、フル稼働しても、乗り継ぎ時に、待ち行列が生じないように、キャパを考えたっぽいな。 こんな事を考えてるから、頂上は、雪で視界ゼロでも良かったんだ。(良く無いって!)

でも、中間乗り継ぎまで下りてくると、足湯が有ったりして、のんびり浸かりながら、紅葉見物 出来ましたよ。飛騨牛のコロッケでも食べればよかったわい。

安房峠(トンネル)を越えて、長野県側へ。安房峠と言い、野麦峠と言い、製糸産業を扱った 女工哀史的な小説には良く登場する舞台だよ。 (現代版哀史はこちら)岐阜から信州への出稼ぎのメインルートですもの。

そんな昔の事はすっかり消えた山の銀座、上高地へ。こちらも紅葉は見事。 青空と紅葉のコラボを大正池の鏡面から眺めていると、心が洗われますよ。 大正池から、梓川に 沿って河童橋まで散策。遊歩道が整備されてて、おいらの田舎を歩くより快適。やっぱり、銀座だね。 ファッション豊かな山ガールも一杯いたよ。トイレも有料だしね。(これ、美化に協力って事です)

さすが、この時期はむさ苦しい山屋さんや岩登屋さんは、見かけなかったな。きっと、シーズンの 谷間なんでしょう。ちゃらちゃら組には嬉しい、ケーキ屋さん(ホテルだけど)が、ゴール地点の 河童橋袂にあって、よく考えられてますなあ。

バスターミナルのちょっと下にある、帝国ホテルでカレーでも食べようと思ったけど、時刻が 中途半端だったので次回へ持ち越し、11月の中旬には、長い長い冬眠生活に入るとか。

熊さんも冬眠生活準備中って事で、目撃情報看板があちこちに出てた。熊さんもゆっくりと 冬を越せるといいね。

cflow

前回は、Cプログラムの中で要となるライブラリィーの使い方をどうやって炙り出そうかという 所で終わった。

おいらが昔のパソコンFM-11で初めてC言語に触れた時、使ってる関数を列挙するプログラムを 作ったなあ。(次作は、スクリーンエディタだった。)もうその当時のコードは勿論残っていない。

でも、同様な機能を実現してるっぽいプログラムの予想はつくよ。こういうの鼻が利くとでも 言うんでしょうか。FreeBSDのports/develの中をつらつらと覗いてみたよ。 そしたら、こんなのが、

[sakae@secd /usr/ports/devel/cflow]$ cat pkg-descr
Cflow reads files as C program source and attempts to print a graph
of the program's function call hierarchy to the standard output.
Called functions are indented with respect to their calling functions,
and printed only once, in the order they occur.

This is version 2.0.

何時もなら、ソースが取れるURLが書いてあるはずなんだけど、今回に限って載っていない。 でも、ちゃんとインストール出来たよ。ソースが手に入った訳なんで、ArchLinuxにも お裾分けしようと思ったんだ。

Archでコンパイルしたら、早速エラーのオンパレード。困った時のググる先生って事で GNU cflow vs パブリックドメイン cflow なんてのを発見。無事に、ArchLinuxにも入りました。

[sakae@arch z]$ cflow -v
/usr/local/bin/cflow [-AaginPXVvxh] [-d n] [-w n] [-r name] [cpp-opts] files
        -a      print a seperate cal graph for each function
        -A      eliminate ansi keywords
        -P      eliminate posix keywords
        -d n    print call graph to depth n
        -g      eliminate gcc keywords
        -r name start tree at name
        -D and -I options
        -x      print each subgraph in full
        -i      inverted index
        -s      save temporary files
        -n      don't run prcg (useful for debugging)

        -v      include variables
        -X      exclude files
        -V      verbose

Accepts the following from the environment
        CPP (default gcc -E -D__attribute__(x)= -D__asm__(x)=)
        CC  (default gcc)
        PRCG (default /usr/local/bin/prcg)
        PRCC (default /usr/local/bin/prcc -v)

ちょっと、どんな具合か確認。

[sakae@arch z]$ cflow F.c
1       __bswap_32 {/usr/include/bits/byteswap.h 45}
2               __builtin_bswap32 {}
3       __bswap_64 {/usr/include/bits/byteswap.h 109}
4               __builtin_bswap64 {}
5       main {F.c 91}
6               getopt {}
7               strlcpy {}
8               errx {}
9               atoi {}
10              usage {F.c 357}
11                      fprintf {}
12                      exit {}
   :

でも、

[sakae@arch z]$ cflow L.c
L.c:61:20: 致命的エラー: config.h: そのようなファイルやディレクトリはありません
コンパイルを停止しました。

怒られたよ。裏で、gccが動いているんだな。これじゃ、おいらの目的からすればリスクが 多き過ぎて使えないよ。

cflowには、GNU版も有るようだけど、先のURLの方が言うように検出率が低そう。何か他に ないかと思っていたら、bsdcflowって銘打ったportsを発見。これを試してみると、なかなか 良さげ。

[sakae@secd /usr/ports/devel/bsdcflow]$ cat pkg-descr
cflow reads files as C or assembler program source and prints a graph
a graph of the function call hierarchy. Called functions are indented
according to their caller-callee relationship, in the order of occurance.

WWW:    http://www.sysfault.org/projects

こちらもArchLinuxへお裾分け。

[sakae@arch cflow-0.0.6]$ cat Makefile
# $FreeBSD$

SUBDIR= asmgraph cgraph scripts

.include <bsd.subdir.mk>

展開した直下に有るMakefileは、もろにFreeBSD用だけど、gnuってdirの中に下りて行って コンパイルすれば、無問題。

commonなんてdirも有って、何が入ってるかと思ったら

[sakae@arch common]$ ls *keyword*
ansi_keywords.h  c99_keywords.h  gcc_keywords.h  posix_keywords.h

適当に開いて見ると、それぞれの規格で定義されたライブラリィー関数(printf等)が格納されてた。 こんなの、何に使うんかと思ってmanしたら、

     -A      Exclude ANSI C keywords. All globals and functions defined by the
             ANSI C standard are ignored. This flag only works for C source
             code files.

     -c      Print all calls within a function, including subsequent invoca-
             tions of the same function.

     -C      Exclude C99 keywords. All globals and functions defined by the
             C99 standard are ignored. This does not include the ANSI C stan-
             dard, but only new keywords of the C99 standard. This flag only
             works for C source code files.

     -G      Exclude GCC specific keywords. All keywords defined and used by
             the GNU C compiler are ignored. This flag only works for C source
             code files.

     -P      Exclude POSIX keywords. All globals and functions defined by the
             IEEE Std 1003.1-2001 (“POSIX.1”) specification are ignored. This
             does not include the ANSI C or C99 standards, but keywords of the
             IEEE Std 1003.1-2001 (“POSIX.1”) specification. This flag only
             works for C source code files.

普通にcflowを使うと

[sakae@secd ~/z]$ cflow L.c
 1 main: int(), <L.c 105>
 2          getargs: void(), <L.c 140>
 3                    isdefault: <>
 4                       strcpy: <>
 5                        error: void(), <L.c 349>
 6                                fprintf: <>
 7                                strncmp: <>
 8                                   exit: <>
 9                         atoi: <>
10                       sscanf: <>
11                       strlen: <>
12                      getprec: int(), <L.c 369>
13                                isdigit: <>
14                    getformat: void(), <L.c 387>
15                                 sprintf: <>
16                                  strcpy: <>
17                                  strcat: <>
18                                 isalpha: <>
19                                   error: void(), <L.c 349>
20                         time: <>
21          srandom: <>
22           random: <>
23          putdata: void(), <L.c 331>
24                       printf: <>
25                        fputs: <>
26          putchar: <>
27             exit: <>

三角括弧の中の表示は、定義されてるファイルと行番号。空なのは、ライブラリィーって事に なる。一度表示された関数は、二度目以降表示されない(これがデフォルトな挙動)。-cを 付けると、おかまい無く表示される。

分かりきった(標準的な)ライブラリィーの表示をオミットする時は、

[sakae@secd ~/z]$ cflow -ACGP L.c
 1 main: int(), <L.c 105>
 2          getargs: void(), <L.c 140>
 3                    isdefault: <>
 4                        error: void(), <L.c 349>
 5                      getprec: int(), <L.c 369>
 6                    getformat: void(), <L.c 387>
 7                                   error: void(), <L.c 349>
 8          putdata: void(), <L.c 331>
[sakae@secd ~/z]$ cflow -ACGP F.c
 1 main: int(), <F.c 89>
 2           strlcpy: <>
 3              errx: <>
 4             usage: void(), <F.c 355>
 5        is_default: <>
 6           getprec: int(), <F.c 368>
 7         getformat: void(), <F.c 389>
 8                         errx: <>
 9                      strlcpy: <>
10                      strlcat: <>
11        arc4random: <>
12           putdata: int(), <F.c 320>

Linux版は標準のライブラリィーだけで書いていあるけど、FreeBSDは特有なのが混じって いるのね。

Cのソースの成分分析

さて、これで道具は揃った。後は、これを軸にしてCのソースの成分分析器を作れば いいんだな。shellスクリプトじゃ荷が重いので、rubyでさくっと、

#!/usr/local/bin/ruby

list = %w(L.c N.c O.c F.c)             # Check files

def col(fc, db)
  p = IO.popen("cflow -c #{fc}")
  while al = p.gets
    db[$& + fc] += 1  if al =~ /\w+:/  # Key = func_name : file_name
  end
end

def ttl(list)
  printf("%16s", "Function")
  list.each {|sn| printf("\t%s", sn)}
  puts ''
end

def show(list, ef, fn)
  printf("%16s", fn)
  list.each do |sn|
    if ef.key?(sn)
      printf("\t%3d", ef[sn])
    else
      printf("\t%s", '---')
    end
  end
  puts ''
end

### main HERE ###################
db = {}; db.default = 0
list.each{|fc| col(fc, db)}

ofn = ""
ef = {}
ttl(list)
db.keys.sort.each do |k|
  fn,sn = k.split(':')
  if fn != ofn
    show(list, ef, ofn) if ofn != ''
    ofn = fn
    ef.clear
  end
  ef[sn] = db[k]
end
show(list, ef, ofn)
ttl(list)

行列の代わりに、Hashを使って、密度を高めてみました。実行してみます。

[sakae@secd ~/z]$ ./col.rb
        Function        L.c     N.c     O.c     F.c
      arc4random        ---     ---       1       1
            atoi          2     ---     ---       1
           error         15     ---     ---     ---
            errx        ---      14      19      18
            exit          2       2       2       2
           floor        ---       1     ---     ---
         fprintf          4     ---       1       1
           fputs          1       1       1       1
         getargs          1       2     ---     ---
       getformat          1       1       1       1
          getopt        ---       1       1       1
          getpid        ---       1     ---     ---
         getprec          2       2       2       2
     getprogname        ---       1     ---     ---
      is_default        ---       4       4       4
         isalpha          1       1       1       1
       isdefault          5     ---     ---     ---
         isdigit          1     ---       2       2
            main          1     ---       1       1
          printf          3       2       6       6
         putchar          1       1       1       1
         putdata          2       1       2       2
          random          1       1     ---       1
        snprintf        ---       2       1       1
         sprintf          1     ---     ---     ---
         srandom          1       1     ---       1
          sscanf          4       2       4       4
          strcat          1       1     ---       1
          strchr        ---       2     ---     ---
          strcpy          5     ---     ---     ---
         strlcat        ---     ---       2       1
         strlcpy        ---       1       4       3
          strlen          2       3       3       3
         strncmp          1     ---     ---     ---
          strspn        ---       1     ---     ---
          strtod        ---       1     ---     ---
          strtol        ---       1     ---     ---
        strtonum        ---     ---       1     ---
         strtoul        ---       1     ---     ---
            time          3       1     ---     ---
           usage        ---       3       2       2
           warnx        ---     ---       1     ---
        Function        L.c     N.c     O.c     F.c

N.cにmainが無いのは、多分cflowの仕様(と言うBug)です。

前回やった、似てる/似てない度を数値で表すより、こうして見える化されると、いろいろ想像 出来て楽しい! 前回のツールが営業用とすれば、今回のは技術屋向けとでも言うんでしょうか。

TODO

ちょいと、希望を書いておきます。

1. cflowに渡すオプションを、コマンドラインから指定出来るように

2. 同じく、解析対象のファイルも、コマンドラインから指定出来るようにする

3. 対象ファイルが今は、それぞれシングルCファイルだけど、複数のファイルで一つのアプリに なってるのがある。その場合は、外でcflowを動かし、そのログファイルをそれぞれ指定 出来るようにする。(Cのソースとログファイルは、自動判定させる)下のような例

4. Hashの名前をdbにしてたのは下心が有って。。。 /usr/srcの下のCソースを漁って、ライブラリィーの DBを構築したらどうかね。一度にやろうとすると大変なので、少しづつマージしてけばOK。dbだけを、 永続化すれば簡単だな。

[sakae@secd /usr/src/usr.bin/dc]$ cflow *c > ~/Fdc.log
[sakae@secd /usr/src/usr.bin/dc]$ cat ~/Fdc.log
  1 main: int(), <dc.c 81>
  2           getopt_long: <>
  3         init_bmachine: void(), <bcode.c 224>
  4                                  calloc: <>
  5                                     err: <>
  6                              stack_init: void(), <stack.c 38>
  7                                 BN_init: <>
  8                                 BN_zero: <>
  9         src_setstring: void(), <inout.c 69>
 10        reset_bmachine: void(), <bcode.c 259>
 11                  eval: void(), <bcode.c 1722>
 12                              readch: int(), <bcode.c 267>
 13                                       readchar: <>
 14                            src_free: void(), <bcode.c 291>
 15                             fprintf: <>
 16                         stack_print: <>
 17                               warnx: <>
 18              procfile: void(), <dc.c 61>
 19                                  fopen: <>
 20                                    err: <>
 21                                 fileno: <>
 22                                S_ISDIR: <>
 23                          src_setstream: void(), <inout.c 61>
 24                         reset_bmachine: void(), <bcode.c 259>
 25                                   eval: void(), <bcode.c 1722>
 26                                 fclose: <>
 27               fprintf: <>
 28                  exit: <>
 29                 usage: void(), <dc.c 53>
 30                            fprintf: <>
 31                               exit: <>
 32            setlinebuf: <>
 33         src_setstream: void(), <inout.c 61>

Linuxでも同様にして採取し、比べてみよう。後は、ac,bc,cc,dc,nc,wcとかでもいいぞ。