Inside newLISP (2)

日本がアメリカの機関から盗聴されてたって、ウィキリークスが暴露したもんだから、大いに 政府が怒って抗議したってのがニュースになってた。アメリカの某はちゃんと調査しますって、 神妙な声明を出していたけど、おまえらよーやるね。茶番劇を。

この騒動の発端を開いて一躍有名になったスノーデン氏。彼が使ってた匿名性に優れたOSと 言うのが、Linuxらしい。Tails OS ってデストロがそれだ。

オイラーも動かしてみよう。 IOSを落としてくる。落とした後、変なものを仕込まれていないけ検証するのが掟だけど、そこはそれ、 掟破りをして何か有っても、事故責任で。LIVE CDなので、そのままvmwareで検証出来る。

起動するとバーが左にバーと延びていく。これって昔お目にかかったぞ。fedora君だったかな。 暫くすると、Welcomeが出てくるので、オプション無しでlogin。

topのバーにたまねぎアイコンが並んでいた。最初は茶色だったけど、暫くしたら緑色に変色。 気になったのでマウスを近づけると、Tor接続完了したしたですって。そうか、Torはたまねぎか。 いくら剥いても芯が出てこないってのに掛けてるんだな。

unameしたら、Debian君だった。psしたら普通にgtkが動いてた。ibusも走っていたので、 設定したら、ちゃんと日本語入力が出来たよ。日本人にも強力者がいるな。頑張ってくださいませ。

Tor Browserの正体はfirefoxだった。匿名IPはいいんだけど、ブラウザのシグネチャから 足が付く事ないの?ああ、みんな同じだから、金太郎飴で見分けがつかんか。Torで匿名性が 担保されても、なお心配な人は、リバースプロキシーを噛ませるようですよ。そこまでするか ってのがありますがね。

心配性と言えば、Windoes10用に買ったPCに付いていた、カメラをガムテープで覆い、同様に 内蔵マイクにも蓋をしてしまった人が居ます。もうひと頑張りして、盗電(波)されても大丈夫な ように、電波暗室とかはいかが。それと、消費電力をモニターされると、PCでの実行内容が バレるそうですから、UPSという大容量コンデンサをかませて、平滑化しちゃいましょう。

特筆すべきは、ソフトウェアキーボード。これって、パソコンにキーロガーを仕込まれた時の 用心のためだな。ブラックスタイルで、なかなかかっこいい。オイラーもこういうの一つ欲しいぞ。

tails OSをUSBに焼くメニューが用意されてた。ISOファイルで950Mぐらいって事は、4Gのスティックで 事が足りるかな。ライブオフィスとかも入っているので、普通の人には普通のLinuxとして使う には十分だろう。足りない物が有ったら、apt-getで追加出来るだろうし。って、sudoどうすんねん? そんな時は、loginする時にオプションを選んで、passwordを設定しておくと、sudo出来る ようになるよ。なお、残念ながら、tailsOSをDISKに入れる事は出来ないようだ。DISKは猫の 首にぶら下げられないけどスティックなら、それが可能と判断されたんだな。

このOSは、ログとか痕跡が残らないように注意深くチューニングしてある。SWAPファイルから 足が付くのを恐れて、OpenBSDではSWAPのクリアなんて事をしてる。TailsOSも同様かな。 そして更に OSを終了すると、最後にメモリー内容を破壊するリーチンが走る。徹底的に痕跡を残さないようにしてるな。

ああ、新たまねぎと言うか、Tor Browser 5.0.1 is released ですね。わざわざTailsOSを入れなくても、Windows単独で利用出来ますよ。

FreeBSD統一

FreeBSDをVMWAREとVirtualBoxの両方に入れている。Diskの無駄だから、どちらかを消そう。 特に意味もなく、VMWAREの方を消す事にした。引越し前にVirtualBoxの方に入っていない物を 移す事にした。/usr/portsね。

