|
W/O index |
シンボルのバージョン(Mon Dec 09 2002)
とやると、わらわらと出てきます。で、これは何? モバイる?(Mon Nov 18 2002)携帯の端末のバッテリがヘロヘロになってしまったのを機に、キャリアもかえてみようかと思って、各社のパンフをもらってきて検討していました。やはり AirH" のつなぎ放題は魅力ですからね。で、Linux だけど、どうなのよ? できれば USB でね。今まで Docomo の 1.5MHz(シティフォン)では KDD の UPA-9664 を、USB Serial (usbserial.o) で使っていました。どうやら「CDC (Communication Device Class 1.1)」なるものに対応していれば usbserial.o で使えるようです。この手のは、平気で最新 OS(MacOS X とか)にも対応しているのも特徴。ただし、ハードに手がかかっているんでしょう、高価な傾向です。というわけで、サン電子の AX64LX。 Java(Wed Oct 30 2002)現実逃避ぎみにオープンソースの Java 環境をさぐっいました。Jikes(超っ速コンパイラ)と Kaffe(GPL の VM とランタイム)の RPM パッケージをほげってみました。 Java で今風になるぞ〜。 無題(Mon Oct 28 2002)てなわけでイマイチすっきりしないので、Amazon.co.jp で「Linkers & Loaders」を購入しました。 では。 ダイナミックリンクを追え!(Fri Oct 04 2002)以下、ELF の仕様書は必携。 PLT (Procedure Linkage Table) は、ダイナミックリンクのためのスタブコードのかたまりで、プログラムヘッダでは、読み出しオンリー実行可に入っています(ユーザコードと同じ。objdump で逆アセンブルすれば見える)。ユーザのコード側からは、直接にはこれらのスタブが呼び出されます。テーブル、とは言うけれど、各エントリの長さが違うこともあるので、配列っぽくはないです、たんなるカタマリです、ラベルもないようだし。GOT (Global Object Table、だっけ?) に、実コード(DLL の)へのポインタが入っています(プログラムヘッダ的には、読み書き可能実行不可。当然実行時にしかない)。初回のコール以前には、GTO のエントリは、スタブの余り部分に制御を跳ばすようになっています(説明しづらいんだけど、gdb で実行をとめながら、逆アセンブルしたり /proc/*/maps を見ていればすぐ分かりますよ)。 道具だては、readelf(1), gdb(1) (list, disassemble, x/x あたりのコマンドを駆使して、実行中の環境下でコードを追うのだ)。 「スタブコード(stub codes)」とは、あるコードが別のコードを呼ぶ際に、仲介を行なうコードのこと。 _dl_runtime_resolve@/lib/ld-linux.so.2 が、「初回の呼びだし時点で」テーブル(GOT)を準備。 GLIBC で、ld.so だけコンパイルする(Sun Sep 22 2002)備忘録として。これができんと、オチオチとデバッグコードもはさめない。
難解ホークス。 ELF も腑分けじゃ(Fri Sep 20 2002)トールキン先生、ごめんなさい。さて、ELF フォーマット(仕様書の PDF はこちら、HOWTO はこちらを参照)について概観します。 ELF ファイルの先頭には ELF ヘッダがあり、そこに二つのテーブルのオフセット位置が書かれています。一つはセクションヘッダのテーブル、今一つはプログラムヘッダのテーブルです。 前者は、実データである、一つ以上のセクションのメタデータですから、理解は容易でしょう。問題はプログラムヘッダ。 以下、カーネルの、ELF の実行部を見ていきます。load_elf_binary() で、まずELF ヘッダのマジックナンバーが ELF のそれと同一であることのチェックをはじめ、各種のチェックが入ります。それらをパスしたら、ELF ヘッダに従って、プログラムヘッダを読み込みます。つづいて、エントリタイプが PT_INTERP なプログラムヘッダを探します。見つかったならば、elf_interpreter に、ELF インタプリタの名前を保持します。 ダメだこりゃ、疲れる。自動化しないとやっておれん。 exec(2) とスタティックリンク、そしてダイナミックリンク(Thu Sep 19 2002)まずきわめておおざっぱに、カーネルの exec() が実行可能ファイルを実行するときにどのように働くかを順に書くと、
というようになります。この際、細かいことは気にしないでください。 <comment>"binprm" stands for "BINary PaRaMeter"</comment> でだ、実行可能 ELF バイナリには上記 "5" で用いられるエントリポイントが書かれています。readelf(1) コマンドで見られます。
さらに、ストリップかけていないバイナリであれば、このアドレスに対応する、以下のようなシンボルの情報も読みとれるはずです。
この "_start" はどこから来たかと言えば、gcc で "--verbose" オプションつければうかがえます。コレ↓です。
御覧のように、_start は、コード込みのグローバルなコード(T)であり、この段階では未定義な main を外部に求めています。きっとこのふたつをつないでくれるのでしょう。ld(1) は、デフォルトのエントリポイントを "_start" とし、"-e" もしくは "--entry" オプションで変えられます(binutils の ld/ldlang.c 参照、'"start"' で検索してみましょう。カーネルの arch/i386/Makefile には "LDFLAGS=-e stext" とあり、vmlinux に readelf かければ、エントリになっているのがわかる)。この場合 /usr/lib/crt1.o は glibc 由来なんですが、たとえばオルタナティブである uClibc の場合には別の初期化コード(crt0.o)があります。 で、スタティックの場合は単にこのエントリからコードがスタートするだけなのですが、ダイナミックの場合(".interp" セクションがある場合)は、カーネルが直接起動するのは、そのセクションに名前を書かれた「インタプリタ」と呼ばれるプログラムで、最近の ELF なバイナリならば /lib/ld-linux.so.2(実行可能)です。このセクションをもともと含むのは、glibc のダイナミックリンクライブラリ /lib/libc.so.* です(glibc の elf/interp.c の __invoke_dynamic_linker__ から)。スタートアップコードに入っていないのは、それらはスタティックでも使うからです。
さて、この /lib/ld-linux.so.2 がどこから来るかと言うと……うーん glibc のビルドシステムがよくわからないので不明です。elf/ ディレクトリの下だとは思うんだけど……あった! elf/rtld.c の RTLD_START マクロは sysdeps/(ARCH)/dl-machine.h で定義されていて、ここにエントリポイント "_start" があります。ld-linux.so.2 は、必要なライブラリをすべてロードしたのち、あらためてホンチャンのエントリポイントにジャンプをかけます。 <comment>"CSU" stands for "C StartUp"</comment> では次回、ELF の解析でも。 /sbin/SuSEconfig の謎なんぞ(Fri Sep 13 2002)要は設定ツールである YaST との談合なんですが、
当然出力された設定ファイルはリードオンリー。手で書きかえても、次回の /sbin/SuSEconfig で上書きされるだけです。気をつけましょう。 その書きかえや反映をリブート時まで遅らせたい、とか、特定の設定モジュールだけ起動したい、などの設定もできます。詳しくは /etc/rc.d/boot や /sbin/SuSEconfig を読んでください。 SuSE の init スクリプトはえらい(Fri Sep 13 2002)/etc/init.d/skeleton(sysinit スクリプトのスケルトン)を読むとよくドキュメントされているんですが、SuSE システムのスクリプトには、init スクリプト間の依存関係を書けます! Turbo では、依存関係を人が解決して実装時に即値で起動順序を決定していましたが、SuSE では chkconfig(8) が動的に順序づけをします。しかも、強い依存関係(絶対 ON)と、弱い依存関係(ON ならばお先にどうぞ)まで指定できる! えらい! ついでに、chkconfig(8) のサブで動いている insserv(8) の機能で、疑似の名前での依存関係もできますので、/etc/insserv.conf に目をとおしておきましょう。 一方 Turbo では、/etc/sysconfig/security ファイルで決定される敷居値によって、セキュリティ設定ごとに全体のおおざっぱなオン・オフを制御する機能があったんです。これほしい。 SuSE のスタートアップを腑分けじゃ(Thu Sep 12 2002)訳あってここしばらく SuSE 系の環境で開発をしているのですが、妙ですねえ。何が分からないって、同じ RPM を用いたディストリビューションでありながら、なぜこうもコアのシステムが異なってしまったのかが分かりません。とは言え、Linux は Linux です。用いているコンポーネントもほぼ同じである以上、理解はそう大変なはずはありません。というわけで、スタートアッププロセスを追ってみます。 カーネルのスタートアップの最後は、スレッド分けして /sbin/init を exec(2) し、元スレッドはアイドルするだけです。で、そんな init(8) のプロセスが、以下。
init(8) は /etc/inittab に従って、「普通の」Linux 環境が動作する限り、初期化・終了のプロセスと、ターミナルの管理をしてくれるわけだ(組み込みやインストーラのような特殊な環境ではその限りではないので、「普通の」と断ったわけですが)。うーん、久しぶりに見ると inittab の読み方を忘れている。inittab(5) の man を見れば書いてあります。init(8) によると、ランレベルって 0〜6 と s または S の 8 つだそうですが、ホントか? そんなときはオープンソース、ソースにあたる。
うん、このへんからして Turbo と名前が違う。あとでわかるんですが、違うのは名前だけではなく、様々な独自の機構が仕込まれています。さすが SuSE、UNIX を熟知しています。では sysvinit のソースをあたります。展開して、src/init.c:read_level() に以下のようにあります。
なんでー、ウソばっか、いっぱいあるじゃん。"QS0123456789ABCU" の 16 文字。しかも、大文字小文字は区別しません。さらに言うと「前のランレベルなし」を意味する "N" もあるようで。
おっと、init(8) をよく読むと書いてありますね。"s または S は 1 のエイリアス、7-9 も仕様できるが、伝統的に使われない" と。どっちにしてもゴリゴリハードコードなコマンドであって、あんまりキレイなもんではないのね。 まあいいや。で sbin(8) は最初に、/etc/inittab を見て "initdefault" アクションのついたランレベルに突入する、わけですが、man によると "はじめてマルチユーザモードに入るときには、boot および bootwait アクションのエントリを実行" だそうです。コード追い切れなくて sysinit との差が不明なんですが、まあそうなんでしょう(可読性低し!)。それら boot 系のエントリは、以下の行だけ。
Turbo では /etc/rc.sysinit なんす。では /etc/init.d/boot 読んでいきましょう。まず、こんなの読んでます。
一つめは rc 系のスクリプトが共通に使う文字列やファンクションですね。だいたい分かるからいいや。ふたつめは、ブートの挙動の設定ですね。これに続いて、proc や /etc/fstab に従ったファイルシステムのマウントを行ないます。 SuSE には /sbin/blogd なるものがありますね。なになに、どうやらシステムコンソール(/dev/console)に仮想端末をつないで、出力をフックして /var/log/boot.msg に追加するようですね。で、そのデーモンたちあげたあとに、/etc/init.d/boot.d/ 以下のコマンドを実行しますが、それが済んだら blogd は落されます。てことは、その間のログは上記ファイルを見ればよいのね。 ここで SuSE 特有の YaST2 が出てきます。でもどうやら、初回起動時にしか実行されないようです。何してたんでしょうねえ。こういう謎なところが、ちょっと SuSE のヤなところ。 さて、ようやくデフォルトにはいれます。以下、3 ね。
Turbo では /etc/rc でした。/etc/init.d/rc へ、第一引数に 3 をつけて実行しているのですが、どうやらこの引数見てないね(Turbo では見てた)。なぜなら、init(8) が子プロセスを実行するときには $RUNLEVEL(現在のランレベル), $PREVLEVEL(その前までのランレベル)などの環境変数を設定するからです。こっちのほうがアテになるしね。 あ、また blogd 上げてる。 で、あとは普通どおりに $PREVLEVEL ディレクトリ(/etc/init.d/rc${PREVLEVEL}.d/)内の "K" つきコマンドを落として、$RUNLEVEL ディレクトリ(/etc/init.d/rc${RUNLEVEL}.d/)内の "S" つきコマンドを上げます。 あとは getty を上げて、おしまい、と。
ふー。 ヒープ管理のサンプルコード(Thu Jul 25 2002)ヒープ管理のサンプルコード(heap.c)です。ほぼそのまま「C プログラマのためのアルゴリズムとデータ構造 Part 2」(近藤嘉雪著・ソフトバンク刊)からの抜粋ですが。 文字列型各種 in C(Wed Apr 17 2002)# 再録なので日付さかのぼっています C 言語で、文字列クラスを定義してみましょう。以下の三種類を実装します。
なお、malloc(3) による領域確保にまつわるエラーの処理は、今回は行なっておりません。それはまた、別の機会に。 1. 構造体を用いた、オーソドックスなもの最初に挙げる文字列クラスは、C 言語においてクラスを実装する際にもっとも一般的と思われる、構造体を用いる手法で実装されたものです。まず最初に、テストコードと実行結果を見てみます。テストコードは、「コマンドに与えられた引数をすべて連結して表示する」という簡単なものです。 # 余談: この手のクラスを C++ でラッパーするのは、赤子の手をひねるより簡単。ていうか、楽しい
テストコードには、"Str" というクラスがあり、クラスメソッドとしては、コンストラクタの "Str_new()"、デストラクタの "Str_free()"、フォーマット文字列を連結するメソッド "Str_appendf()"、構築された文字列を文字列ポインタとしてとり出すメソッド "Str_get()" が用いられています。重要なことは、クラスを利用する側からは、実装の詳細が窺えないようになっているということです。
テストコードで用いられていたクラスとメソッドが宣言されています。各メソッドは、コンストラクタ(Str_new())が返した Str クラスのインスタンスを第一引数にとります。この形式は、POSIX における、クラス FILE と コンストラクタ fopen(3)、メソッド fprintf(3)、デストラクタ fclose(3) などの関係と同じです。「クラスベースのオブジェクト指向実装である」という自覚はなくても、ごく一般的に用いられている手法なのです。 注意して欲しい点は、このクラスを利用する側(つまり、ここでは上記のテストコードの実装者)が知ることができるのは、このインタフェイスまでである、ということです。言いかえると、このクラスの実装(str.c)に依存したプログラムを書いてはいけない、ということです。一般に、インタフェイスは長期にわたって安定していますが、実装は頻繁に改変されることがあります。そのような場合、実装に依存した書きかたをしていると、利用する側も、それに伴って、改変や再コンパイルを要求されることになります。 その一例。"struct StrImp" は、構造体の「名前の宣言だけ」をしています。このインタフェイス宣言ファイルの中では、"struct StrImp" はポインタとしてしか用いられていないので、コンパイラは、そのサイズを、ポインタのサイズとして扱うことができます。ここでもし "Str_get()" メソッドがなかったとしたら、クラスの利用側に、文字列を取得するために内部実装をさらさなければなりません。この場合は、str.c に入っている構造体の定義を、str.h で行なわなければならなくなります。その結果、インタフェイスと実装の分離ができなくなります。
ここではじめて、実際に文字列を格納する場所となる、"struct StrImp" のメンバ変数 psz が登場します。この実装では、文字列へのポインタを利用して、文字列を保持します。この部分はライブラリの利用者側からは見えないので、あとで「やはり配列で実装したい」とか「文字列はリレーショナルデータベースに格納したい(ってなことは実際にはないだろうが)」などの必要が生じた場合には、インタフェイスを変更することなしに、実装を変更することができます。 2. 文字列へのポインタを用いた、お手軽なもの以下のふたつの実装は、亜流です。文字列型を用いるのに、いちいち構造体なんぞ作っとるのはまだるっこしい、という場合に使いましょう。ただし、当然のことながら柔軟性と拡張性は犠牲になります。
さきほどの、構造体を用いた例とは違って、メソッドの第一引数にわたしているのが、文字列へのポインタへのポインタになります。これは、メソッド側で、文字列の領域の確保と解放を行なうためです。ここが理解できない人は、もう一度教科書のポインタの項を読みなおしておいてください。
実行結果は、先の例とまったく同じです。
文字列へのポインタのポインタをとるようになっている点以外は、とくに変わっていません。
実装のアルゴリズムも、先の例と同じです。 3. マクロを用いた、さらにお手軽なもの「インタフェイスだの実装だののファイルが増えるのはイヤだ。Cのソースの先頭にチョロっとコードを追加するだけで使いたい」というものぐさな気分のときに使いましょう。
インクルードしているヘッダファイルが異なる点以外は、"2" のテストコードと一字一句まで同じです。
ごちゃごちゃしていますが、ロジック自体は "2" の例で用いたポインタを使った実装と同じです。なお、可変長引数をとれるマクロの定義("args..." の部分)は、GCC の CPP 固有の機能のようです。 追記(Wed Oct 23 2002): C99 で、晴れて可変長引数がサポートされましたので、書きかえました、Str_appendf99() の部分をね(アンダースコアつきは何となくエレガントじゃない感が強いんですが……)
まとめまとめるほどのことはないです。では。 はじめのことば(Wed Jul 24 2002)何をどう書けばいいのかよくわかりませんが、毎日仕事で(?) Linux の各部をいじっている者もあまりいないと思うので、そんな人間の書くことは、だれかの何かの役に立つこともあるかも知れないから、思いつくこと、やったこと、備忘録などを書きつらねてみようと思います。よろしくおねがいします > 自分。 追記(Fri Sep 13 2002): 書くことがないと思ったら、自分の作業は普遍性に欠くことに気づきました。弱ったな。
| ||||||||||||||||