mojavy.com

ptraceを駆使してscreenifyっぽいことをするreptyrがすごい

July 12, 2013 at 07:15 PM | categories: C, linux |

reptyrというおもしろいものをみつけたのでご紹介

reptyr とは

reptyrとは"re-ptying"するためのプログラムで、起動中のプロセスを新しい別のターミナルにもってくることができます。 例えば、うっかりscreenやtmuxの外で起動してしまった長い時間のかかるバッチ処理を、起動したままscreenの中にもってくることができます。

https://github.com/nelhage/reptyr

使い方

$ reptyr PID

現在のターミナル内にもってきたいプロセスのpidを引数にします。 attach後は、そのプロセスの入出力は^Cや^Zも含めて新しいターミナル側を向きます。

それscreenifyでできるよ

screenifyと呼ばれるスクリプトが昔からあって、それはgdbつかって似たようなことをやってるらしいです。 でもreptyrならもっとうまくできます。

例えば従来のscreenifyには以下のような問題がありました。

  • screenifyでattachしたプロセスは、元のターミナルから入力をうけつけてしまう
  • ncursesをつかってるプログラムをscreenifyすると、そのプログラムはwindowのリサイズがとれなくなる
  • screenifyした新しいターミナルでは^Cがきかない

reptyrはこういった問題を全部解決できます。

移植性

reptyrは対象プロセスを操作するのにptraceをつかっているのでLinuxに強く依存しており、Linuxだけをサポートしています。 SolarisやBSDに移植することも技術的には可能ですが、現状はプラットフォーム固有の部分を抽象化するようにはデザインされていないようです。

reptyrは現状ではi386, x86_64, ARMをサポートしています。他のアーキテクチャへの対応はarch以下に対応コードを追加すれば比較的容易です。

ptrace_scope on Ubuntu Maverick and up

Ubuntu Maverick以降ではptraceの機能がデフォルトで無効になっています。 以下コマンドで一時的に有効にできます。

$ echo 0 > /proc/sys/kernel/yama/ptrace_scope

rootで/etc/sysctl.d/10-ptrace.confを編集すると永続的に変更できます。またptrace_scopeに関する詳細な説明もここに書いてあります。

どうやってるの?

ソースを追ってみたところ以下のような処理をしているようです。

  1. reptyrプロセス側でptyをつくる
  2. attach対象のプロセスのttyのtermios設定をptyにコピーする
  3. ptraceで対象プロセスをattachしてレジスタ内容を一旦退避
  4. attachしたプロセス側でmmapし、そこにreptyrプロセス側でつくったptyをコピー
  5. attachしたプロセス側でコピーしたptyをopenし、setsid〜ioctlでそこに制御端末を割り当てる
  6. attachしたプロセス側でdup2して入出力をttyに向ける
  7. レジスタ内容を復元、後始末してptraceをdetach

reptyrのキモは5の制御端末をptyに割り当てるところで、これをすることによって従来のscreenifyの問題が回避できます。

しかし、単にioctlTIOCSCTTYするだけではうまくいかないのでちょっとしたトリックが必要です。詳細はhttp://blog.nelhage.com/2011/02/changing-ctty/ に解説があります。 reptyrの作者は自力でこの方法を思いついたそうですが、同様のテクニックは injcodeneercs でも使用されているそうです。

reptyrってどう読むの?

repeaterのように発音してもいいけど曖昧なのでre-P-T-Y-er (たぶんリ・ピーティーワイアー)のように発音してもよいそうです。

制約

  • backgroundにしたときは前のターミナルでbgやfgを実行する必要があります。background制御はshellがやっているので、これを直すにはshell側に手をいれる必要があります。
  • 現状では子プロセスがあるプロセスはattachできません

類似のもの

参考

まとめ

reptyrは1000行くらいの小さなプログラムですが、なかなかおもしろいハックだと思うので興味がある方はソースを読んでみて下さい。



C言語でtuple

July 10, 2013 at 09:02 PM | categories: C, programming |

Cをつかってるとtupleっぽいものがあれば便利なのに、と思うときが時々あります。

別にtupleなんてなくても

typedef struct {
   char *s;
   int *i;
} tuple;

のようにして構造体をつかえばいいのですが、必要になるたびにこれをするのはちょっとめんどくさいですよね。

というわけで色々試行錯誤してみたところ、以下のようにしてunionの配列にするというのがそこそこ便利だったので紹介します。

以下は使用例です。

#include <stdio.h>

typedef union {
    void *p;
    char *s;
    int i;
    char c;
} tuple_u;
typedef tuple_u tuple[2];

int main(int argc, char *argv[]) {
    tuple t = { { .s = "hoge" }, { .i = 123 } };

    printf("%s, %d\n", t[0].s, t[1].i);
    return 0;
}

C99のdesignated initializerをつかえば初期化もまあそこそこ書きやすいし、型の組み合わせもある程度柔軟にできます。

C++ではなくあえてCをつかうような人の多くは独自のコンテナライブラリのようなものをもってると思いますが、上記のようなtupleがあれば便利な場面は結構あるのではないかと思います。



C言語の文字列初期化について

January 21, 2013 at 08:00 PM | categories: C, programming |

なんとなく気になったので以下ひとりごと。


Cで文字列を初期化するときは以下のように書く。

char str[] = "xyz";

こう書けばNULL終わりのchar配列としてスタックに格納してくれるので、以下のように書くのと同じことになる。

char str1[4] = "xyz";
char str2[4] = {'x', 'y', 'z', '\0'};

文字列はcharのポインタで扱うからといって、

char *str = "xyz";

のように書くと違う意味になる。 こう書くと"xyz"が格納されているアドレスでポインタを初期化する。文字列リテラルで宣言したデータが格納される領域は通常はread onlyなので*str='X'などとするとセグフォするが、通常の代入と同じ意味なので違和感はない。

でも、char str[] = "xyz"; のほうはは冷静に考えると気持ち悪い。初期化と代入は違うといってしまえばそれまでだけど、この式だけみても予備知識がないとなにがおこるのかわからないと思う。

以下のような挙動も合理的とは思えない。そもそもCに配列なんて必要なかったのではないか。

void f1(char s[]) {
    /* 意味はないけどエラーでもない */
    s = "baz";
}

void f2(void) {
    char s[] = "foo";
    /* これはエラー */
    /* s = "bar"; */
}

などということを今更ながら考えて悶々としていたのだけど、結局のところこういう類の便利機能は欲しくなってくるわけで、便利さのために不合理を許容するとなるとこのあたりが妥当な落とし所のような気もしてきた。

まとめ:プログラミング言語を考える人はすごい



About Me

pic
mojavy

Recent posts






Categories



Badges