pipe(2)

Table of Contents

gdb layout src,asm,regs

25.2 TUI Key Bindings

gdbでemacs似のインターフェースを見つけた、ってか、とある本を見ていて気 付いた。

C-x a      normal <-> TUI のトグル
C-x o      フォーカスの移動
PgUp/down  画面のスクロール
C-x s      singule key mode
           o  ni
	   i  si
	   w  bt
           q quit single key mode

pipe in OpenBSD

前回の続きで、パイプを追跡。まずは、manから抜け出してきた様な簡単なコー ドで、ユーザーランド側を調査。どんな風にシステムコールが実施されるかの 現場を捉えるのが目的。あえて引数は、グローバル側で宣言し、スタック操作 の軽減を図ってみた。

ob$ cat mypipe.c
#include <unistd.h>
int fds[2];

int main(){
  pipe(fds);
}

using lib.so

ob$ cc -g mypipe.c
ob$ nm a.out | grep pipe
00000000 F mypipe.c
         U pipe

普通にコンパイルすると、pipeは未定義と印されている。実行時に、この未定 義の部分がlibc.soから充当される。裏で、ld.soが暗躍するんだな。

覚えたてのgdb TUI-modeで、asm中心に見ていく。

x   0x16c0 <main>           push   %ebp                                        x
x   0x16c1 <main+1>         mov    %esp,%ebp                                   x
x   0x16c3 <main+3>         push   %ebx                                        x
x   0x16c4 <main+4>         push   %eax                                        x
x   0x16c5 <main+5>         call   0x16ca <main+10>                            x
x   0x16ca <main+10>        pop    %ebx                                        x
x   0x16cb <main+11>        xchg   %ebx,%eax                                   x
x   0x16cd <main+13>        add    $0x1fffe9e6,%eax                            x
x   0x16d3 <main+19>        xchg   %ebx,%eax                                   x
x   0x16d5 <main+21>        lea    0x1050(%ebx),%eax                           x
x   0x16db <main+27>        mov    %eax,(%esp)                                 x
x   0x16de <main+30>        call   0x1760 <pipe@plt>                           x
x   0x16e3 <main+35>        xor    %eax,%eax                                   x
x   0x16e5 <main+37>        add    $0x4,%esp                                   x
x   0x16e8 <main+40>        pop    %ebx                                        x
x   0x16e9 <main+41>        pop    %ebp                                        x
x   0x16ea <main+42>        ret            

冒頭にxが付いているのは、いわゆる文字化け。正しくは、枠を示すバーチカ ルバーなんで気にしない。

読める所だけ読むと、サブルーチンが2回呼び出されていて、2回目が本命っぽ いとわかる。C-x s してgdbを1キーで反応する様にし、iでステップ実行して く。

_dl_bind_start () at /usr/src/libexec/ld.so/i386/ldasm.S:99
_dl_bind (object=0x1, index=-813958860)
    at /usr/src/libexec/ld.so/i386/rtld_machine.c:315
Run till exit from #0  0x092784d9 in _dl_bind (object=0x755d1400, index=32)
    at /usr/src/libexec/ld.so/i386/rtld_machine.c:315
_dl_bind_start () at /usr/src/libexec/ld.so/i386/ldasm.S:100

訳わか目なコードが走って、pipeを呼び出す準備をしてるっぽい。そして

x   0x45ccbf8 <pipe>        mov    $0x107,%eax                                 x
x  >0x45ccbfd <pipe+5>      int    $0x80                                       x
x   0x45ccbff <pipe+7>      jae    0x45ccc11 <pipe+25>                         x
x   0x45ccc01 <pipe+9>      mov    %eax,%gs:0x10                               x
x   0x45ccc07 <pipe+15>     mov    $0xffffffff,%eax                            x
x   0x45ccc0c <pipe+20>     mov    $0xffffffff,%edx                            x
x   0x45ccc11 <pipe+25>     ret 

これが本命かな。システムコール番号をeaxレジスターに入れて int 0x80 が、 あちら側への依頼だ。0x107を10進数に変換すると、263。syscall.hの該当部 分を参照。

/* syscall: "pipe" ret: "int" args: "int *" */
#define SYS_pipe        263

using libc.a

訳わか目なコードは188(イヤヤ)。って事で、システムコールを埋め込み作戦。

