prologの核心?

SEGV(2)

この表題、manのセクション2と誤解される恐れがあるな、とか思いながら。。。

改めてSEGVって何よと、疑問を持った。普段何気なく使ってるけどね。こう言うのは、チコちゃんに聞いてみる? いや、自分で調べろ。

kern/exec_subr.c を、何気なく見てたんだ。そしたら、こんなコメントが

*      handle vmcmd which specifies that a vnode should be mmap'd.
*      appropriate for handling demand-paged text and data segments.

こんなのや

* Note that the ep_ssize parameter must be set to be the current stack
* limit; this is adjusted in the body of execve() to yield the
* appropriate stack segment usage once the argument length is
* calculated.

こんなのも出てきた。SEGはsegments(区切りってかエリアぐらいの意訳でいいかな)の略語と気がついた。

それじゃ、Vは? もう想像するしかないけど、violation(違反、もっとえげつない意味も有るんで、気になったら調べてみて)のVから取ったものだろう。

簡単に言ってしまうと、エリア違反行為が発生しましただ。ピンとこない人が居れば、尖閣諸島のあれを思い出せばよい。

Unixでは、一般的にメモリーの下位から、テキストセグメント、データセグメント、空エリア、最上位にスタックセグメントが割り付けられて、それぞれの用途が決まっている。用途によって使える権利が設定される。その権利に違反するとSEGV発生だ。

テキストセグメントには、いわゆるプログラムのコードが置かれる。だから、権利は、読み取りと実行だけ。書き込もうとすると、それはプログラムの改変になるのでSEGVだ。

データセグメントは、プログラムのデータ(いわゆる変数)を置く。細かく言うと、初期値の定まったdataと、初期値が定まっていない場所だけ確保しとくbss領域に分かれる。権利は、勿論、読んだり書いたりだ。実行の権利は無い。若し実行の権利を与えてしまうと、プログラムをデータとして与えて実行出来てしまうんで、ウィルスソフトの天国になってしまう。 なお、このエリアは、プログラムを作成した時に、サイズが決まり、伸び縮みする事は無い。

次はスタックエリア。権利は読み書きだけ。実行は上記の理由で禁止されてる。スタックの性質上、伸び縮みするんで、エリアをはみ出したかの監視は必須だ(前回調べた)。

最後は、空きエリア。俗に言うヒープエリア。malloc系のライブラリィーを使って、動的にエリアを確保する。権利はデータセグメントと同じで、読み書きだけだ。

読み書き実行は、カーネルがプログラムをメモリーに配置した時に決定される。仮想記憶に付随するコントロール部へ設定する訳だ。 sys/mman.h

/*
 * Protections are chosen from these bits, or-ed together
 */
#define PROT_NONE       0x00    /* no permissions */
#define PROT_READ       0x01    /* pages can be read */
#define PROT_WRITE      0x02    /* pages can be written */
#define PROT_EXEC       0x04    /* pages can be executed */

この仮想記憶機構は、CPUと実メモリーの間に有るので、readとwriteは容易に検出出来る。 これは、昔散々メモリーICを使ったので、体で理解出来るよ。

でもメモリーICに、実行用の端子なんてなかったぞ。どういうからくりで、実行の権利の可否を決めてるの? 

CPUが命令を実行するには、命令が格納されてるアドレスからデータを読みだして来る必要がある。専門用語で、命令をfetchすると言う。次にCPUは命令を解読する必要がある、いわゆるdecodeだ。最後にそれを実行exeuteだ(足し算するとか、データを移動するとかね)。

だから、実行の一番最初の時点、fetchを監視すれば良い事になる。CPUにはメモリーから命令を取り出す為の番地指定レジスターが用意されてる。通称はPC(プログラムカウンター)と呼ばれるのがそれだ。 インテルの呼称では、PCの事をIR(instraction pointer)と言う。覚えておくとgdbする時、役立つぞ。

そう、PCの値を監視すれば、それが実行可能エリアを刺しているか分かる。多分、そういう事だね。生憎、仮想記憶のハードウェアの回路図は見た事ないけど。それは当然の事、私インテルの人ではありませんから。

ここまで分かったような口ぶりで書いてきたけど、sbclでネイティブコードにコンパイルされたlispは、どうやって実行されるの? そこまで飛躍しなくても、nprologのコンパイル機能を使って、オブジェクトファイル作成。それを後で読み込んで実行出来るよね。

