ルギア君の戯言

雑多な記事。

gdb の使い方

gdb とは GNU が提供しているデバッガ。
地味だが、使える。


今回は例として、

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

#define SIZE 15
#define CHRMAX 80
char banmen[SIZE][SIZE];

void view_board(char* data, int size);
void init(char* data, int size);
char* get_place(char* data, int size);

int main() {
    int winflag = 0;
    int turn = 0;
    
    init(&(banmen[0][0]), SIZE);

    while(winflag == 0) {
	system("clear");
	view_board(&(banmen[0][0]), SIZE);

	if(turn % 2 == 1) {
	    printf("It\'s White turn (0). \n");
	} else {
	    printf("It\'s Black turn (@). \n");
	}

	*get_place(&(banmen[0][0]), SIZE) = (turn % 2 == 1) ? '0' : '@';

	turn ++;
	if(turn - 1 > SIZE * SIZE) winflag = 3;
    }
    switch(winflag) {
      case 1:
	printf("White (0) Won!\n");
	break;
      case 2:
	printf("Black (@) Won!\n");
	break;
      case 3:
	printf("Draw Game...\n");
	break;
      default:
	printf("Unknown Error...\n");
	break;
    }
    return 0;
}

void init(char* data, int size) {
    int i, j;

    for(i = 0; i < size; i++) {
	for(j = 0; j < size; j++) {
	    *(data + i * size + j) = '+';
	}
    }
}

void view_board(char* data, int size) {
    int i, j;
    char cr, cl;

    cr = 'A';
    for(i = -1; i < size; i++) {
	if(i == -1) {
	    cl = 'a';
	    printf("  ");
	    for(j = 0; j < size; j++) {
		printf("%c ", cl);
		cl++;
	    }
	} else {
	    printf("%c ", cr);
	    cr++;
	    for(j = 0; j < size; j++) {
		printf("%c ", *(data + i * size + j));
	    }
	}
	printf("\n");
    }
    printf("\n");
}

char* get_place(char* data, int size) {
    char tmp[CHRMAX];
    int correct_code = 1;

     while(correct_code == 1) {
	printf("Plase input place code (aa, ab, ..., oo): ");
	if(fgets(tmp, CHRMAX, stdin)) {
	    printf("EOF Error.\n");
	    exit(1);
	}
	tmp[0] = tolower(tmp[0]);
	tmp[1] = tolower(tmp[1]);
	correct_code = 0;
	if(!((tmp[0] >= 'a' && tmp[0] <= 'o') && (tmp[1] >= 'a' && tmp[1] <= 'o'))) {
	    correct_code = 1;
	    printf("Invalid format. [Rawcharactor][Colcharactor]\n");
	} else if(*(data + (tmp[0] - 'a') * size + tmp[1] - 'a') != '+') {
	    correct_code = 1;
	    printf("There is already placed. Choose another.\n");
	}
    }

    return data + (tmp[0] - 'a') * size + tmp[1] - 'a';
}

デバッグしてみましょう。
行番号で参照しますので、自分の好きなエディタなどにコピーして行番号を確認してください。


さて、コンパイルします。コンパイルする時は -g オプションを付けてデバッグ情報を生成してもらいます。

$ gcc -o gomoku gomoku.c -g


では、デバッグを開始しましょう。

$ gdb gomoku

で開始します。gomoku は実行ファイル名です。


実行すると、メッセージとともにプロンプトが登場します。

[lugia@lugia-castle gomoku]$ gdb gomoku
GNU gdb Momonga Linux 5 (6.8-7m.mo5)
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-momonga-linux"...
(gdb)

ブレークポイントがなにもないと普通に実行するのとおなじですので、とりあえずブレークポイントを設定しましょう。

(gdb) break main

これで main 関数の先頭に達した時(実は main 関数の前にもいくらか処理がある)、プログラムがポーズします。


設定すると

(gdb) break main
Breakpoint 1 at 0x8048515: file gomoku.c, line 14.

となり、このブレークポイントの番号(1)、メモリアドレス、ソースファイル名、そして行番号が表示されます。


では実行しましょう。

(gdb) run
Starting program: /home/lugia/Projects/class/gomoku/gomoku
warning: linux_test_for_tracefork: unexpected result from waitpid (8160, status0x57f)

Breakpoint 1, main () at gomoku.c:14
14          int winflag = 0;
(gdb)

