262144
この数値は何?
覚えの目出度い人は、ははん だろうけど。
この数をググルに聞いてみたら、ウィキペディアに案内されたぞ。有名な数値らしい。 そして次の候補には、下記が出て来た。専門家に任せてねって事か。
4桁ぐらいの数値だと、株のページが出て来る事が多いな。みんな大好きなんだな。
その他、エンジェルナンバーとかも多い。ラッキーナンバーと何が違うのさ?
ただの数値を3桁か4桁入れて、ショッピング検索すると飽きないな、商いな。
タイヤが出てきたり靴が出てきたり、電子部品が出てきたり、中古の携帯が出てきたり、ワインが出て来たり。
grep 262144
前回、xargs.cのソースを見てて、ふとしたはずみに、262144なんて数値を引っ張りだしちゃったものだから、どこで定義されてるか検索したんだった。
ob6$ time find /usr/src/sys -name '*.[ch]' | xargs grep 262144 /usr/src/sys/dev/ic/ar9280.c: spur_delta_phase = (spur * 262144) / 10; /usr/src/sys/dev/ic/ar9380.c: spur_delta_phase = (spur * 262144) / 5; /usr/src/sys/dev/ic/lance.c: if (sc->sc_memsize > 262144) /usr/src/sys/dev/ic/lance.c: sc->sc_memsize = 262144; /usr/src/sys/dev/ic/lance.c: case 262144: /usr/src/sys/dev/pci/agp_i810.c: isc->stolen = (262144 - stolen) * 1024 / 4096; /usr/src/sys/ufs/ffs/fs.h:#define SBLOCK_PIGGY 262144
が、結果が思わしく無い。オイラーの予想では、kernの中かsysの中(ここはカーネル用のinclude -- ヘッダーファイルが置いてある)で、見つかると思っていたんだ。
全くそんな素振りが無い世。
はて、どうする? 検索語を変えてみるのが正しい方法です。この数値を16進数に変換して、やってみ。類義語ならぬ類似語ならぬ、基数変換。
10進数 16進数
そんなの、関数電卓で一発じゃんって、オイラーはGUIな人じゃないよ。思いつくままに 行ってみよう。
ob6$ gdb -q (gdb) p/x 262144 $1 = 0x40000
ob6$ dc 16o 262144p 40000
ob6$ bc obase=16 262144 40000
ob6$ printf "%x\n" 262144 40000
ob6$ gosh gosh> (d 262144) 262144 is an instance of class <integer> (#x40000, ~ 256Ki, 1970-01-04T00:49:04Z as unix-time) gosh> ,d #x40000 ;; 括弧が嫌い、16進が嫌いな人用 (普通な人って事だな) 262144 is an instance of class <integer> (#x40000, ~ 256Ki, 1970-01-04T00:49:04Z as unix-time)
中にはマニアックなやつもあるけど、まあいいや。んでもって、16進で登録してあるかと思って検索してみたけど、更に傷口が拡がるばかりだった。
sysctl
方針転換。
ob6$ sysctl kern.argmax kern.argmax=262144
こんな風にカーネルのパラメータになってるのは確かなんで、登録場所を同定してみるか。そして、そこを監視するとかすればいいな。
(gdb) b sys_sysctl Breakpoint 1 at 0xd07268ba: file /usr/src/sys/kern/kern_sysctl.c, line 152.
184│ switch (name[0]) { 185│ case CTL_KERN: 186├───────────────> fn = kern_sysctl; 187│ break; 240├───────> error = (*fn)(&name[1], SCARG(uap, namelen) - 1, SCARG(uap, old), 241│ &oldlen, SCARG(uap, new), SCARG(uap, newlen), p); 272│ kern_sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, void *n 273│ size_t newlen, struct proc *p) 274├>{ : 333│ case KERN_ARGMAX: 334├───────────────> return (sysctl_rdint(oldp, oldlenp, newp, ARG_MAX));
(gdb) s sysctl_rdint (oldp=0xcf7bdb00, oldlenp=0xf3747128, newp=0x0, val=262144) at /usr /src/sys/kern/kern_sysctl.c:860
あれ、決め打ちされてるじゃん。と言う事は、変更不可なんかな?
ob6$ doas sysctl kern.argmax=327680 doas (sakae@ob6.localdomain) password: sysctl: kern.argmax: Operation not permitted
やっぱり、変更出来ひん。sysctl(2)に
Second level name Type Changeable KERN_ALLOWKMEM integer yes KERN_ARGMAX integer no KERN_BOOTTIME struct timeval no :
と、しっかり書いてあった。でも、おかげで、ARG_MAX = 262144 って事が分かった。 今度は、数値じゃなくて、名前で引いてみる。
ob6$ find /usr/src/sys/sys -name '*.[ch]' | xargs grep ARG_MAX /usr/src/sys/sys/param.h:#define NCARGS ARG_MAX /* max bytes for an exec function */ /usr/src/sys/sys/syslimits.h:#define ARG_MAX (256 * 1024) /* max bytes for an exec function */
人間に優しい表記が、検索を妨げていたとな。そう言えば、goshのdescribeで262144を尋ねた時、そこはかとなく、256Ki だよと言ってたな。現場で鍛えられていると、こういうヒントも さりげなく出してくれるのね。
で、本題です、ARG_MAXは、どこで使ってるかな? 多分、あそこだろうけど、一応確認。
ob6$ find /usr/src/sys/kern -name '*.[ch]' | xargs grep ARG_MAX /usr/src/sys/kern/kern_exec.c: len = argp + ARG_MAX - dp; /usr/src/sys/kern/kern_exec.c: len = argp + ARG_MAX - dp; /usr/src/sys/kern/kern_exec.c: copyoutstr(sp, dp, ARG_MAX, &len)) /usr/src/sys/kern/kern_exec.c: copyoutstr(sp, dp, ARG_MAX, &len)) /usr/src/sys/kern/kern_sysctl.c: return (sysctl_rdint(oldp, oldlenp, newp, ARG_MAX)); /usr/src/sys/kern/kern_sysctl.c: *oldlenp = ARG_MAX; /* XXX XXX XXX */
kern_exec.c
ARG_MAXが使われてる所を探してみると、
/* Now get argv & environment */ if (!(cpp = SCARG(uap, argp))) { error = EFAULT; goto bad; } if (pack.ep_flags & EXEC_SKIPARG) cpp++; while (1) { len = argp + ARG_MAX - dp; if ((error = copyin(cpp, &sp, sizeof(sp))) != 0) goto bad; if (!sp) break; if ((error = copyinstr(sp, dp, len, &len)) != 0) { if (error == ENAMETOOLONG) error = E2BIG; goto bad; } dp += len; cpp++; argc++; }
こんなんが見つかった。よく出て来るマクロ、SCARGは、
#define SCARG(p, k) ((p)->k.be.datum) /* get arg from args pointer */
こんな定義、そしてcopyinって何?
The copyin functions are designed to copy contiguous data from one address to another. All but copystr() and kcopy() copy data from user- space to kernel-space or vice-versa. The copyin routines provide the following functionality: copyin() Copies len bytes of data from the user-space address uaddr to the kernel-space address kaddr.
あの世とこの世じゃなかった、ユーザースペースからカーネルスペースへのデータ転送なんだな。E2BIGなんてエラーもあるけど、
#define E2BIG 7 /* Argument list too long */
リストが大きすぎるとな。、、、と、単語が分かっても、全体像が理解出来ない。 いわゆる、言語明瞭意味不明ってやつに陥っているな。
こういう時はどうする? で、思い出したのは、あれ。
xv6-public
セクション2あたりかな。それよりソースだソースだ。
sysfile.cが、システムコールの受け口。
int sys_exec(void) { char *path, *argv[MAXARG]; int i; uint uargv, uarg; : memset(argv, 0, sizeof(argv)); for(i=0;; i++){ : if(uarg == 0){ argv[i] = 0; break; } if(fetchstr(uarg, &argv[i]) < 0) return -1; } return exec(path, argv); }
これが主要な部分。ユーザーランド側のshで、echo hello world とした時、execの引数は
(gdb) p path $7 = 0x1880 "echo" (gdb) p argv $8 = {0x1880 "echo", 0x1885 "hello", 0x188b "world", 0x0 <repeats 29 times>}
と、カーネル側に取り込まれた。次はexec.cの中を見て行くと
// Push argument strings, prepare rest of stack in ustack. for(argc = 0; argv[argc]; argc++) { if(argc >= MAXARG) goto bad; sp = (sp - (strlen(argv[argc]) + 1)) & ~3; if(copyout(pgdir, sp, argv[argc], strlen(argv[argc]) + 1) < 0) goto bad; ustack[3+argc] = sp; }
ユーザースタック用に2ページ確保し、そのうちの1ページをスタック用に使ってる。4Kって事か。そして、引数の数が許された大きさ(MAXARG=32)を越えていないか確認しながら、copyout関数を使って、ユーザーサイド側(のスタック)に、送り返している。
と、まあ、こんな具合だ。引数の数を制限数より多くすると、execは失敗、すなわちユーザーランド側では、引数の数が多すぎますとか言って、起動出来ないんだろうね。
check argv and env
今度は、ユーザーランド側から攻めてみる。下記のようにmain関数回りのデータが、どのような場所に格納されてるかを確認するもの。
ob6$ cat test.c #include <stdio.h> int data0; int data1 = 0xdeadbeef; void main(int argc, char *argv[], char *env[]) { char c; printf("env:\t%p\n", env); printf("argv:\t%p\n", argv); printf("argc:\t%p\n", &argc); printf("stk:\t%p\n", &c); printf("bss:\t%p\n", &data0); printf("data:\t%p\n", &data1); __asm__ __volatile__("int3"); // drop gdb }
最後の所にある __asm__ は、ここにgdbのBPを置けというもの。普通に走らせると、trapでcoreになっちゃうけど。。。
(gdb) r hello world Starting program: /tmp/a.out hello world env: 0x7f7ffffbb888 argv: 0x7f7ffffbb868 argc: 0x7f7ffffbb814 stk: 0x7f7ffffbb813 bss: 0x16147540105c data: 0x161475401000 Program received signal SIGTRAP, Trace/breakpoint trap. 0x00001614752005e3 in main (argc=3, argv=0x7f7ffffbb868, env=0x7f7ffffbb888) at test.c:16 16 __asm__ __volatile__("int3"); // drop gdb
gdbを起動して、いきなり走らせても、既にBPが埋め込んであるんで、gdbに落ちてくれる。
で、まず調べたいのは、argvの配列の状況とかだ。それには、gdbのメモリー確認コマンドを 使うんだけど、いつも間違えるのでおさらい。
(gdb) help x Examine memory: x/FMT ADDRESS. ADDRESS is an expression for the memory address to examine. FMT is a repeat count followed by a format letter and a size letter. Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal), t(binary), f(float), a(address), i(instruction), c(char), s(string) and z(hex, zero padded on the left). Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes). The specified number of objects of the specified size are printed according to the format. If a negative number is specified, memory is examined backward from the address.
argvには、アドレスが入っているはずなんで、FMTにはaを使えとな。
(gdb) p argc $1 = 3 (gdb) x/4a argv 0x7f7ffffbb868: 0x7f7ffffbb998 0x7f7ffffbb9a3 0x7f7ffffbb878: 0x7f7ffffbb9a9 0x0
引数の数は、3個なんで、一つ余計に指定してみた。4個目はNULLって番兵になってるんだな。これが、配列を扱う時の定石か。
(gdb) x/6s 0x7f7ffffbb998 0x7f7ffffbb998: "/tmp/a.out" 0x7f7ffffbb9a3: "hello" 0x7f7ffffbb9a9: "world" 0x7f7ffffbb9af: "_=/tmp/a.out" 0x7f7ffffbb9bc: "LOGNAME=sakae" 0x7f7ffffbb9ca: "HOME=/home/sakae"
argvのポインターが指してる所から、文字列として、多めに6個分表示させた。4個目以降は、envの文字列っぽい。
(gdb) x/30a env 0x7f7ffffbb888: 0x7f7ffffbb9af 0x7f7ffffbb9bc 0x7f7ffffbb898: 0x7f7ffffbb9ca 0x7f7ffffbb9db 0x7f7ffffbb8a8: 0x7f7ffffbb9e4 0x7f7ffffbb9f7 0x7f7ffffbb8b8: 0x7f7ffffbba0f 0x7f7ffffbba1a 0x7f7ffffbb8c8: 0x7f7ffffbba2f 0x7f7ffffbba51 0x7f7ffffbb8d8: 0x7f7ffffbbabd 0x7f7ffffbbae1 0x7f7ffffbb8e8: 0x7f7ffffbbaee 0x7f7ffffbbafa 0x7f7ffffbb8f8: 0x7f7ffffbbb09 0x7f7ffffbbb12 0x7f7ffffbb908: 0x7f7ffffbbb48 0x0
今度は環境変数の配列とそのデータ(一部、伏せ字ご容赦)
(gdb) x/18s 0x7f7ffffbb9af 0x7f7ffffbb9af: "_=/tmp/a.out" 0x7f7ffffbb9bc: "LOGNAME=sakae" 0x7f7ffffbb9ca: "HOME=/home/sakae" 0x7f7ffffbb9db: "PWD=/tmp" 0x7f7ffffbb9e4: "SSH_TTY=/dev/ttyp0" 0x7f7ffffbb9f7: "DISPLAY=xx.xx.xx.xx:0.0" 0x7f7ffffbba0f: "COLUMNS=80" 0x7f7ffffbba1a: "MAIL=/var/mail/sakae" 0x7f7ffffbba2f: "SSH_CLIENT=xx.xx.xx.xx 54393 xxxx" 0x7f7ffffbba51: "PATH=/home/sakae/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/X11R6/bin:/usr/local/bin:/usr/local/sbin:/usr/games" 0x7f7ffffbbabd: "TMUX=/tmp/tmux-1000/default,25559,0" 0x7f7ffffbbae1: "TMUX_PANE=%0" 0x7f7ffffbbaee: "TERM=screen" 0x7f7ffffbbafa: "SHELL=/bin/ksh" 0x7f7ffffbbb09: "LINES=37" 0x7f7ffffbbb12: "SSH_CONNECTION=xx.xx.xx.xx xx.xx.xx.xx xx xxxx" 0x7f7ffffbbb48: "USER=sakae" 0x7f7ffffbbb53: ""
環境変数を追加して、それが反映してるか確認。
ob6$ export HOGE_OS=OpenBSD-6.3-release
格納形式ってCGI風なのね。(違、CGIが真似したんだな。)
0x7f7ffffbec16: "HOGE_OS=OpenBSD-6.3-release"
argvの上側にenvの配列が置いてある。これって、schemeで言うcons領域。そして更にその上に、文字列領域と言うかatom領域のシンボル名が鎮座してるとな。
そして、言うまでもなく、ポインターのサイズは8バイト。これだけ有れば、普通は用が足りるな。
次は getenv("HOGE") とかした時、どういう風に解釈されるか、調べてみるかな。