これらは、どうやってSEGVを回避してるの? それが分かればウィルス作りたい放題だぞ。(をぃ、そんなにたきつけるな)

pp.pl coming !

お願いしたら、即やって来た。有り難い事です。

vbox$ ./npl -c example/pp.pl
N-Prolog Ver 1.51
?- pp.

: ?- inc(3,2).
no
: ?- inc(9,Out), write(Out), nl.
10
yes
: ?- assert(cry(cat,mew)).
yes
: ?- cry(cat,W), write(W).
mewyes
: ?- halt.
?-
   .
Syntax error not one operand with no operator
?-

問い合わせた結果は、自分でwriteを使って処理しなければならない。その場で述語を定義するには、assertを使う。

?- listing(cry).
cry(cat,mew).
yes

ppでの環境と言うかデータベースは、親のnplに相乗りしているので、しっかりとnpl側に記録されている。

predicate_property

上記のppを実現する要は、 predicate_property と言う述語。作者様によると、ISOのprologで要求してるものだそうだ。それで、避けていたとの事。私はISOとは違いますって主張も頷けます。ノスタルジアを冠してるprologですから。

本場のISOもとえ、swiplで確認してみる。

?- help(predicate_property).
predicate_property(:Head, ?Property)
    True when Head refers to a  predicate that  has property  Property. With
    sufficiently  instantiated Head,  predicate_property/2 tries  to resolve
    the predicate the same way as calling it would do: 
      :
    Property is one of:

    autoload(File)
        True if the predicate  can be  autoloaded from  the file  File. Like
        undefined, this property is not generated.
    built_in
     :

与えた述語が、どんな素性か調べられるんだな。素性は山ほど有ったぞ。

?- predicate_property(help,X).
X = interpreted .

?- predicate_property(edit,X).
X = interpreted ;
X = visible ;
X = static ;
X = imported_from(prolog_edit) ;
X = file('/usr/lib/swi-prolog/library/edit.pl') ;
X = line_count(89) ;
X = nodebug ;
X = number_of_clauses(3) ;
X = number_of_rules(3) ;
X = last_modified_generation(24175) ;
X = defined.

これは楽しい道具を発見してしまったわい。楽しいのはいいんだけど、これをそっくりnprologにインプリメントせいと要求されたら、きっと作者様は大いに悲しむだろう。だから、こっそりフェードアウトしたかったのだな。

作者様の弁護をします。pp.plを動かす為の最低限の実装になってます。今後swiplみたいに振る舞わせる計画はありません(ですよね)。

?- edit('/usr/lib/swi-prolog/library/help.pl').
% Waiting for editor ... sh: 1: emacs -nw: not found

% Editor returned failure; skipped make/0 to reload files

嫌われてしまった。環境変数にemacs -nwを設定してるんだから、素直にそれを使ってよね。やけに凝った事してないか? nprologは、素直に動くぞ。

先程、Youtubeで送られてきたInside nprologを閲覧。どうもありがとうございました。 やはり予想した通りでしたね。

それから、構造体cellのメンバーの話、lispをかじった人には衝撃的。

lispには、lisp-1とlisp-2の2種しかないけど、prologは、prolog-n な訳なんですね。使う人は兎も角として作る人は、発狂しそうだな。それを読む人も、心してかかれ、ですね。

inc/dec

前回だったか、マニュアルからinc/decを見つけて、双方向性を試してみた。一体どういう仕組みになってるの? 調べてみる。

builtin.c/b_inc

if(wide_variable_p(arg1))
    return(unify(arg1,minus(arg2,makeint(1))));
else if(wide_variable_p(arg2))
    return(unify(arg2,plus(arg1,makeint(1))));
else
    return(unify(arg2,plus(arg1,makeint(1))));

incもdecもロジックが一緒なので、incを取り上げてみた。要の部分は上記。それ以前の部分では、入念な引数のチェックを行ってる。インタープリターの性である。

inc(In,Out)した時、arg1=In, arg2=Outって形に引数が受け継がれてくる。arg1が変数だと、inc(In,5)のような問いと言う事になる。だから1を引いてものにunifyさせればいいんだな。 arg2が変数だと、arg1に1を足し算したものにunify。