一体どんだけDISKを喰ってるの? 900Mの大所帯。rsyncですっとコピー。こういう時には便利だなあ。 ちゃんと移ったか、VirtualBoxの方でmake updateしたら、何やらportsnapが動いて随分と 待たされたぞ。何してたんだろう?

それはさておき、

[sakae@fb10 /usr/ports]$ ls -lh INDEX-*
-rw-r--r--  1 root  wheel    31M Aug 18 06:29 INDEX-10
-rw-r--r--  1 root  wheel    31M Aug 18 06:29 INDEX-8
-rw-r--r--  1 root  wheel    31M Aug 18 06:29 INDEX-9

インデックスファイルが、ご丁寧にいろいろな版用に出来上がっている。しかも大きい。これって無駄じゃねぇ! どうしてくれようと、Makefileを眺めたら、portsnapが関係者みたいだった。んで、man経由で /etc/portsnap.confを見ろとな。

# REFUSE arabic chinese french german hebrew hungarian japanese
# REFUSE korean polish portuguese russian ukrainian vietnamese

# List of INDEX files to build and the DESCRIBE file to use for each
INDEX INDEX-8 DESCRIBE.8
INDEX INDEX-9 DESCRIBE.9
INDEX INDEX-10 DESCRIBE.10

色々とコメントする所が有りますなあ。

/var/db/portsnap/filesの下が巨大だなあ(約150M)。これは、 随時更新される物なの。要観察対象だなあ。-- 観察結果は随時更新がかかってました。 消しちゃ駄目って事ですな。今度、中身を暴いてみよっと。

VirtualBoxでGuest Additionはどうしたらいいの? VirtualBoxに付属の ISOはLinux用しかないし。。。

FreeBSD 10.0にVirtualBox-Guest Additionsを入れて使う

X Windowの解像度変更は、こちらで良いのかな。

VirtualBoxに入れたLinuxの解像度をワイドに変更

xrandr -s 800x600

Linuxでは当たり前に設定出来る、モニターの解像度設定。FreeBSDでは、裸のコマンドを叩けとな。

紐解くにあたって

Inside newLISPするにあたって、全体を俯瞰しとこうと前回はcflowしたけど、今回は、 木を見て森を見ずにならないように、別の観点から。だって、作者自身による案内図が ありましたから。

[sakae@fb10 ~/newlisp-10.6.2]$ fgrep -n '* ---' newlisp.c
144:/* --------------------- globals -------------------------------------- */
1330:/* -------------------------- initialization -------------------- */
1438:/* ------------------------- evaluate s-expression --------------------- */
1720:/* -------------------- evaluate lambda function ----------------------- */
1915:/* -------------- list/cell creation/deletion routines ---------------- */
2075:/* ------------------------ creating and freeing cells ------------------- */
2249: * --------------- cell / memory allocation and deallocation -------------
2383:/* -------------------------- I/O routines ------------------------------ */
2925:/* -------------------------- error handling --------------------------- */
3133:/* --------------------------- load source file ------------------------- */
3241: * -------------------------- parse / compile -----------------------------
3752:/* -------------------------- utilities ------------------------------------ */
3767:/* -------------------------- functions to get parameters ------------------ */
4204:/* ------------------------------- core predicates ------------------------ */
5409:/* -------------------------- program flow  and logical ------------------ */
6025:/* ------------------------------ I / O --------------------------------- */
6234:/* ----------------------- copy a context with 'new' -------------- */
6460:/* ------------------------------ system ------------------------------ */

replは何処だ?

上の索引はなかなかだな。ボトムアップで、utilities あたりから見てく? それとも main から見てく? ちなみにmainは、666行あたりから始まってましたよ。mainからだと、上から目線の トップダウンだな。まあ、好きにしてくれ。

で、オイラーは、lisperの端くれ(と自認)と思うので、replを探して、print、read、eval の順で見てくかなあ。これ、易しい順番ね。人によっては、read,print,evalって風に、作る 順番に倣う人がいるけどね。

