実行 - みる会図書館


検索対象: UNIX MAGAZINE 2004年4月号
94件見つかりました。

1. UNIX MAGAZINE 2004年4月号

ロプログラミンク・テクニック 多治見寿和 「 c スクリプト ( 5 ) 2 月号から、 /etc/rc. subr ファイルで疋義されている、 /etc/rc. d ディレクトリに置かれたスクリプトカ坏リ用する 関数群を紹介してきました。前回は、そのなかでも最大の 関数である run-rc-command をとりあげました。これ は、実際のコマンド ()c ファイル ) で使われるもっとも重 要な関数でもあります。 今月は、 rc. subr ファイルの残りの関数群をみていきま す。 run rc script 関数 rc. subr ファイルで、 run-rc-command 関数の次に定 義されているのが run-rc-script 関数です。この関数は、 実行すべきファイルや引数を指定して呼び出すことで、指 定されたファイルを実行します。 ファイルの実行だけのためにわざわさ数が用意されて いるのは、不思議に思えるかもしれません。通常は、シェ ル・スクリプトはサプシェルを起動して実行します。しか し、新たにシェルを起動するとなるとプロセスを 1 つ準備 するわけですから、それなりのコストがかかります。 この関数では、指定されたシェル・スクリプトの名前や変 数の状態を調べ、別のシェルを起動して実行するのか、それ とも現在実行中のシェルのなかで実行するかを判断します。 また、ファイルの編集時に自動的に作られるバックアップ・ ファイルが指定された場合には、それを無視します。これ により、あるディレクトリに置かれたすべてのファイルを 順に実行するといった処理も可能になります。 run-rcscript 関数では、まず引数カ症しく指定されてい ることを確認します。この関数は 2 つの引数をとります。 UN 工 X MAGAZINE 2004.4 第 1 引数は実行すべきスクリプトの名前です。第 2 引数 には、そのスクリプトに渡す引数を指定します。ただし、 rc スクリプトの実行を仮定している関数ですから、 指定できるのはあらかじめ決められた引数だけです。具体 的には、前号で紹介した run-rc-command 関数カ甘及える start や stop などの引数がかならず指定されます。 run—rc—script ( ) —file=$l —arg=$2 if [ -z "$_file" ー 0 —z "$-arg" ] ; then err 3 'USAGE: run-rc_script - file arg f i プログラムでは、関数に渡された第 1 引数を一 file 変数 に、第 2 引数を -arg 変数に代入します ( 誌面の都合上、 で折り返しています。以下同様 ) 。 そして、これらの変数に値カ第殳定されていることを確認 し、設疋されていなければ工ラーメッセージを出力してプ ログラムを終了します ( ここで使っている err については 彳あします ) 。 次は、スクリプト内でエラーカ咥生した場合の処理です。 この場合も、メッセージを出力してプログラムを終了しま す。 trap "echO 'Reboot interrupted' ; exit 1 " 3 こでは trap コマンドカ吏われています。スクリプト 内で QUIT シグナルが発生したときは、この tr 叩コマン ドで登録されている echo コマンドが実行されます。 続いて、スクリプトの実行時に参照される変数群を初期 化します ( 図 1 ) 。いくつかの変数は、値か第定されている と、スクリプト内で run-rc-command 関数を実行する際 95

2. UNIX MAGAZINE 2004年4月号

