あるシンボルが どのオブジェクトファイル/共有オブジェクトに含まれているか検索するスクリプト

プログラミングしていると,あるシンボル(変数名,関数名,クラス名,etc.)がどのオブジェクトファイル(or アーカイブファイル)や共有ライブラリで定義されているか,調べたいことがある(どのファイルをリンクすればいいかわからないときなど).通常はシンボル名でググればよろしい.だが,稀に,ググるのでは解決できないことがある.そのような場合の対処法として,無理矢理 /usr/lib や /lib にあるライブラリファイルを検索する方法を紹介する.

ライブラリの種類

予備知識なので,読み飛ばしても可.

プログラムをコンパイルして生成されたオブジェクトファイル (*.o) をコマンド ar でアーカイブすると,アーカイブファイル (*.a) ができる.これが,静的ライブラリ (static library).実行ファイルの生成時に静的にリンクされるため,実行時にはアーカイブファイルは不要.

一方,共有ライブラリ - Program Library HOWTOに書いてあるような方法で,オブジェクトファイルから共有オブジェクト (*.so) を生成することもできる.こちらを,共有ライブラリ (shared library)と呼ぶ.このライブラリは,実行ファイルの実行時に(自動的に)ロードされるため,実行時に所定のパスに共有オブジェクトがないといけない.

コマンド nm

コマンド nm を使うと,アーカイブファイルや共有オブジェクトに含まれているシンボル(変数名,関数名,クラス名,etc.)を一覧表示することができる.

役に立つオプション
  • -C : 人が理解できるシンボル名に変換する (e.g. _ZNSo5flushEv --> std::ostream::flush())
  • -D : 共有オブジェクトのシンボルを表示する
出力の見方

nm を使うと

00000000 T hogehoge::get()

こんなんが表示される.これは,

値 タイプ シンボル名

をそれぞれ表す.タイプの意味は,

  • "U": そのアーカイブファイル(or 共有オブジェクト)で未定義のシンボル(使われている)
  • "T": シンボルがテキスト(コード)セクションにある(定義されている)
  • ……

のような感じ.詳細は info nm コマンドで調べるか,雑録 - Program Library HOWTO を参照.

ライブラリファイル群からシンボルを検索するスクリプト

/usr/lib にある *.so* とか *.a のそれぞれに nm を適用し, grep でシンボルを検索,未定義でないシンボルが見つかったらファイル名を出力する,というスクリプト (search-sym) を作った.

#!/bin/bash
usage='search-sym SYMBOL'

if [ $# -ne 1 ];then
  echo "invalid arguments!"
  echo $usage
  exit 1
fi

sym=$1
egrep_pattern="^[0-9a-f]*[[:space:]]*[^U[:space:]].*$sym"
static_lib_pattern='*.a *.o'
shared_lib_pattern='*.so*'

function search_file() # filename nm-opt
{
  local f=$1
  shift
  local nmopt=$@
  local res
  if [ -f $f ];then
    res=`nm $nmopt $f 2>&1 | grep $sym`
    if [ -n "$res" ];then
      res=`printf -- "$res\n" | grep -E $egrep_pattern`
      if [ -n "$res" ];then
        echo "$f:"
        echo "$res"
      fi
    fi
  fi
}

echo "symbol $sym is included in..."
for f in $static_lib_pattern;do
  search_file $f -f bsd -C
done
for f in $shared_lib_pattern;do
  search_file $f -f bsd -C -D
done

search_file の中で,grep を2段階にしているのは,速度を向上させるため(一発でやると,10倍くらい時間が掛かる).egrep_pattern は未定義のシンボルを除外し,指定されたシンボルを抽出する正規表現. static_lib_pattern とか shared_lib_pattern は,検索対象のファイルパタンなので,変更可.

例えば, /usr/lib で

% search-sym __syscall_error

と実行すれば(すべてのファイルを検索するので時間が掛かる.私の環境では20秒弱),

symbol __syscall_error is included in...
libc.a:
00000000 T __syscall_error
00000002 T __syscall_error_1

という結果が得られ, __syscall_error が libc.a で定義されていることがわかる.

課題

例えば, libc.so は

/* GNU ld script
   Use the shared library, but some functions are only in
   the static library, so try that secondarily.  */
OUTPUT_FORMAT(elf32-i386)
GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a  AS_NEEDED ( /lib/ld-linux.so.2 ) )

という内容で,スクリプトになっている.このようなファイルからシンボルのリストを抽出する方法を知らないため,上のスクリプトはこういうファイルを無視してしまう.(この場合,ディレクトリ /lib でも search-sym を実行すればよいだけなので,比較的軽微な問題)