primes.hからなら

    /* -------------- I/O ------------------ */
    {"print", p_print, 0},
    {"read", p_readBuffer, 0x400},
    /* --------- core ------------------ */
    {"eval", p_eval, 0},

こんな区分けになってた。勿論、readと一口に言っても、read-stringみたいに特化した やつはわんさかとあったけどね。

ユーザーレベルでの区分けでは

[sakae@fb10 ~/newlisp-10.6.2]$ fgrep -n '* --' primes.h
26:    /* --------- core ------------------ */
129:    /* --------- bit ops --------------- */
137:    /* --------- math and float ------- */
220:    /* ------------ string ops ------------- */
271:    /* -------------- I/O ------------------ */
331:    /* ---------  system --------- */
392:    /* --------- predicates ------ */
425:    /* ------------ date and time --------- */
437:    /* ------------ net working ------------ */

こんな感じでした。残念ながらソースの何処を家捜ししても、replって案内は見つからず。 思えば、replって特殊なやつだ。普通に使う時は、スクリプトを書いておいて、それで一仕事 させるってのが多いからね。

普段お世話になってる、/bin/bashも、shellスクリプトで走らせるのとrepl的に、unixへの 橋渡し的に使うのとあるけど、きっと、bashのソース読んでて、replって言葉は出てこない だろうね。(そりゃそうだ、replってlisp業界の専門用語だもの)

