sysctl
怖いもの見たさで、『ゼロからはじめるenchant.js入門』【公式ガイド】なんて本を手に 取ってみた。
enchantって魅惑のって意味が有るそうだ。って事は、直訳すると魅惑のJavascriptって 事で、こう訴えるのは、一度書くと、ブラウザーの有る所なら、どこでも動きまっせって 甘い言葉なんだ。勿論、流行りのスマホ(iPhoneはおろかアンドロドロイドな奴も含む) でもOKって事です。
言語屋の標準語はPythonだと思っていたら、ゲーム屋の標準語はJavascriptなんですなあ。 正確に言うと、舞台はHTML5の上のキャンバスです。ここで、アニメ(の画像)を動かしたり 音を鳴らしたり、写真にキャラクターを乗せた、ポケモンGoっぽい事も出来ます。
enchant.jpは、その為のライブりりぃー集だそうです。これを使えば、いとも簡単に 小学生でも、ゲームが作れるそうです。
下記は、本に載ってたリンクです。
jsdo.it Share JavaScript, HTML5 and CSS.
これを見ながら、やっていくとよいそうですよ。
Ruby on enchant.js / RubyでHTML5ゲームを作ってみる
sysctlの(不思議な)挙動
前回、ddbを起動させようとして、手探りしてたんだ。で、分かった事は、カーネルを コンパイルする時、ddb.consoleが1になるようにオプションを与える事だった。
で、このオプションで、てっきり内部定数がセットされると思っていた。だったら、後から sysctlでは変更不可だろう。以後、このカーネルは何時でも、ddbを呼び出せる危ない奴に なってると思ったんだ。
そこで、そのSWを落としてみる。結果、SWはoffになった。元に戻そうとすると、 壊れたSWみたいに、もう戻らない(onにならない)
# sysctl ddb.console=0 ddb.console: 1 -> 0 # sysctl ddb.console=1 sysctl: ddb.console: Operation not permitted
この挙動、安全側に倒れるんだから、それでいいじゃんと言う見方も出来るけど、いまいち 納得出来ない。そう言えば、sysctlがどう動いているか、調べようとして、路頭に迷って しまっていたな。良い機会なんで、もう少し真面目に調べてみるかな。
予備調査
manをざっと見。カーネル変数(適切な字句が浮かばないので、こう呼んでおく)でも、 変更出来るものと出来ないものが有るって事。タイプと共に列挙されてた。
Name Type Changeable kern.ostype string no kern.osrelease string no : ddb.console integer yes ddb.log integer yes ddb.trigger integer yes vfs.mounts.* struct no :
これを見ると、ddb.consoleはinteger型で変更可能って事だ。
続いてみて行くと、関連ファイルの所にddb関係者の名前が列挙されるとな。
<ddb/db_var.h> definitions for second level ddb identifiers
それを見ると、
#define DBCTL_CONSOLE 6 #define DBCTL_LOG 7 #define CTL_DDB_NAMES { \ { NULL, 0 }, \ : { "console", CTLTYPE_INT }, \ { "log", CTLTYPE_INT }, \ { "trigger", CTLTYPE_INT }, \ } extern int db_console; extern int db_log;
それっぽい定義が出てきたな。
カーネル内で定義されてる変数名は、db_console なんだろうと当たりを付けて、nm /bsdして 探してみると、
00000000 F db_command.c ffffffff81163b40 T db_command_loop ffffffff818b95c0 D db_command_table ffffffff818bcb24 D db_console ffffffff81166e80 T db_continue_cmd
ここから、いきなりカーネルのソースに行ってしまって良いものだろうか。まあ、参考に 眺めておくか。どうやら、勇み足だった。ヘッダーファイルを通じて、変数名が漏れ出して いるのだろう。真面目に探してみる。
[ob: ddb]$ grep db_console * db_usrreq.c: &db_console)); db_usrreq.c: ctlval = db_console; db_usrreq.c: db_console = ctlval; db_usrreq.c: if (newp && db_console) { db_var.h:extern int db_console;
クラスターが見つかったので、ざっと見。 金鉱発見しましただ。
int ddb_sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, void *newp, size_t newlen, struct proc *p) { : case DBCTL_CONSOLE: if (securelevel > 0) return (sysctl_int_lower(oldp, oldlenp, newp, newlen, &db_console)); else { ctlval = db_console; if ((error = sysctl_int(oldp, oldlenp, newp, newlen, &ctlval)) || newp == NULL) return (error); if (ctlval != 1 && ctlval != 0) return (EINVAL); db_console = ctlval; return (0); } break; case DBCTL_LOG: return (sysctl_int(oldp, oldlenp, newp, newlen, &db_log));
これを見ると、更にメインSWが有るな。kern.securelevel=1 この値、boot時に0から1 に変化してたな。
多分、sysctl_int_lowerって関数は、今設定されてるより小さい値への 変更しか許さないんだろう。はて、どこで定義されてる?
kern/kern_sysctl.cが文字通り、カーネル側の処理をまとめたものだな。システムコール っぽいな。
#ifdef DDB case CTL_DDB: fn = ddb_sysctl; break; #endif
カーネルをコンパイルする時に指定したDDBってオプションで、この部分が有効になるとな。 このDDBってオプションは、Makefile中に、 -DDDBと言う形で埋め込まれていた。
その他、DEBUGってオプションも有って、自分の好きなdebuggerでカーネルを弄ぶ事も 出来るみたいだ。そこまで、やる人はいるのか分からんけど。
お目当てのコードが出てきたぞ。
sysctl_int_lower(void *oldp, size_t *oldlenp, void *newp, size_t newlen, int *v\ alp) { unsigned int oval = *valp, val = *valp; int error; : if (val > oval) return (EPERM); /* do not allow raising */ *(unsigned int *)valp = val; return (0); }
EPERMって何だ? そんな時は、man errno するに限る。
1 EPERM Operation not permitted. An attempt was made to perform an operation limited to processes with appropriate privileges or to the owner of a file or other resources.
予備調査と思って始めたのに、いつの間にか本筋に達してしまったぞ。経験値が上がって きた証拠ですかね。
sysctlはシステムコールか?
上では、勝手にシステムコールと断定しちゃったけど、それって見込み逮捕じゃないですかって 言われそう。じゃ、ドキュメントがきちんとしてる、OpenBSDならでは調査をしろ。
man 2 sysctl したらヒットせず。man 3 sysctl したら、出てきた。オブラートで 包んであるのかな。ざっと見すると、
DBCTL_CONSOLE When this variable is set, an architecture dependent magic key sequence on the console or a debugger button will permit entry into the kernel debugger. When running with a securelevel(7) greater than 0, this variable may not be raised.
今、コードに当たって得意満面としてたのが、きっちり文書になってるじゃん。 がっくりしました。いや、補強出来ましたよ。
system callの一覧はmanには無いのかな? 増減が有るんで、manに反映させるのは 諦めたのかな。しょうがないので、/usr/include/sys/syscall.h を見ろって事 なんだろうな。なんせ、それが時価と言うか、一番フレッシュですからね。 ちょいと、裏を取ってみる。
/* syscall: "sysctl" ret: "int" args: "const int *" "u_int" "void *" "size_t *" "void *" "size_t" */ #define SYS_sysctl 202
載ってて、良かった。これで確信を持てたぞ。
/* 326 is obsolete t32_utimensat */ /* 327 is obsolete t32_futimens */ /* 328 is obsolete __tfork51 */ /* syscall: "__set_tcb" ret: "void" args: "void *" */ #define SYS___set_tcb 329 /* syscall: "__get_tcb" ret: "void *" args: */ #define SYS___get_tcb 330 #define SYS_MAXSYSCALL 331
これが、syscall.hの一番最後の部分。生々しいな。次に足す時は、この番号って スタンプが押されている。OSを新しくしたら、毎回眺めてみる事にしよう。
securelevel(7)
上で出てきたセキュアーレベル。なんだか、rubyにもそんなのが有ったような。。。。
-1 Permanently insecure mode - init(8) will not attempt to raise the securelevel - may only be set with sysctl(8) while the system is insecure - otherwise identical to securelevel 0 0 Insecure mode - used during bootstrapping and while the system is single-user - all devices may be read or written subject to their permissions - system file flags may be cleared with chflags(2) 1 Secure mode - default mode when system is multi-user - securelevel may no longer be lowered except by init - /dev/mem and /dev/kmem may not be written to - raw disk devices of mounted file systems are read-only - system immutable and append-only file flags may not be removed - the fs.posix.setuid sysctl(8) variable may not be changed - the hw.allowpowerdown sysctl(8) variable may not be changed - the net.inet.ip.sourceroute sysctl(8) variable may not be changed - the machdep.kbdreset sysctl(8) variable may not be changed - the ddb.console and ddb.panic sysctl(8) variables may not be raised - the machdep.allowaperture sysctl(8) variable may not be raised - gpioctl(8) may only access GPIO pins configured at system startup 2 Highly secure mode - all effects of securelevel 1 - raw disk devices are always read-only whether mounted or not - settimeofday(2) and clock_settime(2) may not set the time backwards or close to overflow - pf(4) filter and NAT rules may not be altered
もうひとつのエラー
確信犯なんだけど、sshでログインした普通の端末から、ddbを起動すると、そんな デバイスはサポートしてないと言われる。
# tty /dev/ttyp0 # sysctl ddb.trigger=1 sysctl: ddb.trigger: Operation not supported by device
このエラーはカーネルが発している(と思われる)ので、man errnoを使って、 逆アセンブルし、エラーコードを得ておこう。ソースを見るのは、それからだ。
19 ENODEV Operation not supported by device. An attempt was made to apply an inappropriate function to a device, for example, trying to read a write-only device such as a printer.
ENODEVが求めるコード。これを使って/sys/ddb/以下を探してみるのが筋。でも、 以前で調べが付いている。db_usrreq.c に、やっこさんは潜んでいるはず。
case DBCTL_TRIGGER: if (newp && db_console) { struct process *pr = curproc->p_p; if (securelevel < 1 || (pr->ps_flags & PS_CONTROLT && cn_tab && cn_tab->cn_dev == pr->ps_session->s_ttyp->t_dev)) { Debugger(); newp = NULL; } else return (ENODEV); } return (sysctl_rdint(oldp, oldlenp, newp, 0)); default: return (EOPNOTSUPP); }
随分複雑な条件で判定してるな。条件を満たせばddbを起動出来るけど、満たさなかったら、 上記のエラーを出して終わりか。
条件にps_flags って、何となくプロセスがらみのコードが出てる。それに対応して、 sys/proc.hがincludeされてるな。cn_tabっていきなり出てきてるけど、sys/tty.hからの 回し者かしら?
proc.cから
#define PS_CONTROLT 0x00000001 /* Has a controlling terminal. */
実行しようとしたプロセスがターミナルをもってなきゃいけないって事だな。ダエモン君とか じゃダメって事。bgになってるプロセスでもターミナルは持ってるんかな。
struct process; struct session { int s_count; /* Ref cnt; pgrps in session. */ struct process *s_leader; /* Session leader. */ struct vnode *s_ttyvp; /* Vnode of controlling terminal. */ struct tty *s_ttyp; /* Controlling terminal. */ char s_login[LOGIN_NAME_MAX]; /* Setlogin() name. */ };
こんな定義も有るな。
次は、cn_tabの方、tty.hに載っていなかったので、検索範囲をincludeの中って広げて みたら、dev/cons.hに出てきた。consって一瞬、lispとかに出て来る *あれ* かと思ったら、 この場合は、コンソールの事なのね。育ちがバレるな。
近辺を洗ってみると、
dev_t cn_dev; /* major/minor of device */
こんなのも有ったし、ヘッダーと対になる、cons.c(なにせ、consですから、対は有りますよ)のcnopenには
/* * always open the 'real' console device, so we don't get nailed * later. This follows normal device semantics; they always get * open() calls. */ cndev = cn_tab->cn_dev; if (cndev == NODEV) return (ENXIO);
こんなのも。上で出てきた判定式の3番目の項は、プロセスがconsoleを使っているかって 判定だな。
(pr->ps_flags & PS_CONTROLT && cn_tab && cn_tab->cn_dev == pr->ps_session->s_ttyp->t_dev)) {
一応cn_tabが有効か見ているのは、ENXIOを避けるためだろう。
6 ENXIO Device not configured. Input or output on a special file referred to a device that did not exist, or made a request beyond the limits of the device. This error may also occur when, for example, a tape drive is not online or no disk pack is loaded on a drive.
こういうエラーを避けるためだな。
実機検証
と言いつつ、使うのは、qemu動くOpenBSD。こいつを実験機にして、ddbを動かしてみる。 この実験機は、残念ながら、コンソールからddbが起動しない設定(コンパイル・オプション) になってる。
ならば、お手軽に、/etc/rcの
# rc.securelevel did not specifically set -1 or 2, so select the default: 1. (($(sysctl -n kern.securelevel) == 0)) && sysctl kern.securelevel=1
この部分をコメントアウトして、いつでもddb可の、危ないマシンにしても良かったんだけど、 それじゃ親分に叱られそうなので、由緒正しく、カーネルを作り直す事にした。
が、途中でコンパイルエラーですよ。エラー内容を見たら、ヘッダーファイルが 足りないとの事。でも、その在処が、i386領域の中。そうなんだ。grepすると、色々な archが引っかかってくるんで、/sys/arch/amd64以外は全部消し去っていたんだ。
ここは、アーキテクチャ事に完全に独立してるかと思っていたんだけど、そうでも無いらしい。 amd64はi386の進化形です。だから、amd64なカーネルを作る時(あるいは、起動の 一時期)に、i386モードで動くっぽい。足りなかったものは、
[ob: ~]$ ls /sys/arch/i386/isa/ isa_machdep.h nvram.h
これってBUGなんでしょうか? 親分に、ほうれんそう しといた方が良いのでしょうか? 自分的には、報告しといた方が良いように思うけど。判断は親分次第? こういうの、下駄を 預けちゃった方が楽だわな。
ddb_sysctlにBPを置いて、その時の状態をモニターしてみる。
(gdb) p *cn_tab $3 = { cn_probe = 0x0, cn_init = 0x0, cn_getc = 0xffffffff8181de79 <wskbd_cngetc>, cn_putc = 0xffffffff81818398 <wsdisplay_cnputc>, cn_pollc = 0xffffffff8181844e <wsdisplay_pollc>, cn_bell = 0xffffffff8181e03c <wskbd_cnbell>, cn_dev = 3072, cn_pri = 1 } (gdb) p *pr->ps_session->s_ttyp : t_dev = 1282, : t_winsize = { ws_row = 38, ws_col = 80, ws_xpixel = 0, ws_ypixel = 0 },
これ、sshでログインした、ttyp2な端末での結果。
(gdb) p *cn_tab $12 = { cn_probe = 0x0, cn_init = 0x0, cn_getc = 0xffffffff8181de79 <wskbd_cngetc>, cn_putc = 0xffffffff81818398 <wsdisplay_cnputc>, cn_pollc = 0xffffffff8181844e <wsdisplay_pollc>, cn_bell = 0xffffffff8181e03c <wskbd_cnbell>, cn_dev = 3072, cn_pri = 1 } (gdb) p *pr->ps_session->s_ttyp : t_dev = 3072, : t_winsize = { ws_row = 25, ws_col = 80, ws_xpixel = 0, ws_ypixel = 0 },
こちらは、vgaなコンソール端末。ttyC0って名前。sttyの結果は、それぞれの端末に 一致してた。端末属性まで、プロセスデータとして抱えこんでいるのね。納得です。
cn_dev とか t_dev が表している数字。メジャー番号とマイナー番号を、一つの整数に まとめたものなのね。(IPAdressのドット表記の2バイト版)
[ob: ~]$ ls -l /dev/{ttyC0,ttyp2} crw------- 1 root wheel 12, 0 Nov 17 14:42 /dev/ttyC0 crw------- 1 sakae tty 5, 2 Nov 17 15:22 /dev/ttyp2
デバイスのメジャー/マイナー番号を得ておいて、デバイス番号に変換。
[ob: ~]$ bc 12 * 256 + 0 3072 5 * 256 + 2 1282
12.0とか、5.2と言えばいいのに。