PostgreSQL の構造とソースツリー(3)
ソースコードの歩き方
デバッガを使ってソースコードを追っ掛けてみよう
PostgreSQL のような巨大なシステムでは、目でソースコードの流れを追うのは容易ではありません。 そこでお勧めなのが、gdb などのデバッガを使って実際のコードの実行の流れを追うことです。 デバッガというとしり込みする方もいらっしゃるかもしれませんが、単に実行の流れを追うだけなら非常に簡単です。
ただ、そのためには多少準備が必要で、PostgreSQL をデバッグシンボル付でコンパイルしておかなければなりません。通常 PostgreSQL を構築する際に configure に --enable-debug オプションを追加してください。 また、できれば src/Makefile.global を編集し、
CFLAGS = -O2 -Wall -Wmissing-prototypes -Wpointer-arith \ -Wdeclaration-after-statement -Wendif-labels -Wformat-security \ -fno-strict-aliasing -fwrapvのような行から "-O2" を削除して代わりに "-g" を付けます。
CFLAGS = -g -Wall -Wmissing-prototypes -Wpointer-arith \ -Wdeclaration-after-statement -Wendif-labels -Wformat-security \ -fno-strict-aliasing -fwrapvとします。 "-O2" はコンパイラの最適化オプションで、これを有効にすると、コードの実行順序が入れ替わってしまったりしてソースとの対応を追っ掛けるのが困難になることがあるので、外してしまってください。 もちろん、こうして作った PostgreSQL の実行バイナリは大きくかつ遅いものになるので、本番環境などに適用してはなりません。 あくまで勉強用あるいは解析用に使う PostgreSQL であると理解してください。
実際に gdb を使ってみよう
今、
select 1;という非常に単純な SELECT 文を例にとって、実際に gdb を使ってみましょう。 SELECT 文を実行すると、エグゼキュータの関数の一つである ExecSelect という関数で停止します。 そこに至るまでにどのような関数が呼ばれているか調べてみましょう。
まず PostgreSQL のスーパユーザでログインします。 私の環境では PostgreSQL をインストールしたユーザが t-ishii になっていますが、通常は postgres ユーザなどを使うと思いますので、適当に読み替えてください。
次に、psql でデータベースに接続します。その状態で ps コマンドで見ると、
$ ps x 3714 ? Ss 0:00 postgres: t-ishii test [local] idleのようなプロセスが見つかると思います。これがバックエンドプロセスです。他にもいろいろなユーザが PostgreSQL に接続しているとこのようなプロセスがたくさん表示されて分かりにくいですし、そういう意味でも実験用の環境を用意した方が良いと思います。
gdb を起動し、ps で表示されたプロセス番号のプロセスにアタッチします。
$ gdb postgres 3714 GNU gdb (GDB) 7.2 Copyright (C) 2010 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 "x86_64-vine-linux". For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>... Reading symbols from /usr/local/pgsql/bin/postgres...done. Attaching to program: /usr/local/pgsql/bin/postgres, process 3714 Reading symbols from /lib64/libdl.so.2...done. Loaded symbols for /lib64/libdl.so.2 Reading symbols from /lib64/libm.so.6...done. Loaded symbols for /lib64/libm.so.6 Reading symbols from /lib64/libc.so.6...done. Loaded symbols for /lib64/libc.so.6 Reading symbols from /lib64/ld-linux-x86-64.so.2...done. Loaded symbols for /lib64/ld-linux-x86-64.so.2 Reading symbols from /lib64/libnss_files.so.2...done. Loaded symbols for /lib64/libnss_files.so.2 0x00007fad266f82e2 in __libc_recv (fd=<value optimized out>, buf=0xbe9900, n=8192, flags=<value optimized out>) at ../sysdeps/unix/sysv/linux/x86_64/recv.c:30 30 ../sysdeps/unix/sysv/linux/x86_64/recv.c: そのようなファイルやディレクトリはありません. in ../sysdeps/unix/sysv/linux/x86_64/recv.c (gdb)(gdb) は gdb のプロンプトです。 この状態で gdb のコマンドを受け付けるようになっているので、ExecResult が呼ばれたら停止するように b コマンドを入力します。
(gdb) b ExecResult Breakpoint 1, ExecResult (node=0xd13eb0) at nodeResult.c:75 (gdb)psql を起動した端末から、SELECT 1; を入力し、バックエンドに実行を依頼します。 しかし、このままでは postgres プロセスが停止したままなので、psql は固まっているはずです。 実行を継続する "c" コマンドを gdb から入力します。 すると、ExecResult で停止します。
Continuing. Breakpoint 1, ExecResult (node=0xd13eb0) at nodeResult.c:75 75 econtext = node->ps.ps_ExprContext; (gdb)ExecSelect までに至る関数呼び出しの道筋は、bt コマンドで表示できます。
(gdb) bt #0 ExecResult (node=0xd13eb0) at nodeResult.c:75 #1 0x00000000005b92a4 in ExecProcNode (node=0xd13eb0) at execProcnode.c:367 #2 0x00000000005b71bb in ExecutePlan (estate=0xd13da0, planstate=0xd13eb0, operation=CMD_SELECT, sendTuples=1 '\001', numberTuples=0, direction=ForwardScanDirection, dest=0xcf9938) at execMain.c:1439 #3 0x00000000005b5835 in standard_ExecutorRun (queryDesc=0xc62820, direction=ForwardScanDirection, count=0) at execMain.c:313 #4 0x00000000005b5729 in ExecutorRun (queryDesc=0xc62820, direction=ForwardScanDirection, count=0) at execMain.c:261 #5 0x00000000006d2f79 in PortalRunSelect (portal=0xc60810, forward=1 '\001', count=0, dest=0xcf9938) at pquery.c:943 #6 0x00000000006d2c4e in PortalRun (portal=0xc60810, count=9223372036854775807, isTopLevel=1 '\001', dest=0xcf9938, altdest=0xcf9938, completionTag=0x7fffa4b0eeb0 "") at pquery.c:787 #7 0x00000000006cd135 in exec_simple_query (query_string=0xcf8420 "select 1;") at postgres.c:1018 #8 0x00000000006d1144 in PostgresMain (argc=2, argv=0xc42da0, username=0xc42c40 "t-ishii") at postgres.c:3926 #9 0x0000000000683ced in BackendRun (port=0xc65600) at postmaster.c:3600 #10 0x00000000006833dc in BackendStartup (port=0xc65600) at postmaster.c:3285 #11 0x0000000000680759 in ServerLoop () at postmaster.c:1454 #12 0x000000000067ff4d in PostmasterMain (argc=3, argv=0xc40e00) at postmaster.c:1115 #13 0x00000000005f7a39 in main (argc=3, argv=0xc40e00) at main.c:199 (gdb)この見方ですが、下の方が呼び出し元で、上の方が呼び出される方になってい ます。つまり、ExecResult を呼び出したのは ExecProcNode であり、ExecProcNode を呼び出したのは ExecutePlan であり、ExecutePlan を呼び出したのは ExecutorRun で...という風になっています。 とくに、真ん中辺りの #7 の行では、
#7 0x00000000006cd135 in exec_simple_query (query_string=0xcf8420 "select 1;") at postgres.c:1018のように表示され、いかにも SELECT 文を処理しているな、という感じがします:-) gdb の出力もじっくり見てみると、このように発見があります。
gdb はソースデバッガですから、ソースコードとの対応をすぐに見られるよう になっています。たとえば list コマンドで現在実行中の行の付近を見ることができます。
(gdb) list 70 TupleTableSlot *resultSlot; 71 PlanState *outerPlan; 72 ExprContext *econtext; 73 ExprDoneCond isDone; 74 75 econtext = node->ps.ps_ExprContext; 76 77 /* 78 * check constant qualifications like (2 > 1), if not already done 79 */
上位の関数への移動は、up コマンドです。 以下のように、list コマンドで実際に ExecSelect を呼び出していることが確認できます。
(gdb) up #1 0x00000000005b92a4 in ExecProcNode (node=0xd13eb0) at execProcnode.c:367 367 result = ExecResult((ResultState *) node); (gdb) list 362 { 363 /* 364 * control nodes 365 */ 366 case T_ResultState: 367 result = ExecResult((ResultState *) node); 368 break; 369 370 case T_ModifyTableState: 371 result = ExecModifyTable((ModifyTableState *) node);
逆に下位の関数への移動は down です。 up と down を組み合わせて、関数の呼び出し関係を調べることができます。
gdb の終了は quit です。
(gdb) quit Inferior 1 [process 3714] will be detached. Quit anyway? (y or n) y Detaching from program: /usr/local/pgsql/bin/postgres, process 3714
ここで gdb は終了しますが、バックエンドプロセスは終了しません。
tags を使って関数を定義した対応したファイルにジャンプ
さて、gdb を使って PostgreSQL の動きを調べることはできましたが、さすがに gdb の list コマンドだけでソースを追うのはつらいので、普通は emacs などのエディタも併用してソースを眺めつつ gdb を使うことになります。
もちろん、gdb モードを使ってもかまいせん。 このときに、たとえば「exec_simple_query」の定義を見たければ、emacs の tags コマンドを使って即座にその関数を定義している個所にジャンプできます。 tagsを使うためには、tags ファイルを作成する必要がありますが、PostgreSQL には tags ファイルを作成するスクリプトが付属しています。$ cd /usr/local/src/postgresql-9.1.1/src $ tools/make_etags (emacsを使う場合) $ tools/make_tags (viを使う場合)これで OK です。後は emacs の中で、「ESC-.」(ESC キーの後にピリオドを入力) で exec_simple_query と叩くか、exec_simple_query という文字列があるところにカーソルを持っていって ESC-. で exec_simple_query の定義されているソースファイルを開くことができます。
まとめ
PostgreSQLを完全に理解するためには、ソースコードを調べるのがもっとも効果的です。ソースコードを理解すれば、目的に応じて自分で必要な機能を追加したり、動作を変更することも可能になり、オープンソースならではのメリットを最大限に享受できるようになります。今回はPostgreSQLをソースレベルで理解するための導入として、PostgreSQL 9.1の全体構造と、ソースツリーを解説しました。また、ソースコードデバッガを使って、PostgreSQLの動作を追いかける方法も説明しました。今後は、PostgreSQLの構造を詳しく見て行きます。
次回は、パーサの構造を調べてみましょう。
(2011年11月15日公開)