強引にmainの中のrepl風な所を探してみると(ってか、今までのgdbセッションの経験から)

   895      while (TRUE) {
   896          cleanupResults(resultStack);
   897          if (isTTY) {
   898              cmd = getCommandLine(FALSE, NULL);
   899              executeCommandLine(cmd, OUT_CONSOLE, &cmdStream);
   900              free(cmd);
   901              continue;
   902          }

ユーザーが端末のキーボードを叩いてライブセッションしてる時って、isTTYがtrueになってる。 898行目で、一行受け取って、それを実行。これをloopingする。lisp内のreplだ。

このループ地獄を抜け出す通常の方法は、(exit)を実行する事。この命令は、p_exitにデコードされて、

(gdb)
6767            result = 0;
(gdb)
6774        exit(result);

こういう終わり方をする。だから、

> (exit 3)
[sakae@fb10 ~/newlisp-10.6.2]$ echo $?
3

こういうダイイングメッセージを残せるのね。マニュアル読まなくても、こういう事が 現地調査で分かるんで楽しい。現場百篇とよく言われる由縁だ。でも、この終わり方、 生活環境のメモリーを残したままだね。後始末はOSさんにお任せってのも有りかな。

潜る

夏ですから、潜りましょ。独身の頃、川崎に住んでいた事があり、同僚に連れられてよく、 三浦半島の先端、三崎へ行っていたなあ。アクアラングを付けての本格的なやつじゃなくて、 手軽に出来る素潜りね。

魚が沢山泳いでいて、海の中は別世界でしたよ。まぐろ丼を食べて、帰りには必ず、特製の 塩辛を買って帰ったなあ。懐具合がよいと、関内で途中下車して中華街へGO。 買った塩辛を肴にして、冷酒をチビチビ。楽しかったな、昔に戻りたいぞ。

今はしょうがないから、ソースの海に潜りましょ。これなら、お金もかからず、何処でも 実行可能。体力いらずで安全。

上で見つけておいたreplは、最上位のそれだ。潜ってみて分かったんだけど、本質的なreplは、 evaluateStreamだ。ここに至る道筋は、

(gdb) bt
#0  evaluateStream (stream=0xcfbdcd40, outDevice=2, flag=0) at newlisp.c:1259
#1  0x182075cc in executeCommandLine (command=0x7eccc0f0 "0x12345", outDevice=2, cmdStream=0xcfbdd1ac) at newlisp.c:1227
#2  0x18208166 in main (argc=1, argv=0xcfbdd634) at newlisp.c:899

getCommandLineで1行入力を受け取り、executeCommandLineの最後の方で、文字列をsteramに 変換した後、evaluateStreamに飛び込んでくる。

上の例は入力に0x12345を与えた場合だ。emacsからgdbを使ってると、ソースが表示される んだけど、ここにソースを載せるには、行番号が付いていた方がいいかな。

emacsに行番号を表示って、余り流行らないみたいだけど、簡単に行番号を付加出来るのね。 検索してみて初めて知ったよ。 M-x linum-mode もう一回、同キーシーケンスを叩けば 非表示になる。

1258evaluateStream(STREAM * stream, UINT outDevice, int flag)
1259=>
1260    CELL           *program;
1261    CELL           *eval = nilCell;
1262    CELL           *xlate;
1263    UINT           *resultIdxSave = resultStackIdx;
1264    int             result = TRUE;
1265
1266    while (result) {
1267        pushResult(program = getCell(CELL_QUOTE));
1268        result = compileExpression(stream, program);
             :
1274        if (result) {
1275            if (flag && eval != nilCell)
1276                deleteList(eval);
1277            eval = evaluateExpression((CELL *) program->contents);
1278            if (outDevice != 0 && !evalSilent) {
1279                printCell(eval, TRUE, outDevice);
1280                varPrintf(outDevice, "\n");

compileExpressionって大仰な名前が付いているけど、文字列をnewLISPの内部表現のS式に 変換してる。それが成功失敗かはresultに載って返ってくる。成功したなら、evaluateExpressionを 呼んで、式を評価。結果をprintCellで表示って流れだ。

read 代わりの compileExpression

少し岩の隅をつついてみる。まずは、read(もどき)ね。

3241 * -------------------------- parse / compile -----------------------------
3242 *
3243 * Takes source in a string stream and and envelope cell and compiles newLISP
3244 * source into an internal LISP cell structure tree. The tree can be
3245 * decompiled to source at any time and is processed by the
3246 * evaluateExpression() function.
3247 *
3248 */
3249
3250int
3251compileExpression(STREAM * stream, CELL * cell)
        :
3269    switch (getToken(stream, token, &tklen)) {
3270    case TKN_ERROR:
         :
3275    case TKN_EMPTY:
          :
3280    case TKN_CHARACTER:
           :
3284    case TKN_HEX:
3285        newCell = stuffInteger64((INT64) strtoull(token, NULL, 0));
3286        break;
            :
3421    case TKN_RIGHT_PAR:
3422        if (parStackCounter == 0)
3423            errorMissingPar(stream);
3424        --parStackCounter;
3425        cell->next = nilCell;
3426        return (TRUE);
            :
3434        linkCell(cell, newCell, listFlag);

こんな具合に、丁寧に場合分けして、必要なS式を作り出してました。なお、linkCellは、 リンクするか、newCellの値をcellのcontentsに埋め込むかの処理を(listFlagによって) コントロールしてる。この場合は、値を埋め込む処理が行われる。

S式が文字列の場合は、tokenで文字列って事を検知され、下記が呼ばれる。

2008stuffStringN(char *string, int len)
2009{
2010    CELL           *cell;
2011
2012=>  cell = getCell(CELL_STRING);
2013    cell->aux = len + 1;
2014    cell->contents = (UINT) allocMemory((UINT) cell->aux);
2015    memcpy((void *) cell->contents, string, len);
2016    *(char *) (cell->contents + len) = 0;
2017    return (cell);
2018}

文字列用のCell(tagが文字列にセットされた)を用意し、必要事項をCellに書き込み、 文字列を確保したエリアにコピーし、C語の文字列って事で、nullを書き込んで体裁を 整えているんだな。 なお、allocMemoryは、mallocへの薄いラッパーだ。

そうだ、(男だったら)大事なシンボルの事を忘れておった。どう扱われるか? 例に登録済みシンボルを指定して追ってみると、compileExpressionのシンボルラベルの中に落ち込み

3359            newCell = getCell(CELL_SYMBOL);
 :
3365=>          newCell->contents = (UINT) translateCreateSymbol(
3366                                        token, CELL_NIL, currentContext, 0);
3367        break;

で、処理が行われます。translateCreateSymbolは、前回に赤黒木で調べてました。 そう、シンボルの検索兼挿入ですな。シンボルって一種のデータベース。これ以降、 lispはデータベースのポインターを持ち回りながら、演算を進めていきます。

printの代わりのprintCell

cellのタイプを見て、必要なルーチンを呼び出している。nilとかtrueはそういう文字列を 出力してる。 プリミティブの場合は、@の右側は関数のアドレスになるんだな。

リストの場合は、printExpressionを呼び出してる。 そして、その中でprintCellを呼び出し。こういうの相互再帰って言うんだったな。 後なnilCellじゃない間、cdrを取りながら、エレメントを表示。

これたのルーチンに埋め込まれている、debugPrintCellは、debugの時の色付き表示をする 目印。

ユーザーが定義した関数とかマクロ類は、printLambdaで特別印字してる。例えば、"(define ("ってのを 表示した後、リストをトラバースするとか。複雑に入り組んでいるけど、難しい事は無い。

Tips

プロセスをgdbにアタッチするには、プロセス番号が必要。今まで、newlispを起動した後、 別端末でps aって叩いてpidを調べ、それからgdbを起動してた。ちと面倒。

[ob: ~]$ newlisp &
[1] 6073
[ob: ~]$ newLISP v.10.6.2 32-bit on BSD IPv4/6 UTF-8, options: newlisp -h


[1] + Stopped (tty output) newlisp
[ob: ~]$ fg
newlisp
>

newlispを起動する時、裏側へ行くように指定すると、pidを報告してくれるのね。今まで 気が付かなかったぞ。これなら、プロセスリストからnewlispを目grepする必要もなくて 楽チン。後は表に持ってきておけばいいんだ。

/bin/ksh

上記の裏へjobを回した時に報告されるpid、どういう風に実装されてるの? 紐解いてみよう。 gdbで追うのが楽そう。が、備え付けのkshはstripされてる。追えないじゃん。

ソースのREADMEを見ろ。 pdksh - the Public Domain Korn Shellにソースがあるよ。 そーすか。

コンパイルして走らせた。pidを知った上で、gdbで暴力的に割り込み。btして一番上の階層に上がり、 それらしい所にbpを仕掛ける。そして追いかけっこ。

(gdb) bt
#0  exchild (t=0x7fed1978, flags=6, close_fd=-1) at jobs.c:431
#1  0x18702d66 in execute (t=0x7fed1978, flags=6) at exec.c:97
#2  0x18703974 in execute (t=0x7fed1878, flags=0) at exec.c:268
#3  0x1870db8f in shell (s=0x837929a8, toplevel=1) at main.c:616
#4  0x1870e899 in main (argc=1, argv=0xcfbbf594) at main.c:429

ここで、pidを表示してたよ。

 665                if (flags & XBGND) {
 666                        j_set_async(j);
 667                        if (Flag(FTALKING)) {
 668                                shf_fprintf(shl_out, "[%d]", j->job);
 669                                for (p = j->proc_list; p; p = p->next)
 670=>                                      shf_fprintf(shl_out, " %d", p->pid);
 671                                shf_putchar('\n', shl_out);
 672                                shf_flush(shl_out);
 673                        }

別端末の表示は

[ob: pdksh-5.2.14]$ ./ksh &
[1] 29523
[ob: pdksh-5.2.14]$ fg
./ksh
$ newlisp &
newLISP v.10.6.2 32-bit on BSD IPv4/6 UTF-8, options: newlisp -h

[1] 6197
[1] + Stopped (tty output) newlisp
$

先ほどと違って、pidがnewlispのバナーの後に表示されてる。ゆっくり追い駆けていたから、 小供の方が先に進んじゃって、親が遅れたんだな。納得。

ああ、bgへ追いやる起動法でpidを報告するのは一部のshellではサポートされてません。 そういう時は、機能満載のzshでも入れてください。(動作確認はしてないけどね)