9front (3)

寒いんで、家から15分の天然かけ流し温泉へ行ってきた。頭寒足暖でいくらでも入っていられそう。そのうちにゆだってしまわないか心配。

で、帰りに併設されてる地元産みやげをみたら、何処か遠くの村とコラボレーションして、クルミを売っていた。試供品と言うか試食品が置いてあったけど、クルミ割り器とかトンカチは無かったなあ。このあたりの人は、殻をかみ砕く丈夫な顎をしてるのかしらん。 家で食べてみようと、控え目に5個貰ってきた。 子供の頃は、形の良い鬼胡桃を油で磨いて、どうだカッコイイだろうとなんてやったものだけど、大人になると食い気だけになるのね。

やや、銀杏も置いてあるぞ。ポップを見ると、このあたりでは由緒ある神社の境内産。なんにでも御利益があるって言ってる神社。交通安全、商売繫盛、家内安全、安産保障、受験合格、くじ運抜群。まあ、言うのはただですからね。

天然天日干しらしです。結構お高くて、その値段ならオイラーが調達すれば4倍の分量を手に 入れられるぞ。ブランド品だし、適正価格を知らない人がかもになるんだろうね。

オイラーが買って来るやつは処理に難があり、ちとウンコ臭い。件のブランド品はそんな事は 無いんだろうね。まあ、フライパンで殻に焦げ目が付くぐらいに煎ってしまえば、そんなの関係ねぇーー。

トンカチで殻を割って、中身を取り出す。そして冷凍。冬の保存食。食べる時は、冷凍したままの物を、少量の油を馴染ませたフライパンで塩を一振りしてあぶればおk。

殻を割った時に取り切れなかった薄皮も綺麗に剥がれる。酒のつまみに最適。冬の楽しみですなあ。

listen1改造

9frontを始めた時、運よくunix側から9frontにセッションログをコピペ出来る方法を知った。 が、ちょっと不満が有るんだ。

不満その1として、プロンプトが表示されない。その2は、投入したコマンドがエラーを出しても、それが残らない。エラー報告は、9frontのターミナル上に出て来る。それは正に絵なので、 unix側のCUI端末には切り貼り出来ないんだ。

この2点を何とかしたいな。で、冷静に考えると、プロンプトもエラーもstderrに出てるんだろう。それがパイプに流れ込まないものだから、unix側に届かない。エラー情報は9frontのターミナル上に置いてきぼりされているんだろう。

以上の考察より、パイプの接続部分に問題があると結論付けられる。その施工主はどのコマンドだ? 悩む事なかれ、使ってるコマンドは、listen1しか無い。じっと目視検査。

term% p /sys/src/cmd/aux/listen1.c
                         :
                        bind(data, "/dev/cons", MREPL);
                        dup(fd, 0);
                        dup(fd, 1);
                        /* dup(fd, 2); keep stderr */
                        close(fd);
                        exec(argv[1], argv+1);

forkした後で、こんな部分が出て切る。パイプを複製してるんだけど、stderrの部分は置き去りにして複製していない。だから、パイプに流れないとな。

このコメントを外しておいてコンパイル。出来たものを$home/bin/mylisten1として置いて おく。後は、このコマンドを使うようにすれば(プロンプトも修正)、

deb9:~$ nc xxx.xxx.xxx.xxx 2323
term% pwd
/usr/glenda
term% p hogefuga
p: can't open hogefuga - 'hogefuga' directory entry not found
term% exit

deb9:~$

こんな具合に、ちゃんとプロンプトを表示するし、エラーもきちんと出てきた。これにて一件落着。

term% cat bin/rc/riostart
#!/bin/rc
window 864,1,1024,119 stats -lmisce
window -miny 130 bin/mytelnetd

なお、9front起動時からmytelnetdを動かしておきたかったので、ちょいとダエモン君(?)が 走るようにしておいた。

term% ps -a
  :
glenda          474    0:00   0:00      252K Sleep    stats -lmisce
glenda          484    0:00   0:00      144K Await    rc -c '/bin/window -x bin/mytelnetd '
glenda          486    0:00   0:00      148K Await    mytelnetd ./bin/mytelnetd
glenda          493    0:00   0:00       44K Open     /usr/glenda/bin/mylisten1 -t tcp!*!2323 /usr/glenda/bin/chat1
glenda          494    0:00   0:00        0K Wakeme   #I0tcpack
glenda          546    0:00   0:00      148K Await    chat1 /usr/glenda/bin/chat1
glenda          548    0:00   0:00       36K Pread    tr -d \015
glenda          549    0:00   0:00      152K Await    /bin/rc -i
glenda          555    0:00   0:00        0K Wakeme   closeproc

netmkaddr, dial

