ぐるっとぐりっど

日曜プログラマがいろいろ試してみたことを、後の自分のためにまとめておく場所

emacs25で追加されたdynamic moduleを使ってemacs上でパケットキャプチャしてみる

これは、 Emacs Advent Calendar 2017の4日目の記事です。

昨日は、j8takagiさんの連想リストのUPSERTでした。自分はどうせassoc使うと前の方から見て最初にequalだったものを取得するし、と、同じキーが入っても気にせずappendで更新したいキーをつっこんでます。更新型RDBMS的な。

dynamic moduleとは

去年の9月にリリースされたemacs 25.1に追加された機能で、リリース時のメールでは、リリースのハイライトの一番上に君臨しております*1

この辺の説明読んでて、これまで誤解してたのですが、この機能は「Emacsで、ネイティブの共有ライブラリが読めるよ」ということではなくて、「共有ライブラリに、emacsの関数を書いて、emacsでrequireできるよ」が正しいです。
任意のライブラリファイルが読めて、emacsの橋渡しというかラッパーは、emacs lispで書けばいいのかと勝手に思ってたのですが、共有ライブラリのところから自分で書いて、emacsから呼びだす関数やらもろもろ自分で作らないといけないようです。わかってから考えれば当然なのですが。

ちなみに、共有ライブラリは、やはりCで書くのが基本のようですが、調べてみるとgolangでもできるようです。というか本質的には共有ライブラリがコンパイルできる言語であればきっとなんでもよいはず。

ということで、思った以上に使うの面倒そうだったので、実際どんな感じで使うのか、試してみました。
普段はgolang書いてますが、golang使えることがわかったのが、Cで書いては消しを繰り返し、内容を自分で消化したあとだったので、今回は、Cで。

作ったもの

emacsでパケットキャプチャできるもの。先行の有名なソフトに敬意を示して、emacsharkと名付けてみた。今はすべてのIP通信をキャプチャして、送信元と送信先IPアドレスを表示するだけになってます。

f:id:grugrut:20171203130243g:plain

ソースコードは、githubにあります。

github.com

dynamic module用の書き方

既存のもの参考にしたり、ドキュメントを参照しながら書いたのだけど、結構クセがあるので、はまったところや感心したところなどをまとめておく

GPL互換ライセンスであることを明記する
int plugin_is_GPL_compatible;

https://github.com/grugrut/emacshark/blob/master/emacshark.c#L23

GPL互換であることを表明しておかなければならないらしい。 GPLって動的リンクするものまでは問わなかった気がするけど、どうせEmacsプラグインGPL互換ライセンス以外選択する気ないので、深くは考えてない

関数の定義
static emacs_value
Femacshark_init(emacs_env *env, ptrdiff_t nargs, emacs_value args[], void *data)
{

https://github.com/grugrut/emacshark/blob/master/emacshark.c#L51-L53

/* Bind NAME to FUN.  */
static void
bind_function (emacs_env *env, const char *name, emacs_value Sfun)
{
  /* Set the function cell of the symbol named NAME to SFUN using
     the 'fset' function.  */

  /* Convert the strings to symbols by interning them */
  emacs_value Qfset = env->intern (env, "fset");
  emacs_value Qsym = env->intern (env, name);

  /* Prepare the arguments array */
  emacs_value args[] = { Qsym, Sfun };

  /* Make the call (2 == nb of arguments) */
  env->funcall (env, Qfset, 2, args);
}

#define DEFUN(lsym, csym, amin, amax, doc, data)                        \
  bind_function (env, lsym,                                             \
                 env->make_function(env, amin, amax, csym, doc, data))

  DEFUN ("emacshark-init", Femacshark_init, 0, 0, "Init emacshark", NULL);

https://github.com/grugrut/emacshark/blob/master/emacshark.c#L129-L158

前半が処理を書くところ、後半がemacsで呼ぶための設定を書くところです。
前半のFemacshark_initの第二引数のnargsに相当するところがemacs側で呼んだ際の引数になり、例えば(some-func a b c)としたら、[a, b, c]が配列としてわたされてくる。
あとは、数値を返したければ

return env->intern(env, "nil");

すればいいし、数値や文字列を返したければ、

return env->make_integer (env, 42);
return env->make_string(env, str, len); // strがchar配列、lenが文字列長(\0のカウントは不要)

とすればよい。

後半で、ラッパー部分を定義していて、関数名や引数の最小数、最大数のほか、docstringなんかも書けるので、cでemacs lispを書いているような、そんな気分。

関数を定義する際は、戻り値がemacs_value型の関数を書いたうえで、さらに、いろいろとしないといけないので、結構面倒。
公式のサンプルソースで、なんか関数や関数マクロ駆使して、いいかんじに書く方法紹介されてたので、そのまんま真似したけど、emacs-module.hをincludeするんだから、そっちになんかいい感じにまとめておいてくれたほうが、もっとうれしい。

ポインタの受け渡し

引数渡して、処理した結果を返しておわり、という純粋な関数だけでなく、継続して処理したいという場合があると思います。
今回作ったパケットキャプチャもそうで、init関数で、デバイスをオープンしたりパケットキャプチャを開始して、get関数を呼ぶたびにinit関数で初期化したところから情報をとってくるようになってます。

init関数では初期化情報をemacs側に返却、get関数では返却された情報を返して、それを元にライブラリ側は処理をする必要があるわけです。

もちろん、その辺もできるようになってまして、emacsにポインタを返却する場合は、

return env->make_user_ptr(env, free, handle); //handleがポインタ

https://github.com/grugrut/emacshark/blob/master/emacshark.c#L79

emacsから受けとる場合は、

  pcap_t *handle = env->get_user_ptr(env, args[0]);
  if (env->non_local_exit_check(env) != emacs_funcall_exit_return) {
    return env->intern(env, "nil");
  }

https://github.com/grugrut/emacshark/blob/master/emacshark.c#L88-L91
でいけます。受けとるだけなら、get_user_ptrだけでよいのですが、その後のif文で、問題ないかチェックしていて、おかしいポインタがわたされた場合は、emacs側にもエラーメッセージをミニバッファに表示させることなどができるようになっています。
これができることに気付かず最初開発中は、セグメンテーション違反(通称セグフォ)でemacsごと落ちまくって大変でした。
make_user_ptrの2番目の変数は、開放時に呼ばれる関数のようです。freeしてるだけですが実はlibpcapなんでもろもろcloseしないといけないのでは、、、?という疑惑があります。

所感

もうちょっとカジュアルに使えるものかと思ったけど、結構面倒。とはいえ、だいたい雛形決まってるので、一度書いてしまえば、Makefileソースコードも半分以上は使い回せる気がする(だからこそ、本体側で吸収してくれればもっとカジュアルに書けるのでは、ということでもある)

emacshark自身の展望としては、フィルタを設定できるようにする、とか、(おそらくバグで)initメソッド呼んだとき、ひとつパケットを受けとるまで待機状態に入ってしまうので、その辺をなんとかする、とか、もうちょっと一般的に作れたらmelpaへの登録を試みる、とか、野望は幅広く持っています。

いっけんとっつきにくい、というか前提が多すぎで本質でないところにとらわれてしまうのですが、なんちゃってで書いても、なんだかんだ動くもの作れる(このemacsharkもバグ修正とか細かいところ除けば大筋は、いちからでも2時間ぐらいでできました)ので、ぜひなんかおもしろパッケージが出てくること楽しみにしています。

明日は、ncaqさんです。自作のパッケージを公開するとのことらしいので楽しみですね。

*1:余談ながら、去年のadvent calendarでは、下から二番目のxwdigetについて書きました