ob$ cc -g -static mypipe.c
ob$ nm a.out | grep pipe
00005f4c t _libc_pipe
00005f4c T _thread_sys_pipe
00000000 F mypipe.c
00005f4c W pipe
xB+>0x14794f25 <main+5>     call   0x14794f2a <main+10>                        x
x   0x14794f2a <main+10>    pop    %ebx                                        x
x   0x14794f2b <main+11>    xchg   %ebx,%eax                                   x
x   0x14794f2d <main+13>    add    $0x1fffa4c6,%eax                            x
x   0x14794f33 <main+19>    xchg   %ebx,%eax                                   x
x   0x14794f35 <main+21>    lea    0x3c48(%ebx),%eax                           x
x   0x14794f3b <main+27>    mov    %eax,(%esp)                                 x
x   0x14794f3e <main+30>    call   0x14794f4c <pipe>                           x
x   0x14794f43 <main+35>    xor    %eax,%eax                                   x
x   0x14794f45 <main+37>    add    $0x4,%esp                                   x
x   0x14794f48 <main+40>    pop    %ebx                                        x
x   0x14794f49 <main+41>    pop    %ebp                                        x
x   0x14794f4a <main+42>    ret 

今度は、変なコードが走る事なく、素直にpipeに到達した。

x  >0x14794f4c <pipe>       mov    $0x107,%eax                                 x
x   0x14794f51 <pipe+5>     int    $0x80                                       x
x   0x14794f53 <pipe+7>     jae    0x14794f65 <pipe+25>                        x
x   0x14794f55 <pipe+9>     mov    %eax,%gs:0x10                               x
x   0x14794f5b <pipe+15>    mov    $0xffffffff,%eax                            x
x   0x14794f60 <pipe+20>    mov    $0xffffffff,%edx                            x
x   0x14794f65 <pipe+25>    ret 

staticだと、libcを抱えこんでいるので、解析が楽だ。こんな風にすれば良い。

ob$ objdump -d a.out | less
00005f20 <main>:
    5f20:       55                      push   %ebp
    5f21:       89 e5                   mov    %esp,%ebp
    5f23:       53                      push   %ebx
    5f24:       50                      push   %eax
    5f25:       e8 00 00 00 00          call   5f2a <main+0xa>
    5f2a:       5b                      pop    %ebx
    5f2b:       87 d8                   xchg   %ebx,%eax
    5f2d:       81 c0 c6 a4 ff 1f       add    $0x1fffa4c6,%eax
    5f33:       87 d8                   xchg   %ebx,%eax
    5f35:       8d 83 48 3c 00 00       lea    0x3c48(%ebx),%eax
    5f3b:       89 04 24                mov    %eax,(%esp)
    5f3e:       e8 09 00 00 00          call   5f4c <_thread_sys_pipe>
    5f43:       31 c0                   xor    %eax,%eax
    5f45:       83 c4 04                add    $0x4,%esp
    5f48:       5b                      pop    %ebx
    5f49:       5d                      pop    %ebp
    5f4a:       c3                      ret
    5f4b:       cc                      int3

00005f4c <_thread_sys_pipe>:
    5f4c:       b8 07 01 00 00          mov    $0x107,%eax
    5f51:       cd 80                   int    $0x80
    5f53:       73 10                   jae    5f65 <_thread_sys_pipe+0x19>
    5f55:       65 a3 10 00 00 00       mov    %eax,%gs:0x10
    5f5b:       b8 ff ff ff ff          mov    $0xffffffff,%eax
    5f60:       ba ff ff ff ff          mov    $0xffffffff,%edx
    5f65:       c3                      ret

lessのサーチ機能を使って、mainなりpipeを探せばOK。pipeはエイリアスの別 名で出演してたぞ。この別名の使い分けってどういう基準なのだろう?

pipe2

#include <unistd.h>
#include <fcntl.h>

int fds[2];

int main(){
  pipe2(fds, O_NONBLOCK);
}

下記は、OpenBSDでの結果。

