sigaction, eisl
Advent Calendar
pythonは現代のBASICなんだな。投稿希望者殺到。もう一つ大人気なのがgolang。C語の置き換え人気なのでしょうか。パッケージも豊富だし、出来上がるのがシングルバイナリーって所が受けているんですかね?
来年は何をやるか? じっくり探してみよう。
signal and sigaction
前回、nprologでsignalが使われていたのを見た。どんな具合に動くのか追ってみたい(kernel観光ね)。nprologをそのままqemu上で動いているOpenBSDにかけるわけにもいかないので、簡易版を作った。
#include <signal.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> void reset(int i){ exit(i); } int main(void){ sleep(1); // printf("%p\n", reset); signal(SIGINT, reset); for (int i=0; i<10; i++) sleep(1); return(0); }
メイン内で割り込みを登録。その後、10秒待ってから終了。10秒以内にCtl-Cすると、resetが呼び出されて、割り込みSIGの番号を終了ステータスとして、アプリは終了。たわいも無いコードである。
これをktraceの元で走らせて、挙動を確認。
30540 a.out CALL nanosleep(0xcf7f4c38,0xcf7f4c28) 30540 a.out RET nanosleep 0 30540 a.out CALL kbind(0xcf7f4be8,12,0x58737154fad7d79) 30540 a.out RET kbind 0 30540 a.out CALL sigaction(SIGINT,0xcf7f4c38,0xcf7f4c28) 30540 a.out RET sigaction 0 30540 a.out CALL nanosleep(0xcf7f4c38,0xcf7f4c28) 30540 a.out RET nanosleep 0 30540 a.out CALL nanosleep(0xcf7f4c38,0xcf7f4c28) 30540 a.out RET nanosleep -1 errno 4 Interrupted system call 30540 a.out CALL kbind(0xcf7f4a98,12,0x58737154fad7d79) 30540 a.out RET kbind 0 : 30540 a.out CALL exit(2)
nanosleepの後にsigactionが呼ばれている。そして、ループ内のsleepを実行中にシステムコールの割り込みが入って、sleepは失敗してる。Ctl-Cが原因なんだな。最後は、SIGINTに相当する番号を返して終了。
と言う事で、signal(3)は、sigactionシステムコールに置き換えられていました。物の本によると、signalは、移植性の面で非推奨らしいです。若しsigactionを使いたいなら、下記のように書き換えれば良いそうです。
#include <string.h> // need for memset : struct sigaction act; memset(&act, 0, sizeof act); act.sa_handler = reset; sigaction(SIGINT, &act, NULL);
と、長い前ぶりの後、 sys_action
を追いかけてみました。
(gdb) p signum $1 = 2 (gdb) p *nsa $3 = {__sigaction_u = {__sa_handler = 0x1b3b3290, __sa_sigaction = 0x1b3b3290}, sa_mask = 0, sa_flags = 2} (gdb) p *osa $4 = {__sigaction_u = {__sa_handler = 0x33, __sa_sigaction = 0x33}, sa_mask = 3481195068, sa_flags = 48664816}
下記は、サンプルプログラムのprintfを有効にして、reset関数のアドレスを表示したものです。OpenBSDは、セキュリティ向上させる為、アプリの実行毎にロードアドレスが変わる仕組みになってます。
ob$ ./a.out 0x1b3b3290
下記は、pgsignalにBPを置いておいて、Ctl-Cした時の図。
(gdb) bt #0 ptsignal (p=0xd17e3904, signum=2, type=SPROCESS) at /usr/src/sys/kern/kern_sig.c:890 #1 0xd0a0d99c in pgsignal (pgrp=0xd17d2d20, signum=2, checkctty=1) at /usr/src/sys/kern/kern_sig.c:749 #2 0xd0ad3b0e in ttyinput (c=3, tp=0xd1882c00) at /usr/src/sys/kern/tty.c:359 #3 0xd08acc94 in comsoft (arg=0xd184d000) at /usr/src/sys/dev/ic/com.c:1039 #4 0xd0ccde6c in softintr_dispatch (which=2) at /usr/src/sys/arch/i386/i386/softintr.c:97 #5 0xd09b21e2 in Xsofttty () #6 0x00000002 in ?? () #7 0xf1cb0020 in ?? () #8 0xd0e524e7 in acpicpu_idle () at /usr/src/sys/dev/acpi/acpicpu.c:1187 #9 0xd09af7e8 in cpu_idle_cycle () #10 0xd0aa0154 in sched_idle (v=0xd10ffff8 <cpu_info_full_primary+8184>) at /usr/src/sys/kern/kern_sched.c:193 #11 0xd09af481 in proc_trampoline ()
物の本では、システムコールの終了時にsignalが発生してるか確認し、signal-handlerに飛び込むらしい。コンテキストに保存してるスタックを操作して実現してるらしいけど、その現場に到達出来ず。ゆっくり追ってみような。
exampele of sigaction
何気にリナのsigactionのmanを見てたんだ。そしたらサンプルがmprotectに有るよと案内が出てた。で、そのコードをパクって、前回のヌルポと組み合わせてみた。
#include <signal.h> #include <stdio.h> #include <stdlib.h> void handler(int sig, siginfo_t *si, void *unused) { printf("SIGSEGV at address: 0x%lx\n", (long) si->si_addr); exit(1); } int main(void){ struct sigaction sa; sa.sa_flags = SA_SIGINFO; sigemptyset(&sa.sa_mask); sa.sa_sigaction = handler; sigaction(SIGSEGV, &sa, NULL); *(int*)256 = 777; }
実行結果。
debian:tmp$ ./a.out SIGSEGV at address: 0x100
SEGVになるとCoreが作成されるのが普通だけど、それは既定の動作。プログラムでsignalをフックした場合は、その限りでは無い。
procmap
procmapなんて言う面白いコマンドを見つけてしまったので、試してみた。
vbox$ ./a.out & [1] 94597 vbox$ 0x14efa600 doas procmap -p 94597 00001000 0K [ anon ] 0212A000 4K read/exec [ uvm_aobj ] 036AE000 132K read /usr/lib/libc.so.96.0 036CF000 572K read/exec /usr/lib/libc.so.96.0 065C6000 8K read /usr/libexec/ld.so 065C8000 48K read/exec /usr/libexec/ld.so 14EF9000 4K read /tmp/a.out 14EFA000 4K read/exec /tmp/a.out 236AE000 8K read /usr/lib/libc.so.96.0 236B0000 8K read/write /usr/lib/libc.so.96.0 : 4AE45000 28K read /var/run/ld.so.hints : 73938000 4K read [ uvm_aobj ] : CD6E3000 29696K [ stack ] CF3E3000 4092K read/write [ anon ] CF7E2000 4K [ anon ] total 5144K
プチ、リナのlsofに似ているな。lsofは例によって機能がてんこ盛りだけど、こちらはすっきり目のオプションしかない。それに特権コマンドだし。
measure in nprolog
nprologのコードをブラウジングしてたら、面白い物を見つけた。shellとかにあるtime相当品。まずは、どんな風に動くか試してみる。
vbox$ rlwrap ./npl -c example/fib.pl N-Prolog Ver 1.66 ?- measure(fib(5,Ans)). Elapsed Time=0.000722 (second) 60948(LIPS) 44 Ans = 5 . yes ?- measure(fib(10,Ans)). Elapsed Time=0.008101 (second) 65424(LIPS) 530 Ans = 55 . yes
rlwrapをかませて起動してるのは、edit機能を殺している為。起動時にfibをロードしてる。 そしてmeasureを使って、fibの実行時間を計測。
arg1 = car(arglist); proof = 0; start_time = getETime(); //time_flag on and it store start time res = prove_all(arg1,sp,0); end_time = getETime(); time = end_time - start_time; lips = (double)proof / time; ESCFGREEN; printf("Elapsed Time=%.6f (second) %.0f(LIPS) %d\n", time,lips,proof);
主要なコードの抜粋(ちょいとオリジナル版に手を入れている)。現在時刻を浮動小数で返してくれる関数に挟まれて、 prove_all
を実行してる。時刻の差分を取れば実行時間になる。proofは、 probe_all
が呼ばれた回数だ。
その回数を実行時間で割れば、1秒当たりの呼び出し回数lipsになる。オリジナル版には無かったけど、回数も表示するようにした。
ここで注目すべきは、 prove_all
だ。lispで言う所のevalだな。そう思うと第一引数は、評価したい式。第二引数はspってなってるけど、これは環境変数相当だな。spってのは、nprologが自前で用意したスタックだ。という事は、ローカル環境と思えばいいのだな。第三変数には0を指定してる。lispのevalには無いものだ。以前に調べた事があるけど、再帰が深くなってスタックを食いつぶさないようにする為の、リミットカウンターだ。
思わぬ所で、evalの使い方が分かってしまった。
eisl
時代はnprologからISLISPに動いているらしい。正直、prologの変態な文法には付き合いきれませんです。番外編で知ったんだけど、'.'が文脈に於いてはconsになったり、文字通り式の終わりを示したりと勝手気まま。付き合いきれませんです。
https://github.com/sasagawa888/eisl
ISLISPを一言で言うと、CommonLispのぐちゃぐちゃな仕様をすっきりさせましたって事だな。
eisl on OpenBSD
早速OpenBSDに移植してみる。makefile内でifによって切り分けしてる部分が有るんだけど、それが BSD makeでは認識しないでエラーになるんで、変更。
ob$ make gcc -O2 -pipe -c main.c main.c:12:17: error: omp.h: No such file or directory :
どうもこのヘッダーファイルは、OpenMPを使う時に必要そう。だって、オイラーはGPGPUなんて言う豪華な設備持っていないもの。
次のエラーは、edit.cの中で使われている UP/DOWN/LEFT/RIGHTが分からんと言うやつ。右往左往したよ。色々調べたら、eisl.hの中で、 __linux
に囲まれたものを発見。作者様は、WindowsとLinuxの両刀使いされてるんで、それの切り分けだな。
eisl.h/function.c/syntax.cで切り分けが有った。BSDとリナは多分、同じ種類でしょって想像を働かせ、 __OpenBSD__
に変更。
ob$ ./eisl Easy-ISLisp Ver1.66 > (load "tests/verify.lsp") enter M&S-GC free=19998175 exit M&S-GC free=19998201 All tests are done! T
これで移植完了、かな? まて、眼玉のcompilerはどうした。
ob$ ./eisl -c Can't open a file at load "/home/sakae/eisl/library/compiler.lsp" debug mode ?(help) >>
main.cの環境変数HOMEを見てる所を /tmpに変更。compiler.lspの中に埋め込まれている $HOMEも /tmp に変更。だって、$HOMEは、SSDの中。頻繁に更新したくない。劣化が進むから。だから、working-dirは、RAM-DISK仕立ての/tmpを常用してるんだ。ここならDISKがすり減る心配をしなくても良いからね。
> (load "tests/tarai.o") T > (time (tarai 12 6 0)) Elapsed Time(second)=0.142353 <undef>
後は、heapdumpの表示を20セル分に変更。更にedlisを止めて、emacsに変更。リナ上では素直にedlisを使う事にしたんだけど、pageup/downのキーバインドが、emacsと逆になってた。edlis.cをちょっと変更してemacsに合わせておいた。
次は、和田先生曰く式年遷宮式なGC、オイラー的には、焼き畑農業式なGC、そう、copy-GCを試してみるか。YouTube上の報告では、M&S-GCより性能が高いらしいからね。
ob$ ./eisl functionSegmentation fault (core dumped)
あらま、起動せぇへん。
(gdb) bt #0 __vfprintf (fp=0x0, fmt0=0xd6ce77ad65e "<class %s>", ap=0x7f7ffffde700) at /usr/src/lib/libc/stdio/vfprintf.c:458 #1 0x00000d6f0302d382 in _libc_vfprintf (fp=0x0, fmt0=0xd6ce77ad65e "<class %s>", ap=0x7f7ffffde700) at /usr/src/lib/libc/stdio/vfprintf.c:263 #2 0x00000d6f0303df38 in _libc_fprintf (fp=0x0, fmt=0xd6ce77ad65e "<class %s>") at /usr/src/lib/libc/stdio/fprintf.c:44 #3 0x00000d6ce77bb3b0 in printclass (addr=9) at main.c:1275 #4 0x00000d6ce77b9ee0 in print (addr=9) at main.c:1045 #5 0x00000d6ce77ddae8 in signal_condition (x=1103, y=0) at error.c:387 #6 0x00000d6ce77dc6d7 in error (errnum=113, fun=0xd6ce77ad695 "apply", arg=1091) at error.c:154 #7 0x00000d6ce77bc49f in apply (func=0, args=1089) at main.c:1497 #8 0x00000d6ce77bba9a in eval (addr=1064) at main.c:1362 #9 0x00000d6ce77bc5cc in evlis (addr=1085) at main.c:1539 #10 0x00000d6ce77bc5e6 in evlis (addr=1086) at main.c:1541 #11 0x00000d6ce77bba80 in eval (addr=1087) at main.c:1362 #12 0x00000d6ce77bef93 in initgeneric () at function.c:370 #13 0x00000d6ce77b7d08 in main (argc=1, argv=0x7f7ffffdec58) at main.c:243
最初の所からコケているようですねぇ。debian(64Bit)なマシンでも、同様な症状で落ちた。 コンパイラーの違いなんですかね?
sakae@pen:/tmp/eisl$ gcc -v : gcc version 8.3.0 (Debian 8.3.0-6)
debianは、控え目なパッケージングだからなあ。
今度はi386なOpenBSDマシンで実行。
vbox$ ./eisl Easy-ISLisp Ver1.66 > (load "tests/verify.lsp") enter COPY-GC free=12998879 exit COPY-GC free=7000000 (GENERIC-FUNCTION-P (FUNCTION READ-FOO-A)) is bad (GENERIC-FUNCTION-P (FUNCTION WRITE-FOO-A)) is bad (GENERIC-FUNCTION-P (FUNCTION ACCESS-FOO-A)) is bad (GENERIC-FUNCTION-P (FUNCTION BOUNDP-FOO-A)) is bad Segmentation fault (core dumped)
OpenBSDのLLVM由来のデフォルトコンパイラーでも、同様に落ちた。
vbox$ gdb -q eisl Reading symbols from eisl...done. (gdb) r Starting program: /tmp/eisl/eisl Easy-ISLisp Ver1.66 > (gbc) enter COPY-GC free=12998884 exit COPY-GC free=7000000 <class basic-array*> > (gbc) enter COPY-GC free=6999999 exit COPY-GC free=7000000 <class basic-array*>
ここまでは良いな。それなりのセル数を表示している。
> (load "tests/verify.lsp") enter COPY-GC free=6999994 exit COPY-GC free=7000000 (GENERIC-FUNCTION-P (FUNCTION READ-FOO-A)) is bad (GENERIC-FUNCTION-P (FUNCTION WRITE-FOO-A)) is bad (GENERIC-FUNCTION-P (FUNCTION ACCESS-FOO-A)) is bad (GENERIC-FUNCTION-P (FUNCTION BOUNDP-FOO-A)) is bad Program received signal SIGSEGV, Segmentation fault. 0x158e451e in cdr (addr=2146435071) at data.c:414 414 return(GET_CDR(addr)); (gdb) bt #0 0x158e451e in cdr (addr=2146435071) at data.c:414 #1 0x158c5301 in matchp (varlist=0, arglist=2146435071) at main.c:1555 #2 0x158c5313 in matchp (varlist=1065, arglist=796) at main.c:1555 #3 0x158c5313 in matchp (varlist=1066, arglist=0) at main.c:1555 #4 0x158c5313 in matchp (varlist=1067, arglist=13001037) at main.c:1555 #5 0x158c4c11 in apply (func=599068, args=13001037) at main.c:1459 #6 0x158bfb8c in eval (addr=13001033) at main.c:1359 #7 0x158ddc0d in f_defglobal (arglist=13001035) at syntax.c:404 #8 0x158c452d in apply (func=1019, args=13001035) at main.c:1379 #9 0x158bfa1f in eval (addr=13001036) at main.c:1348 #10 0x158d13ac in f_load (arglist=13000003) at function.c:2020 #11 0x158c4506 in apply (func=900, args=13000003) at main.c:1378 #12 0x158bf9c4 in eval (addr=13000002) at main.c:1346 #13 0x158d13ac in f_load (arglist=6000003) at function.c:2020 #14 0x158c4506 in apply (func=900, args=6000003) at main.c:1378 #15 0x158bf9c4 in eval (addr=6000002) at main.c:1346 #16 0x158beed2 in main (argc=1, argv=0xcf7f8be4) at main.c:326 (gdb) p/x 2146435071 $2 = 0x7fefffff
gdb上で動かしていると、SEGVをキャッチして止まってくれるので、わざわざsigactionを使って調べる必要も無い。
と言う事で、最後までSEGVに悩まされました。
来年はコロナもSEGVも、何それと言えるような年になって欲しいものです。 来年も宜しく。
Happy Hacking with sources.