前回やったftpの改造時にポート番号の指定が問題になった。担当はnetmkaddrだったので ソースの在処を探してみた。

term% bin/find -D /sys/src | xargs grep netmkaddr
 :
/sys/src/cmd/cfs/cfs.c:         s.fd[0] = dial(netmkaddr(server, 0, "9fs"), 0, 0, 0);
/sys/src/cmd/cifs/cifs.c:       if((addr = netmkaddr(host, "tcp", "cifs")) == nil)
/sys/src/cmd/cifs/netbios.c:    if((addr = netmkaddr(host, "udp", "137")) == nil)
/sys/src/cmd/cifs/netbios.c:    if((addr = netmkaddr(addr, "tcp", "139")) == nil ||
/sys/src/cmd/cifs/ping.c:       if((fd = dial(netmkaddr(host, "icmp", "1"), 0, 0, 0)) == -1)
/sys/src/libc/port/netmkaddr.c:netmkaddr(char *linear, char *defnet, char *defsrv)

ポート番号をマジック番号で指定してるかと思うと、cifsみたいに名前で呼んでたりして、この 使い分けは何だと思っちゃう。とにかくソースをミレー。

netmkaddr(char *linear, char *defnet, char *defsrv)
{
        static char addr[256];
        char *cp;

        /*
         *  dump network name
         */
         :
                        if(defsrv)
                                snprint(addr, sizeof(addr), "%s!%s!%s", defnet,
                                        linear, defsrv);
                        else
                                snprint(addr, sizeof(addr), "%s!%s", defnet,
                                        linear);

なんの事はない。tcp!10.0.2.15!ftp こんな風に正規化してるだけだった。そうなると、本気で見なければいけないのは、dialさんだ。さすがATTは電話会社だけあるな。ダイアルって用語を使ってるよ。プッシュホンより古風な名前なのね。

そしてダイアルして回線が繋がったら、用を済ませ、最後は回線を切断。その関数がhangupとは どこまで電話用語なの。ああ、電話用語と言うとアーランってのを思い出したぞ。あのスウェーデンの電話会社が電子交換機用に落ちない言語を開発してたな。erlangって奴。これ、1時間当たり、どれだけお話し中になってるかを示すものだった。

話を戻して、 ソースはどこだ。manに書いてあったぞ。/sys/src/libc/9sys/dial.c 大事なものだから見ておけっていう親心? そして、素直に関数名がファイル名になってるっぽいな。manに出てこないような関数は、まあ下請けだから無視していいよって事かな。

term% sig netmkaddr dial
        char* netmkaddr(char *addr, char *defnet, char *defservice)
        int dial(char *addr, char *local, char *dir, int *cfdp)

念のために、型を確認。次に、acidにかけられるように、超簡単なプログラムを作成。 最初、u.hなんて要らないと思って省いたら、libc.hの所で盛大にエラーが出て来た。 セットで使えと言う事ね。

term% cat test.c
#include <u.h>
#include <libc.h>

main(){
        dial("tcp!*!ftp", 0, 0, 0);
        return 0;
}

出来立てのほやほやを試験台に載せてみる。

term% acid 8.out
8.out:386 plan 9 executable
/sys/lib/acid/port
/sys/lib/acid/386
acid: new()
703: system call        _main   SUBL    $0x48,SP
703: breakpoint main+0x3        MOVL    $.string(SB),AX
acid: bpset(dial)
acid: bpset(csdial)
acid: bpset(call)
acid: bpset(_dial_string_parse)
acid: bptab()
        0x000010b6 dial  SUBL   $0x12c,SP
        0x00001263 csdial  SUBL $0x230,SP
        0x00001513 call  SUBL   $0x32c,SP
        0x00001844 _dial_string_parse  SUBL     $0x1c,SP

dial.cの関数全てに網を貼ってから、その所在地を確認

acid: cont()
703: breakpoint dial    SUBL    $0x12c,SP
acid: cont()
703: breakpoint dial+0x6        MOVL    local+0x4(FP),AX
703: breakpoint _dial_string_parse      SUBL    $0x1c,SP
acid: cont()
703: breakpoint _dial_string_parse+0x3  MOVL    ds+0x4(FP),AX
703: breakpoint csdial  SUBL    $0x230,SP
acid: cont()
703: breakpoint csdial+0x6      LEAL    buf+0x1a0(SP),AX
703: breakpoint call    SUBL    $0x32c,SP

そろりそろりと進めてみた。

acid: stk()
call(clone=0xdfffed8c,ds=0xdfffeeb4,dest=0xdfffed9b)+0x0 /sys/src/libc/9sys/dial.c:127
csdial(ds=0xdfffeeb4)+0x1f8 /sys/src/libc/9sys/dial.c:109
dial(local=0x0,dir=0x0,cfdp=0x0,dest=0x782c)+0x85 /sys/src/libc/9sys/dial.c:50
main()+0x28 /usr/glenda/tmp/test.c:6
_main+0x31 /sys/src/libc/386/main9.s:16

現在の呼び出し履歴

acid: asm(0x00001513)
call 0x00001513 SUBL    $0x32c,SP
call+0x1 0x00001514     INB     DX,AL
call+0x2 0x00001515     SUBB    $0x3,AL
call+0x4 0x00001517     ADDB    AL,0x0(AX)
call+0x6 0x00001519     MOVL    clone+0x0(FP),DX
call+0xd 0x00001520     MOVBSX  0x0(DX),AX
call+0x10 0x00001523    CMPL    AX,$0x2f
call+0x13 0x00001526    JEQ     call+0x304(SB)
 :

call関数のアセンブルリスト。目が眩むな。C語のソースをemacsで開いた方が得策。 こうしていても埒が明かないので、別の手をやる。

term% acid -l truss 8.out
8.out:386 plan 9 executable
/sys/lib/acid/port
/sys/lib/acid/386
/sys/lib/acid/truss
acid: new()
acid: truss()
open("/net/cs", 2)
        return value: 3
pwrite(3, "tcp!*!ftp", 9, 4294967295)
        return value: 9
seek(0x000079fc, 3, 0, 0)
        return value: 0
pread(3, 0xdfffed8c, 127, 4294967295)
        return value: 17
        data: "/net/tcp/clone 21"
open("/net/tcp/clone", 2)
        return value: 4
pread(4, 0xdfffe9dc, 255, 4294967295)
        return value: 1
        data: "2"
pwrite(4, "connect 21", 10, 4294967295)
        return value: -1
close(4)
        return value: 0
errstr("...", 128)
        return value: 0
        data: "malformed address"
pread(3, 0xdfffed8c, 127, 4294967295)
        return value: 0
        data: ""
close(3)
        return value: 0
errstr("malformed addresstot open...", 128)
        return value: 0
        data: "(null)"
  :

なかなかやるな、acid。ちゃんとしたマニュアルを(斜めって)読んでみるかな。 Acid Manual

acid

で、得た知見を少し試してみる。上の実験だと最初のsyscallで、ネットをオープンし、その ファイルにネット情報を書き込んで、ファイルポインターを戻す。そして読み出すと、どういう風にネットをオープンすべきか教えてくれるように見受けられる。

じゃ、ftpの代わりにhttpでやるとどうなる?

term% acid -l truss 8.out
 :
acid: new()
acid: truss()
open("/net/cs", 2)
        return value: 3
pwrite(3, "tcp!*!http", 10, 4294967295)
        return value: 10
seek(0x000079fc, 3, 0, 0)
        return value: 0
pread(3, 0xdfffed8c, 127, 4294967295)
        return value: 17
        data: "/net/tcp/clone 80"

ふむ、思った通り、名前解決ならぬport解決が行われている。そんじゃ、openとかのOSとの境界まで迫れるかやってみる。

acid: new()
acid: bpset(open)
acid: bpset(pread)
acid: defn hoge() { cont(); stk(); }
acid: hoge()
700: breakpoint open    MOVL    $0xe,AX
open()+0x0 /sys/src/libc/9syscall/open.s:3
csdial(ds=0xdfffeeb4)+0x50 /sys/src/libc/9sys/dial.c:79
dial(local=0x0,dir=0x0,cfdp=0x0,dest=0x782c)+0x85 /sys/src/libc/9sys/dial.c:50
main()+0x28 /usr/glenda/tmp/test.c:6
_main+0x31 /sys/src/libc/386/main9.s:16

今回マニュアルを見てしったんだけど、自分で関数を定義出来るのね。gdbがpythonを導入して、ごちゃごちゃ出来るようになる前に、同様な事を実現してたのね。

acid: src(open)
no source for /sys/src/libc/9syscall/open.s
acid: asm(open)
open 0x00001d1f MOVL    $0xe,AX
open+0x1 0x00001d20     PUSHL   CS
open+0x2 0x00001d21     ADDB    AL,0x0(AX)
open+0x4 0x00001d23     ADDB    CL,CH
open+0x6 0x00001d25     INCL    AX
open+0x7 0x00001d26     RET
errstr 0x00001d27       MOVL    $0x29,AX
acid: casm()
errstr+0x5 0x00001d2c   INTB    $0x40
errstr+0x7 0x00001d2e   RET
close 0x00001d2f        MOVL    $0x4,AX

残念ながらアセンブラのコードは入っていない。でもコード上で翻訳。casm()は、asm()の 続きを見る時に使うとな。

acid: hoge()
700: breakpoint open+0x5        INTB    $0x40
700: breakpoint pread   MOVL    $0x32,AX
pread()+0x0 /sys/src/libc/9syscall/pread.s:3
read(fd=0x3,buf=0xdfffed8c,n=0x7f)+0x2f /sys/src/libc/9sys/read.c:7
csdial(ds=0xdfffeeb4)+0x1aa /sys/src/libc/9sys/dial.c:102
dial(local=0x0,dir=0x0,cfdp=0x0,dest=0x782c)+0x85 /sys/src/libc/9sys/dial.c:50
main()+0x28 /usr/glenda/tmp/test.c:6
_main+0x31 /sys/src/libc/386/main9.s:16
acid: src(read+0x2f)
/sys/src/libc/9sys/read.c:7
 2      #include        <libc.h>
 3
 4      long
 5      read(int fd, void *buf, long n)
 6      {
>7              return pread(fd, buf, n, -1LL);
 8      }
acid: func()
700: breakpoint pread+0x5       INTB    $0x40
700: breakpoint pread+0x7       RET
700: breakpoint read+0x2f       ADDL    $0x18,SP
acid: mem(0xdfffed8c, "s")
/net/tcp/clone 80

preadはOSとの境界なんで、まだデータは来ていない。(読みだされていない)そこで、一つ上の関数のソースを眺めてみる。繋ぎの関数なんだな。func()で出口付近まで進める。そしておいて、bufに相当するメモリーエリアを、文字列と見做してダンプしてみた。

こういうdebuggerの使い方も有りなんですな。

p9p のあれ

9frontばかりと戯れていてもしょうがないんで、p9p(Plan9port)ではどうなってるか当たってみる。

なんと、dial(1)が有ったぞ。その核心部分が下記。指定されたネットアドレスに接続を試みる。成功したらforkして、stdinからの入力をネットワーク側に流すとな。それをEOFになるまで繰り返す。超簡単な、p9p版のncなんだな。

        if((fd = dial(argv[0], nil, nil, nil)) < 0)
                sysfatal("dial: %r");

        switch(pid = fork()){
        case -1:
                sysfatal("fork: %r");
        case 0:
                while((n = read(0, buf, sizeof buf)) > 0)
                        if(write(fd, buf, n) < 0)
                                break;
                if(!waitforeof)
                        postnote(PNPROC, getppid(), "kill");
                exits(nil);
        }

ここで使ってるdial関数は、libc.hで、p9dialって定義されてて 使えるのは、tcp,udp,unixしかない。アドレスでは、0.0.0.0いわゆる*は戒めている。 p9dialparseでは、p9p用の特殊プロトコルを独自に用意してた。

static struct {
        char *net;
        char *service;
        int port;
} porttbl[] = {
        "tcp",  "9fs",  564,
        "tcp",  "whoami",       565,
        "tcp",  "guard",        566,
        "tcp",  "ticket",       567,
        "tcp",  "exportfs",     17007,
        "tcp",  "rexexec",      17009,
        "tcp",  "ncpu", 17010,
        "tcp",  "cpu",  17013,
        "tcp",  "venti",        17034,
        "tcp",  "wiki", 17035,
        "tcp",  "secstore",     5356,
        "udp",  "dns",  53,
        "tcp",  "dns",  53,
};

ちょっと実験って事で、debianに建てたWebサーバーに接続(xxx.xxx.xxx.xxxは、debianのIP-address)

$ 9 dial tcp!xxx.xxx.xxx.xxx!8080
GET / HTTP/1.1
 ;; 空行を送る事(RETを叩く)
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.5.3
  :

こちらは、ncを使って接続したもの

$ nc xxx.xxx.xxx.xxx 8080
GET / HTTP/1.1

HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.5.3
  :

tips

日頃お世話になってる、libc.hとかにどんな事が書いてあるかと、眺めてみたんだ。

term% cat /sys/include/libc.h
#pragma lib     "libc.a"
#pragma src     "/sys/src/
 :

冒頭にコンパイラーのためと思われる情報が載ってた。このヘッダーファイルを使ったら、 libc.aをリンクしてね。acidみたいなdebuggerさんには、ソースの在処を教えてあげようって寸法だな。特に、ライブラリィーに何を使えってのは、日頃苦労してる身としては、有難い仕掛けと思うぞ。(Linuxは、この点に関しては落第)

で、今まで気にも留めなかったけど、libc.aとかは何処にあるの?updatedb/locateみたいな サービスは無いようだしね。無ければ自分で何とかしろが鉄則。

term% bin/find -D / >all
term% grep libc\.a all
//root/386/lib/libc.a
term% wc all
  47884   47884 2662882 all

grepで指定する検索パターンは正規表現なので、特殊文字のドットはエスケープして、普通の文字扱いにしてる。