実行する時は run を使います。run に続けてプログラムに渡す引数を指定できます。
さて、main 関数の先頭で止まりました。


表示されている14行目の

    int winflag = 0;

は次に実行する内容です。では、いまの winflag の中身を確認してみましょう。

(gdb) print winflag
$1 = 134514889

変数の中身を知りたい時は print [変数名] でできます。0 じゃないので、まだ実行されていないことがわかりますね。
変数は型を気にせずに表示できます。


では、先に進めましょう。

コマンド名 意味
continue 次のブレークポイントにぶつかるまで実行する
step 1行実行する。サブルーチン(関数)がある場合は中へはいる。ループの2回目以降も1行ずつ実行する。
until 1行以上実行する。サブルーチン(関数)には入らずその次の行まで実行する。ループの2回目以降はループを抜けるまで一度に実行する。

とりあえず、step で先に進めていきましょう。

(gdb) step
15          int turn = 0;
(gdb) print winflag
$2 = 0

0が代入されましたね。


先に進めます。

(gdb) step
17          init(&(banmen[0][0]), SIZE);
(gdb)
init (data=0x8049c20 "", size=15) at gomoku.c:54
54          for(i = 0; i < size; i++) {

なにも打たずにリターンキーを押すと前に実行したコマンドが実行されます。
いちいち step とか打つのは面倒ですからね。


init というサブルーチンに入ったのがわかりますか?
このとき引数はポインタ data は空の文字列、size は 15 が渡されたことを示しています。
また、このサブルーチンは gomoku.c の 54 行目にあるものであることがわかります。

(gdb)
init (data=0x8049c20 "", size=15) at gomoku.c:54
54          for(i = 0; i < size; i++) {
(gdb) step
55              for(j = 0; j < size; j++) {
(gdb) step
56                  *(data + i * size + j) = '+';
(gdb)
55              for(j = 0; j < size; j++) {
(gdb)

2重ループの内側の2回目に来ています。ここで until するとどこまで実行するでしょうか。
よく見てみてください。

55              for(j = 0; j < size; j++) {
(gdb) until
54          for(i = 0; i < size; i++) {
(gdb)

つまり、一番内側のループを抜けるまで一気に実行されました。
もう一度 until します。

54          for(i = 0; i < size; i++) {
(gdb) until
59      }
(gdb)

ループを全部抜けるまで一度に実行されました。


ここで step か until すれば、main 関数に戻るはずです。

59      }
(gdb) step
main () at gomoku.c:19
19          while(winflag == 0) {
(gdb)

gomoku.c にある main 関数の 19 行目に戻ってきました。

(gdb) until
20              system("clear");
(gdb)
21              view_board(&(banmen[0][0]), SIZE);
(gdb)

さて、ここで until するとどこまで実行するでしょうか。

21              view_board(&(banmen[0][0]), SIZE);
(gdb) until
  a b c d e f g h i j k l m n o
A + + + + + + + + + + + + + + +
B + + + + + + + + + + + + + + +
C + + + + + + + + + + + + + + +
D + + + + + + + + + + + + + + +
E + + + + + + + + + + + + + + +
F + + + + + + + + + + + + + + +
G + + + + + + + + + + + + + + +
H + + + + + + + + + + + + + + +
I + + + + + + + + + + + + + + +
J + + + + + + + + + + + + + + +
K + + + + + + + + + + + + + + +
L + + + + + + + + + + + + + + +
M + + + + + + + + + + + + + + +
N + + + + + + + + + + + + + + +
O + + + + + + + + + + + + + + +

23              if(turn % 2 == 1) {
(gdb)

view_board 関数には入らず、main 関数で次の行まで実行されます。

(gdb) until
26                  printf("It\'s Black turn (@). \n");
(gdb)
It's Black turn (@).
29              *get_place(&(banmen[0][0]), SIZE) = (turn % 2 == 1) ? '0' : '@';
(gdb)
Plase input place code (aa, ab, ..., oo): aa
EOF Error.

Program exited with code 01.
(gdb)

Please input ... には aa, ab, ac, ..., ao, ba, bb, bc, ..., ..., oo のどれかを入れてください。
「Program exited with code 01.」というメッセージが見えますね。


プログラムは終了した・・・ということ見たいですが、その後で step を実行すると

Program exited with code 01.
(gdb) step
The program is not being run.

と、プログラムは実行されていませんと叱られます。


さて、では EOF Error. というメッセージを頼りに、ソースコードを修正してみましょう。
どこかわからない場合には、もう一度 run して get_place に step で入っていきましょう。
get_place にブレークポイントを設定しても良いかもしれません。


fgets 関数は

    while(fgets(...)) {

と言う形で実行した時 EOF にぶち当たるまでループします。なので*1、92行目は

	if(fgets(tmp, CHRMAX, stdin)) {

ではなく

	if(!fgets(tmp, CHRMAX, stdin)) {

と言うことがわかりますね。
エディタで修正したら一度 gdb を抜けましょう。

(gdb) quit
[lugia@lugia-castle gomoku]$

quit で抜けられます。


コンパイルしなおして再度 gdb へ。

[lugia@lugia-castle gomoku]$ gdb gomoku
GNU gdb Momonga Linux 5 (6.8-7m.mo5)   
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.           
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"   
and "show warranty" for details.                                             
This GDB was configured as "i686-momonga-linux"...                           
(gdb) break get_place                                                        
Breakpoint 1 at 0x804877c: file gomoku.c, line 88.                           
(gdb) run                                                                    
Starting program: /home/lugia/Projects/class/gomoku/gomoku                   
warning: linux_test_for_tracefork: unexpected result from waitpid (8483, status 0x57f)                                                                          
Detaching after fork from child process 8485.                                   
  a b c d e f g h i j k l m n o
A + + + + + + + + + + + + + + +
B + + + + + + + + + + + + + + +
C + + + + + + + + + + + + + + +
D + + + + + + + + + + + + + + +
E + + + + + + + + + + + + + + +
F + + + + + + + + + + + + + + +
G + + + + + + + + + + + + + + +
H + + + + + + + + + + + + + + +
I + + + + + + + + + + + + + + +
J + + + + + + + + + + + + + + +
K + + + + + + + + + + + + + + +
L + + + + + + + + + + + + + + +
M + + + + + + + + + + + + + + +
N + + + + + + + + + + + + + + +
O + + + + + + + + + + + + + + +

It's Black turn (@).

Breakpoint 1, get_place (data=0x8049c20 '+' <repeats 200 times>..., size=15)
    at gomoku.c:88
88          int correct_code = 1;
(gdb)

main 関数では無く get_place 関数にブレークポイントを設定したので、そこまで実行されました。


さて、修正した内容で正しいかどうか確認してみましょう。

88          int correct_code = 1;
(gdb) step
90           while(correct_code == 1) {
(gdb)
91              printf("Plase input place code (aa, ab, ..., oo): ");
(gdb)
92              if(!fgets(tmp, CHRMAX, stdin)) {
(gdb)
Plase input place code (aa, ab, ..., oo): aa
96              tmp[0] = tolower(tmp[0]);
(gdb)

EOF Error にはいかず、正しい方向に行きますね。
step で、サブルーチンがあっても、デバッグ情報の無い関数には立ち入りません。なので、printf にも fgets にも入っていかなかったのです。


なお、printf などの出力はフラッシュされるまで出てきません。
フラッシュされるのは以下の時。

  • '\n'を出力した時。
  • 入力をもらおうとする時。


while 文の場合は条件が先頭にありますので、一度先頭に戻ってから条件を満たさなかったらループの最後に飛びます。

96              tmp[0] = tolower(tmp[0]);
(gdb)
97              tmp[1] = tolower(tmp[1]);
(gdb)
98              correct_code = 0;
(gdb)
99              if(!((tmp[0] >= 'a' && tmp[0] <= 'o') && (tmp[1] >= 'a' && tmp[1] <= 'o'))) {
(gdb)
102             } else if(*(data + (tmp[0] - 'a') * size + tmp[1] - 'a') != '+') {
(gdb)
90           while(correct_code == 1) {
(gdb)
108         return data + (tmp[0] - 'a') * size + tmp[1] - 'a';
(gdb)


ここで、bt というコマンドを実行してみましょう。
なんと出てきますかね?

(gdb) bt
#0  get_place (data=0x8049c20 '+' <repeats 200 times>..., size=15)
    at gomoku.c:108
#1  0x080485a7 in main () at gomoku.c:29

bt は backtrace の略で、いままでに関数を呼び出された経歴を表示します。
始めに main 関数が呼ばれ、その次に get_place 関数が呼び出されています。


大きいプログラムをデバッグしたりすると、たまに step で中に入れない関数があったりします。
しかし、その中で Segmentation Fault が起きていて原因を突き止めたい場合には、Segmentation Fault (SIGSEGV *2と表記される)になってから(まだ実行されている)、bt を実行すると、呼出履歴がわかるので、より深いところにブレークポイントを設定できて、Segmentation Fault の根元がわかる場合があり、活躍しますので覚えておくと良いでしょう。


では、続けましょう。
一度プログラムを終了させます。(強制)終了させる時は kill を使います。

(gdb) kill
Kill the program being debugged? (y or n) y

そしたら、設定したブレークポイントを一度削除して、引数に

< list.csv

(list.csv ファイルの中身は後述)
を付けます。

(gdb) delete breakpoint 1

ブレークポイント1 が削除されました。
では引数を付けて実行します。

(gdb) run < list.csv
...(中略)...
Plase input place code (aa, ab, ..., oo):   a b c d e f g h i j k l m n o
A @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
B 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
C @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
D 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
E @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
F 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
G @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
H 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
I @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
J 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
K @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
L 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
M @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
N 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
O @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @

It's White turn (0).
Plase input place code (aa, ab, ..., oo): EOF Error.

Program exited with code 01.
(gdb)

このように、リダイレクトも OK ですのでデータの打ち込みが面倒な時などに活躍します。


さて、このプログラムは五目並べをする書きかけのプログラムですが、全部埋まった場合には EOF Error ではなく、「引き分け」のはずです。
31 行目と 32行目で全部埋まったかどうかをターン数をカウントすることで調べようとしています。
15 \times 15 = 225ですから、turn が 224 になったあたりで止めたいですね。
そんな事もできますよ。


まず、gomoku.c の 31行目にブレークポイントを設定します。
gomoku.c 以外の特定の行番号にブレークポイントを設定するには一度そのファイルのところまで実行する必要があります。

(gdb) break 31
Breakpoint 1 at 0x80485d7: file gomoku.c, line 31.

これで設定されました。では実行しましょう。

(gdb) run < list.csv
Starting program: /home/lugia/Projects/class/gomoku/gomoku < list.csv
warning: linux_test_for_tracefork: unexpected result from waitpid (9186, status 0x57f)                                                                          
Detaching after fork from child process 9188.                                   
  a b c d e f g h i j k l m n o
A + + + + + + + + + + + + + + +
B + + + + + + + + + + + + + + +
C + + + + + + + + + + + + + + +
D + + + + + + + + + + + + + + +
E + + + + + + + + + + + + + + +
F + + + + + + + + + + + + + + +
G + + + + + + + + + + + + + + +
H + + + + + + + + + + + + + + +
I + + + + + + + + + + + + + + +
J + + + + + + + + + + + + + + +
K + + + + + + + + + + + + + + +
L + + + + + + + + + + + + + + +
M + + + + + + + + + + + + + + +
N + + + + + + + + + + + + + + +
O + + + + + + + + + + + + + + +

It's Black turn (@).

Breakpoint 1, main () at gomoku.c:31
31              turn ++;
(gdb)

そしたら、一度31行目のブレークポイントを削除して、turn が 224 になったらブレークを発動するように変えます。

(gdb) delete breakpoint 1
(gdb) break 31 if turn == 224
Breakpoint 2 at 0x80485d7: file gomoku.c, line 31.

そしたら、continue しましょう。

(gdb) continue
...(中略)...
Plase input place code (aa, ab, ..., oo):   a b c d e f g h i j k l m n o
A @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
B 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
C @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
D 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
E @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
F 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
G @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
H 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
I @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
J 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
K @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
L 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
M @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
N 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
O @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 +

It's Black turn (@).

Breakpoint 2, main () at gomoku.c:31
31              turn ++;
(gdb) print turn
$1 = 224

となり、224 になった時にブレークしました。


ここから実行の様子を見ていくことにしましょう。

(gdb) step      
32              if(turn - 1 > SIZE * SIZE) winflag = 3;
(gdb)                                                  
19          while(winflag == 0) {                      
(gdb) print winflag                                    
$2 = 0                                                 

今回はまだ抜けなくて大丈夫ですね。

(gdb) step
20              system("clear");
(gdb)                           
Detaching after fork from child process 9465.

21              view_board(&(banmen[0][0]), SIZE);
(gdb) until
Plase input place code (aa, ab, ..., oo):   a b c d e f g h i j k l m n o
A @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
B 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
C @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
D 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
E @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
F 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
G @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
H 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
I @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
J 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
K @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
L 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
M @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
N 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
O @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @

23              if(turn % 2 == 1) {
(gdb) print turn
$3 = 225
(gdb) step
24                  printf("It\'s White turn (0). \n");
(gdb)
It's White turn (0).
29              *get_place(&(banmen[0][0]), SIZE) = (turn % 2 == 1) ? '0' : '@';
(gdb)
get_place (
    data=0x8049c20 "@00@@0@0@0@0@0@0@@00@0@0@0@0@0@00@@0@0@0@0@0@0@@00@0@0@0@0@0@00@@0@0@0@0@0@0@@00@0@0@0@0@0@00@@0@0@0@0@0@0@@00@0@0@0@0@0@00@@0@0@0@0@0@0@@00@0@0@0@0@0@00@@0@0@0@0@0@0@@00@0@0@0@0@0@00@@0@0@0@0@0@0@@00"..., size=15)
    at gomoku.c:88
88          int correct_code = 1;
(gdb)
90           while(correct_code == 1) {
(gdb)
91              printf("Plase input place code (aa, ab, ..., oo): ");
(gdb)
92              if(!fgets(tmp, CHRMAX, stdin)) {
(gdb)
93                  printf("EOF Error.\n");
(gdb)

となってしまいます。
つまり、224の時点では見掛け上はまだ空きがありますが、その回に入力したもので盤面はいっぱいになります。
したがって turn が 225 になった時点で抜ければよいことになります。
(最初の print は turn++ をまだ実行していませんから。)


つまり、32行目を

	if(turn == SIZE * SIZE) winflag = 3;

と直せばよいのでは無いでしょうか。


これを修正すると

(gdb) run < list.csv
...(中略)...
Plase input place code (aa, ab, ..., oo):   a b c d e f g h i j k l m n o
A @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
B 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
C @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
D 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
E @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
F 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
G @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
H 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
I @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
J 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
K @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
L 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
M @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 @
N 0 @ @ 0 0 @ 0 @ 0 @ 0 @ 0 @ 0
O @ 0 0 @ @ 0 @ 0 @ 0 @ 0 @ 0 +

It's Black turn (@).
Plase input place code (aa, ab, ..., oo): Draw Game...

Program exited normally.
(gdb)

となり、oo に @ が入って引き分けですと表示されます。(実際にはもっと先に決着が付いているが、そこはまだ作成されていない。)


お疲れ様でした。これでひとまず gomoku.c は期待通りの動作をするプログラムになりました。


以下再現用の list.csv

aa
ba
ca
da
ea
fa
ga
ha
ia
ja
ka
la
ma
na
oa
ab
bb
cb
db
eb
fb
gb
hb
ib
jb
kb
lb
mb
nb
ob
ad
bd
cd
dd
ed
fd
gd
hd
id
jd
kd
ld
md
nd
od
ac
bc
cc
dc
ec
fc
gc
hc
ic
jc
kc
lc
mc
nc
oc
ae
be
ce
de
ee
fe
ge
he
ie
je
ke
le
me
ne
oe
af
bf
cf
df
ef
ff
gf
hf
if
jf
kf
lf
mf
nf
of
ag
bg
cg
dg
eg
fg
gg
hg
ig
jg
kg
lg
mg
ng
og
ah
bh
ch
dh
eh
fh
gh
hh
ih
jh
kh
lh
mh
nh
oh
ai
bi
ci
di
ei
fi
gi
hi
ii
ji
ki
li
mi
ni
oi
aj
bj
cj
dj
ej
fj
gj
hj
ij
jj
kj
lj
mj
nj
oj
ak
bk
ck
dk
ek
fk
gk
hk
ik
jk
kk
lk
mk
nk
ok
al
bl
cl
dl
el
fl
gl
hl
il
jl
kl
ll
ml
nl
ol
am
bm
cm
dm
em
fm
gm
hm
im
jm
km
lm
mm
nm
om
an
bn
cn
dn
en
fn
gn
hn
in
jn
kn
ln
mn
nn
on
ao
bo
co
do
eo
fo
go
ho
io
jo
ko
lo
mo
no
oo

*1:「fgets == 偽」で EOF である。

*2:Signal of Segmentation Violence の略