似てる、似てない?(4)
fingerprint 指紋 これ、以前にsha256をやった時に出てきた。そんな繋がりが有ったか 無かったか知らないけれど 『指紋を発見した男』(主婦の友社)なんていう本を読んでみた。
その男とは、ヘンリー・フォールズその人である。イギリスから日本に派遣された宣教師兼医師。 東京築地にある聖路加国際病院を起した人でもある。病院の敷地内には、「指紋研究発祥之地」と 言う記念碑があるそうなので、東京見物の折にでも立ち寄ってみたいぞ。
宣教師である彼は布教の任が有り、明治時代の人々を一生懸命に改教しようと講演に務めるわけだが、 人気がなかなか出ない。ライバルが居たのである。そのライバルとは、大森貝塚を発見したモース。 彼の科学講演の方が、明治の頃の人には人気だったと言う訳。
ライバルであるこの二人も、仕事を離れれば佳き友達。貝塚の発掘にヘンリーが参加したりして いたそうな。そんなある日、発掘された土器に微小な模様があるのを発見する。手の模様(指紋) が付いたんだろうと言う仮定の上で、いろいろな瀬戸物を調べてみると、やはりいろいろな模様が 付いている。詳細に調べてみると、皆違う模様。一方、彼は、日本で行われていた、拇印にも 興味を持っていた。この二つの興味が、やがて彼を指紋研究へ誘う事となる。
何年にも渡る研究の末、2つと同じ指紋は無い事、指紋は再生する事から、同一人の判定に使える と確信した彼は、ネイチャーにその事を発表する。
しかし、彼には傲慢?なライバルが出現した。指紋をどうやって犯罪捜査に応用するかで スコットランド・ヤードとの駆け引きが始まる。120年ぐらい前の戦いが、犯罪捜査をからめて 書かれており、興味は尽きない。
いまだと、 指紋鑑定は言うに及ばず、DNA鑑定やら声紋鑑定やらいろいろ有るけど それぞれに、先人達のドラマがあるんだろうな。
あるある亡き後、皆様のNHKが提供する、試してガッテンがぶっちぎりの強さを見せている ように思うけど、どうだろう?
老人・女性が喜びそうな話題満載。ねちっこく話題を引っ張って話し3分で終わる所を40分に 引き伸ばす水増し番組。手作りの模型が毎回出てきて笑える。
そして、何と言っても権威付け。どこかの大学の先生が毎回登場。先生が言ってるんだから 信用してねってのは、この世界のカリスマであるみのさんに通じるものがあるなあ。
食べ物とかが取り上げられると、翌日はスーパーで売り切れているもんなあ。好いと言われる 食べ物を必ず食べていたら早死にするぞ。
ああ、年寄り増えすぎちゃったんで、みんな早く無くなってねっていう密かな陰謀が隠れて いるんだな。その方が世のため人のためになるか。まあ、どこかの先生が言ってるのw紹介 しただけだから、責任は取らないよってテロップも時々流れているな。
参照するぞ
前回に引き続いて、今回は参照系をつくります。名前は、acc.rbです。ちと長いけどドーンと 載せておきます。
#!/usr/local/bin/ruby require 'pstore' require '/home/sakae/z/c_keywords.rb' # C keywords from cflow DBFILE='/home/sakae/z/FreeBSD.dat' def hdlopt() opt = '' if ARGV[0] == nil puts "Usage: acc.rb [-asACGP] sorce ..." exit end opt = ARGV.shift if ARGV[0][0] == '-' list = ARGV return list,opt end def show(list, ef, fn, opt) noshow = true list.each {|sn| noshow = false if ef.key?(sn)} return if noshow return if opt =~ /A/ and ANSI_KEYWORDS.include?(fn) return if opt =~ /P/ and POSIX_KEYWORDS.include?(fn) return if opt =~ /C/ and C99_KEYWORDS.include?(fn) return if opt =~ /G/ and GCC_KEYWORDS.include?(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 def ttl(list) printf("%16s", "Function") list.each {|sn| printf("\t%s", sn)} puts '' end def dump_all(pb) pb.transaction do db = pb["roots"] db.keys.sort.each do |k| fn,sn = k.split(':') printf("%16s %8s %3d\n", fn, sn, db[k]) end end end def src_all(pb) src = {} pb.transaction do pb['roots'].keys.each do |k| fn,sn = k.split(':') src[sn] = true end end src.keys.each{|n| puts n} end def comp(pb, list, opt) pb.transaction do ofn = "" ef = {} ttl(list) db = pb["roots"] db.keys.sort.each do |k| fn,sn = k.split(':') if fn != ofn show(list, ef, ofn, opt) if ofn != '' ofn = fn ef.clear end ef[sn] = db[k] end show(list, ef, ofn, opt) ttl(list) end end ### main HERE ################### list,opt = hdlopt() pb = PStore.new(DBFILE) if opt == '-a' dump_all(pb) elsif opt == '-s' src_all(pb) else comp(pb,list,opt) end
使い方
[sakae@secd ~/z]$ ./acc.rb Usage: acc.rb [-asACGP] sorce ... [sakae@secd ~/z]$ [sakae@secd ~/z]$ ./acc.rb -a ABORT_READ camcontrol 5 ABS lex 6 ACC kbdcontrol 4 : zread gzip 1 zuncompress gzip 1 zwrite compress 1
引数無しで、使い方が出てくるのは、お約束。 -a は、いわゆる全データのダンプ。左側から 関数名、ソース(と言うかコマンド名)、出現回数って言う並びだ。コマンド名は8文字で収まると 思っていたら、unixらしからぬ長い名前が有るのね。
ダンプなんてそんなに使い道無いじゃんと思うけど、grepとかとの合わせ技で真価が発揮されます。
[sakae@secd ~]$ ./acc.rb -a | grep arc4random arc4random dhclient 4 arc4random ed 1 arc4random jot 1 arc4random keyserv 2 arc4random netcat 1 arc4random newfs 1 arc4random newkey 1 arc4random ping6 1 arc4random_buf hastd 2 arc4random_stir newfs 1 arc4random_uniform rtadvd 2 arc4random_uniform rtsold 1
もう、こういうのを見せられると、あれもそれも摘まみ見したくなっちゃうぞ。
[sakae@secd ~/z]$ ./acc.rb -s cat chflags chio chmod :
登録されてるソース数を数えてみたら、535個になってた。
[sakae@secd ~/z]$ ./acc.rb rm rmdir mv Function rm rmdir mv ISDOT 1 --- --- : warn 4 2 26 warnx 4 --- 8 write --- --- 1 Function rm rmdir mv
rm,rmdir,mvが、どれぐらい似てるかと思ってみたりして。-ACGPの引数は、cflowから引き継いで います。
[sakae@secd ~/z]$ ./acc.rb echo Function echo errexit 2 exit 1 main 1 malloc 1 strcmp 1 strerror 1 strlen 4 write 3 writev 1 Function echo [sakae@secd ~/z]$ ./acc.rb -ACGP echo Function echo errexit 2 main 1 writev 1 Function echo
へぇー、echoでも、malloc なんてのを使ってるよ。使う必然性は有るの? ソース嫁。
パクリ
上記の、-ACGPは、規格に登録されてる関数類は表示しないって機能だ。これをを実現するには、 登録してある関数名を知ってる必要が有ります。
cflowでは、作者さんが一生懸命に調べて、それをC言語用のヘッダーファイルに書いて おいてくれてます。
例えば、ansi_keywords.h の中は下記のようになってました。
static const char* ansi_keywords[] = { "assert", "acos", "asin", "atan", "atan2", "atof", "atoi", "atol", : "wctomb", "wcstombs", NULL };
ruby用にパクらせて貰いました。
[sakae@secd ~]$ cd cflow-0.0.6/common/ [sakae@secd ~/cflow-0.0.6/common]$ ls ansi_keywords.h graph.c printgraph.c c99_keywords.h graph.h wincompat.h gcc_keywords.h posix_keywords.h [sakae@secd ~/cflow-0.0.6/common]$ cat *keywords.h > c_keywords.rb
等としてc_keywords.rb を作り、それを編集すればいいでしょう。配列の最後に入っている NULLは、 C言語では番兵として使われているんで、Rubyでは不用です。それぞれの配列名は、 何処からでもアクセス出来るように、大文字にしています。 配布する場合は、*.h の冒頭にあるコメントに従ってください。
cgraph
折角なので、cflowがどんな技を使ってるか見てみる。自分で自分自身を表示させると言う常套手段だな。
[sakae@secd ~/cflow-0.0.6]$ cflow -ACGP cgraph/*c common/*c 1 main: int(), <cgraph/cgraph.c 65> 2 usage: void(), <cgraph/cgraph.c 50> 3 create_excludes: node_t*(), <common/graph.c 520> 4 add_excludes: node_t*(), <common/graph.c 493> 5 add_node: node_t*(), <common/graph.c 194> 6 free_nodes: void(), <common/graph.c 230> 7 lex_create_graph: bool_t(), <cgraph/clexer.c 512> 8 get_next_token: int(), <cgraph/clexer.c 374> 9 skip_whitespaces: int(), <cgraph/clexer.c 63> 10 skip_strings: int(), <cgraph/clexer.c 145> 11 get_next_token: int(), <cgraph/clexer.c 374> 12 parse_cpp: int(), <cgraph/clexer.c 311> 13 skip_whitespaces: int(), <cgraph/clexer.c 63> 14 get_name: char*(), <cgraph/clexer.c 196> 15 is_c_keyword: bool_t(), <cgraph/clexer.c 264> 16 is_reserved: int(), <cgraph/clexer.c 242> 17 is_excluded: bool_t(), <cgraph/clexer.c 287> 18 get_definition_node: g_node_t*(), <common/graph.c 164> 19 add_g_node: g_node_t*(), <common/graph.c 256> 20 get_definition_node: g_node_t*(), <common/graph.c 164> 21 create_g_node: g_node_t*(), <common/graph.c 89> 22 create_sub_node: g_subnode_t*(), <common/graph.c 145> 23 add_to_call_stack: bool_t(), <common/graph.c 325> 24 get_definition_node: g_node_t*(), <common/graph.c 164> 25 create_sub_node: g_subnode_t*(), <common/graph.c 145> 26 print_graph: void(), <common/printgraph.c 243> 27 print_preorder: void(), <common/printgraph.c 136> 28 nodes_contain: bool_t(), <common/printgraph.c 77> 29 print_node: void(), <common/printgraph.c 97> 30 print_preorder: void(), <common/printgraph.c 136> 31 print_callers: void(), <common/printgraph.c 192> 32 nodes_contain: bool_t(), <common/printgraph.c 77> 33 print_node: void(), <common/printgraph.c 97> 34 print_graphviz_graph: void(), <common/printgraph.c 462> 35 print_graphviz_preorder: void(), <common/printgraph.c 340> 36 nodes_contain: bool_t(), <common/printgraph.c 77> 37 print_graphviz_node: void(), <common/printgraph.c 324> 38 print_graphviz_preorder: void(), <common/printgraph.c 340> 39 print_graphviz_callers: void(), <common/printgraph.c 406> 40 nodes_contain: bool_t(), <common/printgraph.c 77> 41 print_graphviz_node: void(), <common/printgraph.c 324> 42 nodes_contain: bool_t(), <common/printgraph.c 77> 43 free_nodes: void(), <common/graph.c 230> 44 free_g_nodes: void(), <common/graph.c 453> 45 free_g_node: void(), <common/graph.c 50>
余りに詳しく表示されすぎちゃったかな。少し表示レベルを控えめにしてみる。
[sakae@secd ~/cflow-0.0.6]$ cflow -ACGP -d 2 cgraph/*c common/*c 1 main: int(), <cgraph/cgraph.c 65> 2 usage: void(), <cgraph/cgraph.c 50> 3 create_excludes: node_t*(), <common/graph.c 520> 4 add_excludes: node_t*(), <common/graph.c 493> 5 lex_create_graph: bool_t(), <cgraph/clexer.c 512> 6 get_next_token: int(), <cgraph/clexer.c 374> 7 get_definition_node: g_node_t*(), <common/graph.c 164> 8 add_g_node: g_node_t*(), <common/graph.c 256> 9 create_sub_node: g_subnode_t*(), <common/graph.c 145> 10 add_to_call_stack: bool_t(), <common/graph.c 325> 11 print_graph: void(), <common/printgraph.c 243> 12 print_preorder: void(), <common/printgraph.c 136> 13 print_callers: void(), <common/printgraph.c 192> 14 print_graphviz_graph: void(), <common/printgraph.c 462> 15 print_graphviz_preorder: void(), <common/printgraph.c 340> 16 print_graphviz_callers: void(), <common/printgraph.c 406> 17 nodes_contain: bool_t(), <common/printgraph.c 77> 18 free_nodes: void(), <common/graph.c 230> 19 free_g_nodes: void(), <common/graph.c 453> 20 free_g_node: void(), <common/graph.c 50>
表示をオミットする為のテーブルを作り、それから解析して結果をconsセルみたいのに溜めてくんだな。 全部のファイルを収録したら、普通の方式かgraphviz形式かのどちらかで表示する。
全体のコントロールは、cgraph.c、解析はclexer.c、表示系はprintgraph.cか。Web屋さんとかレール屋 さんが好きな、MVCに習って構成されてるな。
起動したのはcflowなのに、それが出てこないのは何故かと思って調べてみたら、cflowはシェルだった。本体は、C言語用のcgraphと アセンブラ用のasmgraphの2本立てになってる。そんじゃ、例によってgdbのお出まし。-g付きで コンパイルしたcgraphをcgとして用意した。
[sakae@secd ~/cflow-0.0.6]$ gdb -q cg (gdb) b print_preorder Breakpoint 1 at 0x804afa7: file ../common/printgraph.c, line 139. (gdb) run cgraph/*.c common/*.c Starting program: /usr/home/sakae/cflow-0.0.6/cg cgraph/*.c common/*.c Breakpoint 1, print_preorder (graph=0xbfbfe704, node=0x28404100, depth=0, maxlen=4, pad=3, count=0xbfbfe6bc) at ../common/printgraph.c:139 139 int sublen = 0; (gdb) c Continuing. 1 main: int(), <cgraph/cgraph.c 65> Breakpoint 1, print_preorder (graph=0xbfbfe704, node=0x28404130, depth=1, maxlen=27, pad=3, count=0xbfbfe6bc) at ../common/printgraph.c:139 139 int sublen = 0; (gdb) c Continuing. 2 setlocale: <> Breakpoint 1, print_preorder (graph=0xbfbfe704, node=0x28404160, depth=1, maxlen=27, pad=3, count=0xbfbfe6bc) at ../common/printgraph.c:139 139 int sublen = 0; (gdb) p *graph $1 = {excludes = 0x0, defines = 0x28404070, defcount = 62, statics = 0, privates = 0, depth = 2147483647, root = 0x804bbc7 "main", rootnode = 0x28404100, complete = 0, reversed = 0} (gdb) p *node $2 = {next = 0x28404190, list = 0x0, callers = 0x2840d0b8, name = 0x2840d0c0 "getopt", namelen = 6, type = 0x0, file = 0x2840e080 "cgraph/cgraph.c", line = -1, ntype = FUNCTION, private = 0, printed = 0}
ふむ、print_preorderは再帰呼び出ししてんのか、nodeがconsセルみたいになってるから、上等 手段だな。次は、ちょっと、 print_nodeの中を見て見る。
(gdb) b print_node Breakpoint 2 at 0x804ae18: file ../common/printgraph.c, line 99. (gdb) c Continuing. Breakpoint 2, print_node (node=0x284040d0, pad=3, maxlen=37, count=7) at ../common/printgraph.c:99 99 if (node->line != -1) (gdb) n 121 printf ("%*d %*s: <>\n", pad, count, (int)maxlen, node->name); (gdb) 7 exit: <> 122 } (gdb) p pad $3 = 3 (gdb) p count $4 = 7 (gdb) p maxlen $5 = 37 (gdb) p node->name $6 = 0x2840d050 "exit"
121行目のprintfの使い方、ちょっと面食らっちゃったぞ。フォーマット指示子の中で%*とすると、 動的に扱ってくれるんだ。初めて知ったよ、こんな事。rubyでも、勿論使えるんだろうな。
irb(main):001:0> x=4 => 4 irb(main):002:0> printf("%.*f\n", x, 12.345678) 12.3457 => nil irb(main):003:0> printf("%.*f\n", x, 12.343210) 12.3432 => nil
小数点以下4桁表示ってのを変数を介して指定、ちゃんと指定通り、しかもご丁寧に5桁目を四捨五入 してくれると言うオマケ(余計なお世話)付き。調べてみたら、この余計なお世話も世界標準に なってましたよ。
エラーの傾向と対策
前回、reg.rbを書いて、いろいろなソースを登録していった。その時に不可解なエラー出てた。 どんなエラーか整理すると、
/usr/src/bin/pax/ar_io.c: Brace level mismatch at line 411 /usr/src/usr.bin/make/main.c: Brace level mismatch at line 326 /usr/src/usr.sbin/jls/jls.c: Brace level mismatch at line 411 /usr/src/usr.sbin/ppp/chap.c: Brace level mismatch at line 911 /usr/src/usr.sbin/sysinstall/cdrom.c: Brace level mismatch at line 173 /usr/src/libexec/rbootd/bpf.c: Brace level mismatch at line 272 /usr/src/contrib/gcc/profile.c: Brace level mismatch at line 802 /usr/src/contrib/less/prompt.c: Brace level mismatch at line 531 /usr/src/contrib/libreadline/display.c: Brace level mismatch at line 1014 /usr/src/contrib/tcp_wrappers/rfc931.c: Brace level mismatch at line 193 /usr/src/contrib/tcsh/ed.defns.c: Brace level mismatch at line 1808
等だ(まだ他にも有ったかも)。こんなエラー何処で出してるか(想像は付くけど)調べてみると、clexer.cに
537 (token == BODYSTART) ? level++ : level--; 538 if (level < 0) 539 { 540 /* That should not happen. */ 541 fprintf (stderr, "%s: Brace level mismatch at line %d\n", 542 filename, line); 543 goto error; : 555 (token == ARGSTART) ? arglevel++ : arglevel--; 556 if (arglevel < 0) 557 { 558 /* That should not happen. */ 559 fprintf (stderr, "%s: Brace level mismatch at line %d\n", 560 filename, line); 561 goto error;
BODYってのは関数の定義だろうな。ARGSTARTってのは、多分関数の引数だろう。どちらも同じ エラーメッセージじゃちと困るんで、どちらか分かるようにしとく。
[sakae@secd /usr/src/bin/pax]$ ~/cflow-0.0.6/cg *.c ar_io.c: Brace level mismatch-ARG at line 411
もう一つぐらい調べてみるか
[sakae@secd /usr/src/usr.bin/make]$ ~/cflow-0.0.6/cg *.c main.c: Brace level mismatch-BODY at line 326
どちらも有りって事か。そんじゃ、解析対象になってるソースを事故分析してみる。まずは、ar_io.cの 方から
403 else if (strcmp(NM_TAR, argv0) != 0) 404 (void)fprintf(listf, 405 # ifdef NET2_STAT 406 "%s: %s vol %d, %lu files, %lu bytes read, %lu bytes written.\n", 407 argv0, frmt->name, arvol-1, flcnt, rdcnt, wrcnt); 408 # else 409 "%s: %s vol %d, %ju files, %ju bytes read, %ju bytes written.\n", 410 argv0, frmt->name, arvol-1, (uintmax_t)flcnt, 411 (uintmax_t)rdcnt, (uintmax_t)wrcnt); 412 # endif 413 (void)fflush(listf);
ふむ、確かに、fprint内の引数部分でエラー検出してるな。#ifdefで切り分けてるんか。cflowは そこまで考慮してないんで、407行目で完結してるはずなのに、411でも完結したって事で、エラーに 落としてるんだな。
ならば、cflowの実験的機能を使って、#ifdefを有効にしてあげよう。
[sakae@secd /usr/src/bin/pax]$ cflow -p -DNET2_STAT=1 ar_io.c 1 pselect: <> 2 select: <> :
エラーにもならずに、ちゃんと解析してくれたよ。そんじゃ次は、make/main.cだな。どんな ソースになってる?
320 found: 321 if (setMAKEFILE) 322 Var_SetGlobal("MAKEFILE", MAKEFILE); 323 Parse_File(fname, stream); 324 } 325 free(fnamesave); 326 return (TRUE); 327 }
326行目がエラーと言われても、おいらにはピピーンと響いてこないね。何かお助けマンはいないかと、 clexer.c内をうろうろしてたら、
#if C_DEBUG printf ("Adding function definition %s\n", curname); #endif
こんなプローブが要所に埋め込まれていました。早速、これを生かしてから実行すると、
Adding function call 'Var_SetGlobal' in func 'ReadMakefile', 321 Adding function call 'Parse_File' in func 'ReadMakefile', 322 main.c: Brace level mismatch-BODY at line 326
うむ、表示してる行番号が1つずれているな。って事は、エラーを検出したのは、325行って事だな。 324行目で、ブロックが終了して、325行目で上位のブロックへ戻るはず。clexer.cのエラー表示 ルーチンを見ると、負のブロック・レベルだよって言ってる。
これを信じれば、main.cに問題有り? viのカッコのバランスを調べるコマンド % を使って 324行目の対を調べると、関数(ReadMakefile)の頭へ飛んで行ったよ。 と言うことは、ソースの字面上では、324行はいらない子って事になる。
いらない子をコメントにしたら、エラーも無くちゃんと解析したよ。
[sakae@secd ~/cflow-0.0.6]$ cflow main.c 1 main: int(), <main.c 867> 2 getenv: <> 3 estrdup: <> 4 check_make_level: void(), <main.c 680> :
これって、make/main.cのBUG かな? 新しいソースが出るまで心のうちにしまっておこうか。 それとも、OpenBSDのそれと比べてみる? おいら的には、似てる/似てない観点からすれば、 OpenBSD5.2を入れてって方が面白いんですけどね。
おまけでemacsでもcflow
上で出てきた、cflowのcflowだが、ちと醜い(見にくい)。情報量が多すぎるから? だったら 必要最低限な呼び出し関係だけに絞ればいいかいなって、なる訳だ。そんなのをこれから作る? 面倒だなあ。
この間、emacsでもpngを表示出来る事を知っちゃったので、それでいいよね。cflowが吐く、dot言語の 原稿では、ちと醜いものになってしまうんで、vim cflow-0.0.6/common/printgraph.c して、補足 してあげる。
466 printf ("digraph \"%s\" {\n", "TODO"); 467 printf (" graph [rankdir=LR,overlap=false];\n");
467行目を追加して、グラフの体裁を整えるのだ。
382 // printf (" [label=\"%ld\"];\n", count); 383 printf ("\n");
また、エッジの数字がうざいと感じたら、抑制しちゃおう。そうすれば複雑な図も少しはすっきりと 見えるだる。
cflowからグラフ(のpngファイル)を作るのは 一発芸と言うか一行野郎なんで、aliasにでも入れておこうとしたら、どうも引数の展開が うまくいかない。
しょうがないので、flowなんて名前のスクリプトを書いたよ。
#!/bin/sh cflow -ACGP -g "$@" | dot -T png > /tmp/flow.png
emacsでpngファイルを見る時、カーソル移動(C-v,M-v,C-a,C-e等)が普通に出来るんで、AAを 見てる気分だよ。emacsでファイルをリロードするにはどうやったらいいか調べたら、C-x C-v と言うのが有るのね。今回初めて知った。
それから、emacsに巨大なpngファイルを 喰わせると、表示を拒否しちゃうのね。 軟弱なんだか自己保身なんだか分からんけど、何とかしてくれ、>emacsな人々よ。