unifyってのは、大雑把に言えば、マッチングの事だ。これがprologの核心部分かな? data.c/unifyに、内部で使うやつが定義されてる(他にも、ユーザー用のunifyが有るけど、最終的には、ここにやって来る。

if(nullp(x) && nullp(y))
    return(YES);
else if(variablep(x) && !variablep(y)){
    x1 = deref1(x);
    if(x1 == x){
        bindsym(x,y);
        return(YES);
    }
    else
        return(unify(x1,y));
}
:

長いので冒頭部分だけ。長い理由は、x,yの各種組み合わせ(変数か?定数か?文字列か?リストか?)で、愚直に処理してるからだ。

最初は、両者共nullの場合、文句なくunify成功。次は、Xが変数でYが変数でない(名前だな)場合の処理。まずxをunificationする。unificationってのは単一化の事だ。述語(ゴール)同士の変数に矛盾のないような代入をして見た目をそろえることだ。 その値が等しければ、名前を付けてから、成功。等しくない場合は、再帰を使って、とことん追及してく。正に、核心部分だな。

単一化(uni cation)

ユニフィケーション(wiki)

言語明瞭、意味不明瞭を、当分抜け出せそうにないな。なにか、もやもやしてる。

inc2

さて、incが分かった(つもり)ので、任意の数をincしたい。面倒そうなので、固定で+2してくれる奴を考える。そんなの簡単さ。

inc2(In,Out) :- inc(In,Bond), inc(Bond,Out).

2つのincをボンドで結合しただけである。ボンドの所はセメダインでも飯粒でも日本の工芸である漆でも良い。漆は、金継ぎに使われる重要な素材だ。

この間、この金継ぎにあこがれたYOUの事をやってた。壊れた瀬戸物を繋ぎ合わせるなんに、外国では信じられないとね。漆の上に金粉を施して、芸術にまで高めてしまう感性にも驚いていたな。

女房がハナタカさんになって、うるしの利用法を吹聴してた。慶長大判には、墨痕鮮やかに花押が書かれているけど、これって長い間に消えてしまわないのって疑問を呈した時だ。やはり、うるしが使われているから、大丈夫らしい。

話が逸れた。

?- inc2(3,Q).
Q = 5 .
yes
?- inc2(3,5).
yes
?- inc2(3,7).
no
?- inc2(Q,5).
Instantation error inc  #[v_1,v_3]

惜しいな。逆伝搬と言うか、AIかぶれ人風に言うと、バックプロパゲーションは機能しないで、エラーになってしまった。フォワードプロパゲーションは、ボンドのおかげで大丈夫。

じゃ、バックプロパゲーション用の述語も定義して、使い分ければ良いか。使い分けの判定をどうする? そうだ、出力端子に変数が指定されてるか調べればいいか。 if ~ then ~ else は、何となくこれだろうってのをやったね。

finc2(In,Out) :- inc(In,Bond), inc(Bond,Out).
rinc2(In,Out) :- inc(Bond,Out), inc(In,Bond).
inc2(In,Out) :- var(Out) -> finc2(In,Out); rinc2(In,Out).

こんなのが出来上がったけど、よりprologらしい解法は無いものだろうか?

heapdump

inc/decの観光をしてる時、data.cへ案内された。普段は立ち入らない所だ。いや、最初にnprologがやって来た時、物珍しさで、チラ見はしましたよ。そして、ああ、ここはlispエリアねって事で、オンデマンド(必要時参照)を決め込んでいたんだ。

が、案内された近辺に、面白い物を発見した。これはもう試してみる鹿。下記はgdbを使ってのheapdump使用記になります。

vbox$ gdb -q pl
Reading symbols from pl...done.
(gdb) r
Starting program: /home/sakae/bin/pl
N-Prolog Ver 1.51
?-                                ;;; Ctl-c to enter gdb
Program received signal SIGINT, Interrupt.
_thread_sys_read () at /tmp/-:3
3       /tmp/-: No such file or directory.
(gdb) p hp                        ;;; check heap pointer
$1 = 709
(gdb) c                           ;;; Go to npl
Continuing.
   inc(3,QRZ).
QRZ = 4 .
yes
?-                                ;;; Ct-c to enter gdb
Program received signal SIGINT, Interrupt.
_thread_sys_read () at /tmp/-:3
3       in /tmp/-
(gdb) p hp                        ;;; check heap pointer
$2 = 722
(gdb) call heapdump($1,$2)        ;;; call user function (with RETURN Key)
addr    F   TAG   CAR     CDR    BIND   NAME
0000709 FRE STRUCT 1073741827 0000000 0000000 (null)
0000710 FRE ATOM    -1000000001 -1000000001 0000002 QRZ
0000711 FRE STRUCT 0000710 0000000 0000000 (null)
0000712 FRE STRUCT 0000710 0000000 0000000 (null)
0000713 FRE STRUCT 0000710 0000000 0000000 (null)
0000714 FRE STRUCT 1073741827 0000713 0000000 (null)
0000715 FRE STRUCT 0000298 0000714 0000000 (null)
0000716 FRE STRUCT 0000715 0000000 0000000 (null)
0000717 FRE STRUCT 0000710 0000000 0000000 (null)
0000718 FRE STRUCT 0000710 0000000 0000000 (null)
0000719 FRE STRUCT 0000710 0000000 0000010 (null)
0000720 FRE STRUCT 0000710 0000000 0000000 (null)
0000721 FRE STRUCT 0000710 0000000 0000000 (null)
0000722 FRE EMP    0000000 0000723 0000000 (null)

Program received signal SIGSEGV, Segmentation fault.
0x19595d22 in heapdump (start=709, end=722) at data.c:1200
1200    }
The program being debugged was signaled while in a function called from GDB.
GDB remains in the frame where the signal was received.
To change this behavior use "set unwindonsignal on".
Evaluation of the expression containing the function
(heapdump) will be abandoned.
When the function is done executing, GDB will silently stop.

