use ld error log

Table of Contents

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だね。

etc