特集 デバッガの基礎知識 図 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
ロプログラミンク・テクニック 多治見寿和 「 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
特集 デバッガの基礎知識 行目を見ると、セグメンテーション・フォルトの原因とな りそうな引数は、ポインタ p しかなさそうです。そこで、 この p の内容を確認してみましよう。 (gdb) p p NO symbol 1n current context . (gdb) 最初のサンプル・プログラム samplel. c では、 print コ マンドで変数の内容をみることができましたが、今度は、、 p というシンポルはみあたらない " と表示されています。 のエラーメッセージのⅲ current context" という文に 注意してください。 gdb では、 print のように変数などのデータを調べるコ マンドは、ほとんど現在のスタックフレームについてだけ 動作します。これを理解するには、 C 言語のスコープ ( 有 効範囲 ) を思い出すといいでしよう。ここで確認しようと している p という変数は、 subl 関数のなかで宣言されて いるローカル変数なので、ほかの関数からは参照できませ ん。現在、このプログラムは -l()-str-overflow_internal という関数で止まっています。したがって、この関数から は p という subl 関数内のローカル変数は参照できないわ けです。スコープの考え方と同様、グローバル変数はどの スタックフレームにいるときでもつねに調べることができ ます。 別のスタックフレームにあるデータを調査したい場合 は、そのスタックフレームを選択状態にする必要がありま す。上記の gdb のエラーメッセージは、、、 p というシンボ ルは、現在選ばれているスタックフレーム ( コンテキスト ) には存在しないため、表示できない " という未です。 いま、自分がどのスタックフレームを選んでいるのかは frame ( 省略形は f) コマンドで知ることができます。 (gdb) f # 0 0X42070fa4 in _IO_str_overf10w—interna1=> ( ) from /lib/tls/libc . so . 6 プログラムが止まっている場合は、自動的にプログラム が中断したときのスタックフレームが選択状態になります。 現時点では、このプログラムで定義されているグロー バル変数があれば、そのグローバル変数と、 ()g オプシ ョン付きでコンパイルされていれば ) その関数内のローカ ル変数を調べることができます。ここでは関数 -IO-str- overflow-internal は—g 付きでコンノヾイルされていない UNIX MAGAZINE 2004.4 ので、この関数内のローカル変数は調べられません。 subl 関数内のローカル変数 p を調べるには、 subl 関 数を実行しているときのスタックフレームを選択状態にす る必要があります。 選択しているスタックフレームを変更するには frame コ マンドか、 up/down コマンドを使います。 frame コマ ンドでは、引数として移動先のスタックフレームの番号を 指定します。一方、 up/down は 1 つ上 / 下のスタックフ レームに移動します。、、上 / 下のスタックフレーム " とい うのは、 backtrace コマンドで表示されるスタックトレー スに含まれるスタックフレームの川印茅のことです。 いまは一番内側のスタックフレームにいるので、 up コマ ンドで 1 つ上のスタックに移動してみましよう。 (gdb) up # 1 0X4206fb28 in _IO_defau1t_xsputn—interna1=> ( ) from /lib/tls/libc . so . 6 (gdb) ご覧のように、 1 つ上にあたる # 1 のスタックに移動し ました ーで選択したいスタックフレームは、 sprintf を 呼び出している # 5 のスタックフレームです。 up コマンド を何回か実行して # 5 まで移動してもかまいませんが、 こでは frame コマンドで一気に # 5 まで移動します。以 下のように、 frame コマンドの引数に、、 5 " と指定します。 (gdb) f 5 # 5 0X080483a9 in subl ( ) at samp1e2. c : 18 sprintf (), "%s" 18 ( gdb ) これで目的とするスタックフレームが選択できたので、 このスタックフレームにあるローカルな変数を調査できる ようになりました。さきほとべようとしていた変数 p の 値を確認してみましよう。 (gdb) p p $ 1 = 0X0 (gdb) 今度はぶじに p の値が表示され、ポインタの値が 0 に なっていることが分かります。つまり、このセグメンテ ーション・フォルトの本当の原因は、 -IO-str-overflow- internal 関数ではなく、 sprintf を呼び出すときの引数 にあったわけです。あとは、この引数がなぜ 0 になった のかを調べればよいでしよう。 sprintf に渡された変数 p の値は、実際には sprintf から呼び出された -IO-str- 41
そして、最後に搗瓜を閉じて関数を終了します。 echo 1 > & 2 " ) " exit 1 err 関数、 warn 関数、 info 関数 err 関数は、エラーメッセージを標準ェラー出力に出力 し、可能なら syslog への出力もおこない、指定された値を 使って exit を呼び出します。関数への引数は出力したい メッセージと戻り値ですが、メッセージには複数の単語が 含まれる可能性もあるので、さきに戻り値を指定し、その 後ろに任意個の引数を使ってメッセージを指定するように なっています。 コードでは、まず exit の引数として使う値を exitval 変数に取り出します。これだけではまだ引数のなかに値が 残っているので、 shift を実行して引数からも取り出してし まいます。 exitval=$l shift こうすることで、引数には出力用のメッセージだけカ り、 $ * などを使ってメッセージ全体を出力できるようにな ります。 syslog への出力には logger コマンドを使います。 log- ger は、引数として指定された文字列を syslog に送るコマ ンドです。このとき、ファシリティには user を、レベル には notice を用います。これらを指定したい場合には一 p err() f i logger " $ 0 : ERROR: $ * " if [ —x /usr/bin/logger ] ; then オプションを使います。 UN 工 X MAGAZ 工 NE 2004.4 ラムを終了します。 存しておいた値を引数として exit 関数を呼び出し、プログ 工ラー出力に出力します。最後に、さきほど exitval に保 さらに、 syslog への出力だけでなく、同じ文字列を標準 て logger コマンドを呼び出し、 syslog に出力します。 関数への引数として指定されたすべての文字列を引数とし ません。スクリプト名と ERROR という文字列、そして コードでは、とくにファシリテイやレベルは指定してい プログラミング・テクニック exit $exitval echo 1 > & 2 " $ 0 : ERROR: ことで、 syslog のレベルを有効に活用できるようになり ${syslog-facility}. info" といったオプションを追加する —p ${syslog-facility}. warning ' 、 info 関数なら、、一 p であれば、、一 p ${syslog-facility}. err" 、 warn 関数なら としておき、 logger コマンドを呼び出すときに err 関数 syslog—facility=user defaults/rc. conf など ) で、 たとえば、 rc. subr ファイルの前のほう ( もしくは /etc/ 時にレベルを調整することも考えられます。 これらの関数の定義において、 logger コマンドの呼出し WARNING が INFO となっている点だけが異なります。 その内容は warn 関数とほとんど同じで、メッセージ中の rc. subr ファイルでは、 info 関数も定義されています。 ば、ほば同じ関数であること力分かると思います。 されている点、 exit 関数を呼び出していない点などを除け ない点、メッセージが ERROR から WARNING に変更 err 関数とくらべると、 exitval 変数に値を取り出してい echo 1 > & 2 " $ 0 : WARNING: $ * " f i logger " $ 0 : WARNING: $ * " if [ —x /usr/bin/logger ] ; then warn ( ) 関数は呼び出しません。 ジの出力が目的なので、プログラムを終了するための exit ラー出力にも出力します。ただし、たんなる警告メッセー えられた引数をメッセージとして syslog に出力し、標準工 warn 関数は、 err 関数とほば同様の動作をします。与 ます。 debug 関数 調べ、これカ填の場合にのみ出力をおこないます。 こないます。ただし、 debug 関数は変数 rc-debug の値を セージの syslog への出力や標準工ラー出力への出力をお bug 関数は、 warn 関数や info 関数と同じように、メッ これらの関数とよく似た関数がもう 1 つあります。 de- 99
特集 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
実際のコードも、前節で紹介した関数とほほ同じですが、 全体が rc-debug 変数の値によって制御されるかたちにな っています。コードでは、 case 文のパターンを複数指定 3- CYy] [Ee] [Ss] lCTt] [Rr] CUu] CEe]l case ${rc—debug} in debug ( ) ることで、 rc-debug の値を検査しています。 [ 00 ] [Nn] は ) f i echO esac 1 > & 2 " $ 0 : DEBUG: $ * " logger " $ 0 : INFO : if [ —x /usr/bin/logger ] ; then 指定しているパターンは、 2 月号で紹介した checkyesno 関数と同じものです。もちろん、 ここで checkyesno 関 数を使うこともできますが、 rc-debug 変数に値が設定さ れていなかったときの挙動が異なるため利用していません。 debug 関数では、 rc-debug 変数に値が設疋されていなけ れば何も出力しませんが、 checkyesno 関数を呼び出した 場合には警告メッセージが出力されてしまいます。 ようやく、 rc. subr ファイルで定義されている最後の関数 にたどり着きました。最後に残った関数は backup-file で す。これは、指定されたファイルをバックアップするため の関数です。引数は 4 つで、最初の引数にはどのような処 理をおこなうかを指定します。ここで指定する単語によっ て、次のように動作カ畯わります。 ・ add : 指定したファイルのバックアップを開始する。 ・ update : ファイルの更新をバックアップ・システムに 告げる。 ・ remove : 指定したファイルのバックアップを終了する。 第 2 引数には、処理の対象となるファイルを指定します。 第 3 引数には、現在処理の対象としているファイルのバッ クアップ・ファイルを、第 4 引数には 1 つ前のバックアッ プ・ファイルを指定します。 この関数の処理では、 backup-uses-rcs 変数の値が大き な役割を果たします。この変数に設定されている値が真の backup file 関数 100 場合、ファイルのバックアップは RCS を使っておこなわ れます。一方、この変数が偽であれば、通常のファイルを 使ったバックアップとなります。コードでは RCS を利用 する処理がさきに言杢されていますが、 RCS を利用しない 場合から紹介したほうが処理の実際を理解しやすいと思う こではコードとは逆の順番で説明します。 ので、 関数の冒頭では、引数を対応する変数にタしています。 backup—file() -back=$4 -cur=$3 —fi1e=$2 —action=$l UNIX MAGAZINE 2004.4 プ・ファイルを以前のバックアップ・ファイルとすること イルを無効にすることになります。ここでは、バックアッ 第 1 引数が remove の場合には、バックアップ・ファ ます。 有者を root 、グループを wheel に変更して処理を終了し ルにコピーします。作成したバックアップ・ファイルの所 に、処理対象のファイル (-file) をバックアップ・ファイ のバックアップ・ファイル (-back) にコピーします。さら バックアップ・ファイル (-cur) があれば、それを以前 chown root :wheel $—cur cp ¯p $-file $-cur cp -p $—cur $-back if [ —f $-cur ] ; then addl update) case $_action in else ピーします。この処理は次のようにおこなわれます。 第 4 引数で指定された以前のバックアップ・ファイルへコ アップ・ファイルへコピーし、バックアップ・ファイルを された処理対象のファイルを第 3 引数で指定されたノヾック 決定されます。 add や update の場合、第 2 引数で指定 こでは、第 1 引数に指定された値にもとづいて処理が RCS を利用しないコードをみていきます。 きますが、上に述べた理由から、さきに else 節にあたる このあと、実際には RCS を利用する場合のコードカ ります。 ログラムのなかでそれぞれの引数の意味が分かりやすくな このように名前の付いた変数に格納しなおすことで、プ
プログラミング・テクニック 74 図 3 /etc/rc. conf ファイルの言蒄入み elif [ ーて /etc/rc . conf ] ; then debug "Sourcing /etc/rc . conf (/etc/defaults/rc . conf doesn't exist) . /etc/rc . conf _rc_conf_10aded=YES します。 if [ —r /etc/defaults/rc . conf ] ; then debug "Sourcing /etc/defaults/rc . conf'l /etc/defaults/rc . conf source_rc_confs source-rc-confs は /etc/defaults/rc. conf ファイルで 定義されており、デフォルトでは /etc/rc. conf ファイル と /etc/rc. conf. local ファイルを読み込むための関数とな っています。ほかの設疋ファイルもこの関数のなかで読み 込んでいます。 /etc/defaults/rc. conf ファイルがない場 合は、直接 /etc/rc. conf ファイルがあるかどうかを確認し ます。ファイルがみつかれば、その内容を読み込みます ( 図 3 。図中の debug については彳 ) 。 売込みが終ると、ファイルを読み込んだことを示すため に -rc-conf 」 oaded 変数に文字列 YES を代入します。 こで値を代入しておくことで、再度この関数が実行された ときにも、ファイルの読込みを繰り返さずにすむようにな っています。 標準的な設定ファイルの読込みが終ったら、続いて特 load-rc-config 関数では、どのコマンドに対する設疋を 定のコマンド用の設定ファイルを読み込みます。このた 読み込むのかを指定する引数が必須です。ここでは、引数 めに、さきほど引数の値を格納しておいた -command 変 が正しく指定されているかを確認し、指定されていなけれ 数を使います。この変数に格納された名前のファイル名 ば工ラーメッセージを出力します。 が /etc/rc. conf. d ディレクトリにあるかどうかを調べ、あ 次に、 rc スクリプト群のための設定ファイルとして、 ればそのファイルの内容を読み込みます。 /etc/defaults/rc. conf と /etc/rc. conf を読み込みます。 ただし、同じファイルを 2 回読み込むのは無未なので、 1 if [ -f /etc/rc . conf . d/"$_command" ] ; then 度設定ファイルを読み込んだあとで再度この関数カ剛乎び出 debug "Sourcing /etc/rc . conf . d / - されても読込みを実行しないように、 -rc-conf 」 oaded 変 /etc/rc . conf . d/"$—command" 数の値を確認しています。この変数に値が設定されていな ければ、まだ設疋ファイルカ毓み込まれていないものとみ 最後に、旧い形式で指定されている変数に代入された値 なし、言囚みを実行します。 を、対応する新しい形式の変数に代入しなおす処理をおこ if [ -z "$_rc_conf—loaded" ] ; then ないます。この処理により、旧い形式の設定ファイルを使 最初に読み込むのは /etc/defaults/rc. conf ファイルで い続けながら、新しいリリースに移行できるようになりま す。このファイルがあるかどうかを確認し、あればその内 す。ここでは、ま $OS が FreeBSD であることを確認し 容を読み込みます。そして、 source-rc-confs 関数を実行 たあと、 portmap-enable 、 portmap-program 、 port- load rc config 関数 次に定義されている load-rc-config 関数は、引数と指 定されたコマンドに関する設疋を読み込むためのものです。 この関数では、まだ rc スクリプト群のための設定ファイ ルカ毓み込まれていなければ、それを読み込みます。さら に、引数として指定されたファイル名のファイルを / etc / rc. conf. d ディレクトリから検索し、ファイルがあればそ の内容も読み込みます。 実際のコードをみてみましよう。この関数でも、まず引 数を確認しています。 load—rc-config() ー c ommand= $ 1 if [ —z "$_command" ] ; then err 3 'USAGE: load_rc_config command ' ニ = ロ f i f i 97 UNIX MAGAZ 工 NE 2004.4
図 4 7 つの変数の値を新しい変数にコピー case ${OSTYPE} in FreeBSD) '$portmap—enable " ] & & rpcbind—enable=" $portmap—enable" ーⅡ '$portmap-program " ] & & rpcbind—program=" $portmap—program' '$portmap-flags " ] & & rpcbind—flags=" $portmap-flags " '$single—mountd—enable" ] & & mountd—enable=" $single—mountd—enable" ーⅡ " $xntpd-enable" ] & & ntpd-enable=" $xntpd—enable " ーⅡ " $xntpd-program" ] & & ntpd-program="$xntpd-program" "$xntpd-flags" ] & & ntpd-flags="$xntpd—flags" e S めのものですこの関数では、スクリプトカ引数を 1 っと map-flags 、 single-mountd-enable 、 xntpd-enable 、 り、引数の先には fast か f 。 , 00 カ咐けられると仮定して xntpd-program 、 xntpd-flags という 7 つの変数の値を 新しい変数にコピーしています ( 図 4 ) 。 います。引数の後ろの部分は、関数への引数を使って表し このコードでは、まず test コマンドを使って変数カ第ス疋 ます。 されているかどうかを確認し、その後、 & & を制御構造の コードでは、まず Usage 出力を表す文字列、コマンド ように使って処理を実現しています。最初の行を例にとる 名、引数の前に fast か force を付けられる点について出力 と、 portmap-enable 変数に空でない値が常内されている します。 ときには & & の後ろの部分を実行し、 rpcbind-enable 変 rc—usage() 数に portmap-enable 変数の値が常内されます。 ech0 -n 1 > & 2 "Usage: $ 0 [fastlforce] ( " この部分は、パラメータ展開をうまく使えば制御構造を まったく利用しなくても実現できます。たとえば、上で説 この echo コマンドでは、—n オプションを指定して改行 明した行は次のように言古杢できます。 しないようにしています。これは、実際の引数を別の echo コマンドで出力するので、行の途中で改行されないように rpcbind—enable=${portmap—enable : ー - $rpcbind—enable} 、 1 > & 2 " としてリダイレクション するためです。さらに、 も指定しています。こちらは、通常の echo コマンドでは ます、等号の右側を見てください。この部分でパラメー 標準出力に出力されてしまうので、標準工ラー出力へ出力 タ展開を使っています。通常は portmap-enable 変数の するためです。 値に展開されますが、この値が設定されていなかったり値 指定できる引数は、関数への引数として渡されます。 カ啌だったときには、パラメータ展開のなかの : ーの右の値 れを出力する際には、どれか 1 つを選択できることを示す カ俐用されます。つまり、等号の右側全体で、 portmap- ため、 、、「 ( パイプ ) で区切って出力する必要があります。 enable 変数に値が設疋されていればその値、設疋されて いなければ rpcbind-enable 変数の値を表すことになりま -sep= elem in $ * ; dO す。この値を rpcbind-enable 変数に代入するので、けっ for echo ーⅡ 1 > & 2 "$—sep$—elem" きよくは portmap-enable 変数に値カ殳定されていたと —sep=" 卩 done きに、その値を rpcbind-enable 変数に代入するのと同じ 結果が得られます。 このため、最初は -sep を空にしておき、 1 つの要素を 出力する際にはかならす、、 $-sep$-elem" と出力されるよ うにします。最初は -sep カ啌なので、たんに -elem を出 力したのと同じですが、それ以降の出力では一 sep がパイプ に置き換えられるため、かならずパイプの後ろに次の単語 が並ぶことになります。 rc usage 関数 rc-usage 関数は、スクリプトの実行中に引数に関するな んらかの問題が発生したときに Usage 出力をおこなうた 98 UNIX MAGAZ 工 NE 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
テパッガの基礎知識 特集 1 : *ptr = {number = 2 , name 0X8049780 - 63 0X0 } (gdb) "cell B", next ( gdb ) 54 行目からは、 base 引数が NULL のときは ptr をその すると、 ptr の内容力哽新され、新たな値が定されたこ まま返しています。そうでない場合は、 qtr という変数を とが分かります。もう 1 回、 continue コマンドで実行を 使って何かをしているようです。 main 関数をみなおして 継続します。プレークポイントで止まったら、 next コマン みると、 base が NULL になるのは、初めて addCell 関数 ドを使って makeCeII 関数をふたたび実行します。 ptr 変 カび出されたときだけのようです。 2 回目以降、 addCe11 数カ哽新され、新しい内容が誌定されたことが分かります。 関数カび出されたときは、何がおこなわれているのでし よう力、。 (gdb) c この部分の動作を確認するため、いままでのプレークポ Continuxng ・ イントは削除して、新たに 57 行目にプレークポイントを Breakpoint 2 , addCe11 (name=0x80485da , 設疋します。さきほど display コマンドで設疋したコマン ” cell C" , number=3 , base = 0X8049750 ) at samp1e4. c : 52 ドも解除しておきます。引数を付けずに display コマンド ptr = makeCe11 (name , number) ; 52 を実行すると、現在譿定されている display のリストカ俵 1 : *ptr = {number = 2 , name = 0X8049780 - 0X0 } "ce11 Bt' next 示されます。このときに表示された番号を引数に指定して (gdb) Ⅱ undisplay コマンドを実行すれば、解除できます。 if (base = = NULL){ 54 {number = 3 , name = 0X80497a0 - 1 : *ptr (gdb) d 0X0 } "ce11 C" next DeIete a11 breakpoints? (y or Ⅱ ) y ( gdb ) (gdb) b 57 Breakpoint 3 at 0X80484ed : file samp1e4. c, 疇 - どうやら、 addCelI 関数のなかで makeCell 関数カ line 57. び出されるたびに新しく 1 つぶんのセル構造体 (struct (gdb) display 1 : *ptr = {number tlist) カ寉保され、そのアドレスカ畯数 ptr に設定されて "cell B", next いるようです。 (gdb) undisplay 1 今度は、 addCeII 関数の後半に注目してみます。もう 1 (gdb) display ( gdb ) 回、 addCeII 関数のソースコードを眺めてみましよう。 もう 1 回、実行してみましよう。 (gdb) 1 addCe 11 44 (gdb) run 1 45 2 The program being debugged has been started 46 already. 47 TLIST* 3 Start it from the beginning? (y or Ⅱ ) y addCe11 (char* name , 48 4 TLIST* base) 5 Starting program : /Programs/Samp1e4/a. out 49 6 50 7 Breakpoint 3 , addCe11 ( name = 0X80485d3 " cell 51 B" , number=2, base = 0X8049750 ) 52 at samp1e4. c : 57 8 53 9 57 qtr=base ; (gdb) (gdb) 1 10 54 55 7 行目を見ると、プレークポイントで停止したときの ad ー 56 57 dCeII 関数の引数が 2 つ目のセルを追加しようとしている 58 ときであることカ吩かります。ます、 qtr 変数に base 変数 59 60 の値を設定しています。 base 変数の値は、初めて make- 61 CeII 関数が実行されたときのアドレス、つまり最初のセル 62 2 , 0X8049780 - 0X0 } return ptr ; TLIST* ptr, *qtr; ptr = makeCe11 (name , number) ; if (base } else { = NULL ) { return ptr ; qtr=base ; while(qtr—>next ! = NULL) qtr = qtr—>next ; qtr—>next = ptr; return base ; 52 UN 工 X MAGAZ 工 NE 2004.4