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

xv6

セクション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") とかした時、どういう風に解釈されるか、調べてみるかな。