pipe(2)
Table of Contents
gdb layout src,asm,regs
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 一発でネットから導入でき る。