00005f20 <main>:
    5f20:       55                      push   %ebp
    5f21:       89 e5                   mov    %esp,%ebp
    5f23:       53                      push   %ebx
    5f24:       e8 00 00 00 00          call   5f29 <main+0x9>
    5f29:       5b                      pop    %ebx
    5f2a:       87 d8                   xchg   %ebx,%eax
    5f2c:       81 c0 c7 a4 ff 1f       add    $0x1fffa4c7,%eax
    5f32:       87 d8                   xchg   %ebx,%eax
    5f34:       8d 83 48 3c 00 00       lea    0x3c48(%ebx),%eax
    5f3a:       6a 04                   push   $0x4
    5f3c:       50                      push   %eax
    5f3d:       e8 0a 00 00 00          call   5f4c <_thread_sys_pipe2>
    5f42:       83 c4 08                add    $0x8,%esp
    5f45:       31 c0                   xor    %eax,%eax
    5f47:       5b                      pop    %ebx
    5f48:       5d                      pop    %ebp
    5f49:       c3                      ret

00005f4c <_thread_sys_pipe2>:
    5f4c:       b8 65 00 00 00          mov    $0x65,%eax
    5f51:       cd 80                   int    $0x80
    5f53:       73 10                   jae    5f65 <_thread_sys_pipe2+0x19>
    5f55:       65 a3 10 00 00 00       mov    %eax,%gs:0x10
    5f5b:       b8 ff ff ff ff          mov    $0xffffffff,%eax
    5f60:       ba ff ff ff ff          mov    $0xffffffff,%edx
    5f65:       c3                      ret

ちょっとゴチャゴチャしてるな。FreeBSDはどうかな? 比べてみるのも一興か と。

004194e0 <main>:
  4194e0: 55                            pushl   %ebp
  4194e1: 89 e5                         movl    %esp, %ebp
  4194e3: 83 ec 08                      subl    $0x8, %esp
  4194e6: 8d 05 08 79 48 00             leal    0x487908, %eax
  4194ec: 89 04 24                      movl    %eax, (%esp)
  4194ef: c7 44 24 04 04 00 00 00       movl    $0x4, 0x4(%esp)
  4194f7: e8 d0 1d 00 00                calll   0x41b2cc <pipe2>
  4194fc: 31 c0                         xorl    %eax, %eax
  4194fe: 83 c4 08                      addl    $0x8, %esp
  419501: 5d                            popl    %ebp
  419502: c3                            retl

0041b2cc <pipe2>:
  41b2cc: b8 1e 02 00 00                movl    $0x21e, %eax            # imm = 0x21E
  41b2d1: cd 80                         int     $0x80
  41b2d3: 0f 82 8f ff ff ff             jb      0x41b268 <.cerror>
  41b2d9: c3                            retl

こちらの方がアセンブラ初心者にも易しいコードになってる。システムコール の引数は、スタックに積むんか。グローバルに宣言してるfdsはBSSに確保して るんだろう。

sakae@fb:/tmp $ nm a.out | grep fds
00487908 B fds

そしてシステムコール名も、エイリアスが色々と登録されてたぞ。

sakae@fb:/tmp $ nm a.out | grep pipe2
0041b2cc T __sys_pipe2
0041b2cc W _pipe2
0041b2cc W pipe2

ArchLinuxでも確認。ソースの冒頭に、#define _GNU_SOURCE が必要。詳しく は、 feature_test_macros(7) らしいけど、色々なソース軍(ex. _BSDSOURCE, _XOPENSOURCE)を判定するのに必要みたい。何だかLinuxの全方位外交を象徴 するような仕掛けだな。こういうのがglibcを複雑怪奇にする要因。それより 深刻と思うのは、ヘッダーファイルが見苦しくなる事。#ifdef の嵐は御免被 りたい。そして、BUGの温床。 そう、毎週セキュリティーホールを塞ぐためにアップデートされる、あのブラ ウザーみたいだ。いい加減、拡張競争は止めませんか。

00000000004017b5 <main>:
  4017b5:       55                      push   %rbp
  4017b6:       48 89 e5                mov    %rsp,%rbp
  4017b9:       be 00 08 00 00          mov    $0x800,%esi
  4017be:       48 8d 05 4b 43 0a 00    lea    0xa434b(%rip),%rax  # 4a5b10 <fds>
  4017c5:       48 89 c7                mov    %rax,%rdi
  4017c8:       e8 83 f5 00 00          call   410d50 <__pipe2>
  4017cd:       b8 00 00 00 00          mov    $0x0,%eax
  4017d2:       5d                      pop    %rbp
  4017d3:       c3                      ret

