The Man Who Fell From The Wrong Side Of The Sky:2007年1月19日分

[最新版] [一覧] [前月] [今月] [翌月]

2007/1/19(Fri)

GNU binutilsで遊んでみよう

前回はソースレベルでの互換性を保つための、事前定義マクロによるトリックを解説しました。
この他にも、libcにはバイナリレベルでもいくつかの仕掛けがあるのですが
それを説明する前に、基礎知識編としてGNU binutilsで遊んでみます。

GNU binutilsはバイナリを操作するツールの詰め合わせパックなのですが
今回はnm(1)とldd(1)で遊びます。

オブジェクトにあるシンボルを確認する為にはnm(1)を使います。
objdump(1)の-tオプションや、オブジェクトがELF形式であれば
readelf(1)の-sオプションでもかまいません。

$ nm /usr/bin/nm
0805b014 A _DYNAMIC
0805b0f4 D _GLOBAL_OFFSET_TABLE_
         w _Jv_RegisterClasses
0805b004 d __CTOR_END__
0805b000 d __CTOR_LIST__
0805b00c d __DTOR_END__
0805b008 d __DTOR_LIST__
(以下略)

わらわらとシンボルの一覧が表示されます。

$ nm /usr/bin/nm
nm: /usr/bin/nm: no symbols

と表示される方は、strip(1)でシンボルが取り除かれています、残念。
私の手元のFedoraCore6がそうみたいです、-Dスイッチを使ってみてください。

nm(1)の出力を簡単に説明します、左からシンボルの

となっています。
グローバルとローカルの違いは他のオブジェクトからの可視性です。
これについては後で説明します。

んで実行ファイルや共有ライブラリの依存関係を表示するコマンド、ldd(1)です。
Cygwinなどではldd(1)がありません、objdump -xの結果をNEEDED(Cygwinの場合DLL Name)でgrep(1)
することで代用してください。

$ ldd /usr/X11R6/bin/xterm
/usr/X11R6/bin/xterm:
	-lX11.6 => /usr/X11R6/lib/libX11.so.6
	-lfreetype.9 => /usr/X11R6/lib/libfreetype.so.9
	(中略)
        -lc.12 => /usr/lib/libc.so.12

出力の読み方については、まあ説明するまでもないでしょう。

では先ほどのグローバルとローカルのシンボルの違いについて。
先ずはグローバルの例です。

global.c
------------------------------
#include <stdio.h>
void foo()
{
	puts("hello, world. i'm global.");
}
------------------------------

 Cygwinの場合は、libglobal.so.0をcygglobal.dllに置き換えてください
$ gcc -shared -Wl,-soname,libglobal.so.0 -o libglobal.so.0 global.c

$ nm libglobal.so.0 | grep foo
000004ec T foo

大文字になっています。

次にローカルの例です。
static修飾子をつけて宣言した関数は他のCソースから使用できなくなります。

local.c
------------------------------
#include <stdio.h>
static void foo()
{
	puts("hello, world. i'm local.");
}
------------------------------

 Cygwinの場合は、liblocal.so.0をcyglocal.dllに置き換えてください
$ gcc -shared -Wl,-soname,liblocal.so.0 -o liblocal.so.0 local.c

$ nm liblocal.so.0 | grep foo
000004dc t foo

ローカルシンボルになりました。

ちなみにtおよびTの意味は、シンボルがテキストセグメント(.text)にあるということです。
テキストセグメントとは?という話は本題とはあんま関係ないので割愛、知らない人は
要するに「シンボルがあるべきとこにある状態」だと思っていてください。

本当に外部から参照できないか、試してみましょう。
まずはlibglobal.so.0とリンクした実行ファイルを作ります。

test.c
------------------------------
extern void foo(void);
int main(void)
{
        foo();
        return 0;
}
------------------------------
$ ln -s libglobal.so.0 libglobal.so
$ gcc -Wl,-rpath=. -L. -lglobal -o test test.c

コンパイルに成功しました、ldd(1)とnm(1)の結果を見てみましょう。

$ ldd test
test:
	-lglobal.0 => ./libglobal.so.0
	-lc.12 => /usr/lib/libc.so.12
$ nm test | grep foo
	U foo

ldd(1)の結果より実行ファイルがlibglobal.so.0に依存していることが判ります。
nmの結果はアドレスが空、型は"U"(未定義/未解決)になっています。
実行ファイルにはfooシンボルの実体がないので、必要な時に外部ライブラリを探すという意味です。

では実行してみましょう。

$ ./test
hello, world. i'm global.

ちゃんとlibglobal.so.0のfoo()が呼び出されていますね。

今度はliblocal.so.0とリンクした実行ファイルができるかやってみましょう。

$ rm -f test
$ ln -s liblocal.so.0 liblocal.so
$ gcc -Wl,-rpath=. -L. -llocal -o test test.c
/var/tmp//cc8c1Zh2.o: In function `main':
test.c:(.text+0x12): undefined reference to `foo'

リンク時にfooというシンボルが解決できずビルドに失敗しました。
リンカからローカルシンボルは不可視ということです。

ではリンクに成功したlibglobalのfooのグローバルシンボルをローカルに変えてしまったらどうなるでしょうか?
local.cをlibglobal.so.0としてビルドします。

$ gcc -shared -Wl,-soname,libglobal.so.0 -o libglobal.so.0 local.c

$ nm libglobal.so.0 | grep foo
000004ec T foo
$ ./test

NetBSDの場合
./test: Undefined PLT symbol "foo" (symnum = 10)

Linuxの場合
./test: symbol lookup error: ./test: undefined symbol: foo

fooなんか見つからないよと怒られてプログラムは実行できませんでした。
リンクローダからもローカルシンボルは不可視という訳です。

まだまだ続きますが、今日はここまで。


[ホームへ] [ページトップへ]