走行中のプロセスを横から覗く

#概要

Linuxにはプロセスの入出力をコマンド等でこっそり？見ることができる。
今回はプロセスの標準入出力を覗いてしまう方法を紹介します。

#その１（straceコマンドの整形）

参考サイト：[走行中のプロセスの標準出力を横取りする方法](http://d.hatena.ne.jp/iww/20080129/stdout) (コマンド版)


```bash:script1
strace -p `pgrep a.out` -e write -s 256 2>&1 | sed -ne 's/^write(1, \"\(.*\)\"\.*, [0-9]*) *= [0-9]*$/\1/p'
```

コマンドで実現するとこんな感じ。
でも出力結果等はある程度縛られているのでカスタマイズするなら
プログラミングに手を出そう。

#その２（ptraceによるツール作成）

参考サイト：[走行中のプロセスの標準出力を横取りする方法](http://d.hatena.ne.jp/iww/20091123/ptrace) (ptrace版)
参考サイト：http://guillot.iiens.net/softs/sniff-noecho.c

```c:main.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <asm/user.h>


void getdata(pid_t p, long addr, char *str, int len)
{
    int i;
    long data;

    for( i=0; i<len; i+=sizeof(long) ){
        // 文字列をコピーする
        data = ptrace( PTRACE_PEEKDATA, p, addr+i, NULL );
        memcpy( str+i, &data, sizeof(long) );
    }
    str[len] = '\0';
    return;
}


int main( int argc, char **argv ) {
    pid_t p;
    int st;
    int fd=-1;
    int in_syscall=1;
    char *str;
    struct user_regs_struct regs;


    if( argc<2 ){
        printf( "usage: %s <pid> [<fd>]\n", argv[0] );
        return 0;
    }
    if( argc>2 ) fd=atoi(argv[2]); // ディスクリプタ指定のあるとき

    setbuf(stdout,NULL); // 標準出力をバッファリングしない

    // アタッチする
    p = atoi(argv[1]);
    if( ptrace(PTRACE_ATTACH,p,NULL,NULL)<0 ){
        perror("ptrace");
        exit(1);
    }
    wait(&st); // 無いと安定しない

    while( ptrace(PTRACE_SYSCALL,p,NULL,NULL)==0 ){
        // 止まるのを待つ
        wait(&st);
        if(WIFEXITED(st)) break;

        ptrace(PTRACE_GETREGS, p, 0, &regs);
        do{
            // 標準入出力以外はスキップ
            if( ( regs.orig_eax != SYS_write )
              &&( regs.orig_eax != SYS_read ) ){
                break;
            }

            // 標準出力 条件判定
            if(regs.orig_eax==SYS_write){
                if( fd != -1 && fd != regs.ebx ){
                    break;
                }
            }
            // 標準入力 条件判定
            if(regs.orig_eax==SYS_read){
                if( 0 != regs.ebx ){
                    break;
                }
            }

            // 標準入出力の処理
            // レジスタを取り出す
            // regs.orig_eax  システムコール番号
            // regs.ebx  ファイルディスクリプタ
            // regs.ecx  文字列のあるアドレス
            // regs.edx  サイズ
            in_syscall = 1-in_syscall; // 交互に
            if(in_syscall){
                str = malloc( regs.edx+sizeof(long) ); // 少し余計に
                getdata( p, regs.ecx, str, regs.edx );
                fputs(str,stdout);
                free(str); // 後始末
            }
        }while(0);
    }

    return 0;
}
```

ちなみに以下の理由でぐだぐだなソースである。

* エラー処理がない
* atoiにて数字以外がくる場合のケア
* 指定したpidに対するケア（存在しない場合、読込み権限がない等）
* システムコールのイン、アウト？をしっかり処理していない。（交互に とかいうコメントのところで適当にやっている）
* システムコールでSIG_HUP等受取った場合の終了処理が無い。（＝ptrace中にCtrl-Cなんてやると標準入出力が迷子になる）
* CentOS6 ではコンパイルに失敗した。（error: asm/user.h: そのようなファイルやディレクトリはありません）


あえて作るほどでもないが、Makefileも以下につけておく。

```make:Makefile
SRCS  = ptrace_io.c
TARGET= ptrace_io

CC      = gcc
CFLAGS  = -g -Wall
INCLUDE =

.SUFFIXES: .c .o
.c.o:
        $(CC) $(CFLAGS) $(INCLUDE) -c $< -o $@

OBJS = $(SRCS:.c=.o)

all: $(TARGET)

$(TARGET):$(OBJS)
        -@/bin/rm $@ > /dev/null 2>&1
        $(CC) -o $(TARGET) $(OBJS)

clean:
        rm -f *.o
```

上記参考サイトにあるような処理を参考に改良する余地がある。