0000000000410d50 <__pipe2>:
  410d50:       f3 0f 1e fa             endbr64
  410d54:       b8 25 01 00 00          mov    $0x125,%eax
  410d59:       0f 05                   syscall
  410d5b:       48 3d 01 f0 ff ff       cmp    $0xfffffffffffff001,%rax
  410d61:       73 01                   jae    410d64 <__pipe2+0x14>
  410d63:       c3                      ret

ふーん、システムコールの引数は、レジスターに保持するのか。その方がスタッ クを経由するより、高速って判断なのかな。普通の関数と引数の渡し方を区別 してるって、一貫性が無いと思うぞ。区別は、ヘッダーファイルにでも記述さ れてるんかな? どうやら、externがシステムコール関数の目印っぽい。 syscall(2),syscalls(2)あたりを眺めておくと、鼻高と自慢できるっぽい。

[sakae@arch tmp]$ nm a.out | grep pipe2
0000000000410d50 T __pipe2
0000000000410d50 W pipe2
[sakae@arch tmp]$ nm a.out | grep fds
0000000000403e50 T __libc_check_standard_fds
00000000004a5b10 B fds

in kernel

OpenBSDの sys_pipe.c を眺めてみる。

int
sys_pipe2(struct proc *p, void *v, register_t *retval)
{
        struct sys_pipe2_args /* {
                syscallarg(int *) fdp;
                syscallarg(int) flags;
        } */ *uap = v;

        if (SCARG(uap, flags) & ~(O_CLOEXEC | FNONBLOCK))
                return (EINVAL);

        return (dopipe(p, SCARG(uap, fdp), SCARG(uap, flags)));
}

callしたプロセス、引数、結果の返値を受けとる。pipeの方は、引数が一つす くないだけ。まあ、システムコールのテンプレートだな。実働の関数は、 dopipe。

pp = pipe_pair_create();
if (pp == NULL)
        return (ENOMEM);
wpipe = &pp->pp_wpipe;
rpipe = &pp->pp_rpipe;

pipe_pair_create の中で、 pipe_create をcallして、パイプの実体を VMに確保。ファイルなんで、ctimeやらatime,mtimeを設定してる。

error = falloc(p, &rf, &fds[0]);
if (error != 0)
        goto free2;
rf->f_flag = FREAD | FWRITE | (flags & FNONBLOCK);
rf->f_type = DTYPE_PIPE;
rf->f_data = rpipe;
rf->f_ops = &pipeops;

次はディスクリプターの作成(wfも同様に作成)。肝は、fallocだ。この関数の 中で、fdallocを使って実体が作成される。空いている若い番号を返却するよ うに苦労の跡がうかがえる。

fdinsert(fdp, fds[0], cloexec, rf);
fdinsert(fdp, fds[1], cloexec, wf);

error = copyout(fds, ufds, sizeof(fds));

最後に、システムテーブルに登録。それから、copyoutを利用して、ユーザー ランドに結果を転送。大体こんな流れかな。次は、実際にそうなってるか追跡。

残骸が残ってた

qemuに構築した実験用システムが削除されちゃったか? 7.5でソースが一新されたんで、それ用にカーネルを作成するんかなあ。えと、 なにか設定が必要だったなあ。

Makefile.i386:          -Wframe-larger-than=2047
makeoptions DEBUG="-g"  

debugをonに、-O0 でコンパイルすると、スタックサイズが肥大化するんで、 その対策でフレームサイズを拡げるとかとかうろ覚えをしてた。

そしたら、7.4で作ったものが、/usr/obj/sys/arch/i386/compile/SEE/に残っ ていた。オブジェクトエリアはソースとは独立してたのを忘れていた。 7.4の稼動システムを7.5のソースで併用するって、どうよ? まあ、そんなに 変化は無いっしょ。

#0  dopipe (p=0xd14e3688, ufds=0x36870038, flags=4)
    at /usr/src/sys/kern/sys_pipe.c:174
#1  0xd09c09c3 in sys_pipe2 (p=0xd14e3688, v=0xf1b3e944, retval=0xf1b3e93c)
    at /usr/src/sys/kern/sys_pipe.c:168
#2  0xd068ba01 in mi_syscall (p=0xd14e3688, code=101, indirect=-1,
    callp=0xd0d0f59c <sysent+1212>, argp=0xf1b3e944, retval=0xf1b3e93c)
    at /usr/src/sys/sys/syscall_mi.h:110