最後は、SEGVを喰らってアプリが落ちちゃうけど、アプリの残骸はそのまま残っている。従って、下記のように他殺体の検視は可能である。gdbからのご宣託のまじないを唱えても、SEGVの回避は出来なかった。

(gdb) call heapdump(400,415)
addr    F   TAG   CAR     CDR    BIND   NAME
0000400 FRE ATOM    425212463 0000000 0000007 tell
0000401 FRE STRUCT 0000400 0000317 0000000 (null)
0000402 FRE ATOM    425212818 0000000 0000007 telling
0000403 FRE STRUCT 0000402 0000269 0000000 (null)
0000404 FRE ATOM    425255599 0000000 0000007 time
0000405 FRE STRUCT 0000404 0000000 0000000 (null)
0000406 FRE ATOM    425212934 0000000 0000007 told
0000407 FRE STRUCT 0000406 0000365 0000000 (null)
0000408 FRE ATOM    425237225 0000000 0000007 trace
0000409 FRE STRUCT 0000408 0000347 0000000 (null)
0000410 FRE ATOM    425239145 0000000 0000007 var
0000411 FRE STRUCT 0000410 0000263 0000000 (null)
0000412 FRE ATOM    425199853 0000000 0000007 write
0000413 FRE STRUCT 0000412 0000279 0000000 (null)
0000414 FRE ATOM    425200625 0000000 0000007 writeq
0000415 FRE STRUCT 0000414 0000000 0000000 (null)

Program received signal SIGSEGV, Segmentation fault.
  :

殺されたはずの奴を(無理に)continuすると、一瞬は蘇生するが、今度は本当にお陀仏になり、coreと言う実体を残して亡くなっていまいましたとさ。

これを機会に楽しい機能が隠されていないか調べてみたら、パーサーにもdebug機能が埋め込まれていた。有る事を知っていれば、何時の日にか助けになるでしょう。作者様からの、隠れたプレゼントだな。それを見つけるのは、根気よく、畑の見回りをした人だけです。って、イソップ物語みたいだな。

(gdb) call heapdump (500,512)
'heapdump' has unknown return type; cast the call to its declared return type

heapdumpのvoidを止めてintを返すように変更。そして

(gdb) call (int)heapdump(300,310)
addr    F   TAG   CAR     CDR    BIND   NAME
0000300 FRE ATOM    4283632 0000000 0000007 instance
0000301 FRE STRUCT 0000300 0000295 0000000 (null)
0000302 FRE ATOM    4307088 0000000 0000007 integer
0000303 FRE STRUCT 0000302 0000185 0000000 (null)
0000304 FRE ATOM    4314560 0000000 0000007 keysort
0000305 FRE STRUCT 0000304 0000000 0000000 (null)
0000306 FRE ATOM    4283840 0000000 0000007 leash
0000307 FRE STRUCT 0000306 0000237 0000000 (null)
0000308 FRE ATOM    4284112 0000000 0000007 length
0000309 FRE STRUCT 0000308 0000267 0000000 (null)
0000310 FRE ATOM    4279440 0000000 0000007 listing
$3 = 1

これで文句を言われなくなった。これは、debianでの出来事。さて、この事実をOpenBSDにバックプロパゲーションしてみるかな。


This year's Index

Home