use ld error log
askOpenAI
ある論文によると、OpenAIの能力を十分に引き出すには、相手の立場を明確に して、褒めちぎる事らしい。プロンプトの記述方法も論文の種になるのね。 GPT-4などのLLM、応援メッセージを含めると精度が向上するとの研究結果 わ ざわざ研究しなくたって、昔からの格言があるじゃん。
豚も、おだてりゃ木に登る
本当か、後追い研究してみる。
Q: あなたは老練なハッカーです。それを見込んで、下記のLOGファイルを整理す るスクリプトを作成して下さい。なお、聞く所によると、ハッカーはpythonを 使わないそうですから、awkとかsedを駆使してください。
LOGファイルは、C言語をコンパイルする工程で、リンカーが報告してくるエラー です。単なるリンク忘れですが、これを逆手に取れば、どのソースファイルの 何と言う関数で、どんなライブラリィーの関数が利用されてるか、一目瞭然に なるはずです。
# 期待出力 gzopen.c gz_ropen crc32 gzopen.c gz_read crc32 gzopen.c gz_ropen inflateInit2_ zipoopen.c zip_ropen inflateInit2_ : # LOGファイル ld: error: undefined symbol: crc32 >>> referenced by gzopen.c >>> gzopen.o:(gz_ropen) >>> referenced by gzopen.c >>> gzopen.o:(gz_read) >>> referenced by gzopen.c >>> gzopen.o:(gz_read) >>> referenced 6 more times ld: error: undefined symbol: inflateInit2_ >>> referenced by gzopen.c >>> gzopen.o:(gz_ropen) >>> referenced by zipopen.c >>> zipopen.o:(zip_ropen) :
A: 以下は、提供されたLOGファイルを整理するためのawkとsedを使用したスクリプトの例です。
awk 'BEGIN { file=""; function_name=""; library=""; } \ /^ld: error: undefined symbol:/ { \ getline; \ file=""; function_name=""; library=""; \ while (match($0, />>> referenced by (.+)$/)) { \ file=substr($0, RSTART+17, RLENGTH-17); \ getline; \ if (match($0, />>>[[:space:]]+([[:alnum:]_]+)\([^)]+\)$/)) { \ function_name=substr($0, RSTART+4, RLENGTH-4); \ getline; \ if (match($0, />>>[[:space:]]+([[:alnum:]_]+)$/)) { \ library=substr($0, RSTART+4, RLENGTH-4); \ printf "%12s %-10s %s\n", file, function_name, library; \ } \ } \ getline; \ } \ }' logfile.txt
このスクリプトは、提供されたLOGファイルを読み取り、各エラーに関する情 報を整理して出力します。awkコマンドは行単位でテキストを処理し、sedはパ ターンの検索と置換を行います。このスクリプトを使用して、期待される出力 が得られるか確認してください。
最初のものは、何の返事もなし。よって、次の候補を依頼。三菱地所のCMでやっ てる、綺麗なオネーサンがエレベータのNEXTボタンを押すと未来へ飛んで行く のを彷彿させるな。未来ボタンとは、Regenerateって奴ね。
awk 'BEGIN { RS = ""; FS = "\n"; OFS = " " } /error: undefined symbol/ { file = "" function = "" library_function = "" for (i = 1; i <= NF; i++) { if (match($i, /referenced by/)) { file = $i } else if (match($i, /\(\w+\)/)) { function = $i } else if (match($i, />>>\s+\w+/)) { library_function = substr($i, RSTART + 4, RLENGTH - 4) } } print file, function, library_function }' LOG
何、このエラーは?
vbox$ ./bb.awk awk: syntax error at source line 4 context is >>> function <<< = "" awk: illegal statement at source line 4 awk: syntax error at source line 7
次、行ってみよう。
awk '/ld: error: undefined symbol:/ { file = ""; symbol = $NF; getline; getline; while (getline) { if ($NF == "times") break; file = $NF; getline; getline; getline; printf("%12s %-12s %s\n", file, $NF, symbol); } }' LOG
第3案にして、やっと端にかかる様になった。
vbox$ ./cc.awk gzopen.c gzopen.o:(gz_read) crc32 zipopen.c deflate inflateInit2_ gzopen.c gzopen.o:(gz_write) inflateInit2_ gzopen.c deflateEnd inflateInit2_ gzopen.c inflateEnd inflateInit2_ gzopen.c zipopen.o:(zip_ropen) inflateInit2_ zipopen.c inflate inflateInit2_ gzopen.c zipopen.o:(zip_read) inflateInit2_ gzopen.o:(gz_read) inflateInit2_ gzopen.o:(gz_wopen) inflateInit2_ invocation) 'compress') inflateInit2_
終いには、awkの一行野郎まで繰り出してきたぞ。全くハッカーらしくないな。豚じゃん。 そんな事を言ったら豚に失礼。ここは穏当に、兵共の夢の後、ぐらいにしてお くか。
だって、二つだけ得るものが有ったから。getlineなんてのがawkでも使えるのね。もう一つは、matchの使い方。一致が取れた場所と長さが、 RSTART,RLENGTHにセットされるなんて知らんわ。
mycode
ヒントを貰ったので、自分でヤッチャえ、と、NISSANのCM風。
#! /bin/sh # Usage: make(cc) |& ./pick.sh # pickUp file function symbol from linker error log awk 'BEGIN {file=""; fn=""; sym="";} /ld: error: undefined symbol:/ { sym = $5 } />>> referenced by/ { file = $4 } />>> / { match($2, /\(.*\)/); fn = substr($2, RSTART + 1, RLENGTH - 2); printf "%12s %-16s %s\n", file,fn,sym } ' | sort
素直になればいいのにネ。
使用上の大前提として、clang系の物でしか動きま せん。残念ながらgcc(Linux)は蚊帳の外です(これ、以前から言ってみたかっ たのよ)。 エラー情報はstderrに流れてくるので、パイプに流れるように、パイプを設定 して下さい( |& )。
testme
早速試験してみる。 zlibの中に有るminigzipのlibz使用状況を、調べてみる。
[sakae@fb /tmp/zlib-1.3/test]$ cc minigzip.c |& ./pick.sh minigzip.c file_compress gzopen minigzip.c file_uncompress gzopen minigzip.c gz_compress gzclose minigzip.c gz_compress gzerror minigzip.c gz_compress gzwrite minigzip.c gz_uncompress gzclose minigzip.c gz_uncompress gzerror minigzip.c gz_uncompress gzread minigzip.c main gzdopen minigzip.c main gzdopen minigzip.c main gzdopen minigzip.c main gzopen
もう一つ例で、今回活躍してくれたawk君。manを参照してたら、sin,cos,log 関数とかが利用可ってなってたから、-lm してるはず。
PROG= awk SRCS= awkgram.tab.c lex.c b.c main.c parse.c proctab.c tran.c lib.c run.c # LDADD= -lm
これMakefileの一部。LDADD= -lm をコメントにして、リンク漏れを発生させ る。
VBOX$ make |& pick.sh run.c arith pow run.c assign pow run.c bltin atan2 run.c bltin cos run.c bltin exp run.c bltin log run.c bltin sin
今度は普通の人も使える電卓bc。 emacs/calcは、その筋の人用だからね。
VBOX$ make |& pick.sh bc.c main el_init bc.c main el_set bc.c main el_set bc.c main el_set bc.c main el_source bc.c main history bc.c main history bc.c main history_init scan.c abort_line el_line scan.c bc_eof el_line scan.c yylex el_get scan.c yylex el_get scan.c yylex el_gets scan.c yylex el_push scan.c yylex history bcode.c bmod BN_div bcode.c bmul BN_mul bcode.c bmul_number BN_mul bcode.c init_bmachine BN_CTX_new bcode.c load_array BN_is_negative bcode.c negate BN_is_negative bcode.c num_digits BN_exp bcode.c scale_number BN_div :
豪華に、 -ledit -lcurses -lcrypto をリンクしてた。暗号電卓かと思ったら 大間違いで、暗号に必須の多倍長演算ルーチンを拝借してんだね。
あれ? -lcurses は、リンクされてるけど使用されていないぞ。これはBUGの 範疇に入いるのだろうか? OpenBSDはミニマム主義なんだから不要な物のリン クはまずいだろね。
mgはemacs風なeditor で、/usr/lib/libcurses.so.14.0 と /usr/lib/libutil.so.17.0 を抱えている。
VBOX$ make |& pick.sh tty.c ttdell tgoto tty.c tteeop tgoto tty.c ttinit setupterm tty.c ttinit tputs tty.c ttinit tputs tty.c ttinit tputs tty.c ttmove tgoto main.c main login_tty main.c main openpty tags.c tagsvisit fparseln tags.c tagsvisit fparseln display.c uline cur_term display.c uline cur_term display.c uline cur_term
tgotoはcursesに属し、fparselnはutilに属してるなんて事がman一発で判明す る。これは便利だなあ。
for ksh
kshは残念ながらstderrもパイプする技( |& )をサポートしていない。で、下 記の様にする。パイプする前に、stderr(2)をstdout(1)に向ける。そしてそれ をパイプに潜りこませるんだ。当然、本来のstdoutはパイプを通過できない。
vbox$ make 2>&1 | pick.sh gzopen.c gz_close deflate gzopen.c gz_close deflateEnd :
こうすれば、bash並みになる。
{ cmd 2>&1 1>&3 | progress; } 3>&1
for gcc
リナではコンパイルをがんがんやると言う遊びは余りしないんだけど、それで も上にあげたスクリプトと同等のものを作っておきたい。
資料収集
リンカーのエラー報告は、標準エラー出力に流れてくるので、単にリダイレク トしただけでは、蓄積してくれない。下記のおまじないが必要だ。
[sakae@deb test]$ gcc minigzip.c >LOG 2>&1 [sakae@deb test]$ cat LOG /usr/bin/ld: /tmp/cclzDiLP.o: in function `gz_compress': minigzip.c:(.text+0xb5): undefined reference to `gzwrite' /usr/bin/ld: minigzip.c:(.text+0xcf): undefined reference to `gzerror' /usr/bin/ld: minigzip.c:(.text+0xfd): undefined reference to `gzclose' /usr/bin/ld: /tmp/cclzDiLP.o: in function `gz_uncompress': minigzip.c:(.text+0x148): undefined reference to `gzread' /usr/bin/ld: minigzip.c:(.text+0x166): undefined reference to `gzerror' /usr/bin/ld: minigzip.c:(.text+0x1dc): undefined reference to `gzclose' /usr/bin/ld: /tmp/cclzDiLP.o: in function `file_compress': minigzip.c:(.text+0x2c4): undefined reference to `gzopen' /usr/bin/ld: /tmp/cclzDiLP.o: in function `file_uncompress': minigzip.c:(.text+0x435): undefined reference to `gzopen' /usr/bin/ld: /tmp/cclzDiLP.o: in function `main': minigzip.c:(.text+0x6ef): undefined reference to `gzdopen' /usr/bin/ld: minigzip.c:(.text+0x74a): undefined reference to `gzdopen' /usr/bin/ld: minigzip.c:(.text+0x7a9): undefined reference to `gzopen' /usr/bin/ld: minigzip.c:(.text+0x870): undefined reference to `gzdopen' collect2: error: ld returned 1 exit status
単にstderrからの出力をログするなら
[sakae@deb test]$ gcc minigzip.c 2>LOG
で、良い。
for gcc
上のログを題材に、でっちあげ。
#! /bin/sh grep '/usr/bin/ld' | awk 'BEGIN { file=""; fn=""; sym="" } /: in function / { fn = $5 } /: undefined reference to/ { p = index($2, ":"); file = substr($2, 0, p - 1); sym = $6; printf "%16s %-16s %s\n", file,fn,sym } ' | sort
test
[sakae@deb test]$ cc minigzip.c |& ./pick.sh minigzip.c `gz_compress': `gzclose' minigzip.c `gz_compress': `gzerror' minigzip.c `gz_uncompress': `gzclose' minigzip.c `gz_uncompress': `gzerror' minigzip.c `main': `gzdopen' minigzip.c `main': `gzdopen' minigzip.c `main': `gzopen'
ビールの極なら歓迎するけど、こちらは手抜きの極め例になってます。 銀杏が入手できたんだけど、ビールと日本酒と、どちらがお勧め? もう、そんな季節なんですねぇ。
名誉回復
りなとpythonは相性が良さそうなので、名誉回復のチャンスを与えてみる。
Q: あなたは優秀なpython hackerです。下記のLOGファイルを整理す るスクリプトを作成して下さい。LOGファイルは、C言語をコンパイルする工程で、リンカーが報告してくるエラー です。単なるリンク忘れですが、これを逆手に取れば、どのソースファイルの 何と言う関数で、どんなライブラリィーの関数が利用されてるか、一目瞭然に なるはずです。
# 期待出力 minigzip.c gz_compress gzclose minigzip.c gz_compress gzerror minigzip.c gz_uncompress gzclose minigzip.c gz_uncompress gzerror minigzip.c main gzdopen minigzip.c main gzdopen minigzip.c main gzopen # LOGファイル /usr/bin/ld: /tmp/cclzDiLP.o: in function `gz_compress': minigzip.c:(.text+0xb5): undefined reference to `gzwrite' /usr/bin/ld: minigzip.c:(.text+0xcf): undefined reference to `gzerror' /usr/bin/ld: minigzip.c:(.text+0xfd): undefined reference to `gzclose' /usr/bin/ld: /tmp/cclzDiLP.o: in function `gz_uncompress': minigzip.c:(.text+0x148): undefined reference to `gzread' :
提示された奴。
import re def extract_errors(log_file): errors = [] with open(log_file, 'r') as file: lines = file.readlines() for line in lines: match = re.search(r'(\S+\.c):\(\S+\): undefined reference to `(\w+)', line) if match: file_name, undefined_function = match.groups() errors.append((file_name, undefined_function)) return errors def print_function_references(errors): references = {} for error in errors: file_name, undefined_function = error match = re.search(fr'{file_name}.*{undefined_function}', log_text) if match: function_name = match.group(1) references.setdefault((file_name, function_name), []).append(undefined_function) for key, value in references.items(): print(f'{key[0]:<12} {key[1]:<15} {" ".join(value)}') if __name__ == "__main__": log_file = "path/to/your/logfile.log" # ログファイルのパスを指定してください errors = extract_errors(log_file) print_function_references(errors)
長ったらしいやっちゃなあ。おまけに、
[sakae@deb test]$ python3 sel.py Traceback (most recent call last): File "/tmp/zlib-1.3/test/sel.py", line 32, in <module> print_function_references(errors) File "/tmp/zlib-1.3/test/sel.py", line 21, in print_function_references match = re.search(fr'{file_name}.*{undefined_function}', log_text) ^^^^^^^^ NameError: name 'log_text' is not defined
エラーにはpdbがお似合いよ。
[sakae@deb test]$ python -m pdb sel.py > /tmp/zlib-1.3/test/sel.py(2)<module>() -> import re (Pdb) b extract_errors Breakpoint 1 at /tmp/zlib-1.3/test/sel.py:4 (Pdb) c > /tmp/zlib-1.3/test/sel.py(5)extract_errors() -> errors = [] (Pdb) p log_file '/tmp/zlib-1.3/test/LOG'
ちゃんとログファイルは指定したからね。やっぱり腐れハッカーだな。次行っ てみようかね。ボタンを連打してみたけど、やっぱり駄目だーーーーー。
どうも易しい事を難しく表現しようとして、自爆してる節があるな。難しい事の 一端を取り出して使うぐらいが、丁度いいかな。いわば辞書代わりね。
盗んで作れ
それって、平たく言えば盗作だよね。
import re with open("./LOG", 'r') as file: lines = file.readlines() for line in lines: match = re.search(r': in function `(\w+)', line) if match: fn = match.group(1) match = re.search(r'(\S+\.c):\(\S+\): undefined reference to `(\w+)', line) if match: file_name, sym = match.groups() print(f'{file_name:>16} {fn:<16} {sym}')
すっきりサッパリ、サロンパスなんちゃってね。
[sakae@arch z]$ python3 sel.py minigzip.c gz_compress gzwrite minigzip.c gz_compress gzerror minigzip.c gz_compress gzclose minigzip.c gz_uncompress gzread minigzip.c gz_uncompress gzerror minigzip.c gz_uncompress gzclose minigzip.c file_compress gzopen minigzip.c file_uncompress gzopen minigzip.c main gzdopen minigzip.c main gzdopen minigzip.c main gzopen minigzip.c main gzdopen
awkの結果と違うな。見比べたら、awk版の方は、grepでフィルターして、/usr/bin/ld 限定にしてた(gccのld的に、 正しいかどうかは、定かではないけど)。
for line in lines: match = re.match(r'/usr/bin/ld:', line) if not match: continue match = re.search(r': in function `(\w+)', line)
下に落ちて行かないように、防止策を入れるといい。
盗作と言うと聞こえが悪いな。今回の場合だと、ジャンク品から使える部品を 取り出して有効利用しましたってエコな生活ですよ。全世界的な流行ですから ね。OpenAIは、その為のジャック品を提供してくれてる訳ね。で、嬉しいのは、 要求した分野にピッタリのジャンク品を提示してくれる事。
今回の学習成果は、正規表現エンジンの駆動方法の違いを知った事。matchは 文字列の頭からの突き合わせ。searchは緩くて、そんな制限は無い。pythonを awk風に使うなら、迷わずにsearchを使っとけ。
もう一つ成果が有ったぞ。printfでフォーマット出力するなんて、らしくないっ てな。python独自のprintによるprintの為のフォーマット記法。新発明で pythonにロックインさせたい思惑が透けてみえます。なんせ、大学でも、こぞっ てpythonを教えるぐらいですから。
折角作ったスクリプトなんで、FreeBSDでも確認。gcc1[012] と豪華な陣容で あった。/usr/local/bin/ld なんだよな。matchを使って頭からの一致じゃ駄 目だ。サッとやるなら、searchだね。