#3  0xd068b604 in syscall (frame=0xf1b3e980)
    at /usr/src/sys/arch/i386/i386/trap.c:574

呼出の履歴

(gdb) p *cpipe
$6 = {
  pipe_lock = 0xf1b71efc,
  pipe_buffer = {
    cnt = 0,
    in = 0,
    out = 0,
    size = 16384,
    buffer = 0xf1b26000 <error: Cannot access memory at address 0xf1b26000>
  },

パイプ用のバッファーってかファイルを確保した所。

#0  fdalloc (p=0xd14e3688, want=0, result=0xf1b3e804)
    at /usr/src/sys/kern/kern_descrip.c:904
#1  0xd044b664 in falloc (p=0xd14e3688, resultfp=0xf1b3e858,
    resultfd=0xf1b3e85c) at /usr/src/sys/kern/kern_descrip.c:1012
#2  0xd09c0684 in dopipe (p=0xd14e3688, ufds=0x36870038, flags=4)
    at /usr/src/sys/kern/sys_pipe.c:190

こちらは、ディスクリプターの確保

x  900                         if (i < last) {                                x
x   901                                 fd_used(fdp, i);                       x
x   902                                 if (want <= fdp->fd_freefile)          x
x   903                                         fdp->fd_freefile = i;          x
x  >904                                 *result = i;  

こんなルーチンを実行しながら、空いている所を見つける。

(gdb) p *fdp
$12 = {
  fd_ofiles = 0xd14cc524,
  fd_ofileflags = 0xd14cc574 "",
  fd_cdir = 0xd138ca00,
  fd_rdir = 0x0,
  fd_nfiles = 20,
  fd_openfd = 4,
  fd_himap = 0xd14cc588,
  fd_lomap = 0xd14cc58c,
  fd_lastfile = 3,
  fd_freefile = 3,
  fd_cmask = 18,
  fd_refcnt = 1,

構造体をダンプして分った様な気になるな。filedesc.hに定義してある、 filedescのコメントを読んで、理解を深めろ!

Mine!

Mineって単語は大好きだ。それオイラーの物ってね。/home/sa/sakae/mine な んてのを作成しておいて、そこに自家製のソフトを入れておく。そうすれば、 HOMEが乱雑になるのを防げるしね。まあ、chroot("/home/sakae/mine")なんて いう凝った使い方はしなかったけどね。

所有権の驚くべき真実を解き明かす! 『Mine! 私たちを支配する「所有」の ルール』

こんな本を読んでいる。これでもか、これでもかと実例がでてくるので、お腹 一杯感がして、そろりそろりと読み進めている。

プロ野球観戦に行って、たまたまホームランボールをキャッチした、が、惜し い事に、ボールを取り落としてしまった。別の人が最終的にそのボールを手に 入れた。この場合、ボールの権利者は、誰になるのか? 裁判に訴えてやる。 結論は? とか、"私には夢がある" で始まる有名な演説は、使用に許可が必要 とか、おもしろくて興味が尽きない話題がいっぱい。

もちろん、特許とか著作権とかの話題も出てくる。そして、Linuxはどうよ? Linuxで儲けているIBMとかは、訴えられる心配無いんかいとかね。

先人のソースを自由に閲覧できるって、最高の楽しみ。但し、Linuxは寄せ集 めなんで、ソースを一堂に集めるって面倒。apt source hoge とかやらないと 駄目だ。面倒この上ない。

ソース見るならBSDに限ると思うぞ。存分に楽しもう。

sakae@fb:~ $ ls -l /usr/freebsd-dist/
total 769028
-rw-r--r--  1 root  wheel        782 Mar  9 17:29 MANIFEST
-rw-r--r--  1 root  wheel  263271964 Mar  9 17:30 base-dbg.txz
-rw-r--r--  1 root  wheel  201955016 Mar  9 17:29 base.txz
-rw-r--r--  1 root  wheel   73213076 Mar  9 17:30 kernel-dbg.txz
-rw-r--r--  1 root  wheel   46386336 Mar  9 17:29 kernel.txz
-rw-r--r--  1 root  wheel  202190096 Mar  9 17:30 src.txz

インストール時に使用したバイナリーそれにソース一式が、保存されてる。手 違いで削除してしまっても、復旧は容易だ。

pkgを作成する為の種も、portsnap fetch extract 一発でネットから導入でき る。


This year's Index

Home