テパッガの基礎知識 図 30 1 (list) コマンドでソースコードを表示 1 TLIST* addCe11 (char* name , int number , TLIST* ptr , *qtr ; 特集 TLIST* base) (gdb) 47 48 49 50 51 52 53 54 55 56 (gdb) 57 58 59 60 61 62 63 ptr = makeCe11(name, number) ; if (base = = NULL){ return ptr ; } else { ← Return キーを入力 qtr=base ; while(qtr—>next ! = NULL) qtr = qtr—>next ; qtr—>next = ptr ; return base ; makeCeII 関数には、引数の name と number が渡さ れています。この値も確認しておきましよう。 (gdb) p name $ 5 = 0X80485CC "cell A" (gdb) p number $ 6 = 1 (gdb) この引数の値を使って makeCeII 関数カび出されてい るわけです。 next コマンドでプログラムを進めて、 make- CelI 関数を実行します。 (gdb) Ⅱ 54 if (base = = NULL) { line 52 . (gdb) at samp1e4. c: 52 "cell B" , number=2, base = 0X8049750 ) Breakpoint 2 , addCe11 ( ame = 0X80485d3 - Continuing ・ (gdb) c までプログラムを進めます。 コマンドを実行すると、次のプレークポイントに遭遇する めの continue ( 省略形は c) コマンドを使います。この クポイントまで一気に実行したいので、実行を継続するた 今度は 1 ステップずっ実行するのではなく、次のプレー 1 : *ptr = {number = 1 , name "cell A" , next = 0X0 } (gdb) next コマンドで 1 行進めると、 display コマンドの設 定によって自動的に ptr の内容力俵示されます。この時点 では makeCell 関数が実行され、 ptr 変数に値か第定され ているので、その内容が表示されています。 makeCell 関 数の引数で渡した name と number を使って、構造体が 確保されたようです。 再度、この行が実行されたときにどうなるかを確認する 0X8049760 Breakpoint 2 at 0X80484Cb : (gdb) b 52 きを実行してみます。 ために、今度は 52 行目にプレークポイントを設定して続 file samp1e4. c,=> UNIX MAGAZ 工 NE 2004.4 52 ptr = makeCeII(name , number) ; 1 : *ptr = {number = 1 , name 0X8049760 ー 0X0 } "ce11 A", next (gdb) プログラムが継続実行され、プレークポイントカ駁疋さ れている 52 行目に再度プログラムカサリ達した時点で、実 行は自動的に中断されます。プログラムの実行が中断され ると、ふたたび自動的に ptr の内容が表示されます。今度 は、どのような引数によって addCeII 関数カび出されて いるのかも最後の行に表示されています。ここでは、 52 行 目はまだ実行されていません。 next コマンドで、もう 1 回 makeCell コマンドを実行してみましよう。 (gdb) Ⅱ 54 if (base = NULL) { 51

3. UNIX MAGAZINE 2004年4月号

図 1 スクリプトの実寺に参照される変数のネ騏用ヒ unset name command command_args command_interpreter \ extra—commands pidfile procname \ rcvar required—dirs required—files required—vars eval unset ${-arg}-cmd ${-arg}-precmd ${-arg}-postcmd 図 2 スクリプト・ファイルをサプシェルで実行 # run if [ —x $_file ] ; then if [ ーⅡ 'l$rc_fast_and—loose" ] ; then set $-arg ( trap "echO 'Reboot interrupted' ; exit 1 " 3 $-file ) set $—arg in subshell $-file f i fi e S に正しく動作しないことがあります。そのため、スクリプ run-rc-command 関数の動作に景彡響 トを実行する前に をおよばす変数を初期化しておきます。 処理自体は簡単で、 unset コマンドの引数に初期化した い変数を指定するだけです。 -arg 変数に依存する変数も初 期化しなければならないため、それらについては eval コマ ンドを使って初期化しています。 次は、指定されたスクリプトのファイル名にもとづいて 処理を変える部分です。ますはファイル名が . sh で終って いる場合です。この場合にはサプシェルを起動せず、カレ ントシェルでファイルを実行します。 ・ orig) # scratch 、 file ; skip case " $ in set $-arg run in current she11 $-file カレントシェルでスクリプトを実行するときは、、 . " コマ ンドを使います。 . コマンドは、引数として指定されたフ ァイルの内容力そのまま入力されたかのように動作します。 ただし、この場合にはスクリプトに引数を渡すことができ ません。そこで、 set コマンドを使い、位置パラメータと して引数を渡します。位置パラメータに設疋した状態でス クリプトを呼び出すと、スクリプト内からは引数として指 定されたかのように扱えます。 次はバックアップ・ファイルの場合です。これらのファ イルカ甘旨定された場合には、 warn ( 彳 ) を使って警告メ ッセージを出力します。 96 'lgnorlng scratch file $—file" バックアップ・ファイルとして認識されるのは、末尾が warn UNIX MAGAZINE 2004.4 使って実行します。 ナルの設定をおこないつつ、 set コマンドと . コマンドを での実行になります。この場合には、 trap コマンドでシグ 行します。これらの条件を満たさないときは、サプシェル set コマンドで引数を設疋して . コマンドでスクリプトを実 定されていたら、さきほどの . sh ファイルの場合と同様に には、 rc-fast-and 」 oose 変数の値を確認します。値カ毅 いなければ、何も処理をおこないません。実行可能な場合 うかを確認します。ファイルのモードが実行可能になって まず、指定されたスクリプト・ファイルが実行可能かど プシェルを起動せずに直接カレントシェルで実行します。 ただし、 rc-fast-and 」 oose 変数カ又疋されていたら、サ プト・ファイルとみなし、サプシェルで実行します ( 図 2 ) 。 こまでの条件にマッチしないファイルは通常のスクリ 理しないようにしているわけです。 こでそれらを処 らのファイルも含まれてしまいますか、 を処理したいときに、メタキャラクタの、、 * " を使うとこれ あります。あるディレクトリに置かれたすべてのファイル したときなどに、バックアップのために作成されることが ルです。これらのファイルは、エデイタでファイルを編集 ~ か # のファイルと、拡張子が OLD または orig のファイ

4. UNIX MAGAZINE 2004年4月号

特集 テパッガの基礎知識 アクセスできないアドレスについて ログラムのなかの静的変数、或変数、 malloc で確保した領域が 自分で書いたソースコードをコンパイルして実行形式を作成し、 データ・セグメントに割り当てられます。この領域にはデータが それを実行するとき、実祭には何か起こっているのでしようか。 置かれているので、読出しだけでなく書込みも可能です。データ・ 実行形式とは、 C 言語 ( やその他の言語 ) で言古杢されたプログラ セグメントは、さらに下記の 3 つの領域に分かれています。 ムを、マシンが解釈できる言葉 ( マシン語 ) に奐したファイルで す。このファイルには、プログラムの命令やデータが含まれてい ・データ : 静的変数やた或変数のうち、初期値カ寸定されている ます。この実行形式を実行するとメモリ上にプログラムカ己置さ データが置かれる。 れますが、その際にプログラムの、命令 " や、データ " の部うゞど 伊リ : static int n = 10 ; のように配置されるのかは決められています。この配置を、メモ この変数 n はデータ領域に割り当てられます。 リマップ " といいます。 たいていの UNIX システムでは、メモリマップは下図のよう ・ BSS : 静的変数やた或変数のうち、初期値カ甘定されていない になっています。 データが置かれる。 例 : static int a; 0X0 この変数 a は BSS 領域に割り当てられます。 ・ヒープ : malloc で確保される変数の領域カり当てられる。 例 : char *p ; p = (char*)ma110c(100) ; この malloc で割り当てられた 100 バイトはヒープ領域に確 保されます。 スタック・セグメントは、スタックを置くための領域です。ス タックにはプログラムの自動変数カリり当てられます。たとえば、 main() int a ; 0xffffffff という関数が定義されていれば、この自動変数 a はスタックに割 り当てられます。 この図からも分かるように、メモリマップはおおまかに 3 つの 関数カ剛乎び出されると、スタックフレームという単位でスタッ 部分 ( テキスト・セグメント、 データ・セグメント、スタック・ ク上に領域カ保され、そこに自動変数や関獅乎出し時の引数の セグメント ) に分けられます。データ・セグメントは、さらにデー 情報、関数カ唳るべきアドレスなどの情報が入れられます。関数 夕、 BSS 、ヒープと呼ばれる 3 つの領域に分けられます。 が終了すると、対応するスタックフレームは削除されます。プロ テキスト・セグメントとは、マシン語の命令が置かれる場所で グラムの実行中は、関数の呼出しによってスタック領域は増えたり す。雹歔この領域は読出しや実行は可能ですが、書込みは禁止 減ったりしています。 されています。したがって、プログラムがこの領域に書込みをお このように、プログラムの実行時には、メモリ上の領域は割り こなおうとするとエラーが発生します。 当てられた領域によって役目が異なっているのです。 データ・セグメントはデータの領域です。 C 言語の場合は、プ ドを実行してみると、 core. 1751 ( 数値はそのときの状況に unlimited よって異なります ) というファイルができています。 ・ tcsh の場合 % 1imit coredumpsize coredumpsize 0 kbyt e s % ls % 1imit coredumpsize unlimited a. out core .1751 samplel . c % limit coredumpsize unlimited coredumpsxze 用語のところでちょっと書いたように、コアファイルの サフィックスの数字 ( この例では 1751 ) は、このプログラ さて、制限を解除してから再度プログラムを実行すると、 ムを実行したときのプロセス番号です。このように、出力 今度はさきほどの例のように、、セグメントエラー (core を されたコアファイルに自動的にプロセス番号を付与してく 出力しました ) " と表示されるはずです。ここで、 ls コマン テキスト・セグメント プ テータ・セグメント スタック・セグメント 33 UNIX MAGAZINE 2004.4

5. UNIX MAGAZINE 2004年4月号

特集 デバッガの基礎知識 図 22 メモリ内容Øi 忍 ( 1 ) (gdb) x / 12 &p 確保された領域 変数 buf として 確保された領域 変数 p として (gdb) 0xbfffe6dc : 0xbfffe6cc : 0xbfffe6bc : 図 23 メモリ上に呆された令或 ( 2 ) 0X42015574 0X080483ae : 0X42130a14 ! 0X42130a14 0X00000001 アドレス 0xbfffe6bc 0X4000C6 お 0 三 0X40015360 0xbfffe724 0xbfffe6d8 0xbfffe6f8 0xbfffe72c 意味のない値 0xbfffe6c0 意味のない値 46 ( 省略形は n) コマンドを使います。どちらもプログラムを 実行する ( ステッフ。実行 ) には step ( 省略形は s) や next 定するところまで実行してみます。プログラムを 1 行すっ もうすこしプログラムを進めて、変数 buf に文字列を設 は末はありません ( 図 23 ) 。 こでみているメモリ内容の値に 確保されただけなので、 域です。この時点では、まだローカルな変数として場所が 域、破線で囲った部分カ畯数 buf として確保されている領 図の実線で囲った部分か変数 p として確保されている領 入力しにくいのでこの例では変数名を利用しています。 のようにアドレスの直を直接指定することもできますが、 x コマンドのアドレスを指定する引数には、 0xbfffe6bc う ( 図 22 ) 。 示できればよいのですから、適当に 12 回としてみましょ 数えてももちろんかまいませんが、大雑把にこの範囲カ畩 5 バイト ( buf のサイズ ) ) までにします。回数はちゃんと e6bc から Oxbfffe6c4 (buf の先頭アドレス 0xbfffe6c0 十 1 行すっ実行していくコマンドですが、ほかの関数を呼び 出しているときの動作が異なります。ソースコードが手許 にあるほかの関数を呼び出している場合、 step コマンドで は、その関数の内部に実行が移動します。一方、 next コ マンドでは、その関数は実行されますが内部では実行は停 止せず、関数を呼び出している関数の次の行で止まります。 ほかの関数を呼び出している場合も、ソースコードのない 関数 ( システムのライプラリ関数など ) の場合は、 step コ マンドを使っても関数内部で実行は停止しません。 図 13 の sample3. c では、ソースコードがある関数の 呼出しはおこなわれていないので、どちらのコマンドを使 っても動作は同じです。ここでは step コマンドを実行し、 sprintf 文の行 ( 8 行目 ) が実行されたところまで進みます。 (gdb) s 9 (gdb) p = buf ; sprintf の行が実行されたので、 buf の領域には、、 ABC" という文字列が格納されているはずです。さきほどと同じ ようにして、メモリの内容を確認してみましよう ( 図 24 ) 。 同じく破線で囲った部分が buf の内容です。 char 型の 配列の値なので、デフォルトのワード ( 4 バイト ) 単位で表 示させると見づらいですね。そこで、今度はバイト単位で 表示してみます ( 図 25 ) 。それには、メモリサイズの単位 として b を使います。ここでは buf の内容力寉認でき ればよいので、指定するアドレスも buf の先頭アドレスに します。 さきほどと同じく、破線で囲った部分が buf の内容です。 今度は、見やすくなったのではないでしようか。文字列が 代入される前と比較すると、、、 0X41 0X42 0X43 0X00 " と いう値が入っているのカ寉認できます。文字列、、 ABC" を 数値で表すと、、 0X41 0X42 0X43 0X00 " となるので、メ モリの値もこのようになります。最後の 0X60 という値は buf[4] に相当するメモリの内容ですが、 buf[4] には何も 代入されなかったため、もともとのメモリの値カそのまま UNIX MAGAZ 工 NE 2004.4

6. UNIX MAGAZINE 2004年4月号

特集 1 , ( gdb ) next 0X0 } {number (gdb) p *base デバッガの基礎知識 name 0X8049760 "cell (gdb) s addCe11 (name=0x80485cc "cell A" , number=l , 、 base=0x0) at samp1e4. c : 52 52 (gdb) ptr = makeCeII(name, number) ; addCeII 関数が実行された結果、変数 base に値カ駁疋 もう 1 行実行して、やはり base 変数の値を確認してみ され、 next は 0 のままであることが分かります。 体の number には 1 が、 name には、、” cell A ” " が設疋 ます。これによると、最初のセルの値として TLIST 構造 ポインタが指している先の値を確認したい場合は * を付け のポインタに設定されたアドレスカ寉認できますが、その されました。 print コマンドに base 変数を指定すると、 ましよう。 (gdb) n 21 base addCeII("ce11 C" 3 , base) ; (gdb) p base $ 4 = (TLIST * ) 0X8049750 (gdb) p *base Breakpoint 1 , main ( ) at samp1e4. c : 17 Starting program : /Programs/Samp1e4/a. out Start it from the beginning? (y or Ⅱ ) y already. The program being debugged has been started=> (gdb) run 関数の内部でステッフ。実行してみます。 もう 1 回プログラムを最初から実行し、今度は addCell 関数の内部を詳しくみてみることにしましよう。 示しているのでしようか。これを調べるために、 addCell のが分かります。この next というポインタの値は、何を ませんが、今度はその内容の next に値カ羸疋されている base 変数の指しているアドレス 0X8049750 は変わり next = 0X8049770 } {number = 1 , name = 0X8049760 "ce11 A" , - $ 5 = 17 (gdb) TLIST* base = NULL; さきほどと同じように、 addCeII コマンドが実行される 行の直前までプログラムを進めます。 (gdb) n 19 (gdb) base = addCe11 ("cell 1 , base); ここで addCeII 関数の内部に制御を移したいので、 step コマンドを実行します。 50 すると、さきほどの next コマンドのときとは異なり、プ ログラムの制御は addCeII 関数の内部に移動し、そこで止 まっています。さらに、 addCell 関数カび出されたとき の引数の情報や、ソースコードの何行目にあたるのかも表 示されています。ここで、 addCell 関数のソースコードを 確認しておきましよう。 ソースコードを表示するコマンドは 1 (list) でした ( 図 30 ) 。 list コマンドはデフォルトでは 10 行しか表示しないの で、関数の全体は確認できません。 gdb のプロンプトカ俵 示されているときに Return キーだけを入力すると、直前 のコマンドにこでは list) が再度実行されます。引数を 付けずに list コマンドを実行すると、ソースコードの続き か表示されるため、図の例では Return キーを入力しただ けでソースコードの残りの部分が表示されています。 これを見ると、 makeCeII 関数を呼び出して、その結果 を ptr に返しています。この ptr は base 変数が NULL のときはそのまま返され、そうでない場合は 60 行目で qtr->next" という変数に代入されています。 この・ -- - ・連の処理、すなわち addCell 関数カ野び出される たびに makeCell 関数カ剛乎び出され、 ptr という値が言定 されるまでの処理を確認してみましよう。ここまでに紹介 したコマンドを使って調べることもできますが、 こでは addCeII コマンド内で実行カ阯まるたびに、自動的に ptr 変数の内容を表示するように設定してみます。 プログラムの実行カ阯まるたびに変数の値を表示させる には display コマンドを使います。ここでは ptr 変数が 指している内容を調べたいので、引数に *Ptr を指定する 必要があります。 (gdb) display *ptr 1 : *Ptr = number = 1108543776 , , name = 0X400160b0 " " 0X4000bCb0 ne Xt この時点では、 ptr 変数は宣言されただけで値は何も設 定されていません。ですから、表示されている値も適当な 値になっています。 UN 工 X MAGAZ 工 NE 2004.4

7. UNIX MAGAZINE 2004年4月号

特集 デバッガの基礎知識 図 17 ブレークポイントの削除 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 (gdb) inf0 break Disp Enb Address What Num Typ e 1 breakpoint keep y 0X08048381 in main at samp1e3. c : 9 breakpoint already hit 1 time 2 breakpoint keep y 0X08048368 in main at samp1e3. c : 6 ( gdb ) run The program being debugged has been started already. Start it from the beginning? (y or Ⅱ ) y Starting program : /Programs/Samp1e3/a. out Breakpoint 2 , main ( ) at samp1e3. c : 8 sprintf (buf , " 7 8 (gdb) cl DeIeted breakpoint 2 (gdb) 図 18 番号指定によるブレークポイントの畭 (gdb) inf0 break Num Type 1 breakpoint (gdb) d 1 (gdb) info break Disp Enb Address What keep y 0X08048381 in main at samp1e3. c : 9 NO breakpoints or watchpoints . (gdb) えます ( 13 ~ 17 行目 ) 。しかし、実際に run コマンドでプ ログラムを実行してみると、プログラムは 8 行目で停止し ます ( 18 ~ 22 行目 ) 。 6 行目以降で最初にプレークポイン トが設定できる行は 8 行目ですから、プレークポイントが 自動的にこの行に変更されたわけです。 以下では、プログラムの先頭から動作を確認したいので、 こまでに設疋したプレークポイントをすべていったん削 除します。そして、 main 関数の入り口に新たにプレーク ポイントを設定してみます。 プレークポイントを削除するには、 clear ( 省略形は cl) か delete ( 省略形は d) コマンドを使います。 clear はプレークポイントで停止しているとき、そのプ レークポイントを削除する際にイリです。 図 17 を見てください。プレークポイント 2 でプログラ ムが止まっているときに clear コマンドを実行すると、プ レークポイント 2 だけカ哨リ除され ( 14 ~ 15 行目 ) 、プレー クポイント 1 はそのまま残ります。 一方、 delete コマンドでは、番号を指定して任意のプ レークポイントを削除することができます ( 図 18 ) 。 すべてのプレークポイントをまとめて削除したいとき は、引数を付けずに delete コマンドを指定します ( 図 19 ) 。 この場合は、、、本当にすべてのプレークポイントを削除して 44 いいですか ? " という確認メッセージが表示され、これに対 して、、 y " と入力すると実際に削除されます。事前に、、 set confirm 。仕 " というコマンドを実行しておけば、この確認 メッセージを表示せずに削除することもできます。 すべてのプレークポイントを削除したら、 main 関数に プレークポイントを設定します ( 図 20 ) 。 実際には、 main 関数のなかで最初にプレークポイント カ第又疋できる行、すなわち 8 行目にプレークポイントカ毅 定されます。プログラムを実行して、変数にどのように値 が本帑内されているのかを調べてみます。 (gdb) run The program being debugged has been started=> already. Start it from the beginning? (y or n) y Starting program : /Programs/Samp1e3/a. out Breakpoint 5 , main ( ) at samp1e3. c : 8 8 (gdb) sprintf (buf , このように、 gdb では任意の時点で繰り返し run コマン ドを実行し、プログラムを最初から実行しなおすことがで きます。すでに実行されている場合は、上例の 3 行目のよ うに、、本当に最初から実行してもいいですか ? " と訊かれま UNIX MAGAZINE 2004.4

8. UNIX MAGAZINE 2004年4月号

連載 /FreeBSD のブートプロセスをみる 表 2 害鈆みとイ歹收トのべクタ番号 タイプ 発生原因 べクタ番号 意味 0 で除算する DIV または IDIV 命令 除算ェラー フォルト 0 EFLAGS レジスタの T フラグがセットされると発生する ( ステッフ。実行 フォルト / デバッグ 1 に使用 ) 。または、 INT 1 命令 トラップ 割込み NMI 用に予約 NMI 割込み INT 3 命令 ( デバッガがプログラム中に埋め込む ) トラップ プレークポイント INTO 命令を実行したとき、 EFLAGS レジスタの OF フラグがセットさ トラップ オーノヾーフロー れていると発生 有効範囲外にあるアドレス値を指定して BOUND 命令を実行すると発生 BOUND 範囲外 フォルト CPU が無効なマシン語命令を検出すると発生 無効オペコード フォルト MMX 命令を実行したとき、 CRO レジスタの TS フラグがセットされてい デバイス使用不可 フォルト ると発生 例外ハンドラを実行中に別の例外が発生し、それを順番に処理できない状態 ダカレフォルト アボート に陥ったとき 386 より新しい IA-32 では発生しない コプロセッサ・セグメント フォルト オーノヾーラン 無効な TSS をもつプロセスに切り替えようとしたとき 10 無効 TSS フォルト 物理メモリ上に存在しないセグメント ( セグメント・デスクリプタの P フラ セグメント不在 フォルト 11 グがクリアされている ) にアクセスしたとき スタック・セグメントの範囲を超える命令を実行したとき スタック・フォルト フォルト プロテクトモードの一隻機能に反する命令を実行したとき フォルト 物理メモリ上に存在しないページフレーム ( ページ・ディレクトリやペー ページフォルト フォルト ン テープルの P フラグがクリアされている ) にアクセスしたとき 浮動小数回路がエラーを検出したとき 浮動 / 」、数点工ラー フォルト 16 オペランドのアドレスカしくアラインされていないとき アラインメント・チェック フォルト 17 CPU チップに依存 アポート マシンチェック 18 SSE および SSE2 浮動′ト数命令により回路がエラーを検出したとき SIMD 浮動 / 」、数点例外 フォルト 19 ューザー定義 割込み タ焙に割込み、または INT 命令 32 ~ 255 込みをマスクします。反対に、 トラップゲートは IF フラ PCPU_SET(common—tss . tss_ioopt , 2101 : (sizeof (struct i386tss)) くく 16 ) ; グを変化させません。 ltr(gsel-tss) ; 2102 : 最後に、 2 , 063 ~ 2 , 065 行目で LIDT 命令を実行し、新 しい IDT を CPU にロードします。 2 , 070 行目の cninit() は、コンソールを初期化します。 これ以降、コンソールに文字カ俵示できるようになります。 TSS の言綻 2 , 094 ~ 2 , 096 行目では、 CPL が 0 に移行したときに 使用するスタック ( 図 1 ) のアドレスとセグメントのセレ 2070 : クタ値を図 11 の SSO と ESPO に設定します。スタック 2094 : の開媼立置と pcb 構造体のあいだに 16 バイトの、、隙間 " 2095 : を確保している理由は分かりませんでした。 2096 : 2 , 101 行目では、 TSS に I/O 許可ビットマップのアド レスを設疋しているようですが、そのアドレスにはビット 2097 : 2098 : マップはありません。実際にファイル machine/tss. h に 2099 も以下のコメントカ艚いてあるので、 I/O 許可ビットマッ 2100 プの機能は使わないようですが、そうだとすると 2 , 101 行 2 っ -4 8 9 2 つひ 4 -1 1 1 予約 20 ~ 31 cninit ( ) ; PCPU_SET(common—tss . tss_espO , threadO. td—kstack + 2 * PAGE—SIZE ー sizeof (struct pcb) ー PCPU_SET(common—tss. tss_ssO, GDATA—SEL くく 3 ー 0 ) ; gsel—tss = GPROCO—SEL くく 3 ー 0 ; private—tss = 0 ; PCPU_SET(tss—gdt ,&gdt [GPROCO—SEL] . (d) ; PCPU_SET(common—tssd, *PCPU_GET(tss—gdt) ) ; 16 ) ; 168 UNIX MAGAZ 工 NE 2004.4

9. UNIX MAGAZINE 2004年4月号

特集 デバッガの基礎知識 図 13 sampIe3. c 1 2 3 4 6 7 8 9 10 11 12 13 #include く stdio . h> int main(void) char buf [ 5 ] ; char* p ; sprintf (buf , p = buf ; return 0 ; printf ("%s\n" , p) ; overflow-internal 関数のなかで使われます。したがって、 プログラムとしてはこの時点でエラーになり、このように 表示されるのです。 スタックトレースを使ってエラーの発生箇所を調べる場 合に注意が必要なのは、エラーの発生箇所 = 問題のある箇 所とはかぎらない、ということです。とくに、エラーがラ イプラリの内部で発生しているようにみえたら、やみくも にライプラリのバグを疑うのではなく、まずは ( その関数 を呼び出すことになる ) 自分の書いたプログラムに問題が ないかを十分に確認するほうがよいでしよう。 デバッガで会得するポインタ C 言語の入門書を見ると、、、ポインタとはアドレスを保 持する変数 " などと書かれています。メモリを箱にたとえ て説明している場合も多いようです。皆さんは、初めてこ のような説明を読んだとき、、、本当にそうなっているの ? " と疑問に思わなかったでしようか ( 私は思ってしまいまし た ) 。デバッガを使えば、いまメモリ上にどのような値が格 納されているのかを直接確認できるので、このような疑問 も、、お ~ 、ほんとにそうなってるんだ " と納得することがで きます。 最初にも書きましたが、デバッガは基本的にはバグにつ いて調べるための道具ですが、プログラムの動作をみると きにも重宝します。以下では、動いているプログラムの情 報を調べる方法を紹介しましよう。 まず、デバッガを使って、ポインタにどのような値が保 持されているかをみてみましよう。 こでは、図 13 の sample3. c というプログラムを例に 説明します。 42 sample3. c には、 char 型の配列 buf と char 型のポイ ンタ p があります。 9 行目でポインタ p に配列 buf のア ドレスを設定しています。つまり、ポインタ p は配列 buf の先頭を指していることになります。このプログラムをコ ンパイルして、変数の様子を確認してみましよう。 このプログラムはコアダンプするわけではないので、コ アファイルはありません。コアファイルがなかったり、あ るいはコアファイルを調査する必要がない場合は、引数に 実彳コ形式の名前を指定して gdb を起動します ( 図 14 ) 。 今回は実行形式そのものについて調べているので、 sam- plel. c や sample2. c のプログラムのように、異常終了し たときの情報は表示されません。 次に、 gdb のなかからこのプログラムを実行してみます。 それには、 run コマンドを使います。 (gdb) run Starting program : /Programs/Samp1e3/a. out ABC Program exited normally. (gdb) このプログラムは正常に実行され、、、 ABC" という文字 列を出力して終了します。 しかし、このままではプログラムの動作を調べることは できません。プログラムが実行されている途中の状態を調 べたいときは、、、プレークポイント " というものを設定し、 プログラムの実行を途中で停止するようにします。そのた めの gdb のコマンドは break ( 省略形は b) です。プレー クポイントは、行数や関数名で指定します。 たとえば、図 13 の 9 行目が実行される直前でプログラ ムを止めたいときは、次のようにして 9 行目にプレークポ イントを設定します。 (gdb) b 9 Breakpoint 1 at 0X8048381 : file samp1e3. c, - line 9 . (gdb) もう 1 回、 run コマンドでプログラムを実行してみま しよう。 (gdb) run Starting program : /Programs/Samp1e3/a. out Breakpoint 1 , main ( ) at samp1e3. c : 9 9 ( gdb ) p = buf ; UNIX MAGAZINE 2004.4

10. UNIX MAGAZINE 2004年4月号

特集 デバッガの基礎知識 図 6 gdb の help (backtrace コマンド ) (gdb) help backtrace Print backtrace of a11 stack frames , or innermost COUNT frames . With a negative argument , print outermost -COUNT frames . Use Of the ' f Ⅱ 11 ' qualifier a1SO prints the values 0f the 10Ca1 variables . (gdb) について、より詳しい清報を得たいときは、 help に続けて コマンド名を指定します。 backtrace の場合は、図 6 のよ うに表示されます。 本題に戻りましよう。 backtrace コマンドは、下記のよ うに、、 bt " と短縮して指定することもできます ( 誌面の都 合上、で折り返しています。以下同様 ) 。このように、 gdb のコマンドは一意に特定できるのであれば省略するこ とも可能です。もちろん、短い名前で入力しても機能に変 わりはありません。入力は短いほうが楽なので、今後はこ # 1 0X0804833d in main ( ) # 0 0X0804836b in subl ( ) (gdb) bt ちらを使うことにします。 36 ・プログラム内のどこで呼出しが発生したのか しに関連する情報が生成されます。これには、 ています。プログラムが関数を呼び出すたびに、その呼出 関数がどのように呼び出されているかということに関係し いいます。フレーム番号は、プログラムが実行されるとき、 齬頁にある # 0 や # 1 、 # 2 という番号はフレーム番号と デバッグ作業では main() 以降をみていけば十分です。 ら呼び出されていることになります。したがって、通常の つまり、自分カ成したプログラムの main 関数はここか ラムの main 関数の前にシステムが実行している関数です。 ー」 ibc-start-main ( ) というのは、自分カ乍成したプログ 話が横道にそれてしまうので詳しい説明は省略しますが、 subl() が呼び出されているということを表しています。 から samplel. c の main() が、さらに、 main() から この例の場合は、ⅱ bc. so. 6 にある一」 ibc-start-main る位置に、どのようにして到達したのかを示しています。 れる情報です。これは、このプログラムか現在止まってい trace)' または、、スタックトレース (stack trace)" と呼ば こで表示されているのは、 ンヾックトレース (back- ( gdb ) from /lib/tls/libc . SO . 6 # 2 0X42015574 in -—libc—start—main ( ) - ・呼び出されたときの関数の引数 ・呼び出された関数のローカル変数 などが含まれています。これらの情報は、、、スタックフレ ーム " というデータ構造に保存されています。スタックフ レームは、メモリ内部の、、呼出しスタック " と呼ばれる領 域に保存されています。 関数を呼び出すたびに 1 つのスタックフレームが生成さ れ、それがメモリ上に保存されていきます ( 図 7 ) 。 その関数から戻ると、そのスタックフレームはスタック から削除されます ( 図 8 ) 。 gdb では、このスタックフレームの情報をもとに、その プログラムの関数がどのように呼び出されてきたのかを調 べています。そして、スタックフレームの情報を調べるコ マンドが backtrace です。 スタックフレームの番号は、小さけれは小さいほど内側 の関数であることを示しています。このサンプル・プログ ラムでは、一」 ibc-start-main 関数に対するスタックフレ ーム番号が # 2 、そこから呼び出された main 関数に対す るスタックフレーム番号が # 1 、さらに subl 関数の呼出し に対するスタックフレーム番号が # 0 、と 3 つのスタック フレームがあることになります。 bt コマンドでスタックトレースを表示させてみました が、表示された情報はあまり分かりやすいとはいえません。 、、アドレス 0X0804836b でプログラムが異常終了した " と いう情報だけでは、何カ起こっているのか、そう簡単には理 解できないでしよう。これは、コンパイルされた実行形式 に詳細な情報が含まれていないためです。最終的にコンパ イルされて実行されるファイルには、実行に不要な情報が 含まれていてもファイルサイズが大きくなるだけで、あま り嬉しくありません。したがって、そのような情報は省か れるのが普通です。しかし、デバッグをするときは、実行 形式についてもっと詳細な情報が必要になります。デノヾッ グ時に有用な情報を含めてプログラムをコンパイルするに は、 -g オプションを使います。いったん gdb を終了させ、 UNIX MAGAZINE 2004.4